From 156d925a3502e29a8871b830bf67f7e0394ea8a2 Mon Sep 17 00:00:00 2001 From: Ovidiu U Date: Sun, 5 Apr 2026 20:17:13 +0100 Subject: [PATCH] docs: add station search page design spec --- .../2026-04-05-station-search-page-design.md | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-05-station-search-page-design.md diff --git a/docs/superpowers/specs/2026-04-05-station-search-page-design.md b/docs/superpowers/specs/2026-04-05-station-search-page-design.md new file mode 100644 index 0000000..b01ca52 --- /dev/null +++ b/docs/superpowers/specs/2026-04-05-station-search-page-design.md @@ -0,0 +1,143 @@ +# Station Search Page — Design Spec + +**Date:** 2026-04-05 +**Status:** Approved + +--- + +## Overview + +A public-facing Livewire page that lets anyone search for nearby petrol stations by postcode, town, or city. The user selects a fuel type and search radius in miles. On submit, results are fetched server-side from `/api/stations` and displayed as a list. + +--- + +## Routing + +- Route: `GET /stations` — public, no auth middleware +- Registered in `routes/web.php` as a full-page Livewire component +- Uses the existing `x-layouts::app` (sidebar) layout + +--- + +## Component + +**Class:** `app/Livewire/Public/StationSearch.php` +**View:** `resources/views/livewire/public/station-search.blade.php` + +### Public properties + +| Property | Type | Default | Notes | +|---|---|---|---| +| `$search` | string | `''` | Postcode, outcode, town or city | +| `$fuelType` | string | `''` | API fuel type alias (e.g. `petrol`, `diesel`) | +| `$radius` | int | `5` | In miles — converted to km before API call | +| `$results` | array | `[]` | Populated from API `data` array | +| `$meta` | array | `[]` | Populated from API `meta` object | +| `$apiError` | string\|null | `null` | Human-readable error from API or network failure | + +### Method: `findStations()` + +1. Validate: `$search` required, `$fuelType` required, `$radius` integer between 1–20 +2. Reset `$results`, `$meta`, `$apiError` +3. Convert radius: `$radiusKm = $this->radius * 1.60934` +4. Call `Http::timeout(10)->withHeaders(['X-Api-Key' => config('services.fuelalert.api_key')])->get(url('/api/stations'), ['postcode' => $this->search, 'fuel_type' => $this->fuelType, 'radius' => $radiusKm, 'sort' => 'price'])` +5. On 422 — extract `errors.postcode[0]` or `message` and set `$apiError` +6. On other non-2xx — set `$apiError` to a generic message +7. On success — set `$results` and `$meta` from response JSON + +### Configuration + +- API key read from `config('services.fuelalert.api_key')` +- `.env` key: `FUELALERT_API_KEY` +- Add to `config/services.php` under `fuelalert` + +--- + +## Form UI + +Three fields in a horizontal row (stacks vertically on mobile): + +1. **Search** — text input, placeholder "Postcode, town or city" +2. **Fuel type** — select with options: + - Petrol (E10) → `petrol` + - Super Unleaded (E5) → `e5` + - Diesel → `diesel` + - Premium Diesel → `b7_premium` + - B10 Biodiesel → `b10` + - HVO → `hvo` +3. **Radius** — select: 1, 2, 5, 10, 20 miles (default 5) + +Submit button with `wire:loading` spinner and disabled state while request is in flight. + +Inline validation errors shown below each field using `@error`. + +--- + +## Results List + +Shown below the form after a successful search. + +**Meta bar** (above results): "{count} stations found · Cheapest: {lowest}p · Average: {avg}p" + +**Each station row:** +- Station name + brand (badge if supermarket) +- Address / postcode +- Distance in miles (convert `distance_km` from response: `× 0.621371`, rounded to 1dp) +- Price: formatted as `{price}p` per litre (e.g. `143.9p`) +- Last updated: human-readable relative time (e.g. "2 hours ago") using `Carbon::parse(...)->diffForHumans()` + +Sorted cheapest first (default). No client-side re-sorting in v1. + +--- + +## States + +| State | UI | +|---|---| +| Initial | Form only, no results area | +| Loading | Submit button shows spinner + disabled, no results change | +| Results | Meta bar + station list below form | +| API error | Error alert above results (e.g. "Postcode not found.") | +| Empty results | "No stations found within {radius} miles of {search}." | + +--- + +## Data Flow + +``` +User fills form → submit → findStations() → Http::get /api/stations + → success → $results + $meta populated → Livewire re-renders list + → 422 → $apiError = postcode error message + → other → $apiError = "Unable to fetch stations. Please try again." +``` + +--- + +## Config + +Add to `config/services.php`: +```php +'fuelalert' => [ + 'api_key' => env('FUELALERT_API_KEY'), +], +``` + +Add to `.env` and `.env.example`: +``` +FUELALERT_API_KEY= +``` + +--- + +## Testing + +Feature test: `tests/Feature/Livewire/StationSearchTest.php` + +Test cases: +- Form renders with empty state +- Validation: search required, fuel type required, radius required +- Successful search populates `$results` and `$meta` (Http::fake) +- 422 response sets `$apiError` +- Network failure sets `$apiError` +- Distance converted correctly from km to miles +- Radius converted correctly from miles to km in outgoing request