docs: add station search page design spec
This commit is contained in:
143
docs/superpowers/specs/2026-04-05-station-search-page-design.md
Normal file
143
docs/superpowers/specs/2026-04-05-station-search-page-design.md
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user