- Delete `.claude/rules/livewire.md` (Livewire is vestigial – only starter-kit auth screens remain) - Add `.claude/rules/frontend.md` documenting Vue 3 SPA stack, router, composables, search-URL mirroring, and Leaflet integration - Add `docs/ops/nginx-log-masking.md` runbook for redacting lat/lng from Nginx access/error logs and PHP-FPM on IONOS VPS - Update `CLAUDE.md` and `architecture.md` to reflect Vue SPA as primary frontend, REST API as backend contract, and Livewire's reduced role - Note stale service names in legacy domain docs (`scoring.md`, `prediction.md`, `notifications.md`) now refactored into `Services/Forecasting/`, `Services/StationSearch/`, and `PlanFeatures`
3.4 KiB
Frontend — Vue 3 SPA
The entire user-facing app (landing, station search, dashboard, settings) is a
Vue 3 single-page application under resources/js/. It is served from the
Blade shell resources/views/app.blade.php via the SPA catch-all in
routes/web.php and consumes the REST API (routes/api.php). There is no
Livewire in the product UI — do not add Livewire / Volt / Alpine components for
new features. Build Vue.
Stack
- Vue 3.5 with
<script setup>(Composition API), Vue Router 4 (createWebHistory) - Vite 8 (
npm run dev/npm run build),@vitejs/plugin-vue - Tailwind CSS v4 (
@tailwindcss/vite) - Leaflet for maps, iconify-icon + Lucide for icons, axios for HTTP
Layout
resources/js/
├── app.js # Entry — createApp(App).use(router).mount('#app')
├── App.vue # Root — <RouterView/>, fetches the user on mount
├── axios.js # Configured axios instance (baseURL '/api', XSRF + credentials)
├── router/index.js # Routes + requiresAuth guard (redirects to /login)
├── views/
│ ├── Home.vue # Landing + station search (mirrors state to the URL query)
│ └── dashboard/ # Overview, SavedStations, Preferences, settings/{Profile,Security,Appearance}
├── components/ # LeafletMap, StationCard, StationList, PredictionCard/Full,
│ # PostSearchFilters, UpsellBanner, landing/*
├── composables/ # useAuth, useStations, useSavedStations
└── constants/ # fuelTypes.js — shared fuel-type source of truth (front + back)
Conventions
- Composition API +
<script setup>only. No Options API. - All server state goes through composables that call the configured
apiaxios instance (axios.js) — neverfetch()or a bareaxioscall inside a component. - Auth is cookie/session (Sanctum SPA mode). axios is configured with
withCredentials+withXSRFToken; don't add bearer-token handling. - Route guards live in
router/index.js(meta.requiresAuth). Hard navigations (/login,/logout) usewindow.location.href, not the router. - Keep business logic in the API / Services. Vue components render and orchestrate.
Search & the shareable URL
Home.vue is the search surface. Search params (postcode or lat/lng,
fuel_type, radius, sort) are mirrored into the URL query via
router.push (queryFromParams) so searches are shareable and bookmarkable, and
restored on load (paramsFromQuery). Because the URL is shareable, any
coordinates written to it must be coarsened — a precise GPS pair from "Use my
location" (HeroSearch.vue → browser Geolocation) would otherwise broadcast the
sharer's exact position to everyone they send the link to.
Maps (Leaflet)
components/LeafletMap.vue wraps Leaflet directly (Leaflet is a plain JS library,
not a Vue plugin). Station and user markers and the Google-Maps directions links
are built from station.lat / station.lng; the user marker comes from the
browser Geolocation API.
Prediction
The fuel prediction ships inside the /api/stations response under the
prediction key and is rendered by PredictionCard.vue / PredictionFull.vue
(useStations reads response.data.prediction). See prediction.md for the
payload shape and the tier gate.
paths:
- "resources/js/**/*.vue"
- "resources/js/**/*.js"