diff --git a/website/app/group-rides/page.tsx b/website/app/group-rides/page.tsx index ee8c65e..2489b66 100644 --- a/website/app/group-rides/page.tsx +++ b/website/app/group-rides/page.tsx @@ -1,591 +1,303 @@ 'use client'; -import React from 'react'; -import Link from 'next/link'; +import React, { useState } from 'react'; +import { INITIAL_GROUP_RIDES } from '../../components/group-rides/fixtures'; +import { GroupRideSummary } from '../../components/group-rides/types'; +import { LivePackRadar } from '../../components/group-rides/live-pack-radar'; +import { GroupRidesDirectory } from '../../components/group-rides/group-rides-directory'; +import { JoinRideModal } from '../../components/group-rides/join-ride-modal'; +import { CreateRideModal } from '../../components/group-rides/create-ride-modal'; export default function GroupRidesPage() { + const [rides, setRides] = useState(INITIAL_GROUP_RIDES); + const [isJoinModalOpen, setIsJoinModalOpen] = useState(false); + const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); + const [activeAlert, setActiveAlert] = useState<{ message: string; type: 'success' | 'info' } | null>( + null + ); + + const showAlert = (message: string, type: 'success' | 'info' = 'success') => { + setActiveAlert({ message, type }); + setTimeout(() => { + setActiveAlert(null); + }, 4500); + }; + + const handleJoinSuccess = (code: string, rideTitle: string) => { + showAlert(`Successfully synchronized telemetry with "${rideTitle}" (Code: ${code})!`, 'success'); + }; + + const handleCreateRide = (newRide: GroupRideSummary) => { + setRides((prev) => [newRide, ...prev]); + showAlert(`Published group run "${newRide.title}"! Pack radar is now active.`, 'success'); + }; + + const handleRequestSlot = (rideId: string) => { + setRides((prev) => + prev.map((r) => { + if (r.id !== rideId) return r; + if (r.participantCount >= r.participantLimit) { + showAlert(`Added to waitlist for "${r.title}". You will be alerted if a slot opens.`, 'info'); + return { ...r, userStatus: 'waitlisted' }; + } + showAlert(`Slot requested for "${r.title}". Telemetry sync confirmed with Lead Marshal!`, 'success'); + return { + ...r, + userStatus: 'joined', + participantCount: r.participantCount + 1, + }; + }) + ); + }; + return (
-{/* Dynamic Atmospheric Glow Background Layers */} -
-
-
-{/* Hero Section */} -
-
-{/* Eyebrow Badge */} -
- - - - -Live Pack Radar & Formation Telemetry -
-{/* Headline */} -

- Ride in pack formation.
-Never lose a wingman. -

-{/* Subheading */} -

- Real-time rider radar, staggered formation spacing alerts, instant incident broadcast, and automated regroup checkpoints calibrated for fast canyon runs and transcontinental tours. -

-{/* Dual CTAs & Quick Metrics Bar */} -
- - -
-
-
-{/* Interactive Live Pack Radar Map & Dashboard */} -
-
-{/* Tactical Header Strip */} -
-
-
- -Swarm Recon: Skyline Blvd > Coast Hwy -
-Session #CA-84-SW -
-
- - cell_tower Mesh Active - -● 6 Synchronized -Gap Delta: ±0.4s -Repair Squad: Ready -
-
-{/* Cockpit Layout: Map & Radar Feed */} -
-{/* Tactical Canvas & Radar Screen */} -
-{/* Map background placeholder with tactical overlay styling */} -
-{/* Tactical Coordinate Grids (Inline SVG Overlay) */} - - - - - - - - -{/* Tactical Route Trace */} - -{/* Safe Spacing Buffers */} - - - - - - - -{/* Map HUD Top Left: Sector Status */} -
-
-Formation Profile -Staggered Dual Column [B] -Paceline Lock: Optimal -
-{/* Map HUD Top Right: Telemetry Ping */} -
-
-GPS Frequency -10.0 Hz • GNSS Lock -
-satellite_alt -
-
-{/* Visual Pin Indicators plotted across the canyon trace */} -
-{/* Lead Rider Marker */} -
- -1 • ApexLead -68 MPH -
-{/* Hard Braking Warning Marker (Rider 5) */} -
-warning -5 • Alex_Ducati --1.2G Decel -
-
-{/* Map HUD Bottom Control Strip */} -
-
- - - -
-
-Regroup Point -Alice's Restaurant (14.8 mi) -
-
-
-{/* Live Swarm Nodes Telemetry Column */} -
-
-Pack Telemetry Stream -6 Active -
-{/* Rider 1: ApexLead */} -
-
-
- -ApexLead -Pace Setter -
-Lead -
-
-
Speed: 68 MPH
-
Lean: 42°
-
Throttle: 78%
-
-
-{/* Rider 2: Ghost99 */} -
-
-
- -Ghost99 -Wingman L -
-+45m Gap -
-
-
Speed: 67 MPH
-
Lean: 40°
-
Brake: 0%
-
-
-{/* Rider 3: Sarah_RR (Warning: Tire Pressure) */} -
-
-
-
- -Sarah_RR -Wingman R -
-+110m Gap -
-
-
-tire_repair -Alert: Rear tire 28 PSI (low) -
-
-
-{/* Rider 4: Dan_KTM */} -
-
-
- -Dan_KTM -Mid Pack -
-+180m Gap -
-
-
Speed: 63 MPH
-
Lean: 28°
-
Temp: 88°C
-
-
-{/* Rider 5: Alex_Ducati (Yellow Flag: Hard Braking) */} -
-
-
-
- -Alex_Ducati -Decel Event -
-+320m Gap -
-
-Hard Braking (-1.2G) -ABS: Engaged -
-
-{/* Rider 6: Sweep_Marshal */} -
-
-
- -Sweep_Marshal -Rear Guard -
-+450m Gap -
-
-
Speed: 59 MPH
-
Radio: Open
-
Pack Lock: 100%
-
-
-
-
-{/* Formation Safety Metrics Panel Strip */} -
-{/* Metric 1: Spacing Integrity */} -
-
-Spacing Integrity -verified -
-
-94% -Safety Index -
-
-
-
-
-{/* Metric 2: Average Pack Speed */} -
-
-Average Pack Velocity -speed -
-
-64.2 -MPH -
-Delta Drift: -1.8 MPH (tightening) -
-{/* Metric 3: Atmospheric & Track Conditions */} -
-
-Surface & Climate -thermostat -
-
-21°C -Dry Asphalt -
-0% Precip • Grip Optimal -
-{/* Metric 4: Next Regroup Waypoint */} -
-
-Regroup Point -pin_drop -
-
-14.8 -Miles -
-Alice's Restaurant Checkpoint -
-
-
-
-{/* Group Ride Features Grid */} -
-
-Cockpit Architecture -

Engineered for Squadron Cohesion

-

High-frequency mesh communications keep the entire formation synchronized through blind apexes, switchbacks, and offline mountain passes.

-
-
-{/* Feature 1 */} -
-
-
-spatial_audio_off -
-01 / Paceline Audit -

Staggered Paceline Warnings

-

- Spatial audio HUD chimes dynamically sound whenever rider follow distance or lane positioning falls beneath calculated emergency stopping thresholds. -

-
-
-Response: <12ms -arrow_forward -
-
-{/* Feature 2 */} -
-
-
-fmd_bad -
-02 / Swarm Broadcast -

Crash & Separation Alerts

-

- Autonomous IMU-based impact detection pings the entire pack instantly. Visual yellow and red telemetry flags highlight precise coordinates for immediate regrouping. -

-
-
-IMU Sensing: 6-Axis -arrow_forward -
-
-{/* Feature 3 */} -
-
-
-hub -
-03 / Offline Link -

Peer-to-Peer Mesh Sync

-

- Never rely on cellular towers in deep canyons. High-throughput ad-hoc Bluetooth and Wi-Fi Direct protocols daisy-chain vital bike metrics from lead to sweep. -

-
-
-Zero-cell Ready -arrow_forward -
-
-{/* Feature 4 */} -
-
-
-history_toggle_off -
-04 / Mission Debrief -

Group Replay & Debrief

-

- Relive the ride with synced multi-bike timelines. Compare apex apex entries, sector speeds, and individual lean angles on an interactive 3D map renderer. -

-
-
-3D Replay Engine -arrow_forward -
-
-
-
-{/* Visual Divider / Action Interstitial */} -
-
-
-
-
-Hardware Compatibility -

Sync directly with your helmet comms & bike dash

-

- Integrates seamlessly with Cardo, Sena, Apple Watch, and OBD-II CAN bus links for live peg-to-peg data overlays. -

-
-
-
-headset_mic -Cardo / Sena HUD -
-
-watch -Haptic Alerts -
-
-
-
-
-{/* Active Public & Regional Group Rides Directory */} -
-
-
-Regional Operations -

Active & Upcoming Pack Runs

-
-{/* Filter Toggles */} -
- - - - -
-
-{/* Search and Region Filters Bar */} -
-
-search - -
-
-Sort by: -Start Time ↓ -
-
-{/* Rides Cards Grid */} -
-{/* Ride 1 */} -
-
-
-
-
-
- Saturday • 06:30 AM PST -
-
- Track Pace -
-
-
-
-San Francisco, CA -82.4 Miles -
-

Bay Area Dawn Patrol

-

- Fast sweepers from Woodside down to San Gregorio beach before morning traffic. Strict staggered formation enforced. -

-
-
-
AL
-Lead: ApexLead -
-
-group -14/16 Riders -
-
-
-
-
- -
-
-{/* Ride 2 */} -
-
-
-
-
-
- Sunday • 07:15 AM PST -
-
- Brisk Pace -
-
-
-
-Malibu, CA -64.0 Miles -
-

Malibu Canyons Fast Pace

-

- Tuna Canyon descent to Latigo Canyon technical curves. Regroup checkpoint planned at Rock Store overlook. -

-
-
-
DK
-Lead: Dan_KTM -
-
-group -9/12 Riders -
-
-
-
-
- -
-
-{/* Ride 3 */} -
-
-
-
-
-
- Fri-Sun • Oct 12-14 -
-
- Endurance / Chill -
-
-
-
-Robbinsville, NC -318 Curves -
-

Tail of the Dragon Run

-

- 11 miles, 318 technical curves. Telemetry debrief sessions every evening with custom apex analysis and gear logs. -

-
-
-
SM
-Lead: Sweep_Marshal -
-
-group -Full (24/24) -
-
-
-
-
- -
-
-
-
-{/* Modal: Join Code Trigger Overlay (Interactive Script Attached) */} -
-
-
-
-qr_code_scanner -

Join Pack Formation

-
- -
-

- Enter the 6-character encrypted ride code generated by your Lead Pace Setter or scan their cockpit radar QR display. -

-
- -
- -Or Scan Camera - -
-
-photo_camera -Activate Radar Lens -
-
- -
-
-
+ {/* Dynamic Atmospheric Glow Background Layers */} +
+
+
-
+ {/* Toast Notification Alert */} + {activeAlert && ( +
+ + {activeAlert.type === 'success' ? 'check_circle' : 'info'} + + {activeAlert.message} + +
+ )} + + {/* Hero Section */} +
+
+ {/* Eyebrow Badge */} +
+ + + + + + Live Pack Radar & Formation Telemetry + +
+ + {/* Headline */} +

+ Ride in pack formation.
+ + Never lose a wingman. + +

+ + {/* Subheading */} +

+ Real-time rider radar, staggered formation spacing alerts, instant incident broadcast, and + automated regroup checkpoints calibrated for fast canyon runs and transcontinental tours. +

+ + {/* Dual CTAs & Quick Metrics Bar */} +
+ + +
+
+
+ + {/* Interactive Live Pack Radar Cockpit */} +
+ +
+ + {/* Group Ride Features Grid */} +
+
+ + Cockpit Architecture + +

+ Engineered for Squadron Cohesion +

+

+ High-frequency mesh communications keep the entire formation synchronized through blind + apexes, switchbacks, and offline mountain passes. +

+
+ +
+ {/* Feature 1 */} +
+
+
+ spatial_audio_off +
+ + 01 / Paceline Audit + +

+ Staggered Paceline Warnings +

+

+ Spatial audio HUD chimes dynamically sound whenever rider follow distance or lane + positioning falls beneath calculated emergency stopping thresholds. +

+
+
+ Response: <12ms + arrow_forward +
+
+ + {/* Feature 2 */} +
+
+
+ fmd_bad +
+ + 02 / Swarm Broadcast + +

+ Crash & Separation Alerts +

+

+ Autonomous IMU-based impact detection pings the entire pack instantly. Visual yellow and + red telemetry flags highlight precise coordinates for immediate regrouping. +

+
+
+ IMU Sensing: 6-Axis + arrow_forward +
+
+ + {/* Feature 3 */} +
+
+
+ hub +
+ + 03 / Offline Link + +

+ Peer-to-Peer Mesh Sync +

+

+ Never rely on cellular towers in deep canyons. High-throughput ad-hoc Bluetooth and Wi-Fi + Direct protocols daisy-chain vital bike metrics from lead to sweep. +

+
+
+ Zero-cell Ready + arrow_forward +
+
+ + {/* Feature 4 */} +
+
+
+ history_toggle_off +
+ + 04 / Mission Debrief + +

+ Group Replay & Debrief +

+

+ Relive the ride with synced multi-bike timelines. Compare apex entries, sector speeds, + and individual lean angles on an interactive 3D map renderer. +

+
+
+ 3D Replay Engine + arrow_forward +
+
+
+
+ + {/* Visual Divider / Action Interstitial */} +
+
+
+
+
+ + Hardware Compatibility + +

+ Sync directly with your helmet comms & bike dash +

+

+ Integrates seamlessly with Cardo, Sena, Apple Watch, and OBD-II CAN bus links for live + peg-to-peg data overlays. +

+
+
+
+ headset_mic + Cardo / Sena HUD +
+
+ watch + Haptic Alerts +
+
+
+
+
+ + {/* Active Public & Regional Group Rides Directory */} + + + {/* Modals */} + setIsJoinModalOpen(false)} + onJoinSuccess={handleJoinSuccess} + /> + + setIsCreateModalOpen(false)} + onCreateRide={handleCreateRide} + /> +
+
); } diff --git a/website/components/group-rides/create-ride-modal.tsx b/website/components/group-rides/create-ride-modal.tsx new file mode 100644 index 0000000..f553de7 --- /dev/null +++ b/website/components/group-rides/create-ride-modal.tsx @@ -0,0 +1,291 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { GroupRideSummary } from './types'; + +interface CreateRideModalProps { + isOpen: boolean; + onClose: () => void; + onCreateRide: (ride: GroupRideSummary) => void; +} + +export function CreateRideModal({ isOpen, onClose, onCreateRide }: CreateRideModalProps) { + const [title, setTitle] = useState(''); + const [region, setRegion] = useState(''); + const [scheduledDate, setScheduledDate] = useState('Saturday'); + const [scheduledTime, setScheduledTime] = useState('07:00 AM PST'); + const [paceClassification, setPaceClassification] = useState('Brisk Pace'); + const [formationProfile, setFormationProfile] = useState('Staggered Dual Column [B]'); + const [distance, setDistance] = useState('50.0 Miles'); + const [meetingPoint, setMeetingPoint] = useState(''); + const [participantLimit, setParticipantLimit] = useState(12); + const [description, setDescription] = useState(''); + const [errorMsg, setErrorMsg] = useState(null); + + // Reset form when modal opens + useEffect(() => { + if (isOpen) { + setTitle(''); + setRegion(''); + setScheduledDate('Saturday'); + setScheduledTime('07:00 AM PST'); + setPaceClassification('Brisk Pace'); + setFormationProfile('Staggered Dual Column [B]'); + setDistance('50.0 Miles'); + setMeetingPoint(''); + setParticipantLimit(12); + setDescription(''); + setErrorMsg(null); + } + }, [isOpen]); + + // 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 handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + if (!title.trim()) { + setErrorMsg('Please enter a ride title.'); + return; + } + if (!region.trim()) { + setErrorMsg('Please enter a location or region.'); + return; + } + + const newRide: GroupRideSummary = { + id: `ride-${Date.now()}`, + title: title.trim(), + description: description.trim() || 'Custom squadron pack run with MotorSignal live telemetry.', + region: region.trim(), + scheduledDate, + scheduledTime, + paceClassification, + formationProfile, + joinPolicy: 'requestToJoin', + leadHandle: 'You (Organizer)', + leadAvatarText: 'YOU', + participantCount: 1, + participantLimit: Number(participantLimit) || 12, + distance: distance.trim() || '45.0 Miles', + meetingPoint: meetingPoint.trim() || 'Designated Rally Staging Point', + userStatus: 'joined', + }; + + onCreateRide(newRide); + onClose(); + }; + + return ( +
{ + if (e.target === e.currentTarget) onClose(); + }} + > +
+ {/* Header */} +
+
+ + add_circle + +

+ Create Group Ride Run +

+
+ +
+ +

+ Organize a new squadron run with real-time radar tracking, safety spacing buffers, and synchronized telemetry check-ins. +

+ + {errorMsg && ( +
+ error + {errorMsg} +
+ )} + +
+ {/* 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. 58.4 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 */} +
+
+ + 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" + /> +
+
+ + setScheduledTime(e.target.value)} + placeholder="e.g. 06:45 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" + /> +
+
+ + {/* Pace & Formation */} +
+
+ + +
+
+ + 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 */} +
+ +