chore: remove Livewire public components and homepage, prepare for Vue

This commit is contained in:
Ovidiu U
2026-04-10 17:53:03 +01:00
parent 771f499f36
commit 69e52afa7c
12 changed files with 2091 additions and 823 deletions

BIN
.DS_Store vendored

Binary file not shown.

BIN
.claude/.DS_Store vendored Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,203 +0,0 @@
import L from 'leaflet';
import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
import markerIcon from 'leaflet/dist/images/marker-icon.png';
import markerShadow from 'leaflet/dist/images/marker-shadow.png';
delete L.Icon.Default.prototype._getIconUrl;
L.Icon.Default.mergeOptions({
iconUrl: markerIcon,
iconRetinaUrl: markerIcon2x,
shadowUrl: markerShadow,
});
function escHtml(str) {
return String(str ?? '')
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
const CLASSIFICATION_COLOURS = {
current: '#22c55e',
recent: '#64748b',
stale: '#f59e0b',
outdated: '#ef4444',
};
const UK_CENTRE = [54.0, -2.0];
const UK_ZOOM = 7;
const USER_MARKER_CSS = `
@keyframes fuelalert-pulse {
0% { transform: scale(1); opacity: 0.6; }
70% { transform: scale(2.8); opacity: 0; }
100% { transform: scale(1); opacity: 0; }
}
.fuelalert-user-marker { position: relative; width: 16px; height: 16px; }
.fuelalert-user-dot { position: absolute; inset: 0; border-radius: 50%; background: #3b82f6; border: 2px solid #fff; box-shadow: 0 0 0 2px #3b82f6; }
.fuelalert-user-ring { position: absolute; inset: 0; border-radius: 50%; background: #3b82f6; animation: fuelalert-pulse 2s ease-out infinite; }
`;
function injectUserMarkerStyles() {
if (document.getElementById('fuelalert-user-marker-styles')) return;
const style = document.createElement('style');
style.id = 'fuelalert-user-marker-styles';
style.textContent = USER_MARKER_CSS;
document.head.appendChild(style);
}
export function stationMap(results, meta, radius) {
return {
results,
meta,
radius,
_map: null,
_markers: [],
_userMarker: null,
init() {
injectUserMarkerStyles();
this._map = L.map(this.$el, { zoomControl: true }).setView(UK_CENTRE, UK_ZOOM);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 19,
}).addTo(this._map);
window.addEventListener('map-update', (e) => {
this.results = e.detail.results;
this.meta = e.detail.meta;
this.radius = e.detail.radius;
this._plotMarkers();
});
this.locateUser();
},
getZoomForRadius(radiusMiles) {
if (radiusMiles <= 1) return 15;
if (radiusMiles <= 2) return 14;
if (radiusMiles <= 5) return 12;
if (radiusMiles <= 10) return 11;
if (radiusMiles <= 15) return 10;
if (radiusMiles <= 25) return 9;
if (radiusMiles <= 50) return 8;
return 7;
},
_clearMarkers() {
this._markers.forEach((m) => m.remove());
this._markers = [];
},
addUserMarker(lat, lng) {
if (this._userMarker) {
this._userMarker.remove();
}
const icon = L.divIcon({
className: '',
html: '<div class="fuelalert-user-marker"><div class="fuelalert-user-ring"></div><div class="fuelalert-user-dot"></div></div>',
iconSize: [16, 16],
iconAnchor: [8, 8],
});
this._userMarker = L.marker([lat, lng], { icon, zIndexOffset: 1000 })
.bindPopup('Your location')
.addTo(this._map);
console.log(`[stationMap] user marker lat=${lat} lng=${lng}`);
},
locateUser() {
if (!navigator.geolocation) {
console.warn('[stationMap] Geolocation not supported');
return;
}
const ipFallback = () => {
fetch('https://ipapi.co/json/')
.then((r) => r.json())
.then((d) => d.latitude && d.longitude && this.addUserMarker(d.latitude, d.longitude))
.catch(() => {});
};
// Quick low-accuracy fix first — places the marker immediately.
navigator.geolocation.getCurrentPosition(
(pos) => {
this.addUserMarker(pos.coords.latitude, pos.coords.longitude);
// Then refine with high accuracy if GPS is available.
navigator.geolocation.getCurrentPosition(
(precise) => this.addUserMarker(precise.coords.latitude, precise.coords.longitude),
() => {},
{ enableHighAccuracy: true, timeout: 10000, maximumAge: 0 },
);
},
() => ipFallback(),
{ enableHighAccuracy: false, timeout: 5000, maximumAge: 60000 },
);
},
destroy() {
if (this._map) {
this._map.remove();
this._map = null;
this._markers = [];
this._userMarker = null;
}
},
_plotMarkers() {
if (!this._map) {
return;
}
this._clearMarkers();
if (!this.results || this.results.length === 0) {
return;
}
this.results.forEach((station) => {
const colour = escHtml(CLASSIFICATION_COLOURS[station.price_classification] ?? '#64748b');
const miles = (station.distance_km * 0.621371).toFixed(1);
const supermarketTag = station.is_supermarket
? '<span style="display:inline-block;background:#84cc16;color:#fff;font-size:10px;padding:1px 5px;border-radius:3px;margin-left:4px;">Supermarket</span>'
: '';
const popup = `
<div style="min-width:160px">
<strong style="font-size:13px">${escHtml(station.name)}</strong>${supermarketTag}<br>
<span style="font-size:20px;font-weight:700;color:${colour}">${Number(station.price).toFixed(1)}p</span><br>
<span style="font-size:12px;color:#6b7280">${escHtml(miles)} miles away</span><br>
<span style="font-size:11px;color:#9ca3af">${escHtml(station.address)}, ${escHtml(station.postcode)}</span>
</div>
`;
const marker = L.circleMarker([station.lat, station.lng], {
radius: 9,
fillColor: colour,
color: '#ffffff',
weight: 2,
opacity: 1,
fillOpacity: 0.85,
}).bindPopup(popup);
marker.addTo(this._map);
this._markers.push(marker);
});
const map = this._map;
const lat = this.meta?.lat;
const lng = this.meta?.lng;
const zoom = this.getZoomForRadius(this.radius);
setTimeout(() => {
map.invalidateSize();
map.setView([lat, lng], zoom, { animate: true, duration: 0.5 });
console.log(`[stationMap] setView lat=${lat} lng=${lng} zoom=${zoom} (radius=${this.radius}mi)`);
}, 50);
},
};
}

