'use client'; import React, { useState, useMemo, useEffect, useRef } from 'react'; import Link from 'next/link'; import { getApps, initializeApp } from 'firebase/app'; import { getAuth, connectAuthEmulator } from 'firebase/auth'; import { collection, query, where, onSnapshot, doc, writeBatch, updateDoc, deleteDoc, setDoc, arrayUnion, arrayRemove, increment, serverTimestamp, getDoc, getFirestore, connectFirestoreEmulator, } from 'firebase/firestore'; import { firestore } from '../../lib/firebase-client'; import { commitGroupRide } from '../../lib/create-group-ride'; function getActiveFirestore() { if ( typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') ) { let qaApp = getApps().find((a) => a.name === 'motorsignal-local-qa'); if (!qaApp) { try { qaApp = initializeApp( { apiKey: 'demo-key', projectId: 'demo-motorsignal-qa', authDomain: 'localhost' }, 'motorsignal-local-qa' ); const db = getFirestore(qaApp); connectFirestoreEmulator(db, '127.0.0.1', 8088); const auth = getAuth(qaApp); connectAuthEmulator(auth, 'http://127.0.0.1:9099'); return db; } catch (_) {} } if (qaApp) { try { return getFirestore(qaApp); } catch (_) {} } } return firestore; } import { INITIAL_GROUP_RIDES } from './fixtures'; import { GroupRideSummary } from './types'; import { EditRideModal } from './edit-ride-modal'; import { DeleteConfirmModal } from './delete-confirm-modal'; import { JoinRideModal } from './join-ride-modal'; import { LivePackRadar } from './live-pack-radar'; import { RideDetailsModal } from './ride-details-modal'; interface GroupRidesManagementProps { currentUser?: { uid?: string; email?: string | null; displayName?: string | null; getIdToken?: () => Promise } | null; onViewShowcase?: () => void; } function mapFirestoreDocToSummary( data: any, docId: string, currentUid?: string ): GroupRideSummary { const isOrganizer = Boolean(currentUid && data.organizerUid === currentUid); const isParticipant = Boolean( currentUid && Array.isArray(data.accessUids) && data.accessUids.includes(currentUid) ); let scheduledDate = 'Upcoming'; let scheduledTime = 'TBD'; if (data.scheduledStartAt) { try { const d = new Date(data.scheduledStartAt); if (!isNaN(d.getTime())) { scheduledDate = d.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', }); scheduledTime = d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', }); } } catch (_) {} } let distance = 'Open Route'; if (typeof data.routeDistanceMeters === 'number' && data.routeDistanceMeters > 0) { distance = `${(data.routeDistanceMeters / 1609.34).toFixed(1)} Miles`; } else if (data.distance) { distance = String(data.distance); } const meetingPoint = data.meetingPoint?.displayName || data.meetingPoint?.address || 'Designated Rally Point'; const region = data.meetingPoint?.address || data.meetingPoint?.displayName || data.region || 'Regional Run'; let routeSnippet = data.routeTitle; if (!routeSnippet && data.destination?.displayName) { routeSnippet = `${meetingPoint} > ${data.destination.displayName}`; } if (!routeSnippet) { routeSnippet = data.routeSnippet || 'Custom pack route staged on MotorSignal'; } const leadHandle = data.organizerDisplayName || data.organizerHandle || (isOrganizer ? 'You (Organizer)' : 'Pack Marshal'); const leadAvatarText = ( data.organizerDisplayName?.[0] || data.organizerHandle?.[0] || (isOrganizer ? 'Y' : 'P') ).toUpperCase(); const status = data.status || 'scheduled'; let userStatus: GroupRideSummary['userStatus'] = 'none'; if (isOrganizer || isParticipant) { userStatus = 'joined'; } return { id: docId, title: data.title || 'Untitled Ride', description: data.description || '', region, scheduledDate, scheduledTime, paceClassification: data.paceClassification || 'Brisk Pace', discipline: data.discipline || 'Sport / Canyon', formationProfile: data.formationProfile || 'Staggered Dual Column [B]', joinPolicy: data.joinPolicy || 'requestToJoin', leadHandle, leadAvatarText, participantCount: typeof data.participantCount === 'number' ? data.participantCount : 1, participantLimit: typeof data.participantLimit === 'number' ? data.participantLimit : 12, distance, meetingPoint, routeSnippet, userStatus, isOrganizer, status, rideCode: data.id || docId, clubName: data.clubName || undefined, accessUids: Array.isArray(data.accessUids) ? data.accessUids : (data.organizerUid ? [data.organizerUid] : []), organizerUid: data.organizerUid, meetingPointCoords: data.meetingPoint?.latitude && data.meetingPoint?.longitude ? { lat: Number(data.meetingPoint.latitude), lon: Number(data.meetingPoint.longitude) } : undefined, } as any; } type ManagementTab = 'my-rides' | 'discover' | 'radar'; type MyRidesFilter = 'all' | 'organized' | 'joined'; /** * Redesigned Create Group Ride Modal * Features: * - Plain, clear labels and helpful hints * - Sensible defaults: Current date/time + 1 hour, standard pace, paved surface, 12 riders, moderated join * - Explicit Review Step before final submission * - Preserves personal vs club ride distinctions, visibility settings, join policies, and authorization rules */ interface CreateRideModalProps { isOpen: boolean; onClose: () => void; onCreateRide: (ride: GroupRideSummary) => void | boolean | Promise; } function CreateGroupRideModal({ isOpen, onClose, onCreateRide }: CreateRideModalProps) { // Sensible default date/time calculation: current time + 1 hour const defaultDateValues = useMemo(() => { const oneHourAhead = new Date(Date.now() + 60 * 60 * 1000); const dateStr = oneHourAhead.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', }); const timeStr = oneHourAhead.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true, }); return { dateStr, timeStr }; }, [isOpen]); const [activeStep, setActiveStep] = useState<'details' | 'review'>('details'); const [title, setTitle] = useState(''); const [region, setRegion] = useState(''); const [scheduledDate, setScheduledDate] = useState(defaultDateValues.dateStr); const [scheduledTime, setScheduledTime] = useState(defaultDateValues.timeStr); const [surfaceType, setSurfaceType] = useState('Paved Asphalt'); const [paceClassification, setPaceClassification] = useState('Brisk Pace'); const [discipline, setDiscipline] = useState>('Sport / Canyon'); const [rideType, setRideType] = useState<'personal' | 'club'>('personal'); const [clubName, setClubName] = useState(''); const [joinPolicy, setJoinPolicy] = useState('requestToJoin'); const [visibility, setVisibility] = useState<'public' | 'unlisted' | 'private'>('public'); const [formationProfile, setFormationProfile] = useState('Staggered Dual Column [B]'); const [distance, setDistance] = useState('45.0 Miles'); const [meetingPoint, setMeetingPoint] = useState(''); const [routeSnippet, setRouteSnippet] = useState('Skyline Blvd > Stage Rd > PCH Pacific Coast Hwy'); const [participantLimit, setParticipantLimit] = useState(12); const [description, setDescription] = useState(''); const [errorMsg, setErrorMsg] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); // Reset form when modal opens useEffect(() => { if (isOpen) { setActiveStep('details'); setTitle(''); setRegion(''); setScheduledDate(defaultDateValues.dateStr); setScheduledTime(defaultDateValues.timeStr); setSurfaceType('Paved Asphalt'); setPaceClassification('Brisk Pace'); setDiscipline('Sport / Canyon'); setRideType('personal'); setClubName(''); setJoinPolicy('requestToJoin'); setVisibility('public'); setFormationProfile('Staggered Dual Column [B]'); setDistance('45.0 Miles'); setMeetingPoint(''); setRouteSnippet('Skyline Blvd > Stage Rd > PCH Pacific Coast Hwy'); setParticipantLimit(12); setDescription(''); setErrorMsg(null); setIsSubmitting(false); } }, [isOpen, defaultDateValues]); // Handle ESC key press useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && isOpen) { onClose(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose]); if (!isOpen) return null; const validate = (): boolean => { if (!title.trim()) { setErrorMsg('Please enter a ride title.'); setActiveStep('details'); return false; } if (!region.trim()) { setErrorMsg('Please enter a location or region.'); setActiveStep('details'); return false; } if (participantLimit < 2) { setErrorMsg('Participant limit must be at least 2 riders.'); setActiveStep('details'); return false; } setErrorMsg(null); return true; }; const handleProceedToReview = (e: React.MouseEvent) => { e.preventDefault(); if (validate()) { setActiveStep('review'); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (isSubmitting) return; if (!validate()) return; const newRide: GroupRideSummary & { visibility?: 'public' | 'unlisted' | 'private'; surfaceType?: string; clubId?: string; } = { id: `ride-${Date.now()}`, title: title.trim(), description: description.trim() || 'Custom squadron pack run with MotorSignal live telemetry.', region: region.trim(), scheduledDate, scheduledTime, paceClassification, discipline, formationProfile, joinPolicy, leadHandle: 'You (Organizer)', leadAvatarText: 'YOU', participantCount: 1, participantLimit: Number(participantLimit) || 12, distance: distance.trim() || '45.0 Miles', meetingPoint: meetingPoint.trim() || 'Designated Rally Staging Point', routeSnippet: routeSnippet.trim() || 'Custom GPX route uploaded by organizer.', userStatus: 'joined', status: 'scheduled', visibility, surfaceType, ...(rideType === 'club' && clubName.trim() ? { clubName: clubName.trim(), clubId: `club-${clubName.toLowerCase().replace(/\s+/g, '-')}` } : {}), }; setIsSubmitting(true); try { const outcome = await onCreateRide(newRide); if (outcome !== false) { onClose(); } } finally { setIsSubmitting(false); } }; return (
{ if (e.target === e.currentTarget) onClose(); }} >
{/* Header */}
add_circle

Create Group Ride Run

{/* Step Tabs Indicator */}
{errorMsg && (
error {errorMsg}
)}
{activeStep === 'details' && ( <> {/* Ride Title */}
setTitle(e.target.value)} placeholder="e.g. Sierra Foothills Canyon Run" className="w-full bg-surface-container-lowest font-body-md text-body-md text-on-surface px-space-md py-2.5 rounded-xl border border-outline-variant/30 focus:border-primary-container outline-none" />
{/* Region & Distance Grid */}
setRegion(e.target.value)} placeholder="e.g. Sacramento, CA" className="w-full bg-surface-container-lowest font-body-md text-body-md text-on-surface px-space-md py-2.5 rounded-xl border border-outline-variant/30 focus:border-primary-container outline-none" />
setDistance(e.target.value)} placeholder="e.g. 45.0 Miles" className="w-full bg-surface-container-lowest font-body-md text-body-md text-on-surface px-space-md py-2.5 rounded-xl border border-outline-variant/30 focus:border-primary-container outline-none" />
{/* Date & Time Grid (Defaults to current time + 1 hour) */}
setScheduledDate(e.target.value)} placeholder="e.g. Saturday" className="w-full bg-surface-container-lowest font-body-md text-body-md text-on-surface px-space-md py-2.5 rounded-xl border border-outline-variant/30 focus:border-primary-container outline-none" /> Defaults to upcoming run date
setScheduledTime(e.target.value)} placeholder="e.g. 09:30 AM PST" className="w-full bg-surface-container-lowest font-body-md text-body-md text-on-surface px-space-md py-2.5 rounded-xl border border-outline-variant/30 focus:border-primary-container outline-none" /> Defaulted to 1 hour from now
{/* Surface & Pace Classification */}
{/* Ride Classification (Personal vs Club) & Privacy/Join Policy */}
{rideType === 'club' && ( setClubName(e.target.value)} className="mt-2 w-full bg-surface-container-lowest text-xs text-on-surface px-3 py-1.5 rounded-lg border border-outline-variant/30" /> )}
{/* Rider Limit & Discipline */}
setParticipantLimit(Number(e.target.value))} className="w-full bg-surface-container-lowest font-body-md text-body-md text-on-surface px-space-md py-2.5 rounded-xl border border-outline-variant/30 focus:border-primary-container outline-none" />
{/* Meeting Point */}
setMeetingPoint(e.target.value)} placeholder="e.g. Chevron Staging Lot at Highway 49" className="w-full bg-surface-container-lowest font-body-md text-body-md text-on-surface px-space-md py-2.5 rounded-xl border border-outline-variant/30 focus:border-primary-container outline-none" />
{/* Description */}