Consolidate prediction functionality by merging /api/prediction endpoint into /api/stations response. Move prediction logic from PredictionController into StationController, returning prediction data alongside station results. Replace usePrediction composable with unified useStations that returns {stations, meta, prediction}. Remove PredictionRequest, related tests, and unused Vue components (FuelFinderTest, MapTest, RecommendationTest, StationListTest). Add PredictionFull component and UpsellBanner. Extend NationalFuelPredictionService to include weekly_summary (7-day series, yesterday/today averages, cheapest/priciest days) and oil signal from price_predictions table. Update Home.vue to consume prediction from stations response. Add Plan::resolveCadenceForUser helper and configure Cashier to use custom Subscription model.
66 lines
2.2 KiB
Vue
66 lines
2.2 KiB
Vue
<template>
|
|
<div>
|
|
<!-- Loading state -->
|
|
<div
|
|
v-if="loading"
|
|
class="p-6 bg-white rounded-2xl border border-zinc-300 animate-pulse space-y-2"
|
|
>
|
|
<div class="h-4 bg-zinc-200 rounded w-1/3"></div>
|
|
<div class="h-6 bg-zinc-200 rounded w-2/3"></div>
|
|
</div>
|
|
|
|
<!-- Free / guest: compact one-liner -->
|
|
<div
|
|
v-else-if="!isPaidTier"
|
|
class="flex items-center gap-3 px-4 py-3 bg-white rounded-2xl border border-zinc-300"
|
|
>
|
|
<div :class="['shrink-0 w-10 h-10 rounded-full flex items-center justify-center', accentBg]">
|
|
<iconify-icon :icon="genericIcon" class="text-xl text-white"></iconify-icon>
|
|
</div>
|
|
<p class="flex-1 text-sm text-zinc-800 font-medium leading-snug">
|
|
{{ genericSentence }}
|
|
</p>
|
|
<a
|
|
class="hidden sm:inline-flex shrink-0 text-sm font-bold text-accent hover:text-accent-content whitespace-nowrap"
|
|
href="/pricing"
|
|
>
|
|
See full prediction →
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Paid: full prediction -->
|
|
<PredictionFull v-else :prediction="prediction" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
import PredictionFull from './PredictionFull.vue'
|
|
|
|
const props = defineProps({
|
|
prediction: { type: Object, default: null },
|
|
loading: { type: Boolean, default: false },
|
|
isPaidTier: { type: Boolean, default: false },
|
|
})
|
|
|
|
const direction = computed(() => props.prediction?.predicted_direction ?? 'stable')
|
|
|
|
const genericSentence = computed(() => ({
|
|
up: 'UK fuel prices are trending upward this week.',
|
|
down: 'UK fuel prices have been falling this week.',
|
|
stable: 'UK fuel prices have been steady this week.',
|
|
})[direction.value] ?? 'UK fuel prices have been steady this week.')
|
|
|
|
const genericIcon = computed(() => ({
|
|
up: 'lucide:trending-up',
|
|
down: 'lucide:trending-down',
|
|
stable: 'lucide:minus',
|
|
})[direction.value] ?? 'lucide:minus')
|
|
|
|
const accentBg = computed(() => ({
|
|
up: 'bg-mauve',
|
|
down: 'bg-teal',
|
|
stable: 'bg-tan',
|
|
})[direction.value] ?? 'bg-tan')
|
|
</script>
|