# 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 `api`
  axios instance (`axios.js`) — never `fetch()` or a bare `axios` call 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`) use `window.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"
---
