Hash Routing for Venue Navigation โ
Date: 2026-03-22 Status: Complete
Problem โ
The Dashboard component manages internal navigation via React state (view), but these transitions are invisible to the browser's history stack. On mobile, the back swipe pops the previous hash route (e.g. #/profile) instead of going back within the Dashboard (e.g. venue detail โ places list).
Solution โ
Promote Dashboard's internal view states to proper hash routes so the browser history stack tracks venue navigation correctly.
New Routes โ
| Route | View | Replaces |
|---|---|---|
#/ | Places list (home) | view === 'home' |
#/?view=map | Places map view | viewMode === 'map' |
#/venue/:id | Venue detail | view === 'venue' |
#/venue/:id/light | Light lantern form | view === 'form' |
#/active | Active lantern screen | view === 'active' |
#/profile/privacy | Profile privacy tab | activeTab === 'privacy' in ProfileSettings |
Not routed (transient states, no back-button expectation):
successโ redirects to#/activeafter confirmationschedule-confirmationโ stays as internal state (rare flow)
Implementation โ
1. App.jsx โ
- Expand Dashboard rendering to match
#/,#/venue/*, and#/activeroutes - Pass
routeprop to Dashboard - Add
#/profile/privacyroute that renders ProfileSettings withinitialTab="privacy"
2. Dashboard.jsx โ
- Accept
routeprop, deriveviewandvenueIdfrom it - Replace
setViewWithScrollSave(x)calls withwindow.location.hash = ...navigation - When route contains a venue ID, load the venue from local state or Firestore
- Preserve scroll position save/restore logic by keying off the derived
view
3. ProfileSettings.jsx โ
- Accept
initialTabprop, default to'profile' - Sync tab changes to hash: clicking Privacy tab navigates to
#/profile/privacy
Key Considerations โ
- Venue loading on direct navigation: If user lands on
#/venue/abc123(e.g. from a shared link or back button), checkvenuesarray first, fall back togetVenue(id)from Firestore - Scroll restoration: Save scroll position before navigating away from home, restore on return โ same logic, just triggered by route changes instead of state changes
- Lantern expiry redirect: When myLantern expires on active screen, navigate to
#/instead of callingsetViewWithScrollSave('home') - Success screen: Goes directly to
#/active(success is shown briefly in the flow)