View File

@@ -9,7 +9,7 @@
@php
$cardClass = match(true) {
$dark => 'bg-zinc-800 border border-zinc-800 text-white',
$dark => 'bg-primary border border-primary text-white',
$featured => 'bg-white border-2 border-primary',
default => 'bg-white border border-zinc-300',
};

View File

@@ -1,369 +0,0 @@
<x-layouts::public title="FuelAlert — Fill up now or wait?">
<div class="min-h-screen bg-zinc-100">
{{-- Navigation --}}
<nav class="fixed top-0 w-full z-50 bg-zinc-50 border-b border-zinc-300 px-6 py-4 md:px-12">
<div class="max-w-7xl mx-auto flex items-center justify-between">
<a href="{{ route('home') }}" class="flex items-center gap-3">
<div class="w-10 h-10 md:w-12 md:h-12 rounded-lg bg-primary flex items-center justify-center shadow-md">
<iconify-icon icon="lucide:fuel" class="text-white text-xl md:text-2xl"></iconify-icon>
</div>
<span class="font-display text-2xl md:text-3xl font-black tracking-tighter text-primary">FuelAlert</span>
</a>
<div class="hidden md:flex items-center gap-10">
<a href="#how-it-works" class="text-sm font-semibold text-zinc-500 hover:text-primary transition-colors">How it Works</a>
<a href="#features" class="text-sm font-semibold text-zinc-500 hover:text-primary transition-colors">Features</a>
<a href="#pricing" class="text-sm font-semibold text-zinc-500 hover:text-primary transition-colors">Pricing</a>
</div>
<div class="flex items-center gap-4">
@auth
<a href="{{ route('dashboard') }}" class="text-sm font-bold text-zinc-500 hover:text-zinc-800 transition-colors">Dashboard</a>
@else
<a href="{{ route('login') }}" class="text-sm font-bold text-zinc-500 hover:text-zinc-800 transition-colors">Login</a>
<a href="{{ route('register') }}" class="bg-primary text-white px-6 py-2.5 rounded-full text-sm font-bold shadow-lg hover:bg-primary-dark transition-all hover:scale-105 active:scale-95">Get Started</a>
@endauth
</div>
</div>
</nav>
{{-- Hero Section --}}
<section class="relative pt-40 pb-24 px-6 hero-gradient overflow-hidden">
<div class="max-w-7xl mx-auto grid lg:grid-cols-2 gap-16 items-center">
<div class="space-y-8">
<div class="inline-flex items-center gap-2 px-3 py-1 bg-primary/10 text-primary rounded-full text-[10px] md:text-xs font-bold uppercase tracking-wider">
<iconify-icon icon="lucide:sparkles"></iconify-icon>
Save up to £250/year on fuel
</div>
<h1 class="font-display text-3xl md:text-5xl lg:text-7xl font-black text-zinc-800 leading-[1.1] tracking-tighter">
Stop Overpaying <br> <span class="text-primary">for Fuel.</span>
</h1>
<p class="text-base md:text-xl text-zinc-500 max-w-lg leading-relaxed">
Join 50,000+ UK drivers using real-time insights to find the cheapest petrol and time their fill-ups perfectly.
</p>
{{-- Search Section --}}
<div class="flex flex-col sm:flex-row gap-3 max-w-md">
<div class="relative flex-1">
<iconify-icon icon="lucide:map-pin" class="absolute left-4 top-1/2 -translate-y-1/2 text-zinc-500 text-xl"></iconify-icon>
<input type="text" placeholder="Enter Postcode"
class="w-full h-14 pl-12 pr-4 bg-white border border-zinc-300 rounded-xl text-lg shadow-inner focus:outline-none focus:ring-2 focus:ring-primary">
</div>
<button class="h-14 px-8 bg-primary text-white rounded-xl font-bold text-lg shadow-xl hover:bg-primary-dark transition-all">
Find Prices
</button>
</div>
{{-- Search Section --}}
<div class="flex items-center gap-4 pt-4">
<div class="flex -space-x-2">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=1" alt="" class="w-8 h-8 rounded-full border-2 border-white">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=2" alt="" class="w-8 h-8 rounded-full border-2 border-white">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=3" alt="" class="w-8 h-8 rounded-full border-2 border-white">
</div>
<span class="text-sm text-zinc-500 font-medium italic">"Saved me £12 on my first tank!"</span>
</div>
</div>
{{-- Hero card mockup --}}
<div class="relative hidden lg:block">
<div class="absolute -inset-4 bg-primary/5 rounded-[2.5rem] blur-2xl"></div>
<div class="relative glass-card p-6 rounded-4xl shadow-2xl space-y-4 max-w-md mx-auto rotate-2">
<div class="flex justify-between items-center">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded bg-primary flex items-center justify-center">
<iconify-icon icon="lucide:fuel" class="text-white"></iconify-icon>
</div>
<span class="font-display font-black text-primary">FuelAlert</span>
</div>
<span class="text-xs font-bold text-zinc-500">SW1A 1AA</span>
</div>
<div class="bg-zinc-50 p-4 rounded-xl border border-zinc-300 shadow-sm">
<p class="text-[10px] font-bold uppercase tracking-widest text-zinc-500 mb-1">Recommendation</p>
<h3 class="font-display text-2xl font-black text-mauve">Fill up now</h3>
<div class="mt-2 h-1.5 w-full bg-zinc-200 rounded-full overflow-hidden">
<div class="h-full bg-mauve w-4/5"></div>
</div>
</div>
<div class="space-y-2">
<div class="flex justify-between items-center p-3 bg-white rounded-lg border border-zinc-200">
<span class="font-bold text-sm">Tesco Superstore</span>
<span class="font-black text-status-good">142.9p</span>
</div>
<div class="flex justify-between items-center p-3 bg-white rounded-lg border border-zinc-200">
<span class="font-bold text-sm">Shell V-Power</span>
<span class="font-black text-zinc-500">148.9p</span>
</div>
</div>
</div>
</div>
</div>
</section>
{{-- How It Works --}}
<section id="how-it-works" class="py-24 px-6 bg-zinc-50">
<div class="max-w-7xl mx-auto">
<div class="text-center mb-16 space-y-4">
<h2 class="font-display text-4xl md:text-5xl font-black text-zinc-800">Smart Savings in 3 Steps</h2>
<p class="text-zinc-500 text-lg max-w-2xl mx-auto">Stop guessing when to fill up. Our engine analyses thousands of data points daily to save you money.</p>
</div>
<div class="grid md:grid-cols-3 gap-12">
<div class="text-center space-y-4">
<div class="w-16 h-16 bg-primary/10 text-primary rounded-2xl flex items-center justify-center mx-auto text-3xl">
<iconify-icon icon="lucide:search"></iconify-icon>
</div>
<h3 class="font-display text-2xl font-bold">1. Search</h3>
<p class="text-zinc-500">Enter your postcode or location to find every forecourt within a 520 mile radius instantly.</p>
</div>
<div class="text-center space-y-4">
<div class="w-16 h-16 bg-primary/10 text-primary rounded-2xl flex items-center justify-center mx-auto text-3xl">
<iconify-icon icon="lucide:trending-up"></iconify-icon>
</div>
<h3 class="font-display text-2xl font-bold">2. Get Advice</h3>
<p class="text-zinc-500">Our AI compares local prices against national wholesale trends to give you a Fill Up/Wait recommendation.</p>
</div>
<div class="text-center space-y-4">
<div class="w-16 h-16 bg-primary/10 text-primary rounded-2xl flex items-center justify-center mx-auto text-3xl">
<iconify-icon icon="lucide:wallet"></iconify-icon>
</div>
<h3 class="font-display text-2xl font-bold">3. Fill Up Smart</h3>
<p class="text-zinc-500">Navigate to the cheapest station and fill up with confidence knowing you've secured the best price.</p>
</div>
</div>
</div>
</section>
{{-- Features --}}
<section id="features" class="py-24 px-6">
<div class="max-w-7xl mx-auto">
<div class="grid lg:grid-cols-2 gap-20 items-center">
<div class="order-2 lg:order-1 grid grid-cols-2 gap-6">
<div class="p-6 bg-zinc-50 border border-zinc-300 rounded-2xl space-y-3">
<iconify-icon icon="lucide:zap" class="text-3xl text-primary"></iconify-icon>
<h4 class="font-bold text-lg">Real-Time Prices</h4>
<p class="text-sm text-zinc-500">Verified daily prices from thousands of UK forecourts.</p>
</div>
<div class="p-6 bg-zinc-50 border border-zinc-300 rounded-2xl space-y-3">
<iconify-icon icon="lucide:calendar" class="text-3xl text-primary"></iconify-icon>
<h4 class="font-bold text-lg">Timing Predictions</h4>
<p class="text-sm text-zinc-500">Proprietary 14-day forecasts for petrol and diesel trends.</p>
</div>
<div class="p-6 bg-zinc-50 border border-zinc-300 rounded-2xl space-y-3">
<iconify-icon icon="lucide:shopping-bag" class="text-3xl text-primary"></iconify-icon>
<h4 class="font-bold text-lg">Supermarket Anchors</h4>
<p class="text-sm text-zinc-500">Track local supermarkets to find the absolute lowest base price.</p>
</div>
<div class="p-6 bg-zinc-50 border border-zinc-300 rounded-2xl space-y-3">
<iconify-icon icon="lucide:bell-ring" class="text-3xl text-primary"></iconify-icon>
<h4 class="font-bold text-lg">Smart Price Alerts</h4>
<p class="text-sm text-zinc-500">Get notified when local prices drop below your set target.</p>
</div>
</div>
<div class="order-1 lg:order-2 space-y-8">
<h2 class="font-display text-4xl md:text-5xl font-black text-zinc-800">The ultimate fuel companion.</h2>
<p class="text-lg text-zinc-500">Whether you're a daily commuter, a delivery professional, or just planning a weekend road trip, FuelAlert gives you the edge at the pump.</p>
<ul class="space-y-4">
<li class="flex items-center gap-3 font-bold">
<iconify-icon icon="lucide:check-circle-2" class="text-primary"></iconify-icon>
Coverage for 98% of UK Forecourts
</li>
<li class="flex items-center gap-3 font-bold">
<iconify-icon icon="lucide:check-circle-2" class="text-primary"></iconify-icon>
Hyper-local Map Visualisation
</li>
<li class="flex items-center gap-3 font-bold">
<iconify-icon icon="lucide:check-circle-2" class="text-primary"></iconify-icon>
Historic Price Benchmarking
</li>
</ul>
<a href="{{ route('register') }}" class="inline-flex items-center gap-2 text-primary font-black text-lg group">
Explore all features
<iconify-icon icon="lucide:arrow-right" class="transition-transform group-hover:translate-x-1"></iconify-icon>
</a>
</div>
</div>
</div>
</section>
{{-- Pricing --}}
<section id="pricing" class="py-24 px-6 bg-zinc-50">
<div class="max-w-7xl mx-auto">
<div class="text-center mb-16">
<h2 class="font-display text-4xl md:text-5xl font-black text-zinc-800 mb-4">Pricing for every driver</h2>
<p class="text-zinc-500 text-lg">Save hundreds for less than the cost of a coffee.</p>
</div>
<div class="grid sm:grid-cols-2 lg:grid-cols-4 gap-6">
<x-pricing-card
name="Free"
price="£0"
button-text="Get Started"
:perks="[
['text' => 'Basic Search', 'included' => true],
['text' => 'Daily Updates', 'included' => true],
['text' => 'No Alerts', 'included' => false],
]"
/>
<x-pricing-card
name="Basic"
price="£0.99"
button-text="Select Basic"
:perks="[
['text' => 'Ad-free Experience', 'included' => true],
['text' => '14-day Trend Data', 'included' => true],
['text' => '3 Daily Price Alerts','included' => true],
]"
/>
<x-pricing-card
name="Plus"
price="£2.49"
button-text="Join Plus"
:featured="true"
:perks="[
['text' => 'Supermarket Anchor', 'included' => true],
['text' => 'Priority Price Alerts', 'included' => true],
['text' => 'Multi-location Tracking','included' => true],
]"
/>
<x-pricing-card
name="Pro"
price="£3.99"
button-text="Go Pro"
:dark="true"
:perks="[
['text' => 'AI Price Predictions', 'included' => true],
['text' => 'Multi-Vehicle Fleet', 'included' => true],
['text' => 'Exportable Price History','included' => true],
]"
/>
</div>
</div>
</section>
{{-- Social Proof --}}
<section class="py-24 px-6">
<div class="max-w-7xl mx-auto">
<div class="flex flex-col md:flex-row gap-12 items-center">
<div class="md:w-1/3">
<h2 class="font-display text-4xl font-black text-zinc-800 mb-4">Loved by commuters.</h2>
<div class="flex items-center gap-1 text-status-warn mb-4 text-xl">
<iconify-icon icon="lucide:star"></iconify-icon>
<iconify-icon icon="lucide:star"></iconify-icon>
<iconify-icon icon="lucide:star"></iconify-icon>
<iconify-icon icon="lucide:star"></iconify-icon>
<iconify-icon icon="lucide:star"></iconify-icon>
</div>
<p class="text-zinc-500">Join thousands of UK drivers saving every single month.</p>
</div>
<div class="md:w-2/3 grid sm:grid-cols-2 gap-6">
<div class="p-6 bg-zinc-50 border border-zinc-300 rounded-2xl shadow-sm italic text-zinc-800">
"I used to just go to the station on my way home. Now I check FuelAlert and realise there's a station 2 miles away that's 5p cheaper! Over a month, it adds up to a free tank per year."
<div class="mt-4 flex items-center gap-3 not-italic">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=John" alt="James R." class="w-10 h-10 rounded-full">
<div>
<p class="font-bold text-sm">James R.</p>
<p class="text-[10px] text-zinc-500 uppercase font-bold tracking-widest">Daily Commuter</p>
</div>
</div>
</div>
<div class="p-6 bg-zinc-50 border border-zinc-300 rounded-2xl shadow-sm italic text-zinc-800">
"The predictions are eerily accurate. I was going to fill up Friday, but FuelAlert said 'Hold on' for Monday. Sure enough, prices dropped at my local Tesco by 3p. Brilliant."
<div class="mt-4 flex items-center gap-3 not-italic">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah" alt="Sarah M." class="w-10 h-10 rounded-full">
<div>
<p class="font-bold text-sm">Sarah M.</p>
<p class="text-[10px] text-zinc-500 uppercase font-bold tracking-widest">Delivery Driver</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{{-- CTA Banner --}}
<section class="py-24 px-6 bg-primary text-white text-center">
<div class="max-w-3xl mx-auto space-y-8">
<h2 class="font-display text-4xl md:text-5xl font-black leading-tight">Ready to outsmart the pumps?</h2>
<p class="text-xl text-white/80">Sign up for free today and never pay over the odds for fuel again.</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="{{ route('register') }}" class="bg-white text-primary px-10 py-4 rounded-xl text-lg font-black shadow-2xl hover:bg-zinc-100 transition-all">Create Free Account</a>
<a href="#" class="border-2 border-white/30 text-white px-10 py-4 rounded-xl text-lg font-bold hover:bg-white/10 transition-all">Watch Demo Video</a>
</div>
</div>
</section>
{{-- Footer --}}
<footer class="bg-zinc-50 border-t border-zinc-300 pt-16 pb-8 px-6">
<div class="max-w-7xl mx-auto grid grid-cols-2 md:grid-cols-4 gap-12 mb-12">
<div class="col-span-2 md:col-span-1 space-y-4">
<a href="{{ route('home') }}" class="flex items-center gap-2">
<div class="w-8 h-8 rounded bg-primary flex items-center justify-center">
<iconify-icon icon="lucide:fuel" class="text-white"></iconify-icon>
</div>
<span class="font-display text-xl font-black tracking-tighter text-primary">FuelAlert</span>
</a>
<p class="text-sm text-zinc-500 leading-relaxed">Helping UK drivers save money at the pump since 2021. Real-time data, smarter choices.</p>
<div class="flex gap-4">
<iconify-icon icon="mdi:twitter" class="text-2xl text-zinc-500 hover:text-primary cursor-pointer transition-colors"></iconify-icon>
<iconify-icon icon="mdi:facebook" class="text-2xl text-zinc-500 hover:text-primary cursor-pointer transition-colors"></iconify-icon>
<iconify-icon icon="mdi:instagram" class="text-2xl text-zinc-500 hover:text-primary cursor-pointer transition-colors"></iconify-icon>
</div>
</div>
<div class="space-y-4">
<h5 class="font-black uppercase text-xs text-zinc-800 tracking-widest">Product</h5>
<ul class="space-y-2 text-sm text-zinc-500">
<li><a href="#pricing" class="hover:text-primary transition-colors">Pricing</a></li>
<li><a href="#features" class="hover:text-primary transition-colors">Features</a></li>
<li><a href="#" class="hover:text-primary transition-colors">FuelAlert Pro</a></li>
<li><a href="#" class="hover:text-primary transition-colors">Enterprise API</a></li>
</ul>
</div>
<div class="space-y-4">
<h5 class="font-black uppercase text-xs text-zinc-800 tracking-widest">Resources</h5>
<ul class="space-y-2 text-sm text-zinc-500">
<li><a href="#" class="hover:text-primary transition-colors">Market Insights</a></li>
<li><a href="#" class="hover:text-primary transition-colors">How We Track</a></li>
<li><a href="#" class="hover:text-primary transition-colors">Help Centre</a></li>
<li><a href="#" class="hover:text-primary transition-colors">Driver Safety</a></li>
</ul>
</div>
<div class="space-y-4">
<h5 class="font-black uppercase text-xs text-zinc-800 tracking-widest">Legal</h5>
<ul class="space-y-2 text-sm text-zinc-500">
<li><a href="#" class="hover:text-primary transition-colors">Privacy Policy</a></li>
<li><a href="#" class="hover:text-primary transition-colors">Terms of Service</a></li>
<li><a href="#" class="hover:text-primary transition-colors">Cookie Settings</a></li>
</ul>
</div>
</div>
<div class="max-w-7xl mx-auto pt-8 border-t border-zinc-300 flex flex-col md:flex-row justify-between items-center gap-4 text-[10px] font-bold uppercase tracking-widest text-zinc-500">
<p>© {{ date('Y') }} FuelAlert UK Limited. All Rights Reserved.</p>
<p>Data provided by official UK retail price transparency schemes.</p>
</div>
</footer>
</div>
</x-layouts::public>

