diff --git a/backlog/tasks/task-1 - Implement-navigation-routes.md b/backlog/tasks/task-1 - Implement-navigation-routes.md index 630442a..34f07ab 100644 --- a/backlog/tasks/task-1 - Implement-navigation-routes.md +++ b/backlog/tasks/task-1 - Implement-navigation-routes.md @@ -1,10 +1,11 @@ --- id: task-1 title: Implement navigation routes -status: To Do -assignee: [] +status: Done +assignee: [@claude] created_date: '2025-12-15 19:37' -labels: [] +completed_date: '2025-12-28' +labels: [feature, navigation] dependencies: [] priority: high --- @@ -14,3 +15,27 @@ priority: high Add routing between participants and waypoints using c3nav for indoor routes and OSRM/GraphHopper for outdoor routes + +## Implementation Notes + +### Components Added +- `/api/routing/route.ts` - API endpoint for route calculation +- `RouteOverlay.tsx` - Map overlay component for visualizing routes +- `NavigationPanel.tsx` - UI panel for selecting navigation targets + +### Features Implemented +- **Outdoor routing**: Uses OSRM public API for walking/driving routes +- **Indoor routing**: Integrates c3nav API for CCC venue navigation +- **Mixed routes**: Handles transitions between indoor/outdoor +- **Route visualization**: GeoJSON line layer on MapLibre GL map +- **Navigation UI**: Click on participant/waypoint to get directions +- **State management**: Route state integrated into Zustand store + +### Technical Details +- Route segments are typed as `outdoor`, `indoor`, or `transition` +- Distance/time estimates calculated from OSRM and c3nav responses +- Route line rendered with outline for visibility +- Map auto-fits to route bounds when calculated + +### Commit +`a6c124c` - feat: Add navigation routes feature with indoor/outdoor routing diff --git a/src/app/page.tsx b/src/app/page.tsx index f448259..45d0b65 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { nanoid } from 'nanoid'; @@ -17,8 +17,32 @@ export default function HomePage() { const [isCreating, setIsCreating] = useState(false); const [joinSlug, setJoinSlug] = useState(''); const [name, setName] = useState(''); - const [emoji, setEmoji] = useState(EMOJI_OPTIONS[Math.floor(Math.random() * EMOJI_OPTIONS.length)]); + const [emoji, setEmoji] = useState(''); const [roomName, setRoomName] = useState(''); + const [isLoaded, setIsLoaded] = useState(false); + + // Load saved user info from localStorage on mount + useEffect(() => { + let loadedEmoji = ''; + try { + const stored = localStorage.getItem('rmaps_user'); + if (stored) { + const user = JSON.parse(stored); + if (user.name) setName(user.name); + if (user.emoji) { + setEmoji(user.emoji); + loadedEmoji = user.emoji; + } + } + } catch { + // Ignore parse errors + } + // Set random emoji if none loaded + if (!loadedEmoji) { + setEmoji(EMOJI_OPTIONS[Math.floor(Math.random() * EMOJI_OPTIONS.length)]); + } + setIsLoaded(true); + }, []); const handleCreateRoom = async () => { if (!name.trim()) return; diff --git a/src/app/room/[slug]/page.tsx b/src/app/room/[slug]/page.tsx index 00ddc63..8e3107e 100644 --- a/src/app/room/[slug]/page.tsx +++ b/src/app/room/[slug]/page.tsx @@ -208,6 +208,8 @@ export default function RoomPage() { currentUserId={currentParticipantId || undefined} currentLocation={currentLocation} eventId="38c3" + isSharing={isSharing} + onToggleSharing={handleToggleSharing} onParticipantClick={(p) => { setSelectedParticipant(p); setShowParticipants(true); diff --git a/src/components/map/DualMapView.tsx b/src/components/map/DualMapView.tsx index 5e22021..04b3bbd 100644 --- a/src/components/map/DualMapView.tsx +++ b/src/components/map/DualMapView.tsx @@ -38,6 +38,10 @@ interface DualMapViewProps { onParticipantClick?: (participant: Participant) => void; onWaypointClick?: (waypoint: Waypoint) => void; onIndoorPositionSet?: (position: { level: number; x: number; y: number }) => void; + /** Whether location sharing is active */ + isSharing?: boolean; + /** Callback to toggle location sharing */ + onToggleSharing?: () => void; } // CCC venue bounds (Hamburg Congress Center) @@ -58,6 +62,8 @@ export default function DualMapView({ onParticipantClick, onWaypointClick, onIndoorPositionSet, + isSharing = false, + onToggleSharing, }: DualMapViewProps) { const [mode, setMode] = useState(initialMode); const [activeView, setActiveView] = useState<'outdoor' | 'indoor'>('outdoor'); @@ -152,6 +158,42 @@ export default function DualMapView({ /> )} + {/* Location sharing button - floating inside map for mobile visibility */} + {onToggleSharing && ( + + )} + {/* Indoor Map button - switch to indoor view */} {activeView === 'outdoor' && (