feat: build full Home.vue with search, station list, map, and prediction
This commit is contained in:
@@ -1,5 +1,149 @@
|
||||
<template>
|
||||
<div class="min-h-screen flex items-center justify-center">
|
||||
<p class="text-xl font-bold text-[#bb5b3e]">FuelAlert — Home (coming soon)</p>
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user