View File

@@ -1,77 +0,0 @@
<div class="flex h-dvh flex-col bg-surface-page">
{{-- HEADER --}}
<header class="shrink-0 z-50 bg-surface border-b border-border shadow-sm
flex items-center justify-between px-5 pb-4 md:px-8"
style="padding-top: max(1rem, env(safe-area-inset-top))">
<a href="{{ route('home') }}" class="flex items-center gap-2.5">
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-primary shadow-md md:h-10 md:w-10">
<iconify-icon icon="lucide:fuel" class="text-lg text-white md:text-xl"></iconify-icon>
</div>
<span class="text-xl font-black tracking-tighter text-primary md:text-2xl">FuelAlert</span>
</a>
<nav class="hidden items-center gap-8 md:flex">
<a href="#" class="text-sm font-semibold text-text-muted transition-colors hover:text-primary">Prices</a>
<a href="#" class="text-sm font-semibold text-text-muted transition-colors hover:text-primary">Alerts</a>
<a href="#" class="text-sm font-semibold text-text-muted transition-colors hover:text-primary">Trends</a>
</nav>
@auth
<a href="{{ route('dashboard') }}"
class="flex h-9 w-9 items-center justify-center rounded-full border border-border bg-surface-subtle">
<iconify-icon icon="lucide:user" class="text-base text-text-muted"></iconify-icon>
</a>
@else
<a href="{{ route('login') }}"
class="flex h-9 w-9 items-center justify-center rounded-full border border-border bg-surface-subtle">
<iconify-icon icon="lucide:user" class="text-base text-text-muted"></iconify-icon>
</a>
@endauth
</header>
{{-- MAIN --}}
<main class="flex-1 overflow-y-auto" style="-ms-overflow-style:none;scrollbar-width:none;">
<div class="md:mx-auto md:max-w-3xl">
<livewire:public.fuel.search />
<livewire:public.fuel.recommendation />
<livewire:public.fuel.map />
<livewire:public.fuel.station-list />
<section class="px-5 pb-8">
<x-fuel.forecast />
</section>
</div>
</main>
{{-- BOTTOM TAB BAR --}}
@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],
];
@endphp
<nav class="shrink-0 border-t border-border bg-surface md:hidden"
style="padding-bottom: max(0.5rem, env(safe-area-inset-bottom))">
<div class="flex pt-3">
@foreach ($tabs as $tab)
@php $active = $tab['route'] && request()->routeIs($tab['route']); @endphp
<div class="flex flex-1 flex-col items-center gap-1">
<iconify-icon
icon="{{ $tab['icon'] }}"
class="text-xl {{ $active ? 'text-primary' : 'text-text-muted' }}"
></iconify-icon>
<span class="text-[10px] font-bold uppercase tracking-wide {{ $active ? 'text-primary' : 'text-text-muted' }}">
{{ $tab['label'] }}
</span>
</div>
@endforeach
</div>
</nav>
</div>

