82 lines
2.8 KiB
TypeScript
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;
|
|
}
|