19 KiB
Gas Stations Feature - Complete Implementation Plan (K8s-Aligned Secrets)
Overview
Implement complete Gas Stations feature with full integration, map visualization, backend improvements, and comprehensive testing. Uses K8s-aligned runtime secrets pattern for frontend Google Maps API key.
Phase 1: Frontend Secrets Infrastructure (K8s-Aligned)
1.1 Create Frontend Config Loader
Create frontend/scripts/load-config.sh:
- Bash script that runs as container entrypoint
- Reads /run/secrets/google-maps-api-key
- Generates /usr/share/nginx/html/config.js with: window.CONFIG = { googleMapsApiKey: '...' }
- Falls back to empty string if secret not found
- Logs success/failure for debugging
1.2 Update Frontend Dockerfile
Modify frontend/Dockerfile:
- Copy load-config.sh into image
- Make script executable
- Add SECRETS_DIR environment variable (default: /run/secrets)
- Update CMD to run script before nginx: ["sh", "-c", "/app/load-config.sh && nginx -g 'daemon off;'"]
1.3 Update Frontend index.html
Modify frontend/index.html:
- Add <script src="/config.js"></script> before app bundle
- Config loads before React app initializes
- Add TypeScript types for window.CONFIG
1.4 Create Frontend Config Types
Create frontend/src/core/config/config.types.ts:
- Interface for window.CONFIG
- Export typed accessor: getConfig()
- Runtime validation (throw if required values missing)
1.5 Update docker-compose.yml
Add to mvp-frontend service: volumes:
- ./secrets/app/google-maps-api-key.txt:/run/secrets/google-maps-api-key:ro environment: SECRETS_DIR: /run/secrets
1.6 Document Pattern
Create frontend/docs/RUNTIME-CONFIG.md:
- Explain runtime config pattern
- How to add new runtime secrets
- K8s deployment notes
- Local development setup
Phase 2: Backend Improvements
2.1 Add Circuit Breaker Pattern
- Install opossum package: backend/package.json
- Create backend/src/features/stations/external/google-maps/google-maps.circuit-breaker.ts
- Update google-maps.client.ts to wrap API calls
- Follow platform feature pattern (reference: backend/src/features/platform/)
- Config: 10s timeout, 50% threshold, 30s reset
2.2 Backend Unit Tests
Create backend/src/features/stations/tests/unit/:
- stations.service.test.ts - Business logic, user isolation
- stations.repository.test.ts - Database operations, SQL queries
- google-maps.client.test.ts - API mocking, distance calculation
- Use test fixtures for sample data
2.3 Backend Integration Tests
Create backend/src/features/stations/tests/integration/:
- stations.api.test.ts - Full endpoint testing with real database
- saved-stations.flow.test.ts - Save, retrieve, delete workflow
- Use test database (configured in test setup)
2.4 Test Fixtures
Create backend/src/features/stations/tests/fixtures/:
- mock-stations.ts - Sample station objects
- mock-google-response.ts - Mock Google Places API responses
- test-coordinates.ts - Test location data
2.5 Cache Cleanup Job
Create backend/src/features/stations/jobs/cache-cleanup.job.ts:
- Scheduled job (daily at 2 AM)
- Delete station_cache entries older than 24 hours
- Log cleanup metrics (rows deleted)
- Register in main app scheduler
Phase 3: Frontend Foundation
3.1 Type Definitions
Create frontend/src/features/stations/types/stations.types.ts:
- Station interface (id, name, address, location, rating, distance, photoUrl, prices)
- SavedStation interface (extends Station, adds nickname, notes, isFavorite)
- SearchRequest interface (latitude, longitude, radius?, fuelType?)
- SearchResponse interface
- MapMarker interface for map pins
3.2 API Client
Create frontend/src/features/stations/api/stations.api.ts:
- searchStations(request: SearchRequest): Promise<Station[]>
- saveStation(placeId: string, data: SaveStationData): Promise
- getSavedStations(): Promise<SavedStation[]>
- deleteSavedStation(placeId: string): Promise
- Use axios with proper error handling
- Add request/response logging
3.3 React Query Hooks
Create frontend/src/features/stations/hooks/:
useStationsSearch.ts:
- Mutation hook for search (not cached by default)
- Accepts SearchRequest
- Returns Station array
- Integrates with useGeolocation
useSavedStations.ts:
- Query hook with React Query caching
- Auto-refetch on window focus
- Invalidate on save/delete mutations
useSaveStation.ts:
- Mutation hook
- Optimistic updates to saved list
- Invalidates saved stations cache on success
useDeleteStation.ts:
- Mutation hook
- Optimistic removal from list
- Invalidates cache on success
useGeolocation.ts:
- Browser Geolocation API wrapper
- Permission handling
- Error states (permission denied, unavailable, timeout)
- Returns current position
3.4 Google Maps Integration
Create frontend/src/features/stations/utils/maps-loader.ts:
- Load Google Maps JavaScript API dynamically
- Use runtime config: getConfig().googleMapsApiKey
- Promise-based API
- Singleton pattern (load once)
- TypeScript types for Google Maps objects
Phase 4: Frontend Components
4.1 Core Components
Create frontend/src/features/stations/components/:
StationCard.tsx:
- Material-UI Card component
- Props: station, onSave, onDelete, isSaved
- Display: name (Typography h6), address (Typography body2), distance (Chip)
- Rating stars (Rating component)
- Price display (if available)
- Photo thumbnail (if available)
- Save/Unsave IconButton (BookmarkIcon / BookmarkBorderIcon)
- Directions link (opens Google Maps)
- Mobile: 44px min height, touch targets
- Desktop: hover effects
StationsList.tsx:
- Container for search results
- Props: stations[], loading, error, onSaveStation
- Grid layout (responsive: 1 col mobile, 2 cols tablet, 3 cols desktop)
- Loading skeleton (Skeleton components)
- Empty state: "No stations found. Try adjusting your search."
- Error state with retry button
SavedStationsList.tsx:
- User's favorites display
- Props: savedStations[], onDelete, onSelect
- List layout (vertical)
- Show nickname if set, otherwise station name
- Notes preview (truncated)
- Distance from current location (optional)
- Delete IconButton
- Empty state: "No saved stations yet. Save stations from search results."
StationsSearchForm.tsx:
- Form with search parameters
- Current Location button (uses useGeolocation)
- Manual lat/lng inputs (number fields)
- Radius slider (FormControl, Slider: 1-25 miles, default 5)
- Fuel type select (optional filter)
- Search button (LoadingButton)
- Loading states on button
- Validation: require location (current OR manual)
- Error display for geolocation failures
StationMap.tsx:
- Google Maps embed component
- Props: stations[], center, zoom, onMarkerClick
- Load map using maps-loader utility
- Station markers (color-coded: blue=normal, gold=saved)
- Current location marker (red pin)
- Info windows on marker click (station details + save button)
- Directions button in info window
- Zoom controls, pan controls
- Responsive height (300px mobile, 500px desktop)
4.2 UI Utilities
Create frontend/src/features/stations/utils/:
distance.ts:
- formatDistance(meters: number): string - "1.2 mi" or "0.3 mi"
- calculateDistance(lat1, lng1, lat2, lng2): number - Haversine formula
location.ts:
- getCurrentPosition(): Promise
- requestLocationPermission(): Promise
- Error handling helpers
map-utils.ts:
- createStationMarker(station, map, isSaved): google.maps.Marker
- createInfoWindow(station): google.maps.InfoWindow
- fitBoundsToMarkers(map, markers): void
Phase 5: Desktop Implementation
5.1 Desktop Page
Create frontend/src/features/stations/pages/StationsPage.tsx:
- Layout: Grid container
- Left column (60%): StationMap (full height)
- Right column (40%):
- StationsSearchForm at top
- Tabs: "Search Results" | "Saved Stations"
- Tab 1: StationsList
- Tab 2: SavedStationsList
- State management:
- searchResults (from search mutation)
- savedStations (from query)
- selectedStation (for map focus)
- Effects:
- Load saved stations on mount
- Update map when search results change
- Mobile breakpoint: Stack vertically (map on top)
5.2 Desktop Routing
Update frontend/src/App.tsx (line 556):
- Remove: <Route path="/stations" element={Stations (TODO)} />
- Add: <Route path="/stations" element={} />
- Import: import { StationsPage } from './features/stations/pages/StationsPage'
- Ensure route is inside ProtectedRoute wrapper
Phase 6: Mobile Implementation
6.1 Mobile Screen
Create frontend/src/features/stations/pages/StationsMobileScreen.tsx:
- BottomNavigation with 3 tabs: Search, Saved, Map
- Tab 0 (Search):
- StationsSearchForm (compact mode)
- StationsList (vertical scroll)
- Pull-to-refresh support
- Tab 1 (Saved):
- SavedStationsList (full screen)
- Swipe to delete gestures
- Tab 2 (Map):
- StationMap (full screen, 100vh)
- Floating search button (FAB) to go back to search tab
- Bottom sheet for station details (SwipeableDrawer)
- Touch targets: 44px minimum
- Safe area insets for notched devices
6.2 Mobile Routing
Update frontend/src/App.tsx:
- Add mobile route: <Route path="/m/stations" element={} />
- Update mobile navigation items (add stations)
- Ensure icon is interactive (onClick navigation)
Phase 7: Fuel Logs Integration
7.1 Station Picker Component
Create frontend/src/features/fuel-logs/components/StationPicker.tsx:
- Autocomplete component (Material-UI)
- Props: value, onChange, userLocation?
- Options:
- Saved stations (shown first, grouped)
- Nearby stations (if location available)
- Manual text input (freeSolo mode)
- Option rendering:
- Station name (primary)
- Distance + address (secondary)
- Bookmark icon if saved
- Debounced search (300ms)
- Loading indicator while searching
- Fallback to text input if API fails
7.2 Update Fuel Log Form
Modify frontend/src/features/fuel-logs/components/FuelLogForm.tsx:
- Replace LocationInput with StationPicker
- Props: pass user location from geolocation
- Value binding to stationName field
- Maintain backward compatibility (accepts text string)
- Show nearby stations on field focus
- Optional: "Save this station" checkbox when new station entered
7.3 Integration Features
Optional enhancements:
- In FuelLogCard, link station name to station details
- In StationsPage, show "Recent fuel logs at this station"
- Suggest saving station after logging fuel at new location
Phase 8: Testing
8.1 Backend Tests Execution
cd backend npm test -- features/stations
- Verify all unit tests pass
- Verify all integration tests pass
- Check coverage report (aim for >80%)
- Fix any failing tests
8.2 Frontend Component Tests
Create frontend/src/features/stations/tests/components/:
- StationCard.test.tsx - Rendering, save/delete actions
- StationsList.test.tsx - List rendering, empty/error states
- SavedStationsList.test.tsx - Saved list, delete action
- StationsSearchForm.test.tsx - Form validation, submission
- StationMap.test.tsx - Map initialization (with mocks)
- Use React Testing Library
- Mock API calls with MSW (Mock Service Worker)
8.3 Frontend Page Tests
Create frontend/src/features/stations/tests/pages/:
- StationsPage.test.tsx - Desktop page integration
- StationsMobileScreen.test.tsx - Mobile navigation, tabs
- Test user workflows: search, save, delete
- Test error handling and loading states
8.4 API Client Tests
Create frontend/src/features/stations/api/tests/:
- stations.api.test.ts - Test all API methods
- Mock axios responses
- Test error handling (network, 401, 500)
- Verify request payloads
8.5 React Query Hook Tests
Create frontend/src/features/stations/hooks/tests/:
- Test each hook in isolation
- Mock React Query
- Test loading, success, error states
- Test cache invalidation logic
- Test optimistic updates
8.6 E2E Tests
Create frontend/cypress/e2e/stations.cy.ts (or similar E2E framework):
- Test: Search for nearby stations
- Test: Save a station to favorites
- Test: View saved stations list
- Test: Delete a saved station
- Test: Use station in fuel log entry
- Test: Mobile navigation flow
- Test: Map marker click and info window
- Use real backend API in test environment
Phase 9: Documentation
9.1 Backend Feature Docs
Create backend/src/features/stations/docs/:
ARCHITECTURE.md:
- System design overview
- Data flow diagrams
- External dependencies (Google Maps API)
- Caching strategy
- Database schema
- Circuit breaker pattern
API.md:
- All endpoint documentation
- Request/response examples (curl commands)
- Authentication requirements
- Rate limits and quotas
- Error responses
TESTING.md:
- How to run tests
- Test database setup
- Writing new tests
- Coverage goals
GOOGLE-MAPS-SETUP.md:
- API key creation in Google Cloud Console
- Required APIs to enable
- Quota management
- Cost estimation
- Security best practices
9.2 Frontend Feature Docs
Create frontend/src/features/stations/README.md:
- Feature overview
- Component hierarchy
- Hook usage examples
- Adding new functionality
- Runtime config pattern
- Testing guide
9.3 Runtime Config Documentation
Already created in Phase 1.6: frontend/docs/RUNTIME-CONFIG.md
9.4 Update Main Documentation
Update docs/README.md:
- Add "Gas Stations" to features list
- Link to backend and frontend docs
- Mention Google Maps integration
Update backend/src/features/stations/README.md:
- Complete feature overview
- Configuration requirements
- API endpoints summary
- Testing instructions
- Deployment notes
Phase 10: Validation & Polish
10.1 Docker Build & Test
make rebuild # Rebuild all containers make logs # Watch for errors make migrate # Run database migrations make test # Run all tests
Verify:
- Frontend config.js is generated at startup
- Backend loads Google Maps API key from secret
- All containers start without errors
- Database migrations create tables
- Tests pass in container environment
10.2 Linting & Formatting
Backend
cd backend && npm run lint && npm run type-check
Frontend
cd frontend && npm run lint && npm run type-check
Fix all issues (MUST be 100% green per CLAUDE.md):
- Zero TypeScript errors
- Zero ESLint warnings
- Zero Prettier formatting issues
10.3 Manual Testing - Desktop
- Open https://motovaultpro.com/stations in browser
- Test geolocation permission flow
- Search for stations near current location
- Verify map loads with markers
- Click marker, verify info window
- Save a station
- View saved stations list
- Delete a saved station
- Test manual lat/lng search
- Test with no results (remote location)
- Test error handling (API failure)
10.4 Manual Testing - Mobile
- Open mobile nav, navigate to Stations
- Test all 3 tabs (Search, Saved, Map)
- Verify 44px touch targets
- Test swipe gestures
- Test bottom sheet interactions
- Verify responsive layout
- Test on actual mobile device (if possible)
10.5 Fuel Logs Integration Testing
- Navigate to Fuel Logs
- Create new fuel log entry
- Test StationPicker autocomplete
- Select a saved station
- Enter new station name
- Submit fuel log
- Verify station name is saved
10.6 Cross-Browser Testing
Test on:
- Chrome (latest)
- Safari (latest)
- Firefox (latest)
- Mobile Safari (iOS)
- Mobile Chrome (Android)
10.7 Performance Validation
- Check map load time (<2s)
- Verify API response times (<500ms)
- Check Redis cache hits (backend logs)
- Monitor Google Maps API quota usage
- Verify no memory leaks (React DevTools Profiler)
Phase 11: Deployment Preparation
11.1 Configuration Checklist
Backend:
- Secret file exists: ./secrets/app/google-maps-api-key.txt
- Docker compose mounts secret to /run/secrets/google-maps-api-key
- Backend config loader reads from SECRETS_DIR
Frontend:
- Secret file mounted: /run/secrets/google-maps-api-key
- Entrypoint script generates /usr/share/nginx/html/config.js
- index.html loads config.js before app
- App accesses via getConfig().googleMapsApiKey
11.2 Database Migration
make migrate Verify:
- Migration 001_create_stations_tables.sql runs successfully
- Tables created: station_cache, saved_stations
- Indexes created on both tables
- Test queries work
11.3 Secrets Verification
Verify backend can read secret
docker compose exec mvp-backend cat /run/secrets/google-maps-api-key
Verify frontend generates config
docker compose exec mvp-frontend cat /usr/share/nginx/html/config.js
Should show: window.CONFIG = { googleMapsApiKey: "..." }
11.4 Health Checks
- Verify /health endpoint includes stations feature
- Test stations API endpoints with curl:
Get JWT token first
TOKEN="your-jwt-token"
Search stations
curl -X POST http://localhost:3001/api/stations/search
-H "Authorization: Bearer $TOKEN"
-H "Content-Type: application/json"
-d '{"latitude": 37.7749, "longitude": -122.4194}'
Get saved stations
curl http://localhost:3001/api/stations/saved
-H "Authorization: Bearer $TOKEN"
11.5 Production Readiness
Update config/app/production.yml.example:
- Document Google Maps API key requirement
- Add example configuration
Create deployment checklist:
- Google Maps API key in secrets
- Frontend container mounts secret
- Backend container mounts secret
- Database migrations run
- All tests pass
- Linters pass
- Manual testing complete
- Performance acceptable
- Documentation complete
Success Criteria (per CLAUDE.md)
- All linters pass with zero issues
- All tests pass (backend + frontend)
- Feature works end-to-end on desktop
- Feature works end-to-end on mobile
- Fuel logs integration functional
- Map visualization working
- Google Maps API integration live via runtime secrets
- K8s-aligned secrets pattern implemented
- Documentation complete
- Old code deleted (no orphaned files)
K8s Secrets Pattern Summary
Backend (already implemented):
- Secret mounted: /run/secrets/google-maps-api-key
- Read at runtime by config-loader.ts
- Never in environment variables or code
Frontend (new implementation):
- Secret mounted: /run/secrets/google-maps-api-key
- Read at container startup by entrypoint script
- Injected into config.js served with static files
- App loads config at runtime via window.CONFIG
- Can be updated by restarting container (no rebuild)
This mirrors K8s deployment where:
- Secrets are mounted as volumes
- Applications read them at runtime
- Secrets can be rotated without rebuilding images
Estimated Timeline
- Phase 1 (Frontend Secrets): 1 day
- Phase 2 (Backend): 1 day
- Phase 3 (Foundation): 0.5 day
- Phase 4 (Components): 1 day
- Phase 5 (Desktop): 0.5 day
- Phase 6 (Mobile): 0.5 day
- Phase 7 (Integration): 0.5 day
- Phase 8 (Testing): 1.5 days
- Phase 9 (Docs): 0.5 day
- Phase 10 (Validation): 0.5 day
- Phase 11 (Deployment): 0.5 day
Total: 8 days (full implementation with K8s-aligned secrets)
Reference Implementations
- Secrets pattern: Backend config-loader.ts and docker-compose.yml
- Feature structure: frontend/src/features/vehicles/
- Component patterns: Vehicles components
- Testing: Vehicles test suite
- Mobile + Desktop: Vehicles pages