# FuelFinder Mobile Landing Page Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Replace the static homepage with a Livewire-powered `FuelFinder` component that provides search, map, recommendation, stations list, and forecast sections for mobile. **Architecture:** A single `FuelFinder` Livewire component drives all page state — it calls `/api/stations` and `/api/prediction`, then delegates rendering to reusable `x-fuel.*` and layout Blade components. The existing `StationSearch` at `/stations` is untouched. **Tech Stack:** Livewire 4, Alpine.js, Tailwind CSS v4, Flux UI v2, Leaflet (existing `station-map.js`), iconify-icon (existing). --- ## File Map | Action | Path | Responsibility | |---|---|---| | Create | `app/Livewire/Public/FuelFinder.php` | Component class: properties, `findStations()`, updated* hooks | | Create | `resources/views/livewire/public/fuel-finder.blade.php` | Full mobile page template | | Create | `resources/views/components/fuel/type-select.blade.php` | Fuel type `` with `wire:model` passthrough | | Create | `resources/views/components/fuel/sort-select.blade.php` | Sort ` ``` - [ ] **Step 4: Run the tests** ```bash php artisan test --compact tests/Feature/Livewire/FuelFinderTest.php ``` Expected: All tests PASS. - [ ] **Step 5: Commit** ```bash git add app/Livewire/Public/FuelFinder.php resources/views/livewire/public/fuel-finder.blade.php tests/Feature/Livewire/FuelFinderTest.php git commit -m "feat: add FuelFinder Livewire component with tests" ``` --- ## Task 3: Create `x-fuel.*` Blade Components **Files:** - Create: `resources/views/components/fuel/type-select.blade.php` - Create: `resources/views/components/fuel/radius-select.blade.php` - Create: `resources/views/components/fuel/sort-select.blade.php` - Create: `resources/views/components/fuel/station-card.blade.php` - Create: `resources/views/components/fuel/station-map.blade.php` - Create: `resources/views/components/fuel/recommendation.blade.php` - Create: `resources/views/components/fuel/forecast.blade.php` These are anonymous Blade components (no class file needed). Create the `fuel/` directory, then create each file. - [ ] **Step 1: Create `fuel/type-select.blade.php`** ```blade ``` - [ ] **Step 2: Create `fuel/radius-select.blade.php`** ```blade ``` - [ ] **Step 3: Create `fuel/sort-select.blade.php`** ```blade ``` - [ ] **Step 4: Create `fuel/station-card.blade.php`** ```blade @props(['station']) @php $colourClass = match($station['price_classification'] ?? '') { 'current' => 'text-green-500', 'recent' => 'text-slate-500', 'stale' => 'text-amber-500', 'outdated' => 'text-red-500', default => 'text-zinc-400', }; $miles = number_format(($station['distance_km'] ?? 0) * 0.621371, 1); $price = number_format($station['price'] ?? 0, 1); @endphp

{{ $station['name'] ?? '' }}

@if (! empty($station['is_supermarket'])) Supermarket @endif

{{ $station['address'] ?? '' }}, {{ $station['postcode'] ?? '' }}

{{ $miles }} miles away

{{ $price }}p

{{ $station['price_classification_label'] ?? '' }} @if (! empty($station['price_updated_at'])) · {{ \Carbon\Carbon::parse($station['price_updated_at'])->diffForHumans() }} @endif

``` - [ ] **Step 5: Create `fuel/station-map.blade.php`** ```blade @props(['results' => []])
``` - [ ] **Step 6: Create `fuel/recommendation.blade.php`** ```blade @props(['prediction']) @if ($prediction) @php $action = $prediction['action'] ?? 'no_signal'; $headline = match ($action) { 'fill_now' => 'Fill up now', 'wait' => 'Wait', default => 'No signal', }; $score = (float) ($prediction['confidence_score'] ?? 0); $circumference = 125.6; // 2π × 20 $offset = round($circumference * (1 - $score / 100), 1); $label = $prediction['confidence_label'] ?? 'low'; $labelColour = match ($label) { 'high' => 'bg-green-100 text-green-700', 'medium' => 'bg-amber-100 text-amber-700', default => 'bg-zinc-100 text-zinc-500', }; @endphp

Recommendation

{{ $headline }}

{{ (int) $score }}%
Confidence

{{ $prediction['reasoning'] ?? '' }}

{{ ucfirst($label) }} confidence
@endif ``` - [ ] **Step 7: Create `fuel/forecast.blade.php`** ```blade
{{-- Pro badge --}}

14-Day Forecast

Price Trend

Pro
{{-- Decorative squiggle chart --}} {{-- Blurred overlay --}}

14-day forecast is a Pro feature

