feat: add location-based search, redesign station cards, and implement URL state management
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

- Support geolocation search (lat/lng) as alternative to postcode with automatic fallback
- Redesign StationCard with expanded layout showing address, distance in miles, reliability status, directions link, and optional remove button
- Add directions integration with Google Maps including origin parameter support
- Persist search parameters (postcode/coords, fuel type, radius, sort) in URL query and hydrate on mount
- Implement compact map markers with inline directions link and click-to-zoom behavior
- Auto-trigger search when filters change (fuel type, radius, sort) if search already performed
- Add removable prop to StationCard for watchlist integration
- Display reliability status (Current/Stale/Outdated) with color-coded pricing
- Remove 2-mile radius option from search filters
This commit is contained in:
Ovidiu U
2026-04-20 15:51:02 +01:00
parent d29f3e6487
commit 7dc41ba9ee
5 changed files with 236 additions and 86 deletions

View File

@@ -44,7 +44,7 @@
Join 50,000+ UK drivers using real-time insights to find the cheapest petrol and time their fill-ups perfectly.
</p>
<SearchBar @search="onSearch" />
<SearchBar :initial="searchInitial" @search="onSearch" />
<div class="flex items-center gap-4 pt-4">
<div class="flex -space-x-2">
@@ -118,8 +118,8 @@
<span class="font-medium">No stations found near you. Try a different postcode or increase the radius.</span>
</div>
<template v-else>
<LeafletMap :default-open="true" :radius-miles="radiusMiles" :stations="stations" />
<StationList :stations="stations" :current-sort="sort" @sort="onSort" />
<LeafletMap :default-open="true" :origin="searchOrigin" :radius-miles="radiusMiles" :stations="stations" />
<StationList :current-sort="sort" :origin="searchOrigin" :stations="stations" @sort="onSort" />
</template>
</template>
@@ -427,8 +427,8 @@
</template>
<script setup>
import { ref } from 'vue'
import { RouterLink } from 'vue-router'
import { ref, computed, watch } from 'vue'
import { RouterLink, useRoute, useRouter } from 'vue-router'
import { useAuth } from '../composables/useAuth.js'
import { useStations } from '../composables/useStations.js'
import SearchBar from '../components/SearchBar.vue'
@@ -471,14 +471,63 @@ const PRICES = {
annual: { basic: '£9.90', plus: '£24.90', pro: '£39.90' },
}
const PRICE_SUFFIX = { monthly: '/mo', annual: '/yr' }
const { stations, loading, error, search } = useStations()
const { stations, meta, loading, error, search } = useStations()
const searchOrigin = computed(() => {
if (meta.value?.lat != null && meta.value?.lng != null) {
return { lat: meta.value.lat, lng: meta.value.lng }
}
return null
})
const route = useRoute()
const router = useRouter()
const sort = ref('reliable')
const lastParams = ref(null)
const searchAttempted = ref(false)
const radiusMiles = ref(10)
async function onSearch(params) {
const searchInitial = computed(() => ({
postcode: route.query.postcode ?? '',
lat: route.query.lat ? Number(route.query.lat) : null,
lng: route.query.lng ? Number(route.query.lng) : null,
fuelType: route.query.fuel_type ?? 'e10',
radius: route.query.radius ? Number(route.query.radius) : 10,
sort: route.query.sort ?? 'reliable',
}))
function paramsFromQuery(query) {
const hasPostcode = typeof query.postcode === 'string' && query.postcode.trim().length > 0
const hasCoords = query.lat && query.lng
if (!hasPostcode && !hasCoords) return null
return {
postcode: hasPostcode ? query.postcode.trim() : null,
lat: hasCoords ? Number(query.lat) : null,
lng: hasCoords ? Number(query.lng) : null,
fuelType: query.fuel_type ?? 'e10',
radius: query.radius ? Number(query.radius) : 10,
sort: query.sort ?? 'reliable',
}
}
function queryFromParams(params) {
const q = {
fuel_type: params.fuelType,
radius: String(params.radius),
sort: params.sort,
}
if (params.postcode) {
q.postcode = params.postcode
} else if (params.lat && params.lng) {
q.lat = String(params.lat)
q.lng = String(params.lng)
}
return q
}
async function runSearch(params) {
lastParams.value = params
sort.value = params.sort ?? sort.value
radiusMiles.value = params.radius ?? radiusMiles.value
@@ -486,10 +535,24 @@ async function onSearch(params) {
await search(params)
}
async function onSort(newSort) {
sort.value = newSort
if (lastParams.value) {
await search({ ...lastParams.value, sort: newSort })
}
async function onSearch(params) {
await router.push({ query: queryFromParams(params) })
await runSearch(params)
}
async function onSort(newSort) {
if (!lastParams.value) return
const next = { ...lastParams.value, sort: newSort }
await router.push({ query: queryFromParams(next) })
await runSearch(next)
}
watch(() => route.query, (query) => {
const params = paramsFromQuery(query)
if (!params) return
const sameAsLast = lastParams.value
&& JSON.stringify(queryFromParams(lastParams.value)) === JSON.stringify(queryFromParams(params))
if (sameAsLast) return
runSearch(params)
}, { immediate: true })
</script>