Files
fuel-price/docs/superpowers/specs/2026-04-05-station-search-page-design.md
2026-04-05 20:17:13 +01:00

144 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 120
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