Unlock Forecast
``` - [ ] **Step 8: Run tests to confirm they still pass** ```bash php artisan test --compact tests/Feature/Livewire/FuelFinderTest.php ``` Expected: All PASS. - [ ] **Step 9: Commit** ```bash git add resources/views/components/fuel/ git commit -m "feat: add x-fuel.* blade components for FuelFinder" ``` --- ## Task 4: Create Mobile Layout Components **Files:** - Create: `resources/views/components/mobile-header.blade.php` - Create: `resources/views/components/mobile-footer.blade.php` - [ ] **Step 1: Create `mobile-header.blade.php`** ```blade
FuelAlert
@auth @else @endauth
``` - [ ] **Step 2: Create `mobile-footer.blade.php`** ```blade @php $tabs = [ ['label' => 'Prices', 'icon' => 'lucide:fuel', 'route' => 'home'], ['label' => 'Alerts', 'icon' => 'lucide:bell', 'route' => null], ['label' => 'Forecourts', 'icon' => 'lucide:map-pin', 'route' => null], ['label' => 'Trends', 'icon' => 'lucide:trending-up','route' => null], ]; $currentRoute = request()->routeIs('home') ? 'home' : null; @endphp ``` - [ ] **Step 3: Run tests** ```bash php artisan test --compact tests/Feature/Livewire/FuelFinderTest.php ``` Expected: All PASS. - [ ] **Step 4: Commit** ```bash git add resources/views/components/mobile-header.blade.php resources/views/components/mobile-footer.blade.php git commit -m "feat: add x-mobile-header and x-mobile-footer components" ``` --- ## Task 5: Build the FuelFinder View Template **Files:** - Modify: `resources/views/livewire/public/fuel-finder.blade.php` - [ ] **Step 1: Replace the stub with the full mobile template** ```blade
{{-- Scrollable main content, offset for fixed header (~80px) and footer (~80px) --}}
{{-- #search --}}
@error('search')

{{ $message }}

@enderror {{-- Filter pills (scrollable row) --}}
@if ($apiError)
{{ $apiError }}
@endif
{{-- #recommendation --}} @if ($prediction)
@endif {{-- #map --}}
{{-- #stations --}} @if ($hasSearched)
@if (! empty($meta))

Stations Nearby

{{ $meta['count'] ?? 0 }} {{ str('Result')->plural($meta['count'] ?? 0) }}
@endif @forelse ($results as $station)
@empty

No stations found within {{ $radius }} {{ str('mile')->plural($radius) }} of "{{ $search }}".

@endforelse
@endif {{-- #forecast --}}
``` - [ ] **Step 2: Run tests** ```bash php artisan test --compact tests/Feature/Livewire/FuelFinderTest.php ``` Expected: All PASS. - [ ] **Step 3: Commit** ```bash git add resources/views/livewire/public/fuel-finder.blade.php git commit -m "feat: build fuel-finder view with mobile layout" ``` --- ## Task 6: Update the Route and Run Final Checks **Files:** - Modify: `routes/web.php` - [ ] **Step 1: Update the home route** In `routes/web.php`, replace: ```php Route::view('/', 'homepage')->name('home'); ``` With: ```php Route::get('/', \App\Livewire\Public\FuelFinder::class)->name('home'); ``` - [ ] **Step 2: Run all tests** ```bash php artisan test --compact ``` Expected: Full suite PASS. (The old homepage tests in `ExampleTest.php` and `DashboardTest.php` should still pass since they don't hit `/` directly.) - [ ] **Step 3: Run Pint on all modified PHP files** ```bash vendor/bin/pint --dirty --format agent ``` - [ ] **Step 4: Re-run tests after Pint** ```bash php artisan test --compact ``` Expected: All PASS. - [ ] **Step 5: Commit** ```bash git add routes/web.php git commit -m "feat: wire FuelFinder to home route, replacing static homepage" ``` --- ## Self-Review Against Spec | Spec Requirement | Covered by | |---|---| | `Route::get('/', FuelFinder::class)->name('home')` | Task 6 Step 1 | | All FuelFinder properties | Task 2 Step 2 | | `findStations()` — validate, reset, call `/api/stations`, populate results/meta, set `$hasSearched` | Task 2 Step 2 | | `findStations()` — call `/api/prediction` | Task 2 Step 2 | | `updatedFuelType/Radius/Sort` re-run if `$hasSearched` | Task 2 Step 2 | | `x-fuel.type-select` with wire passthrough | Task 3 Step 1 | | `x-fuel.radius-select` with wire passthrough | Task 3 Step 2 | | `x-fuel.sort-select` with wire passthrough | Task 3 Step 3 | | `x-fuel.station-card` — name, address, distance, price, classification colour, supermarket badge | Task 3 Step 4 | | `x-fuel.station-map` — UK centre default, re-centres after search | Task 3 Step 5 (JS already handles this in `station-map.js`) | | `x-fuel.recommendation` — action headline, confidence ring, reasoning, label badge | Task 3 Step 6 | | `x-fuel.forecast` — SVG chart, blur overlay, Pro badge, Unlock CTA | Task 3 Step 7 | | `x-mobile-header` — logo + user icon (auth-aware) | Task 4 Step 1 | | `x-mobile-footer` — 4 tabs, active highlight | Task 4 Step 2 | | View structure — search, recommendation, map, stations, forecast | Task 5 Step 1 | | Recommendation hidden until `$prediction` set | Task 5 Step 1 (`@if $prediction`) | | Map always visible | Task 5 Step 1 (outside any conditional) | | Stations list under `$hasSearched` guard | Task 5 Step 1 (`@if $hasSearched`) | | Forecast always shown | Task 5 Step 1 (always rendered) |