View File

@@ -1,3 +0,0 @@
<div class="mb-4" wire:ignore>
<x-fuel.station-map />
</div>

View File

@@ -1,7 +0,0 @@
<div>
@if ($prediction)
<div class="px-5 pb-5">
<x-fuel.recommendation :prediction="$prediction" />
</div>
@endif
</div>

View File

@@ -1,130 +0,0 @@
<div class="space-y-3 px-5 pb-4 pt-5">
<form wire:submit="findStations">
<div
x-data="{
query: @js($search),
locatingUser: false,
_usedIpFallback: false,
async _postcodeFromLatLng(lat, lng) {
const res = await fetch(`https://api.postcodes.io/postcodes?lon=${lng}&lat=${lat}&limit=1&radius=1000`);
const data = await res.json();
return data?.result?.[0]?.postcode ?? null;
},
async locateUser() {
this.locatingUser = true;
this._usedIpFallback = false;
try {
let lat, lng;
try {
const pos = await new Promise((resolve, reject) =>
navigator.geolocation.getCurrentPosition(resolve, reject, { enableHighAccuracy: false, timeout: 5000, maximumAge: 60000 })
);
lat = pos.coords.latitude;
lng = pos.coords.longitude;
} catch (e) {
const d = await fetch('https://ipapi.co/json/').then(r => r.json());
lat = d.latitude;
lng = d.longitude;
this._usedIpFallback = true;
}
const postcode = await this._postcodeFromLatLng(lat, lng);
if (postcode) {
this.query = postcode;
this.$wire.set('search', postcode);
this.$wire.findStations();
}
} catch (e) {
// silent
} finally {
this.locatingUser = false;
}
}
}"
class="relative mb-3"
>
<iconify-icon
icon="lucide:map-pin"
class="pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-xl text-text-muted"
></iconify-icon>
<input
wire:model="search"
x-model="query"
x-ref="searchInput"
type="text"
name="search"
@focus="_usedIpFallback = false"
placeholder="Postcode, town or city"
class="h-14 w-full rounded-xl border border-border bg-surface pl-12 pr-36 text-base font-semibold text-text-base focus:border-transparent focus:outline-none focus:ring-2 focus:ring-primary"
/>
<div class="absolute inset-y-0 right-0 flex items-center gap-2 pr-3">
<button
x-show="query.length > 0"
x-cloak
type="button"
@click="query = ''; $wire.set('search', ''); _usedIpFallback = false"
class="text-text-muted hover:text-text-base"
>
<iconify-icon icon="lucide:x" class="text-base"></iconify-icon>
</button>
<button
type="button"
@click="locateUser()"
:disabled="locatingUser"
class="flex items-center gap-1.5 rounded-full bg-surface-subtle px-3 py-1.5 text-sm font-semibold text-text-base disabled:opacity-40"
>
<iconify-icon x-show="!locatingUser" icon="lucide:locate-fixed" class="text-sm"></iconify-icon>
<iconify-icon x-show="locatingUser" icon="lucide:loader-circle" class="animate-spin text-sm"></iconify-icon>
<span x-text="locatingUser ? 'Finding...' : 'Near me'">Near me</span>
</button>
<button
type="submit"
wire:loading.attr="disabled"
class="text-text-muted disabled:opacity-60"
>
<iconify-icon wire:loading.remove wire:target="findStations" icon="lucide:search" class="text-xl"></iconify-icon>
<iconify-icon wire:loading wire:target="findStations" icon="lucide:loader-circle" class="animate-spin text-xl"></iconify-icon>
</button>
</div>
{{-- IP fallback nudge --}}
<div
x-show="_usedIpFallback"
x-cloak
x-transition:enter="transition ease-out duration-300 delay-500"
x-transition:enter-start="opacity-0 -translate-y-2"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-end="opacity-0"
class="absolute left-0 right-0 top-full z-40 mt-2"
>
<div
@click="$refs.searchInput.focus(); _usedIpFallback = false"
class="cursor-pointer rounded-xl border border-amber-200 bg-amber-50 px-3 py-2 transition-colors hover:bg-amber-100"
>
<p class="text-center text-xs text-amber-800">
<span class="font-medium">Showing approximate location.</span>
<span class="underline">Enter your postcode above</span> for exact results.
</p>
</div>
</div>
</div>
@error('search')
<p class="mb-2 text-sm text-red-600">{{ $message }}</p>
@enderror
<div class="flex gap-2 overflow-x-auto pb-1" style="-ms-overflow-style:none;scrollbar-width:none;">
<div class="shrink-0"><x-fuel.type-select wire:model.live="fuelType" /></div>
<div class="shrink-0"><x-fuel.sort-select wire:model.live="sort" /></div>
<div class="shrink-0"><x-fuel.radius-select wire:model.live="radius" /></div>
</div>
</form>
@if ($apiError)
<div class="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
{{ $apiError }}
</div>
@endif
</div>

