150 lines
6.3 KiB
Vue
150 lines
6.3 KiB
Vue
<template>
|
|
<div class="min-h-screen bg-[#f5ede5]">
|
|
<!-- Navigation -->
|
|
<nav class="fixed top-0 w-full z-50 bg-[#faf6f3] border-b border-[#e5ded7] px-6 py-4 md:px-12">
|
|
<div class="max-w-7xl mx-auto flex items-center justify-between">
|
|
<RouterLink to="/" class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-lg bg-[#bb5b3e] flex items-center justify-center shadow-md">
|
|
<iconify-icon icon="lucide:fuel" class="text-white text-xl"></iconify-icon>
|
|
</div>
|
|
<span class="text-2xl font-black tracking-tighter text-[#bb5b3e]">FuelAlert</span>
|
|
</RouterLink>
|
|
|
|
<div class="flex items-center gap-4">
|
|
<template v-if="isAuthenticated">
|
|
<RouterLink to="/account" class="text-sm font-bold text-[#89726c] hover:text-[#4a3f3b]">Account</RouterLink>
|
|
</template>
|
|
<template v-else>
|
|
<a href="/login" class="text-sm font-bold text-[#89726c] hover:text-[#4a3f3b]">Login</a>
|
|
<a href="/register" class="bg-[#bb5b3e] text-white px-6 py-2.5 rounded-full text-sm font-bold shadow-lg hover:bg-[#a34a31] transition-all">Get Started</a>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Hero -->
|
|
<section class="relative pt-36 pb-16 px-6">
|
|
<div class="max-w-2xl mx-auto text-center space-y-6">
|
|
<div class="inline-flex items-center gap-2 px-3 py-1 bg-[#bb5b3e]/10 text-[#bb5b3e] rounded-full text-xs font-bold uppercase tracking-wider">
|
|
<iconify-icon icon="lucide:sparkles"></iconify-icon>
|
|
Save up to £250/year on fuel
|
|
</div>
|
|
<h1 class="text-5xl md:text-6xl font-black text-[#4a3f3b] leading-tight tracking-tighter">
|
|
Stop Overpaying <span class="text-[#bb5b3e]">for Fuel.</span>
|
|
</h1>
|
|
<p class="text-lg text-[#89726c] max-w-lg mx-auto">Find the cheapest petrol near you and know the best time to fill up.</p>
|
|
|
|
<div class="flex justify-center">
|
|
<SearchBar @search="onSearch" />
|
|
</div>
|
|
|
|
<p v-if="stationError" class="text-sm text-red-500 font-medium">
|
|
{{ Object.values(stationError).flat().join(' ') }}
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Results -->
|
|
<section v-if="hasSearched" class="px-6 pb-24">
|
|
<div class="max-w-4xl mx-auto space-y-6">
|
|
<!-- Fuel type selector -->
|
|
<div class="flex gap-2 flex-wrap">
|
|
<button
|
|
v-for="fuel in fuelOptions"
|
|
:key="fuel.value"
|
|
@click="changeFuelType(fuel.value)"
|
|
:class="[
|
|
'px-4 py-1.5 rounded-full text-sm font-bold transition-colors',
|
|
currentFuelType === fuel.value
|
|
? 'bg-[#4a3f3b] text-white'
|
|
: 'bg-white border border-[#e5ded7] text-[#89726c] hover:border-[#4a3f3b]'
|
|
]"
|
|
>
|
|
{{ fuel.label }}
|
|
</button>
|
|
</div>
|
|
|
|
<div class="grid lg:grid-cols-3 gap-6">
|
|
<!-- Map + List (2/3 width) -->
|
|
<div class="lg:col-span-2 space-y-4">
|
|
<LeafletMap :stations="stations" />
|
|
|
|
<template v-if="stationsLoading">
|
|
<div class="space-y-2">
|
|
<div v-for="i in 5" :key="i" class="h-16 bg-white rounded-xl animate-pulse border border-[#e5ded7]"></div>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<StationList
|
|
:stations="stations"
|
|
:current-sort="currentSort"
|
|
@sort="changeSort"
|
|
/>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Prediction (1/3 width) -->
|
|
<div>
|
|
<PredictionCard
|
|
:prediction="prediction"
|
|
:loading="predictionLoading"
|
|
:is-paid-tier="isPaidTier"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref } from 'vue'
|
|
import { RouterLink } from 'vue-router'
|
|
import { useAuth } from '../composables/useAuth.js'
|
|
import { useStations } from '../composables/useStations.js'
|
|
import { usePrediction } from '../composables/usePrediction.js'
|
|
import SearchBar from '../components/SearchBar.vue'
|
|
import LeafletMap from '../components/LeafletMap.vue'
|
|
import StationList from '../components/StationList.vue'
|
|
import PredictionCard from '../components/PredictionCard.vue'
|
|
|
|
const { isAuthenticated, isPaidTier } = useAuth()
|
|
const { stations, loading: stationsLoading, error: stationError, search } = useStations()
|
|
const { prediction, loading: predictionLoading, fetch: fetchPrediction } = usePrediction()
|
|
|
|
const hasSearched = ref(false)
|
|
const currentSort = ref('price')
|
|
const currentFuelType = ref('petrol')
|
|
const lastPostcode = ref('')
|
|
|
|
const fuelOptions = [
|
|
{ label: 'Petrol (E10)', value: 'petrol' },
|
|
{ label: 'Diesel', value: 'diesel' },
|
|
{ label: 'Premium Unleaded', value: 'e5' },
|
|
{ label: 'Premium Diesel', value: 'b7_premium' },
|
|
]
|
|
|
|
async function onSearch(postcode) {
|
|
lastPostcode.value = postcode
|
|
hasSearched.value = true
|
|
await Promise.all([
|
|
search({ postcode, fuelType: currentFuelType.value, sort: currentSort.value }),
|
|
fetchPrediction(),
|
|
])
|
|
}
|
|
|
|
async function changeSort(sort) {
|
|
currentSort.value = sort
|
|
if (lastPostcode.value) {
|
|
await search({ postcode: lastPostcode.value, fuelType: currentFuelType.value, sort })
|
|
}
|
|
}
|
|
|
|
async function changeFuelType(fuelType) {
|
|
currentFuelType.value = fuelType
|
|
if (lastPostcode.value) {
|
|
await search({ postcode: lastPostcode.value, fuelType, sort: currentSort.value })
|
|
}
|
|
}
|
|
</script>
|