diff --git a/.claude/rules/notifications.md b/.claude/rules/notifications.md
index 2ed93c9..1a05486 100644
--- a/.claude/rules/notifications.md
+++ b/.claude/rules/notifications.md
@@ -6,7 +6,7 @@
|------------|--------|-------|------------|----------|-----------|
| Free | £0 | ✓ weekly digest | ✗ | ✗ | ✗ |
| 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 |
## NotificationDispatchService
@@ -41,8 +41,8 @@ Subject line / message copy adapts based on `recommendation` and `confidence`.
- Same Vonage client, SMS channel
- Triggered only (not daily) — fires when signal strength ≥ 2 AND price event warrants it
-- Plus: max 8 SMS/month (enforced via alerts table count)
-- Pro: max 30 SMS/month
+- Plus (Smart): max 3 SMS/day (enforced via notification_log count)
+- Pro: deferred from launch — see `tiers.md`
- Cost: ~3.5p per message UK
## Email
diff --git a/.claude/rules/tiers.md b/.claude/rules/tiers.md
index 9b8511d..701844d 100644
--- a/.claude/rules/tiers.md
+++ b/.claude/rules/tiers.md
@@ -50,7 +50,7 @@ All six are available to all tiers. The restriction is quantity only:
| email | weekly digest | daily | ✓ triggered | ✓ triggered |
| push | ✗ | ✓ 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
price triggers — available to any tier that has WhatsApp enabled.
diff --git a/database/factories/PlanFactory.php b/database/factories/PlanFactory.php
index d771cd5..222053e 100644
--- a/database/factories/PlanFactory.php
+++ b/database/factories/PlanFactory.php
@@ -80,7 +80,7 @@ class PlanFactory extends Factory
'whatsapp_daily_limit' => 5,
'whatsapp_scheduled_updates' => 2,
'sms_enabled' => true,
- 'sms_daily_limit' => 1,
+ 'sms_daily_limit' => 3,
'ai_predictions' => true,
'price_threshold' => true,
'score_alerts' => true,
diff --git a/database/seeders/PlanSeeder.php b/database/seeders/PlanSeeder.php
index 7e50ae4..beacbf9 100644
--- a/database/seeders/PlanSeeder.php
+++ b/database/seeders/PlanSeeder.php
@@ -57,7 +57,7 @@ class PlanSeeder extends Seeder
'whatsapp_daily_limit' => 5,
'whatsapp_scheduled_updates' => 2,
'sms_enabled' => true,
- 'sms_daily_limit' => 1,
+ 'sms_daily_limit' => 3,
'ai_predictions' => true,
'price_threshold' => true,
'score_alerts' => true,
diff --git a/docs/tiers.md b/docs/tiers.md
index cc66f31..f2d39c1 100644
--- a/docs/tiers.md
+++ b/docs/tiers.md
@@ -12,7 +12,7 @@ decision — which channels a user can receive, how often, and what features the
|-------|--------|---------------|-----------|-----------|------------|----------------|-----------------|--------------|------------|
| free | £0 | weekly digest | — | — | — | — | — | — | 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 |
Tiers are stored in the `plans` table. The `features` JSON column defines every limit and flag.
diff --git a/resources/js/components/PricingGrid.vue b/resources/js/components/PricingGrid.vue
new file mode 100644
index 0000000..6382b96
--- /dev/null
+++ b/resources/js/components/PricingGrid.vue
@@ -0,0 +1,125 @@
+
+
+
+
+
+
Pricing for every driver
+
Save hundreds for less than the cost of a coffee.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Daily
+
+ {{ PRICES[cadence].basic }}
+ {{ PRICE_SUFFIX[cadence] }}
+
+
+
+ - Buy-or-wait score + reason
+ - Daily email, push & WhatsApp
+ - Price-drop & score alerts
+
+
{{ ctaLabel('basic') }}
+
+
+
+
+
Most pick this
+
+
Smart
+
+ {{ PRICES[cadence].plus }}
+ {{ PRICE_SUFFIX[cadence] }}
+
+
+
+ - AI fuel-price prediction
+ - Real-time email, push & WhatsApp
+ - SMS alerts (up to 3/day)
+
+
{{ ctaLabel('plus') }}
+
+
+
+
+
+
+
diff --git a/resources/js/components/UpsellBanner.vue b/resources/js/components/UpsellBanner.vue
index 4f5c1e9..d13adfa 100644
--- a/resources/js/components/UpsellBanner.vue
+++ b/resources/js/components/UpsellBanner.vue
@@ -47,6 +47,6 @@ const stationCountLabel = computed(() => {
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')
diff --git a/resources/js/components/landing/LandingNav.vue b/resources/js/components/landing/LandingNav.vue
index 2fb1c79..0f384ac 100644
--- a/resources/js/components/landing/LandingNav.vue
+++ b/resources/js/components/landing/LandingNav.vue
@@ -11,7 +11,7 @@
diff --git a/resources/js/components/landing/SiteFooter.vue b/resources/js/components/landing/SiteFooter.vue
new file mode 100644
index 0000000..379a857
--- /dev/null
+++ b/resources/js/components/landing/SiteFooter.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
diff --git a/resources/js/router/index.js b/resources/js/router/index.js
index 3d80650..69d91ac 100644
--- a/resources/js/router/index.js
+++ b/resources/js/router/index.js
@@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import { useAuth } from '../composables/useAuth.js'
+const Pricing = () => import('../views/Pricing.vue')
const DashboardLayout = () => import('../views/dashboard/DashboardLayout.vue')
const Overview = () => import('../views/dashboard/Overview.vue')
const SavedStations = () => import('../views/dashboard/SavedStations.vue')
@@ -13,6 +14,7 @@ const Appearance = () => import('../views/dashboard/settings/Appearance.vue')
const routes = [
{ path: '/', component: Home, name: 'home' },
+ { path: '/pricing', component: Pricing, name: 'pricing' },
{
path: '/logout',
name: 'logout',
diff --git a/resources/js/views/Home.vue b/resources/js/views/Home.vue
index 3f0c6bc..e730349 100644
--- a/resources/js/views/Home.vue
+++ b/resources/js/views/Home.vue
@@ -225,88 +225,6 @@
-
-
-
-
-
Pricing for every driver
-
Save hundreds for less than the cost of a coffee.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Daily
-
- {{ PRICES[cadence].basic }}
- {{ PRICE_SUFFIX[cadence] }}
-
-
-
- - Buy-or-Wait Score
- - 14-day Trend Data
- - 3 Daily Price Alerts
-
-
{{ ctaLabel('basic') }}
-
-
-
-
-
Most pick this
-
-
Smart
-
- {{ PRICES[cadence].plus }}
- {{ PRICE_SUFFIX[cadence] }}
-
-
-
- - Supermarket Anchor
- - Priority Price Alerts
- - Multi-location tracking
-
-
{{ ctaLabel('plus') }}
-
-
-
-
-
-
+
diff --git a/resources/views/components/pricing-card.blade.php b/resources/views/components/pricing-card.blade.php
deleted file mode 100644
index 8a05332..0000000
--- a/resources/views/components/pricing-card.blade.php
+++ /dev/null
@@ -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
-
-merge(['class' => "p-8 rounded-3xl flex flex-col relative $cardClass"]) }}>
-
- @if ($featured)
-
- Most Popular
-
- @endif
-
-
-
{{ $name }}
-
- $featured])>{{ $price }}
- !$dark, 'text-zinc-400' => $dark])>/mo
-
-
-
-
- @foreach ($perks as $perk)
- - !$perk['included'] && !$dark])>
- @if ($perk['included'])
-
- @else
-
- @endif
- {{ $perk['text'] }}
-
- @endforeach
-
-
-
- {{ $buttonText }}
-
-
-
diff --git a/tests/Feature/Tiers/PlanFeaturesTest.php b/tests/Feature/Tiers/PlanFeaturesTest.php
index 1c40e20..8abd27f 100644
--- a/tests/Feature/Tiers/PlanFeaturesTest.php
+++ b/tests/Feature/Tiers/PlanFeaturesTest.php
@@ -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 {
- $plan = Plan::where('name', 'plus')->first(); // sms_daily_limit = 1
+ $plan = Plan::where('name', 'plus')->first(); // sms_daily_limit = 3
$user = User::factory()->create();
UserNotificationPreference::factory()->create([
@@ -70,7 +70,7 @@ it('canSendNow returns false when daily limit is reached', function (): void {
'created_at' => now(),
]);
- expect($plan->sms_daily_limit)->toBe(1);
+ expect($plan->sms_daily_limit)->toBe(3);
$sentCount = NotificationLog::where('user_id', $user->id)
->where('channel', 'sms')