View File

@@ -1,24 +0,0 @@
<div>
@if ($hasSearched)
<div class="px-5 pb-5">
@if (! empty($meta))
<div class="mb-3 flex items-center justify-between">
<h3 class="text-base font-bold text-text-base">Stations Nearby</h3>
<span class="text-[10px] font-bold uppercase tracking-widest text-text-muted">
{{ $meta['count'] ?? 0 }} {{ str('Result')->plural($meta['count'] ?? 0) }}
</span>
</div>
@endif
@forelse ($results as $station)
<div class="mb-2">
<x-fuel.station-card :station="$station" />
</div>
@empty
<p class="text-sm text-text-muted">
No stations found within {{ $radius }} {{ str('mile')->plural($radius) }}.
</p>
@endforelse
</div>
@endif
</div>

View File

@@ -1,15 +1,9 @@
<?php
//use App\Livewire\Public\FuelFinder;
use Illuminate\Support\Facades\Route;
//Route::get('/fuel-finder', FuelFinder::class)->name('fuel-finder');
Route::view('/', 'homepage')->name('home');
Route::middleware(['auth', 'verified'])->group(function () {
Route::view('dashboard', 'dashboard')->name('dashboard');
Route::middleware(['auth', 'verified'])->group(function (): void {
Route::view('dashboard', 'dashboard')->name('dashboard');
});
require __DIR__.'/settings.php';
require __DIR__.'/settings.php';