Compare commits
4 Commits
adf0b93a45
...
pricing-lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fe3461adf | ||
|
|
ad2230728c | ||
|
|
5bd909d227 | ||
|
|
a841a7c900 |
@@ -6,7 +6,7 @@
|
|||||||
|------------|--------|-------|------------|----------|-----------|
|
|------------|--------|-------|------------|----------|-----------|
|
||||||
| Free | £0 | ✓ weekly digest | ✗ | ✗ | ✗ |
|
| Free | £0 | ✓ weekly digest | ✗ | ✗ | ✗ |
|
||||||
| Basic | £0.99 | ✓ daily | ✓ daily | ✓ daily | ✗ |
|
| Basic | £0.99 | ✓ daily | ✓ daily | ✓ daily | ✗ |
|
||||||
| Plus | £2.49 | ✓ | ✓ | ✓ | ✓ max 1/day triggered |
|
| Plus | £2.49 | ✓ | ✓ | ✓ | ✓ max 3/day triggered |
|
||||||
| Pro | £3.99 | ✓ | ✓ | ✓ | ✓ max 3/day triggered |
|
| Pro | £3.99 | ✓ | ✓ | ✓ | ✓ max 3/day triggered |
|
||||||
|
|
||||||
## NotificationDispatchService
|
## NotificationDispatchService
|
||||||
@@ -41,8 +41,8 @@ Subject line / message copy adapts based on `recommendation` and `confidence`.
|
|||||||
|
|
||||||
- Same Vonage client, SMS channel
|
- Same Vonage client, SMS channel
|
||||||
- Triggered only (not daily) — fires when signal strength ≥ 2 AND price event warrants it
|
- Triggered only (not daily) — fires when signal strength ≥ 2 AND price event warrants it
|
||||||
- Plus: max 8 SMS/month (enforced via alerts table count)
|
- Plus (Smart): max 3 SMS/day (enforced via notification_log count)
|
||||||
- Pro: max 30 SMS/month
|
- Pro: deferred from launch — see `tiers.md`
|
||||||
- Cost: ~3.5p per message UK
|
- Cost: ~3.5p per message UK
|
||||||
|
|
||||||
## Email
|
## Email
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ All six are available to all tiers. The restriction is quantity only:
|
|||||||
| email | weekly digest | daily | ✓ triggered | ✓ triggered |
|
| email | weekly digest | daily | ✓ triggered | ✓ triggered |
|
||||||
| push | ✗ | ✓ daily | ✓ triggered | ✓ triggered |
|
| push | ✗ | ✓ daily | ✓ triggered | ✓ triggered |
|
||||||
| whatsapp | ✗ | ✓ daily | ✓ triggered | ✓ triggered |
|
| whatsapp | ✗ | ✓ daily | ✓ triggered | ✓ triggered |
|
||||||
| sms | ✗ | ✗ | ✓ max 1/day | ✓ max 3/day |
|
| sms | ✗ | ✗ | ✓ max 3/day | ✓ max 3/day |
|
||||||
|
|
||||||
WhatsApp also supports scheduled updates (morning + evening) independent of
|
WhatsApp also supports scheduled updates (morning + evening) independent of
|
||||||
price triggers — available to any tier that has WhatsApp enabled.
|
price triggers — available to any tier that has WhatsApp enabled.
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class PlanFactory extends Factory
|
|||||||
'whatsapp_daily_limit' => 5,
|
'whatsapp_daily_limit' => 5,
|
||||||
'whatsapp_scheduled_updates' => 2,
|
'whatsapp_scheduled_updates' => 2,
|
||||||
'sms_enabled' => true,
|
'sms_enabled' => true,
|
||||||
'sms_daily_limit' => 1,
|
'sms_daily_limit' => 3,
|
||||||
'ai_predictions' => true,
|
'ai_predictions' => true,
|
||||||
'price_threshold' => true,
|
'price_threshold' => true,
|
||||||
'score_alerts' => true,
|
'score_alerts' => true,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class PlanSeeder extends Seeder
|
|||||||
'whatsapp_daily_limit' => 5,
|
'whatsapp_daily_limit' => 5,
|
||||||
'whatsapp_scheduled_updates' => 2,
|
'whatsapp_scheduled_updates' => 2,
|
||||||
'sms_enabled' => true,
|
'sms_enabled' => true,
|
||||||
'sms_daily_limit' => 1,
|
'sms_daily_limit' => 3,
|
||||||
'ai_predictions' => true,
|
'ai_predictions' => true,
|
||||||
'price_threshold' => true,
|
'price_threshold' => true,
|
||||||
'score_alerts' => true,
|
'score_alerts' => true,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ decision — which channels a user can receive, how often, and what features the
|
|||||||
|-------|--------|---------------|-----------|-----------|------------|----------------|-----------------|--------------|------------|
|
|-------|--------|---------------|-----------|-----------|------------|----------------|-----------------|--------------|------------|
|
||||||
| free | £0 | weekly digest | — | — | — | — | — | — | 1 |
|
| free | £0 | weekly digest | — | — | — | — | — | — | 1 |
|
||||||
| basic | £0.99 | daily | daily | daily | — | — | ✓ | ✓ | 1 |
|
| basic | £0.99 | daily | daily | daily | — | — | ✓ | ✓ | 1 |
|
||||||
| plus | £2.49 | triggered | triggered | triggered | max 1/day | ✓ | ✓ | ✓ | 1 |
|
| plus | £2.49 | triggered | triggered | triggered | max 3/day | ✓ | ✓ | ✓ | 1 |
|
||||||
| pro | £3.99 | triggered | triggered | triggered | max 3/day | ✓ | ✓ | ✓ | unlimited |
|
| pro | £3.99 | triggered | triggered | triggered | max 3/day | ✓ | ✓ | ✓ | unlimited |
|
||||||
|
|
||||||
Tiers are stored in the `plans` table. The `features` JSON column defines every limit and flag.
|
Tiers are stored in the `plans` table. The `features` JSON column defines every limit and flag.
|
||||||
|
|||||||
135
resources/js/components/PricingGrid.vue
Normal file
135
resources/js/components/PricingGrid.vue
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<!-- Pricing -->
|
||||||
|
<section id="pricing" class="py-4 md:py-24 px-6 bg-zinc-50">
|
||||||
|
<div class="max-w-7xl mx-auto">
|
||||||
|
<div class="text-center mb-16">
|
||||||
|
<h2 class="text-4xl md:text-5xl font-black font-display text-zinc-800 mb-4">Pricing for every driver</h2>
|
||||||
|
<p class="text-zinc-500 text-lg mb-8">Save hundreds for less than the cost of a coffee.</p>
|
||||||
|
<div class="inline-flex items-center gap-1 p-1 bg-white border border-zinc-300 rounded-full">
|
||||||
|
<button
|
||||||
|
:class="cadence === 'monthly' ? 'bg-accent text-white' : 'text-zinc-500'"
|
||||||
|
class="px-5 py-2 rounded-full text-sm font-bold transition-colors"
|
||||||
|
type="button"
|
||||||
|
@click="cadence = 'monthly'"
|
||||||
|
>
|
||||||
|
Monthly
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="cadence === 'annual' ? 'bg-accent text-white' : 'text-zinc-500'"
|
||||||
|
class="px-5 py-2 rounded-full text-sm font-bold transition-colors"
|
||||||
|
type="button"
|
||||||
|
@click="cadence = 'annual'"
|
||||||
|
>
|
||||||
|
Annual <span class="text-[10px] opacity-80">(save 17%)</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 max-w-5xl mx-auto">
|
||||||
|
<!-- Free -->
|
||||||
|
<div class="bg-white border border-zinc-300 p-8 rounded-3xl flex flex-col h-full">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h4 class="text-xl font-bold font-display mb-2">Free</h4>
|
||||||
|
<div class="flex items-baseline gap-1">
|
||||||
|
<span class="text-4xl font-black">£0</span>
|
||||||
|
<span class="text-zinc-500 text-sm">/mo</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-4 mb-8 flex-1">
|
||||||
|
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Local station price search</li>
|
||||||
|
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Buy-or-wait verdict</li>
|
||||||
|
<li class="text-sm flex gap-2 text-zinc-500"><iconify-icon class="text-zinc-300" icon="lucide:x"></iconify-icon> No price alerts</li>
|
||||||
|
</ul>
|
||||||
|
<a :href="ctaHref('free')" class="w-full py-3 px-4 border border-zinc-300 rounded-xl text-center font-bold hover:bg-zinc-100 transition-colors">{{ ctaLabel('free') }}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Daily (backend: basic) -->
|
||||||
|
<div class="bg-white border border-zinc-300 p-8 rounded-3xl flex flex-col h-full">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h4 class="text-xl font-bold font-display mb-2">Daily</h4>
|
||||||
|
<div class="flex items-baseline gap-1">
|
||||||
|
<span class="text-4xl font-black">{{ PRICES[cadence].basic }}</span>
|
||||||
|
<span class="text-zinc-500 text-sm">{{ PRICE_SUFFIX[cadence] }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-4 mb-8 flex-1">
|
||||||
|
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Buy-or-wait score + reason</li>
|
||||||
|
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Daily email, push & WhatsApp</li>
|
||||||
|
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Price-drop & score alerts</li>
|
||||||
|
</ul>
|
||||||
|
<a v-if="!isComingSoon('basic')" :href="ctaHref('basic')" class="w-full py-3 px-4 border border-zinc-300 rounded-xl text-center font-bold hover:bg-zinc-100 transition-colors">{{ ctaLabel('basic') }}</a>
|
||||||
|
<button v-else type="button" disabled class="w-full py-3 px-4 border border-zinc-200 bg-zinc-50 rounded-xl text-center font-bold text-zinc-400 cursor-not-allowed">Coming soon</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Smart (backend: plus) -->
|
||||||
|
<div class="bg-white border-2 border-accent p-8 rounded-3xl flex flex-col h-full relative">
|
||||||
|
<div class="absolute -top-4 left-1/2 -translate-x-1/2 bg-accent text-white px-4 py-1 rounded-full text-[10px] font-black uppercase tracking-widest whitespace-nowrap">Most pick this</div>
|
||||||
|
<div class="mb-8">
|
||||||
|
<h4 class="text-xl font-bold font-display mb-2">Smart</h4>
|
||||||
|
<div class="flex items-baseline gap-1">
|
||||||
|
<span class="text-4xl font-black text-accent">{{ PRICES[cadence].plus }}</span>
|
||||||
|
<span class="text-zinc-500 text-sm">{{ PRICE_SUFFIX[cadence] }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul class="space-y-4 mb-8 flex-1">
|
||||||
|
<li class="text-sm flex gap-2 font-bold"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> AI fuel-price prediction</li>
|
||||||
|
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Real-time email, push & WhatsApp</li>
|
||||||
|
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> SMS alerts (up to 3/day)</li>
|
||||||
|
</ul>
|
||||||
|
<a v-if="!isComingSoon('plus')" :href="ctaHref('plus')" class="w-full py-3 px-4 bg-accent text-white rounded-xl text-center font-bold shadow-lg hover:bg-primary-dark transition-all">{{ ctaLabel('plus') }}</a>
|
||||||
|
<button v-else type="button" disabled class="w-full py-3 px-4 bg-zinc-100 rounded-xl text-center font-bold text-zinc-400 cursor-not-allowed">Coming soon</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { useAuth } from '../composables/useAuth.js'
|
||||||
|
|
||||||
|
const { isAuthenticated, userTier } = useAuth()
|
||||||
|
|
||||||
|
const cadence = ref('monthly')
|
||||||
|
|
||||||
|
const PRICES = {
|
||||||
|
monthly: { basic: '£0.99', plus: '£2.49', pro: '£3.99' },
|
||||||
|
annual: { basic: '£9.90', plus: '£24.90', pro: '£39.90' },
|
||||||
|
}
|
||||||
|
const PRICE_SUFFIX = { monthly: '/mo', annual: '/yr' }
|
||||||
|
|
||||||
|
// Paid tiers whose alerting features aren't fully shipped yet — their CTAs are
|
||||||
|
// disabled until then. Remove a tier from this list to make its button live.
|
||||||
|
const COMING_SOON = ['basic', 'plus']
|
||||||
|
|
||||||
|
function isComingSoon(tier) {
|
||||||
|
return COMING_SOON.includes(tier)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ctaHref(tier) {
|
||||||
|
if (tier === 'free') {
|
||||||
|
return isAuthenticated.value ? '/dashboard' : '/register'
|
||||||
|
}
|
||||||
|
if (!isAuthenticated.value) {
|
||||||
|
return '/register?tier=' + tier + '&cadence=' + cadence.value
|
||||||
|
}
|
||||||
|
if (userTier.value === tier) {
|
||||||
|
return '/billing/portal'
|
||||||
|
}
|
||||||
|
return '/billing/checkout/' + tier + '/' + cadence.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function ctaLabel(tier) {
|
||||||
|
if (tier === 'free') {
|
||||||
|
return isAuthenticated.value ? 'Go to dashboard' : 'Start free'
|
||||||
|
}
|
||||||
|
if (isAuthenticated.value && userTier.value === tier) {
|
||||||
|
return 'Manage subscription'
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
basic: 'Choose Daily',
|
||||||
|
plus: 'Choose Smart',
|
||||||
|
pro: 'Choose Pro',
|
||||||
|
}[tier]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -47,6 +47,6 @@ const stationCountLabel = computed(() => {
|
|||||||
return new Intl.NumberFormat('en-GB').format(props.stationCount)
|
return new Intl.NumberFormat('en-GB').format(props.stationCount)
|
||||||
})
|
})
|
||||||
|
|
||||||
const ctaHref = computed(() => isAuthenticated.value ? '#pricing' : '/register?tier=plus&cadence=monthly')
|
const ctaHref = computed(() => isAuthenticated.value ? '/pricing' : '/register?tier=plus&cadence=monthly')
|
||||||
const ctaLabel = computed(() => isAuthenticated.value ? 'See plans' : 'Start saving')
|
const ctaLabel = computed(() => isAuthenticated.value ? 'See plans' : 'Start saving')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<div class="hidden lg:flex items-center gap-8 font-mono text-[11px] uppercase tracking-widest text-zinc-600">
|
<div class="hidden lg:flex items-center gap-8 font-mono text-[11px] uppercase tracking-widest text-zinc-600">
|
||||||
<a class="hover:text-accent transition-colors" href="#how-it-works">How it works</a>
|
<a class="hover:text-accent transition-colors" href="#how-it-works">How it works</a>
|
||||||
<a class="hover:text-accent transition-colors" href="#features">Why it works</a>
|
<a class="hover:text-accent transition-colors" href="#features">Why it works</a>
|
||||||
<a class="hover:text-accent transition-colors" href="#pricing">Pricing</a>
|
<RouterLink class="hover:text-accent transition-colors" to="/pricing">Pricing</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3 md:gap-5">
|
<div class="flex items-center gap-3 md:gap-5">
|
||||||
|
|||||||
55
resources/js/components/landing/SiteFooter.vue
Normal file
55
resources/js/components/landing/SiteFooter.vue
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 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">
|
||||||
|
<RouterLink class="flex items-center gap-2" to="/">
|
||||||
|
<div class="w-8 h-8 rounded bg-accent flex items-center justify-center">
|
||||||
|
<iconify-icon class="text-white" icon="lucide:fuel"></iconify-icon>
|
||||||
|
</div>
|
||||||
|
<span class="text-xl font-black font-display tracking-tighter text-accent">FuelAlert</span>
|
||||||
|
</RouterLink>
|
||||||
|
<p class="text-sm text-zinc-500 leading-relaxed">
|
||||||
|
Helping UK drivers save money at the pump. Real-time data, smarter choices.
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-zinc-500">
|
||||||
|
Questions? <a class="text-accent hover:underline" href="mailto:hello@fuel-alert.co.uk">hello@fuel-alert.co.uk</a>
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<iconify-icon class="text-2xl text-zinc-500 hover:text-accent cursor-pointer transition-colors" icon="mdi:twitter"></iconify-icon>
|
||||||
|
<iconify-icon class="text-2xl text-zinc-500 hover:text-accent cursor-pointer transition-colors" icon="mdi:facebook"></iconify-icon>
|
||||||
|
<iconify-icon class="text-2xl text-zinc-500 hover:text-accent cursor-pointer transition-colors" icon="mdi:instagram"></iconify-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h5 class="font-black text-xs text-zinc-800 tracking-widest">Product</h5>
|
||||||
|
<ul class="space-y-2 text-sm text-zinc-500">
|
||||||
|
<li><RouterLink class="hover:text-accent transition-colors" to="/pricing">Pricing</RouterLink></li>
|
||||||
|
<li><a class="hover:text-accent transition-colors" href="#how-it-works">How it works</a></li>
|
||||||
|
<li><a class="hover:text-accent transition-colors" href="#features">Why it works</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h5 class="font-black text-xs text-zinc-800 tracking-widest">Legal</h5>
|
||||||
|
<ul class="space-y-2 text-sm text-zinc-500">
|
||||||
|
<li><a class="hover:text-accent transition-colors" href="/legal/privacy">Privacy Policy</a></li>
|
||||||
|
<li><a class="hover:text-accent transition-colors" href="/legal/terms">Terms of Service</a></li>
|
||||||
|
<li><a class="hover:text-accent transition-colors" href="/legal/refund">Refund & Cancellation</a></li>
|
||||||
|
<li><a class="hover:text-accent transition-colors" href="/legal/cookies">Cookie Policy</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] tracking-widest text-zinc-500">
|
||||||
|
<p>© 2026 FuelAlert UK. FuelAlert is a trading name of Ovidiu Ungureanu, sole trader, based in Peterborough, UK.</p>
|
||||||
|
<p>Data provided by official UK retail price transparency schemes.</p>
|
||||||
|
<p>Postcode data from <a class="underline hover:text-accent" href="https://geoportal.statistics.gov.uk/datasets/ons::onspd-online-latest-centroids-1/about" rel="noopener" target="_blank">ONS Postcode Directory</a>: contains OS data © Crown copyright & database right, Royal Mail data © Royal Mail copyright & database right, and National Statistics data © Crown copyright & database right.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
</script>
|
||||||
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
|
|||||||
import Home from '../views/Home.vue'
|
import Home from '../views/Home.vue'
|
||||||
import { useAuth } from '../composables/useAuth.js'
|
import { useAuth } from '../composables/useAuth.js'
|
||||||
|
|
||||||
|
const Pricing = () => import('../views/Pricing.vue')
|
||||||
const DashboardLayout = () => import('../views/dashboard/DashboardLayout.vue')
|
const DashboardLayout = () => import('../views/dashboard/DashboardLayout.vue')
|
||||||
const Overview = () => import('../views/dashboard/Overview.vue')
|
const Overview = () => import('../views/dashboard/Overview.vue')
|
||||||
const SavedStations = () => import('../views/dashboard/SavedStations.vue')
|
const SavedStations = () => import('../views/dashboard/SavedStations.vue')
|
||||||
@@ -13,6 +14,7 @@ const Appearance = () => import('../views/dashboard/settings/Appearance.vue')
|
|||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/', component: Home, name: 'home' },
|
{ path: '/', component: Home, name: 'home' },
|
||||||
|
{ path: '/pricing', component: Pricing, name: 'pricing' },
|
||||||
{
|
{
|
||||||
path: '/logout',
|
path: '/logout',
|
||||||
name: 'logout',
|
name: 'logout',
|
||||||
|
|||||||
@@ -225,88 +225,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Pricing -->
|
|
||||||
<section id="pricing" class="py-4 md:py-24 px-6 bg-zinc-50">
|
|
||||||
<div class="max-w-7xl mx-auto">
|
|
||||||
<div class="text-center mb-16">
|
|
||||||
<h2 class="text-4xl md:text-5xl font-black font-display text-zinc-800 mb-4">Pricing for every driver</h2>
|
|
||||||
<p class="text-zinc-500 text-lg mb-8">Save hundreds for less than the cost of a coffee.</p>
|
|
||||||
<div class="inline-flex items-center gap-1 p-1 bg-white border border-zinc-300 rounded-full">
|
|
||||||
<button
|
|
||||||
:class="cadence === 'monthly' ? 'bg-accent text-white' : 'text-zinc-500'"
|
|
||||||
class="px-5 py-2 rounded-full text-sm font-bold transition-colors"
|
|
||||||
type="button"
|
|
||||||
@click="cadence = 'monthly'"
|
|
||||||
>
|
|
||||||
Monthly
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
:class="cadence === 'annual' ? 'bg-accent text-white' : 'text-zinc-500'"
|
|
||||||
class="px-5 py-2 rounded-full text-sm font-bold transition-colors"
|
|
||||||
type="button"
|
|
||||||
@click="cadence = 'annual'"
|
|
||||||
>
|
|
||||||
Annual <span class="text-[10px] opacity-80">(save 17%)</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 max-w-5xl mx-auto">
|
|
||||||
<!-- Free -->
|
|
||||||
<div class="bg-white border border-zinc-300 p-8 rounded-3xl flex flex-col h-full">
|
|
||||||
<div class="mb-8">
|
|
||||||
<h4 class="text-xl font-bold font-display mb-2">Free</h4>
|
|
||||||
<div class="flex items-baseline gap-1">
|
|
||||||
<span class="text-4xl font-black">£0</span>
|
|
||||||
<span class="text-zinc-500 text-sm">/mo</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="space-y-4 mb-8 flex-1">
|
|
||||||
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Basic Search</li>
|
|
||||||
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Daily Updates</li>
|
|
||||||
<li class="text-sm flex gap-2 text-zinc-500"><iconify-icon class="text-zinc-300" icon="lucide:x"></iconify-icon> No Alerts</li>
|
|
||||||
</ul>
|
|
||||||
<a :href="ctaHref('free')" class="w-full py-3 px-4 border border-zinc-300 rounded-xl text-center font-bold hover:bg-zinc-100 transition-colors">{{ ctaLabel('free') }}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Daily (backend: basic) -->
|
|
||||||
<div class="bg-white border border-zinc-300 p-8 rounded-3xl flex flex-col h-full">
|
|
||||||
<div class="mb-8">
|
|
||||||
<h4 class="text-xl font-bold font-display mb-2">Daily</h4>
|
|
||||||
<div class="flex items-baseline gap-1">
|
|
||||||
<span class="text-4xl font-black">{{ PRICES[cadence].basic }}</span>
|
|
||||||
<span class="text-zinc-500 text-sm">{{ PRICE_SUFFIX[cadence] }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="space-y-4 mb-8 flex-1">
|
|
||||||
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Buy-or-Wait Score</li>
|
|
||||||
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> 14-day Trend Data</li>
|
|
||||||
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> 3 Daily Price Alerts</li>
|
|
||||||
</ul>
|
|
||||||
<a :href="ctaHref('basic')" class="w-full py-3 px-4 border border-zinc-300 rounded-xl text-center font-bold hover:bg-zinc-100 transition-colors">{{ ctaLabel('basic') }}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Smart (backend: plus) -->
|
|
||||||
<div class="bg-white border-2 border-accent p-8 rounded-3xl flex flex-col h-full relative">
|
|
||||||
<div class="absolute -top-4 left-1/2 -translate-x-1/2 bg-accent text-white px-4 py-1 rounded-full text-[10px] font-black uppercase tracking-widest whitespace-nowrap">Most pick this</div>
|
|
||||||
<div class="mb-8">
|
|
||||||
<h4 class="text-xl font-bold font-display mb-2">Smart</h4>
|
|
||||||
<div class="flex items-baseline gap-1">
|
|
||||||
<span class="text-4xl font-black text-accent">{{ PRICES[cadence].plus }}</span>
|
|
||||||
<span class="text-zinc-500 text-sm">{{ PRICE_SUFFIX[cadence] }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ul class="space-y-4 mb-8 flex-1">
|
|
||||||
<li class="text-sm flex gap-2 font-bold"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Supermarket Anchor</li>
|
|
||||||
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Priority Price Alerts</li>
|
|
||||||
<li class="text-sm flex gap-2"><iconify-icon class="text-accent" icon="lucide:check"></iconify-icon> Multi-location tracking</li>
|
|
||||||
</ul>
|
|
||||||
<a :href="ctaHref('plus')" class="w-full py-3 px-4 bg-accent text-white rounded-xl text-center font-bold shadow-lg hover:bg-primary-dark transition-all">{{ ctaLabel('plus') }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Testimonials -->
|
<!-- Testimonials -->
|
||||||
<!-- <section class="py-12 md:py-24 px-6">
|
<!-- <section class="py-12 md:py-24 px-6">
|
||||||
<div class="max-w-7xl mx-auto">
|
<div class="max-w-7xl mx-auto">
|
||||||
@@ -360,72 +278,14 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<footer class="bg-zinc-50 border-t border-zinc-300 pt-16 pb-8 px-6">
|
<SiteFooter />
|
||||||
<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">
|
|
||||||
<RouterLink class="flex items-center gap-2" to="/">
|
|
||||||
<div class="w-8 h-8 rounded bg-accent flex items-center justify-center">
|
|
||||||
<iconify-icon class="text-white" icon="lucide:fuel"></iconify-icon>
|
|
||||||
</div>
|
|
||||||
<span class="text-xl font-black font-display tracking-tighter text-accent">FuelAlert</span>
|
|
||||||
</RouterLink>
|
|
||||||
<p class="text-sm text-zinc-500 leading-relaxed">
|
|
||||||
Helping UK drivers save money at the pump. Real-time data, smarter choices.
|
|
||||||
</p>
|
|
||||||
<p class="text-sm text-zinc-500">
|
|
||||||
Questions? <a class="text-accent hover:underline" href="mailto:hello@fuel-alert.co.uk">hello@fuel-alert.co.uk</a>
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-4">
|
|
||||||
<iconify-icon class="text-2xl text-zinc-500 hover:text-accent cursor-pointer transition-colors" icon="mdi:twitter"></iconify-icon>
|
|
||||||
<iconify-icon class="text-2xl text-zinc-500 hover:text-accent cursor-pointer transition-colors" icon="mdi:facebook"></iconify-icon>
|
|
||||||
<iconify-icon class="text-2xl text-zinc-500 hover:text-accent cursor-pointer transition-colors" icon="mdi:instagram"></iconify-icon>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<h5 class="font-black text-xs text-zinc-800 tracking-widest">Product</h5>
|
|
||||||
<ul class="space-y-2 text-sm text-zinc-500">
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="#pricing">Pricing</a></li>
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="#features">Features</a></li>
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="#">FuelAlert Pro</a></li>
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="#">Enterprise API</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<h5 class="font-black text-xs text-zinc-800 tracking-widest">Resources</h5>
|
|
||||||
<ul class="space-y-2 text-sm text-zinc-500">
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="#">Market Insights</a></li>
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="#">How We Track</a></li>
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="#">Help Center</a></li>
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="#">Driver Safety</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
|
||||||
<h5 class="font-black text-xs text-zinc-800 tracking-widest">Legal</h5>
|
|
||||||
<ul class="space-y-2 text-sm text-zinc-500">
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="/legal/privacy">Privacy Policy</a></li>
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="/legal/terms">Terms of Service</a></li>
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="/legal/refund">Refund & Cancellation</a></li>
|
|
||||||
<li><a class="hover:text-accent transition-colors" href="/legal/cookies">Cookie Policy</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] tracking-widest text-zinc-500">
|
|
||||||
<p>© 2026 FuelAlert UK. FuelAlert is a trading name of Ovidiu Ungureanu, sole trader, based in Peterborough, UK.</p>
|
|
||||||
<p>Data provided by official UK retail price transparency schemes.</p>
|
|
||||||
<p>Postcode data from <a class="underline hover:text-accent" href="https://geoportal.statistics.gov.uk/datasets/ons::onspd-online-latest-centroids-1/about" rel="noopener" target="_blank">ONS Postcode Directory</a>: contains OS data © Crown copyright & database right, Royal Mail data © Royal Mail copyright & database right, and National Statistics data © Crown copyright & database right.</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch, onMounted, nextTick, defineAsyncComponent } from 'vue'
|
import { ref, computed, watch, onMounted, nextTick, defineAsyncComponent } from 'vue'
|
||||||
import { RouterLink, useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useAuth } from '../composables/useAuth.js'
|
import { useAuth } from '../composables/useAuth.js'
|
||||||
import { useStations } from '../composables/useStations.js'
|
import { useStations } from '../composables/useStations.js'
|
||||||
import api from '../axios.js'
|
import api from '../axios.js'
|
||||||
@@ -441,8 +301,9 @@ import LiveTicker from '../components/landing/LiveTicker.vue'
|
|||||||
import VerdictCard from '../components/landing/VerdictCard.vue'
|
import VerdictCard from '../components/landing/VerdictCard.vue'
|
||||||
import HeroSearch from '../components/landing/HeroSearch.vue'
|
import HeroSearch from '../components/landing/HeroSearch.vue'
|
||||||
import StatsRow from '../components/landing/StatsRow.vue'
|
import StatsRow from '../components/landing/StatsRow.vue'
|
||||||
|
import SiteFooter from '../components/landing/SiteFooter.vue'
|
||||||
|
|
||||||
const { isAuthenticated, userTier } = useAuth()
|
const { isAuthenticated } = useAuth()
|
||||||
|
|
||||||
const liveStats = ref({ stationCount: null, latestPriceAt: null })
|
const liveStats = ref({ stationCount: null, latestPriceAt: null })
|
||||||
|
|
||||||
@@ -458,40 +319,6 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cadence = ref('monthly')
|
|
||||||
|
|
||||||
function ctaHref(tier) {
|
|
||||||
if (tier === 'free') {
|
|
||||||
return isAuthenticated.value ? '/dashboard' : '/register'
|
|
||||||
}
|
|
||||||
if (!isAuthenticated.value) {
|
|
||||||
return '/register?tier=' + tier + '&cadence=' + cadence.value
|
|
||||||
}
|
|
||||||
if (userTier.value === tier) {
|
|
||||||
return '/billing/portal'
|
|
||||||
}
|
|
||||||
return '/billing/checkout/' + tier + '/' + cadence.value
|
|
||||||
}
|
|
||||||
|
|
||||||
function ctaLabel(tier) {
|
|
||||||
if (tier === 'free') {
|
|
||||||
return isAuthenticated.value ? 'Go to dashboard' : 'Start free'
|
|
||||||
}
|
|
||||||
if (isAuthenticated.value && userTier.value === tier) {
|
|
||||||
return 'Manage subscription'
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
basic: 'Choose Daily',
|
|
||||||
plus: 'Choose Smart',
|
|
||||||
pro: 'Choose Pro',
|
|
||||||
}[tier]
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRICES = {
|
|
||||||
monthly: { basic: '£0.99', plus: '£2.49', pro: '£3.99' },
|
|
||||||
annual: { basic: '£9.90', plus: '£24.90', pro: '£39.90' },
|
|
||||||
}
|
|
||||||
const PRICE_SUFFIX = { monthly: '/mo', annual: '/yr' }
|
|
||||||
const { stations, meta, prediction, loading, error, search, reset } = useStations()
|
const { stations, meta, prediction, loading, error, search, reset } = useStations()
|
||||||
const showFullPrediction = computed(() => Boolean(prediction.value) && !prediction.value.tier_locked)
|
const showFullPrediction = computed(() => Boolean(prediction.value) && !prediction.value.tier_locked)
|
||||||
|
|
||||||
|
|||||||
15
resources/js/views/Pricing.vue
Normal file
15
resources/js/views/Pricing.vue
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<div class="min-h-screen bg-zinc-50">
|
||||||
|
<LandingNav />
|
||||||
|
<main class="pt-20 md:pt-24">
|
||||||
|
<PricingGrid />
|
||||||
|
</main>
|
||||||
|
<SiteFooter />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import LandingNav from '../components/landing/LandingNav.vue'
|
||||||
|
import PricingGrid from '../components/PricingGrid.vue'
|
||||||
|
import SiteFooter from '../components/landing/SiteFooter.vue'
|
||||||
|
</script>
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="flex pt-20 max-w-7xl mx-auto w-full px-6 py-8 gap-8">
|
<div class="flex flex-1 pt-20 max-w-7xl mx-auto w-full px-6 py-8 gap-8">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside class="w-56 flex-shrink-0 hidden md:block">
|
<aside class="w-56 flex-shrink-0 hidden md:block">
|
||||||
<nav class="space-y-1">
|
<nav class="space-y-1">
|
||||||
@@ -88,6 +88,8 @@
|
|||||||
<RouterView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SiteFooter />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -95,6 +97,7 @@
|
|||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { RouterLink, RouterView, useRoute, useRouter } from 'vue-router'
|
import { RouterLink, RouterView, useRoute, useRouter } from 'vue-router'
|
||||||
import { useAuth } from '../../composables/useAuth.js'
|
import { useAuth } from '../../composables/useAuth.js'
|
||||||
|
import SiteFooter from '../../components/landing/SiteFooter.vue'
|
||||||
|
|
||||||
const { user } = useAuth()
|
const { user } = useAuth()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
@props([
|
|
||||||
'name',
|
|
||||||
'price',
|
|
||||||
'buttonText',
|
|
||||||
'perks' => [],
|
|
||||||
'featured' => false,
|
|
||||||
'dark' => false,
|
|
||||||
])
|
|
||||||
|
|
||||||
@php
|
|
||||||
$cardClass = match(true) {
|
|
||||||
$dark => 'bg-primary border border-primary text-white',
|
|
||||||
$featured => 'bg-white border-2 border-primary',
|
|
||||||
default => 'bg-white border border-zinc-300',
|
|
||||||
};
|
|
||||||
|
|
||||||
$buttonClass = $dark
|
|
||||||
? 'bg-white text-primary hover:bg-zinc-100'
|
|
||||||
: 'bg-primary text-white hover:bg-primary-dark';
|
|
||||||
@endphp
|
|
||||||
|
|
||||||
<div {{ $attributes->merge(['class' => "p-8 rounded-3xl flex flex-col relative $cardClass"]) }}>
|
|
||||||
|
|
||||||
@if ($featured)
|
|
||||||
<div class="absolute -top-4 left-1/2 -translate-x-1/2 bg-primary text-white px-4 py-1 rounded-full text-[10px] font-black uppercase tracking-widest whitespace-nowrap">
|
|
||||||
Most Popular
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div class="flex items-baseline justify-between mb-6">
|
|
||||||
<h4 class="text-xl font-bold">{{ $name }}</h4>
|
|
||||||
<div class="flex items-baseline gap-1">
|
|
||||||
<span @class(['text-3xl font-black', 'text-primary' => $featured])>{{ $price }}</span>
|
|
||||||
<span @class(['text-sm', 'text-zinc-500' => !$dark, 'text-zinc-400' => $dark])>/mo</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="space-y-2 mb-6 flex-1">
|
|
||||||
@foreach ($perks as $perk)
|
|
||||||
<li @class(['text-sm flex gap-2 items-center', 'text-zinc-500' => !$perk['included'] && !$dark])>
|
|
||||||
@if ($perk['included'])
|
|
||||||
<iconify-icon icon="lucide:check" class="text-primary shrink-0"></iconify-icon>
|
|
||||||
@else
|
|
||||||
<iconify-icon icon="lucide:x" class="text-zinc-300 shrink-0"></iconify-icon>
|
|
||||||
@endif
|
|
||||||
{{ $perk['text'] }}
|
|
||||||
</li>
|
|
||||||
@endforeach
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<a href="{{ route('register') }}" class="w-full py-2.5 px-6 {{ $buttonClass }} rounded-full text-sm text-center font-bold shadow-lg transition-all hover:scale-105 active:scale-95">
|
|
||||||
{{ $buttonText }}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
is the <strong>data controller</strong> for personal data collected through this service.
|
is the <strong>data controller</strong> for personal data collected through this service.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
As the data controller, Ovidiu Ungureanu is registering with the UK Information
|
Ovidiu Ungureanu is registered with the UK Information Commissioner's Office (ICO) as a
|
||||||
Commissioner's Office (ICO). Our registration number will be published here once issued.
|
data controller. <strong>ICO registration reference: 00014395133.</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you have any questions about this policy or how we handle your personal data, contact us at
|
If you have any questions about this policy or how we handle your personal data, contact us at
|
||||||
@@ -31,8 +31,8 @@
|
|||||||
<h3 class="font-semibold text-zinc-900">Contact data for alerts</h3>
|
<h3 class="font-semibold text-zinc-900">Contact data for alerts</h3>
|
||||||
<p>
|
<p>
|
||||||
If you opt in to WhatsApp or SMS alerts, your mobile phone number. We collect it only to
|
If you opt in to WhatsApp or SMS alerts, your mobile phone number. We collect it only to
|
||||||
send the alerts you have requested, and only after you confirm the number through an
|
send the alerts you have requested, and only after you verify the number through a
|
||||||
opt-in step.
|
one-time passcode (OTP) sent to that number.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="font-semibold text-zinc-900">Location data</h3>
|
<h3 class="font-semibold text-zinc-900">Location data</h3>
|
||||||
@@ -64,6 +64,15 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<h3 class="font-semibold text-zinc-900">Search and query logs</h3>
|
||||||
|
<p>
|
||||||
|
When you search for stations or prices, we log the approximate search location, fuel
|
||||||
|
type selected, result count, timestamp, a one-way hashed IP address, and basic device
|
||||||
|
information (browser type, device type). We use these logs for abuse prevention,
|
||||||
|
troubleshooting, and aggregate service statistics. We do not use them to build a profile
|
||||||
|
of your individual behaviour. Logs are retained for a maximum of 24 months.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h3 class="font-semibold text-zinc-900">Payment data</h3>
|
<h3 class="font-semibold text-zinc-900">Payment data</h3>
|
||||||
<p>
|
<p>
|
||||||
Payment card details are collected and processed by <strong>Stripe</strong>, our payment
|
Payment card details are collected and processed by <strong>Stripe</strong>, our payment
|
||||||
@@ -72,16 +81,25 @@
|
|||||||
renewal date).
|
renewal date).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h3 class="font-semibold text-zinc-900">Push notification data</h3>
|
||||||
|
<p>
|
||||||
|
If you opt in to push notifications via OneSignal, we store your push subscription
|
||||||
|
endpoint (a browser-specific URL), the encryption keys needed for secure message
|
||||||
|
delivery, and your notification preferences. This data is retained until you unsubscribe,
|
||||||
|
revoke browser permission, or your subscription becomes stale.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h3 class="font-semibold text-zinc-900">Usage data</h3>
|
<h3 class="font-semibold text-zinc-900">Usage data</h3>
|
||||||
<p>
|
<p>
|
||||||
Features you use, queries you make, and alerts you configure — used to deliver the
|
Features you use and alerts you configure — used to deliver the service and improve it.
|
||||||
service and improve it.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="font-semibold text-zinc-900">Technical data</h3>
|
<h3 class="font-semibold text-zinc-900">Technical data</h3>
|
||||||
<p>
|
<p>
|
||||||
IP address, browser type and version, device type, and operating system — used for
|
IP address, browser type and version, device type, and operating system. IP address is
|
||||||
security, fraud prevention, and basic analytics.
|
collected alongside account actions and searches for security, abuse prevention, and
|
||||||
|
fraud detection (lawful basis: legitimate interests, Art. 6(1)(f)). We do not use IP
|
||||||
|
addresses to identify you as an individual in any other context.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3 class="font-semibold text-zinc-900">Marketing preferences</h3>
|
<h3 class="font-semibold text-zinc-900">Marketing preferences</h3>
|
||||||
@@ -97,7 +115,8 @@
|
|||||||
<li><strong>Finding stations near you on request (device location)</strong> — consent (Art. 6(1)(a)), given through your browser's location permission and withdrawable at any time.</li>
|
<li><strong>Finding stations near you on request (device location)</strong> — consent (Art. 6(1)(a)), given through your browser's location permission and withdrawable at any time.</li>
|
||||||
<li><strong>Storing your saved location as a registered user</strong> — contract (Art. 6(1)(b)).</li>
|
<li><strong>Storing your saved location as a registered user</strong> — contract (Art. 6(1)(b)).</li>
|
||||||
<li><strong>Payment processing</strong> — contract (Art. 6(1)(b)).</li>
|
<li><strong>Payment processing</strong> — contract (Art. 6(1)(b)).</li>
|
||||||
<li><strong>Security and fraud prevention</strong> — legitimate interests (Art. 6(1)(f)).</li>
|
<li><strong>Security, abuse prevention, and fraud detection (including IP address logging)</strong> — legitimate interests (Art. 6(1)(f)).</li>
|
||||||
|
<li><strong>Search and query logging for aggregate statistics and troubleshooting</strong> — legitimate interests (Art. 6(1)(f)).</li>
|
||||||
<li><strong>Aggregated, non-identifying analytics and product improvement</strong> — legitimate interests (Art. 6(1)(f)).</li>
|
<li><strong>Aggregated, non-identifying analytics and product improvement</strong> — legitimate interests (Art. 6(1)(f)).</li>
|
||||||
<li><strong>Marketing emails</strong> — consent (Art. 6(1)(a)). You can withdraw consent at any time.</li>
|
<li><strong>Marketing emails</strong> — consent (Art. 6(1)(a)). You can withdraw consent at any time.</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -118,7 +137,19 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">5. Who we share data with</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">5. Automated recommendations</h2>
|
||||||
|
<p>
|
||||||
|
FuelAlert generates fill-up timing recommendations (for example, "fill up now" or "wait")
|
||||||
|
using an algorithm that analyses local price trends, historical patterns, and market
|
||||||
|
signals. These recommendations are <strong>informational only</strong> and are produced
|
||||||
|
automatically without human review. They do not have legal or similarly significant
|
||||||
|
effects on you, and we do not use them to make decisions that affect your rights or
|
||||||
|
interests in any material way.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="space-y-3">
|
||||||
|
<h2 class="font-display text-2xl font-bold text-zinc-900">6. Who we share data with</h2>
|
||||||
<p>We use the following processors to deliver the service. We do not sell your data to anyone.</p>
|
<p>We use the following processors to deliver the service. We do not sell your data to anyone.</p>
|
||||||
<ul class="list-disc space-y-2 pl-6">
|
<ul class="list-disc space-y-2 pl-6">
|
||||||
<li>
|
<li>
|
||||||
@@ -131,15 +162,17 @@
|
|||||||
<strong>Umami Analytics</strong> — we run our own self-hosted Umami instance to
|
<strong>Umami Analytics</strong> — we run our own self-hosted Umami instance to
|
||||||
collect aggregated, cookieless usage metrics (pages viewed, referrer, country, device
|
collect aggregated, cookieless usage metrics (pages viewed, referrer, country, device
|
||||||
type). It does not store data that identifies you as an individual, and no analytics
|
type). It does not store data that identifies you as an individual, and no analytics
|
||||||
data is shared with third parties.
|
data is shared with third parties. We periodically review our analytics setup to
|
||||||
|
confirm it remains cookieless; if this changes we will update our Cookie Policy and
|
||||||
|
request consent before setting any non-essential cookies.
|
||||||
</li>
|
</li>
|
||||||
<li><strong>Vonage</strong> — delivers WhatsApp and SMS alerts if you opt in to those channels. Your phone number is shared only to send messages you have requested. See <a class="text-accent underline" href="https://www.vonage.co.uk/legal/privacy-policy/" target="_blank" rel="noopener">Vonage's privacy policy</a>.</li>
|
<li><strong>Vonage</strong> — delivers WhatsApp and SMS alerts if you opt in to those channels. Your phone number is shared only to send messages you have requested. See <a class="text-accent underline" href="https://www.vonage.co.uk/legal/privacy-policy/" target="_blank" rel="noopener">Vonage's privacy policy</a>.</li>
|
||||||
<li><strong>OneSignal</strong> — delivers web push notifications if you opt in to push alerts. See <a class="text-accent underline" href="https://onesignal.com/privacy_policy" target="_blank" rel="noopener">OneSignal's privacy policy</a>.</li>
|
<li><strong>OneSignal</strong> — delivers web push notifications if you opt in to push alerts. Push subscription data (endpoint, encryption keys, device type) is processed by OneSignal on our behalf. See <a class="text-accent underline" href="https://onesignal.com/privacy_policy" target="_blank" rel="noopener">OneSignal's privacy policy</a>.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">6. International transfers</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">7. International transfers</h2>
|
||||||
<p>
|
<p>
|
||||||
Some of our processors — including Stripe, Vonage and OneSignal — operate
|
Some of our processors — including Stripe, Vonage and OneSignal — operate
|
||||||
outside the UK and EEA, including in the United States. Where personal data is transferred
|
outside the UK and EEA, including in the United States. Where personal data is transferred
|
||||||
@@ -150,18 +183,22 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">7. How long we keep data</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">8. How long we keep data</h2>
|
||||||
<ul class="list-disc space-y-1 pl-6">
|
<ul class="list-disc space-y-1 pl-6">
|
||||||
<li><strong>Active account data:</strong> for as long as your account is open, plus 12 months after closure.</li>
|
<li><strong>Active account data:</strong> for as long as your account is open, plus 12 months after closure.</li>
|
||||||
<li><strong>Saved location (registered users):</strong> while your account is active; deleted when you delete your account.</li>
|
<li><strong>Saved location (registered users):</strong> while your account is active; deleted when you delete your account.</li>
|
||||||
|
<li><strong>Alert and notification preferences:</strong> while your account is active; deleted when you close your account or remove the preference.</li>
|
||||||
|
<li><strong>Push notification subscriptions:</strong> until you unsubscribe, revoke browser permission, or the subscription becomes stale.</li>
|
||||||
<li><strong>Payment records:</strong> 6 years, to meet HMRC requirements for self-employed traders.</li>
|
<li><strong>Payment records:</strong> 6 years, to meet HMRC requirements for self-employed traders.</li>
|
||||||
<li><strong>Marketing data:</strong> until you withdraw consent.</li>
|
<li><strong>Marketing data:</strong> until you withdraw consent.</li>
|
||||||
<li><strong>Logs and analytics:</strong> a maximum of 24 months.</li>
|
<li><strong>Security and fraud logs (including IP records):</strong> a maximum of 12 months.</li>
|
||||||
|
<li><strong>Search and query logs:</strong> a maximum of 24 months.</li>
|
||||||
|
<li><strong>Aggregated analytics:</strong> retained indefinitely in anonymised, non-identifiable form only.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">8. Your rights under UK GDPR</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">9. Your rights under UK GDPR</h2>
|
||||||
<p>You have the following rights in relation to your personal data:</p>
|
<p>You have the following rights in relation to your personal data:</p>
|
||||||
<ul class="list-disc space-y-1 pl-6">
|
<ul class="list-disc space-y-1 pl-6">
|
||||||
<li><strong>Right of access</strong> — ask for a copy of the data we hold about you.</li>
|
<li><strong>Right of access</strong> — ask for a copy of the data we hold about you.</li>
|
||||||
@@ -170,7 +207,7 @@
|
|||||||
<li><strong>Right to restrict processing</strong> — ask us to pause processing in certain circumstances.</li>
|
<li><strong>Right to restrict processing</strong> — ask us to pause processing in certain circumstances.</li>
|
||||||
<li><strong>Right to data portability</strong> — receive your data in a machine-readable format.</li>
|
<li><strong>Right to data portability</strong> — receive your data in a machine-readable format.</li>
|
||||||
<li><strong>Right to object</strong> — object to processing based on legitimate interests.</li>
|
<li><strong>Right to object</strong> — object to processing based on legitimate interests.</li>
|
||||||
<li><strong>Rights related to automated decision-making</strong> — we do <strong>not</strong> make solely automated decisions with legal or similarly significant effects on you.</li>
|
<li><strong>Rights related to automated decision-making</strong> — our fill-up timing recommendations are generated algorithmically but are informational only and do not have legal or similarly significant effects on you.</li>
|
||||||
<li><strong>Right to withdraw consent</strong> — where we rely on consent (for example, device location or marketing).</li>
|
<li><strong>Right to withdraw consent</strong> — where we rely on consent (for example, device location or marketing).</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
@@ -181,7 +218,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">9. Cookies</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">10. Cookies</h2>
|
||||||
<p>
|
<p>
|
||||||
We use only a small number of essential cookies to operate the service, and self-hosted,
|
We use only a small number of essential cookies to operate the service, and self-hosted,
|
||||||
cookieless analytics. Full details are in our
|
cookieless analytics. Full details are in our
|
||||||
@@ -190,7 +227,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">10. Security</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">11. Security</h2>
|
||||||
<p>
|
<p>
|
||||||
All traffic between your device and our service is encrypted with HTTPS. Passwords are
|
All traffic between your device and our service is encrypted with HTTPS. Passwords are
|
||||||
stored as one-way hashes — we never see your plaintext password. Sensitive fields in
|
stored as one-way hashes — we never see your plaintext password. Sensitive fields in
|
||||||
@@ -201,7 +238,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">11. Children</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">12. Children</h2>
|
||||||
<p>
|
<p>
|
||||||
FuelAlert is not directed at children. We do not knowingly collect data from anyone under
|
FuelAlert is not directed at children. We do not knowingly collect data from anyone under
|
||||||
16. If you believe a child has provided us with personal data, contact us and we will
|
16. If you believe a child has provided us with personal data, contact us and we will
|
||||||
@@ -210,7 +247,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">12. Complaints</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">13. Complaints</h2>
|
||||||
<p>
|
<p>
|
||||||
We hope you'll contact us first if you have a complaint, so we can try to put it right.
|
We hope you'll contact us first if you have a complaint, so we can try to put it right.
|
||||||
You also have the right to lodge a complaint with the UK Information Commissioner's Office
|
You also have the right to lodge a complaint with the UK Information Commissioner's Office
|
||||||
@@ -223,7 +260,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">13. Changes to this policy</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">14. Changes to this policy</h2>
|
||||||
<p>
|
<p>
|
||||||
We may update this policy from time to time. If we make material changes we will notify
|
We may update this policy from time to time. If we make material changes we will notify
|
||||||
registered users by email. Non-material changes will be shown by an updated "Last updated"
|
registered users by email. Non-material changes will be shown by an updated "Last updated"
|
||||||
@@ -232,10 +269,14 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">14. Contact</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">15. Contact</h2>
|
||||||
<p>
|
<p>
|
||||||
For any privacy queries, email
|
For any privacy or data protection queries, email
|
||||||
<a href="mailto:hello@fuel-alert.co.uk" class="text-accent underline">hello@fuel-alert.co.uk</a>.
|
<a href="mailto:hello@fuel-alert.co.uk" class="text-accent underline">hello@fuel-alert.co.uk</a>.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="text-sm text-zinc-600">
|
||||||
|
Data controller: Ovidiu Ungureanu trading as FuelAlert, Peterborough, United Kingdom.
|
||||||
|
ICO registration reference: 00014395133.
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</x-layouts.legal>
|
</x-layouts.legal>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{{-- DRAFT: Generated {{ date('Y-m-d') }}. Review by UK-qualified solicitor recommended before launch. --}}
|
{{-- DRAFT: Generated {{ date('Y-m-d') }}. Review by UK-qualified solicitor recommended before launch. --}}
|
||||||
<x-layouts.legal
|
<x-layouts.legal
|
||||||
title="Refund & Cancellation Policy"
|
title="Refund & Cancellation Policy"
|
||||||
heading="Refund & Cancellation Policy"
|
heading="Refund & Cancellation Policy"
|
||||||
lastUpdated="{{ now()->format('j F Y') }}"
|
lastUpdated="{{ now()->format('j F Y') }}"
|
||||||
metaDescription="Your right to cancel a FuelAlert subscription, including the 14-day cooling-off period under UK law.">
|
metaDescription="Your right to cancel a FuelAlert subscription, including the 14-day cooling-off period under UK law.">
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{{-- DRAFT: Generated {{ date('Y-m-d') }}. Review by UK-qualified solicitor recommended before launch. --}}
|
{{-- DRAFT: Generated {{ date('Y-m-d') }}. Review by UK-qualified solicitor recommended before launch. --}}
|
||||||
<x-layouts.legal
|
<x-layouts.legal
|
||||||
title="Terms of Service"
|
title="Terms of Service"
|
||||||
heading="Terms of Service"
|
heading="Terms of Service"
|
||||||
lastUpdated="{{ now()->format('j F Y') }}"
|
lastUpdated="{{ now()->format('j F Y') }}"
|
||||||
metaDescription="The terms that govern your use of FuelAlert's subscription service.">
|
metaDescription="The terms that govern your use of FuelAlert's subscription service.">
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">1. About these terms</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">1. About these terms</h2>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
FuelAlert is a trading name of <strong>Ovidiu Ungureanu</strong>, a sole trader based in
|
FuelAlert is a trading name of <strong>Ovidiu Ungureanu</strong>, a sole trader based in
|
||||||
Peterborough, United Kingdom ("we", "us", "our"). These terms form a legally binding
|
Peterborough, United Kingdom ("we", "us", "our"). These terms form a legally binding
|
||||||
contract between you and Ovidiu Ungureanu trading as FuelAlert.
|
contract between you and Ovidiu Ungureanu trading as FuelAlert.
|
||||||
|
ICO registration reference: 00014395133.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
By creating an account or using the service, you confirm that you have read, understood
|
By creating an account or using the service, you confirm that you have read, understood
|
||||||
@@ -22,8 +23,12 @@
|
|||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">2. The service</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">2. The service</h2>
|
||||||
<p>
|
<p>
|
||||||
FuelAlert provides UK fuel price comparison and forecasting. We aggregate publicly
|
FuelAlert provides UK fuel price comparison and fill-up timing recommendations. We act
|
||||||
available UK fuel price data and surface it through a web app, alerts and forecasts.
|
as a downstream consumer of publicly available UK government fuel price data feeds
|
||||||
|
(including the UK Fuel Finder / Pump Watch transparency scheme) and surface that data
|
||||||
|
through a web app, alerts, and forecasts. We do not control the prices submitted by fuel
|
||||||
|
retailers to those upstream schemes and are not responsible for errors or delays in
|
||||||
|
that data.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We offer a free tier and one or more paid subscription plans. The current list of plans
|
We offer a free tier and one or more paid subscription plans. The current list of plans
|
||||||
@@ -49,8 +54,8 @@
|
|||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">4. Subscriptions, billing and payment</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">4. Subscriptions, billing and payment</h2>
|
||||||
<p>
|
<p>
|
||||||
Paid plans are billed in advance, either monthly or annually. The current price for each
|
Paid plans are billed monthly in advance. The current price for each plan is shown on
|
||||||
plan is shown on the <a class="text-accent underline" href="/#pricing">pricing page</a> at the time you subscribe.
|
the <a class="text-accent underline" href="/#pricing">pricing page</a> at the time you subscribe.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Auto-renewal.</strong> Subscriptions renew automatically at the end of each
|
<strong>Auto-renewal.</strong> Subscriptions renew automatically at the end of each
|
||||||
@@ -71,7 +76,13 @@
|
|||||||
<p>
|
<p>
|
||||||
<strong>VAT.</strong> FuelAlert is currently below the UK VAT registration threshold and is
|
<strong>VAT.</strong> FuelAlert is currently below the UK VAT registration threshold and is
|
||||||
not VAT-registered, so no VAT is charged on your subscription. The price shown is the total
|
not VAT-registered, so no VAT is charged on your subscription. The price shown is the total
|
||||||
amount you pay.
|
amount you pay. If our VAT status changes, we will update these terms and notify you before
|
||||||
|
any price change takes effect.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Downgrade on cancellation.</strong> When a paid subscription ends or is cancelled,
|
||||||
|
your account reverts to the free tier. Paid alert channels (WhatsApp, SMS) are deactivated,
|
||||||
|
but your alert settings are retained and will reactivate if you resubscribe.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -94,11 +105,14 @@
|
|||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">6. Acceptable use</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">6. Acceptable use</h2>
|
||||||
<p>You agree not to:</p>
|
<p>You agree not to:</p>
|
||||||
<ul class="list-disc space-y-1 pl-6">
|
<ul class="list-disc space-y-1 pl-6">
|
||||||
<li>Scrape, reverse-engineer, or bulk-extract data from the service.</li>
|
<li>Scrape, reverse-engineer, or bulk-extract data from the service without our written permission.</li>
|
||||||
|
<li>Mirror, republish, or systematically reproduce our compiled price data, station rankings, scoring outputs, or any other value-added data derived from the service.</li>
|
||||||
|
<li>Use the service or its outputs for commercial exploitation, resale, or competitor monitoring without our written consent.</li>
|
||||||
<li>Resell or redistribute fuel price data taken from FuelAlert.</li>
|
<li>Resell or redistribute fuel price data taken from FuelAlert.</li>
|
||||||
<li>Use the service for any unlawful purpose.</li>
|
<li>Use the service for any unlawful purpose.</li>
|
||||||
<li>Attempt to circumvent or compromise our security measures.</li>
|
<li>Attempt to circumvent or compromise our security measures.</li>
|
||||||
<li>Use automated tools to make queries beyond what a single human user would reasonably make.</li>
|
<li>Use automated tools to make queries beyond what a single human user would reasonably make.</li>
|
||||||
|
<li>Use the service while operating a motor vehicle. You must not interact with the service while a vehicle is in motion. Compliance with the Road Traffic Act 1988, the Highway Code, and all applicable road traffic laws is your sole responsibility.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -107,12 +121,14 @@
|
|||||||
<p>
|
<p>
|
||||||
Fuel prices shown on FuelAlert are sourced from official UK government data feeds
|
Fuel prices shown on FuelAlert are sourced from official UK government data feeds
|
||||||
(including the Pump Watch / Fuel Finder transparency schemes) and refreshed
|
(including the Pump Watch / Fuel Finder transparency schemes) and refreshed
|
||||||
periodically. Stations can change prices at any time, and there is usually a delay
|
periodically. FuelAlert acts as a downstream consumer of those feeds and does not
|
||||||
between a forecourt change and the feed update.
|
control the data submitted by fuel retailers to the central aggregator. Stations can
|
||||||
|
change prices at any time, and there is usually a delay between a forecourt change
|
||||||
|
and the feed update.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
We make reasonable efforts to display accurate prices but <strong>we cannot guarantee
|
We make reasonable efforts to display accurate prices but <strong>we cannot guarantee
|
||||||
that the price shown will match the price at the pump</strong> when you arrive.
|
that the price shown will match the price at the pump</strong> when you arrive.
|
||||||
<strong>Always confirm the price at the pump before fuelling.</strong>
|
<strong>Always confirm the price at the pump before fuelling.</strong>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -123,21 +139,22 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">8. Forecasts and predictions</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">8. Forecasts and recommendations</h2>
|
||||||
<p>
|
<p>
|
||||||
FuelAlert may show forecasts and recommendations (e.g. "fill up now" or "wait"). These
|
FuelAlert may show forecasts and recommendations (e.g. "fill up now" or "wait"). These
|
||||||
are <strong>informational only</strong>, are not financial advice, and should not be
|
are generated algorithmically based on local price trends, historical patterns, and
|
||||||
relied upon as a guarantee of future prices. Past trends do not guarantee future prices.
|
market signals. They are <strong>informational only</strong>, are not financial advice,
|
||||||
We do not warrant the accuracy of any forecast.
|
and should not be relied upon as a guarantee of future prices. Past trends do not
|
||||||
|
guarantee future prices. We do not warrant the accuracy of any forecast or recommendation.
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="space-y-3">
|
<section class="space-y-3">
|
||||||
<h2 class="font-display text-2xl font-bold text-zinc-900">9. Intellectual property</h2>
|
<h2 class="font-display text-2xl font-bold text-zinc-900">9. Intellectual property</h2>
|
||||||
<p>
|
<p>
|
||||||
The FuelAlert name, logo, software, and original content are owned by Ovidiu Ungureanu.
|
The FuelAlert name, logo, software, scoring algorithms, and original content are owned
|
||||||
You receive a limited, non-exclusive, revocable licence to use the service for personal,
|
by Ovidiu Ungureanu. You receive a limited, non-exclusive, revocable licence to use the
|
||||||
non-commercial purposes.
|
service for personal, non-commercial purposes.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Underlying fuel price data is owned by the respective fuel retailers and published under
|
Underlying fuel price data is owned by the respective fuel retailers and published under
|
||||||
@@ -176,6 +193,11 @@
|
|||||||
including but not limited to outages or errors at our payment processor, hosting
|
including but not limited to outages or errors at our payment processor, hosting
|
||||||
provider, or upstream data sources.
|
provider, or upstream data sources.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
We are not responsible for the accuracy, completeness, or timeliness of data
|
||||||
|
submitted by fuel retailers to the UK Fuel Finder scheme or any other upstream
|
||||||
|
source we consume as a downstream aggregator.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -218,5 +240,9 @@
|
|||||||
For questions about these terms, email
|
For questions about these terms, email
|
||||||
<a href="mailto:hello@fuel-alert.co.uk" class="text-accent underline">hello@fuel-alert.co.uk</a>.
|
<a href="mailto:hello@fuel-alert.co.uk" class="text-accent underline">hello@fuel-alert.co.uk</a>.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="text-sm text-zinc-600">
|
||||||
|
Ovidiu Ungureanu trading as FuelAlert, Peterborough, United Kingdom.
|
||||||
|
ICO registration reference: 00014395133.
|
||||||
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</x-layouts.legal>
|
</x-layouts.legal>
|
||||||
@@ -53,7 +53,7 @@ it('canSendNow returns false when tier does not allow the channel', function ():
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('canSendNow returns false when daily limit is reached', function (): void {
|
it('canSendNow returns false when daily limit is reached', function (): void {
|
||||||
$plan = Plan::where('name', 'plus')->first(); // sms_daily_limit = 1
|
$plan = Plan::where('name', 'plus')->first(); // sms_daily_limit = 3
|
||||||
$user = User::factory()->create();
|
$user = User::factory()->create();
|
||||||
|
|
||||||
UserNotificationPreference::factory()->create([
|
UserNotificationPreference::factory()->create([
|
||||||
@@ -70,7 +70,7 @@ it('canSendNow returns false when daily limit is reached', function (): void {
|
|||||||
'created_at' => now(),
|
'created_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect($plan->sms_daily_limit)->toBe(1);
|
expect($plan->sms_daily_limit)->toBe(3);
|
||||||
|
|
||||||
$sentCount = NotificationLog::where('user_id', $user->id)
|
$sentCount = NotificationLog::where('user_id', $user->id)
|
||||||
->where('channel', 'sms')
|
->where('channel', 'sms')
|
||||||
|
|||||||
Reference in New Issue
Block a user