Files
fuel-price/docs/superpowers/specs/2026-04-07-mobile-landing-fuelfinder-design.md
2026-04-07 14:11:04 +01:00

6.7 KiB
Raw Blame History

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

// 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.

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)