MotorSignal-App/website/lib/group-ride-payload.ts

82 lines
2.8 KiB
TypeScript

import type { GroupRideSummary } from '../components/group-rides/types';
export interface GroupRidePayloadContext {
rideId: string;
uid: string;
leadHandle: string;
leadDisplayName: string;
now: Date;
}
export type WebGroupRideInput = GroupRideSummary & {
visibility?: string;
clubId?: string;
clubName?: string;
};
/**
* Builds the Firestore `group_rides/{id}` document for a ride created from the web hub.
*
* The web create form captures a free-text rally point only (there is no map picker or
* geocoder), so the meeting point stores `displayName`/`address` text and deliberately omits
* `latitude`/`longitude`. We never fabricate coordinates: a missing coordinate means
* "location not pinned yet", which `mapFirestoreRideToSummary` already handles (it derives
* `meetingPointCoords` only when both lat and lon are present).
*
* Pure and dependency-free so it can be unit-tested without Firebase.
*/
export function buildWebGroupRidePayload(
newRide: WebGroupRideInput,
ctx: GroupRidePayloadContext
): Record<string, unknown> {
const { rideId, uid, leadHandle, leadDisplayName, now } = ctx;
let scheduledStartAt = new Date(now.getTime() + 3600000).toISOString();
let departureAt = new Date(now.getTime() + 3600000 + 1800000).toISOString();
if (newRide.scheduledDate) {
const parsed = new Date(`${newRide.scheduledDate} ${newRide.scheduledTime}`);
if (!isNaN(parsed.getTime()) && parsed.getTime() > now.getTime() - 86400000) {
scheduledStartAt = parsed.toISOString();
departureAt = new Date(parsed.getTime() + 30 * 60000).toISOString();
}
}
const payload: Record<string, unknown> = {
id: rideId,
organizerUid: uid,
organizerHandle: leadHandle,
organizerDisplayName: leadDisplayName,
title: newRide.title.trim(),
description: newRide.description?.trim() || '',
visibility: newRide.visibility || 'public',
joinPolicy: newRide.joinPolicy || 'requestToJoin',
status: 'scheduled',
scheduledStartAt,
departureAt,
participantLimit: Number(newRide.participantLimit) || 12,
participantCount: 1,
accessUids: [uid],
meetingPoint: {
displayName: newRide.meetingPoint || newRide.region || 'Designated Rally Point',
address: newRide.region || '',
locationType: 'meetup',
notes: '',
},
stops: [],
createdAt: now.toISOString(),
updatedAt: now.toISOString(),
paceClassification: newRide.paceClassification || 'Brisk Pace',
discipline: newRide.discipline || 'Sport / Canyon',
formationProfile: newRide.formationProfile || 'Staggered Dual Column [B]',
routeTitle: newRide.routeSnippet || '',
};
if (newRide.clubId) {
payload.clubId = newRide.clubId;
if (newRide.clubName) payload.clubName = newRide.clubName;
}
return payload;
}