docs: add FuelFinder mobile landing page design spec

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ovidiu U
2026-04-07 14:11:04 +01:00
parent 4b29e6fdb0
commit 3d552e8fcb
2 changed files with 203 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
# FuelFinder Mobile Landing Page — Design Spec
**Date:** 2026-04-07
**Scope:** Replace static mobile homepage with a fully functional Livewire-powered landing page, backed by reusable Blade components shared with the desktop search.
---
## Goals
- Mobile landing page (`/`) becomes the working app — search, recommendation, map, stations list, forecast
- All interactive sections are driven by a single new Livewire component (`FuelFinder`)
- Presentation elements extracted into reusable Blade components consumed by both `FuelFinder` and the existing `StationSearch`
- As lean as possible — no duplicated logic, one station repeater, one map component
---
## Route Change
```php
// routes/web.php
Route::get('/', FuelFinder::class)->name('home');
```
`/stations` (StationSearch) is kept as-is for now. Migration to shared components happens separately.
---
## Livewire Component: `FuelFinder`
**File:** `app/Livewire/Public/FuelFinder.php`
**View:** `resources/views/livewire/public/fuel-finder.blade.php`
### Properties
| Property | Type | Default | Notes |
|---|---|---|---|
| `$search` | `string` | `''` | Validated: required |
| `$fuelType` | `string` | `'petrol'` | Validated: required |
| `$radius` | `int` | `5` | Validated: 120 |
| `$sort` | `string` | `'reliable'` | Validated: in allowed list |
| `$results` | `array` | `[]` | Populated from `/api/stations` |
| `$meta` | `array` | `[]` | Count, lowest/avg price |
| `$prediction` | `?array` | `null` | Populated from `/api/prediction` |
| `$apiError` | `?string` | `null` | Surface API/connection errors |
| `$hasSearched` | `bool` | `false` | Controls section visibility |
### Methods
**`findStations()`**
1. Validate all properties
2. Reset results, meta, prediction, apiError, hasSearched
3. Call `GET /api/stations` with postcode, fuel_type, radius (km), sort
4. On success: populate `$results`, `$meta`, set `$hasSearched = true`
5. Call `GET /api/prediction` with `fuel_type` (no lat/lng — national fallback)
6. On success: populate `$prediction`
7. On any failure: set `$apiError`
**`updatedFuelType()` / `updatedRadius()` / `updatedSort()`**
Re-run `findStations()` if `$hasSearched` is true (live filter refresh).
---
## Blade Components
All components live under `resources/views/components/fuel/` and are namespaced as `x-fuel.*`.
### `x-fuel.type-select`
Fuel type `<select>`. Accepts `wire:model` passthrough via `$attributes->whereStartsWith('wire:')`.
Options: Petrol (E10), Super Unleaded (E5), Diesel, Premium Diesel, B10 Biodiesel, HVO.
Reused in: `FuelFinder`, `StationSearch`.
### `x-fuel.radius-select`
Radius `<select>`. Options: 1, 2, 5, 10, 20 miles.
Accepts `wire:model` passthrough.
### `x-fuel.sort-select`
Sort `<select>`. Options: Best price (reliable), Cheapest first, Nearest first, Recently updated, Brand AZ.
Accepts `wire:model` passthrough.
### `x-fuel.station-card :station`
Single station row. Props from `/api/stations` response:
- Name, address, postcode, distance (km → miles)
- Price in pence (formatted to 1dp)
- Price colour: `current` → green, `recent` → slate, `stale` → amber, `outdated` → red
- `is_supermarket` → shows "Supermarket" badge
### `x-fuel.station-map :results`
Leaflet map wrapper using Alpine `x-data="stationMap(@entangle('results'))"` pattern.
- Default centre: UK (`54.0, -2.0`), zoom 6 — shown before search
- After search: re-centres to fit result bounds automatically (handled in `station-map.js`)
- Height: `h-56` mobile / `h-96` desktop
### `x-fuel.recommendation :prediction`
Recommendation card driven by `/api/prediction` response.
- Shows `action` as headline: `fill_now` → "Fill up now", `wait` → "Wait", else "No signal"
- Confidence ring (SVG) from `confidence_score` (0100)
- `reasoning` text below
- Confidence label badge (`confidence_label`: low / medium / high)
- Hidden when `$prediction` is null
### `x-fuel.forecast`
Static 14-day forecast Pro upsell card.
- SVG squiggle line chart (decorative)
- Blurred/locked overlay with "Unlock Forecast" CTA button
- "Pro" badge
### `x-mobile-header`
Mobile app header: FuelAlert logo + user icon button (links to login/dashboard based on auth state).
`pt-14` for safe area, fixed positioning.
### `x-mobile-footer`
Mobile tab bar: Prices, Alerts, Forecourts, Trends. Sticky bottom, `pb-8` for safe area.
Active tab highlight for current route.
---
## View: `fuel-finder.blade.php`
Mobile layout only (desktop layout handled by existing homepage / StationSearch).
Structure:
```
<x-mobile-header />
<main> {{-- scrollable --}}
{{-- #search --}}
Search input + x-fuel.type-select + x-fuel.radius-select + x-fuel.sort-select
Displayed as pill-style filter buttons (scroll horizontally on mobile)
Submit triggers findStations()
{{-- #recommendation --}}
<x-fuel.recommendation :prediction="$prediction" />
Hidden (@if $prediction) until searched
{{-- #map --}}
<x-fuel.station-map :results="$results" />
Always visible — UK centre until search
{{-- #stations --}}
@if $hasSearched
@forelse $results as $station
<x-fuel.station-card :station="$station" />
@empty
"No stations found" message
@endforelse
@endif
{{-- #forecast --}}
<x-fuel.forecast />
</main>
<x-mobile-footer />
```
---
## API Contracts
### `/api/stations`
| Param | Type | Notes |
|---|---|---|
| `postcode` | string | Full postcode, outcode, or place name |
| `fuel_type` | string | e.g. `petrol`, `diesel`, `e5` |
| `radius` | float | km (convert from miles: × 1.60934) |
| `sort` | string | `reliable`, `price`, `distance`, `updated`, `brand` |
Response shape: `{ data: Station[], meta: { count, lowest_pence, avg_pence } }`
### `/api/prediction`
| Param | Type | Notes |
|---|---|---|
| `fuel_type` | string | e.g. `petrol`, `diesel` |
| `lat` | float? | Optional — falls back to national |
| `lng` | float? | Optional |
Key response fields used: `action`, `confidence_score`, `confidence_label`, `reasoning`, `predicted_direction`, `predicted_change_pence`.
---
## Data Flow
```
User types postcode → submits → findStations()
→ GET /api/stations → $results, $meta, $hasSearched=true
→ GET /api/prediction → $prediction
→ blade re-renders:
#recommendation shows with prediction data
#map re-centres to results
#stations lists results
```
Filter change (fuel type / radius / sort) → `updated*()``findStations()` if `$hasSearched`
---
## Not in scope
- Desktop layout changes (StationSearch at `/stations` unchanged)
- Migrating StationSearch to use shared Blade components (follow-up)
- lat/lng resolution for `/api/prediction` (uses national fallback for now)
- Tab bar navigation routes (Alerts, Forecourts, Trends pages don't exist yet)