From 6da626347bb306ef1f86a442df971d099ddf490e Mon Sep 17 00:00:00 2001 From: Ovidiu U Date: Tue, 7 Apr 2026 14:43:03 +0100 Subject: [PATCH] docs: add FuelFinder mobile landing implementation plan --- .../2026-04-07-fuelfinder-mobile-landing.md | 963 ++++++++++++++++++ .../components/public/⚡fuel-finder.blade.php | 13 + 2 files changed, 976 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-07-fuelfinder-mobile-landing.md create mode 100644 resources/views/components/public/⚡fuel-finder.blade.php diff --git a/docs/superpowers/plans/2026-04-07-fuelfinder-mobile-landing.md b/docs/superpowers/plans/2026-04-07-fuelfinder-mobile-landing.md new file mode 100644 index 0000000..54196a4 --- /dev/null +++ b/docs/superpowers/plans/2026-04-07-fuelfinder-mobile-landing.md @@ -0,0 +1,963 @@ +# 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) | diff --git a/resources/views/components/public/⚡fuel-finder.blade.php b/resources/views/components/public/⚡fuel-finder.blade.php new file mode 100644 index 0000000..e625966 --- /dev/null +++ b/resources/views/components/public/⚡fuel-finder.blade.php @@ -0,0 +1,13 @@ + + +
+ {{-- Nothing in life is to be feared, it is only to be understood. Now is the time to understand more, so that we may fear less. - Maria Skłodowska-Curie --}} +
\ No newline at end of file