4.4 KiB
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.phpas 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()
- Validate:
$searchrequired,$fuelTyperequired,$radiusinteger between 1–20 - Reset
$results,$meta,$apiError - Convert radius:
$radiusKm = $this->radius * 1.60934 - 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']) - On 422 — extract
errors.postcode[0]ormessageand set$apiError - On other non-2xx — set
$apiErrorto a generic message - On success — set
$resultsand$metafrom response JSON
Configuration
- API key read from
config('services.fuelalert.api_key') .envkey:FUELALERT_API_KEY- Add to
config/services.phpunderfuelalert
Form UI
Three fields in a horizontal row (stacks vertically on mobile):
- Search — text input, placeholder "Postcode, town or city"
- Fuel type — select with options:
- Petrol (E10) →
petrol - Super Unleaded (E5) →
e5 - Diesel →
diesel - Premium Diesel →
b7_premium - B10 Biodiesel →
b10 - HVO →
hvo
- Petrol (E10) →
- 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_kmfrom response:× 0.621371, rounded to 1dp) - Price: formatted as
{price}pper 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:
'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
$resultsand$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