# Stations (Gas/Fuel) Feature — Dispatchable Change Plan This document is written as an execution plan that can be handed to multiple AI agents to implement in parallel. ## Repo Constraints (Must Follow) - Docker-first workflow (production builds): validate changes via `make rebuild` and container logs. - Mobile + Desktop requirement: every UI change must be validated on both `frontend/src/features/stations/pages/StationsPage.tsx` (desktop) and `frontend/src/features/stations/mobile/StationsMobileScreen.tsx` (mobile). - Never expose the Google Maps API key to the browser or logs. ## Scope 1. Fix broken station photo rendering on stations UI after the “hide Google API key” change. 2. Add navigation links for saved/favorite stations: - “Navigate in Google” (Google Maps) - “Navigate in Apple Maps” (Apple Maps) - “Navigate in Waze” (Waze) ## Bug: Station Photos Not Displaying ### Current Implementation (What Exists Today) - Frontend cards render an `` via MUI `CardMedia` when `station.photoReference` is present: - `frontend/src/features/stations/components/StationCard.tsx` - URL generation: `frontend/src/features/stations/utils/photo-utils.ts` → `/api/stations/photo/:reference` - Backend exposes a proxy endpoint that fetches the Google Places photo (server-side, using the secret key): - Route: `GET /api/stations/photo/:reference` - `backend/src/features/stations/api/stations.routes.ts` - `backend/src/features/stations/api/stations.controller.ts` - Google client: `backend/src/features/stations/external/google-maps/google-maps.client.ts` (`fetchPhoto`) ### Likely Root Cause (Agents Must Confirm) The photo endpoint is protected by `fastify.authenticate`, but `` requests do not include the Authorization header. This results in `401 Unauthorized` responses and broken images. Second thing to confirm while debugging: - Verify what `Station.photoReference` contains at runtime: - expected: Google `photo_reference` token - risk: code/docs mismatch where `photoReference` became a URL like `/api/stations/photo/{reference}`, causing double-encoding by `getStationPhotoUrl()`. ### Repro Checklist (Fast Confirmation) - Open stations page and observe broken images in browser devtools Network: - `GET /api/stations/photo/...` should show `401` if auth-header issue is the cause. - Confirm backend logs show JWT auth failure for photo requests. ## Decision: Image Strategy (Selected) Selected: **Option A1** (keep images; authenticated blob fetch in frontend; photo endpoint remains JWT-protected). ### Option A (Keep Images): Fix Auth Mismatch Without Exposing API Key #### Option A1 (Recommended): Fetch Photo as Blob via Authenticated XHR Why: Keeps `/api/stations/photo/:reference` protected (prevents public key abuse), avoids putting JWT in query params, and avoids exposing the Google API key. Implementation outline: - Frontend: replace direct `` usage with an authenticated fetch that includes the JWT (via existing Axios `apiClient` interceptors), then render via `blob:` object URL. - Add a small component like `StationPhoto` used by `StationCard`: - `apiClient.get('/stations/photo/:reference', { responseType: 'blob' })` - `URL.createObjectURL(blob)` for display - `URL.revokeObjectURL` cleanup on unmount / reference change - graceful fallback (hide image) on 401/500 - Backend: no route auth changes required. Tradeoffs: - Slightly more frontend code, but minimal security risk. - Must ensure caching behavior is acceptable (browser cache won’t cache `blob:` URLs; rely on backend caching headers + client-side memoization). #### Option A2 (Simplest Code, Higher Risk): Make Photo Endpoint Public Why: Restores `` behavior with minimal frontend work. Implementation outline: - Backend: remove `preHandler: [fastify.authenticate]` from `/stations/photo/:reference`. - Add lightweight protections to reduce abuse (choose as many as feasible without adding heavy deps): - strict input validation (length/charset) for `reference` - low maxWidth clamp and no arbitrary URL fetching - maintain `Cache-Control` header (already present) - optionally add server-side rate limit (only if repo already uses a rate-limit plugin; avoid introducing new infra unless necessary) Tradeoffs: - Anyone can hit `/api/stations/photo/:reference` and spend your Google quota. ### Option B (Remove Images): Simplify Cards Why: If image delivery adds too much complexity or risk, remove images from station cards. Implementation outline: - Frontend: remove `CardMedia` photo block from `StationCard` and any other station photo rendering. - Leave `photoReference` in API/types untouched for now (or remove later as a cleanup task, separate PR). - Update any tests that assert on image presence. Tradeoffs: - Reduced UX polish, but simplest and most robust. ## Feature: Navigation Links on Saved/Favorite Stations ### UX Requirements - On saved station UI (desktop + mobile), provide 3 explicit navigation options: - Google Maps - Apple Maps - Waze - “Saved/favorite” is interpreted as “stations in the Saved list”; favorites are a subset. ### URL Construction (Preferred) Use coordinates if available; fall back to address query if not. - Google Maps: - Preferred: `https://www.google.com/maps/dir/?api=1&destination=LAT,LNG&destination_place_id=PLACE_ID` - Fallback: `https://www.google.com/maps/search/?api=1&query=ENCODED_QUERY` - Apple Maps: - Preferred: `https://maps.apple.com/?daddr=LAT,LNG` - Fallback: `https://maps.apple.com/?q=ENCODED_QUERY` - Waze: - Preferred: `https://waze.com/ul?ll=LAT,LNG&navigate=yes` - Fallback: `https://waze.com/ul?q=ENCODED_QUERY&navigate=yes` Important: some saved stations may have `latitude/longitude = 0` if cache miss; treat `(0,0)` as “no coordinates”. ### UI Placement Recommendation - Desktop saved list: add a “Navigate” icon button that opens a small menu with the 3 links (cleaner than inline links inside `ListItemText`). - File: `frontend/src/features/stations/components/SavedStationsList.tsx` - Mobile bottom sheet (station details): add a “Navigate” section with the same 3 links (buttons or list items). - File: `frontend/src/features/stations/mobile/StationsMobileScreen.tsx` ## Work Breakdown for Multiple Agents ### Agent 1 — Confirm Root Cause + Backend Adjustments (If Needed) Deliverables: - Confirm whether photo requests return `401` due to missing Authorization. - Confirm whether `photoReference` is a raw reference token vs a URL string. - Implement backend changes only if Option A2 is chosen. Files likely touched (Option A2 only): - `backend/src/features/stations/api/stations.routes.ts` (remove auth preHandler on photo route) - `backend/src/features/stations/api/stations.controller.ts` (add stricter validation; keep cache headers) - `backend/src/features/stations/docs/API.md` (update auth expectations for photo endpoint) ### Agent 2 — Frontend Photo Fix (Option A1) OR Photo Removal (Option B) Deliverables: - Option A1: implement authenticated blob photo loading for station cards. - Option B: remove station photos from cards cleanly (no layout regressions). Files likely touched: - `frontend/src/features/stations/components/StationCard.tsx` - Option A1: - Add `frontend/src/features/stations/components/StationPhoto.tsx` (or similar) - Potentially update `frontend/src/features/stations/utils/photo-utils.ts` - Add unit tests under `frontend/src/features/stations/__tests__/` ### Agent 3 — Navigation Links for Saved Stations (Desktop + Mobile) Deliverables: - Create a single URL-builder utility with tests. - Add a “Navigate” menu/section in saved stations UI (desktop + mobile). Files likely touched: - `frontend/src/features/stations/utils/` (new `navigation-links.ts`) - `frontend/src/features/stations/components/SavedStationsList.tsx` - `frontend/src/features/stations/mobile/StationsMobileScreen.tsx` - Optional: reuse in `frontend/src/features/stations/components/StationCard.tsx` (only if product wants it outside Saved) ### Agent 4 — Tests + QA Pass (Update What Breaks) Deliverables: - Update/extend tests to cover: - navigation menu/links present for saved stations - photo rendering behavior per chosen option - Ensure both desktop and mobile flows still pass basic E2E checks. Files likely touched: - `frontend/cypress/e2e/stations.cy.ts` - `frontend/src/features/stations/__tests__/components/StationCard.test.tsx` - New tests for `navigation-links.ts` ## Acceptance Criteria - Station photos render on station cards via Option A1 without exposing Google API key (no `401` responses for photo requests in Network). - Saved stations show 3 navigation options (Google, Apple, Waze) on both desktop and mobile. - No lint/test regressions; container build succeeds. ## Validation (Container-First) - Rebuild and watch logs: `make rebuild` then `make logs` - Optional focused logs: `make logs-frontend` and `make logs-backend` - Run feature tests where available (prefer container exec): - Backend: `docker compose exec mvp-backend npm test -- features/stations` - Frontend: `docker compose exec mvp-frontend npm test -- stations` - E2E: `docker compose exec mvp-frontend npm run e2e`