update subscription module

This commit is contained in:
root
2026-05-25 04:58:56 -04:00
parent 1606ea0053
commit 8d0b7a5ceb
17 changed files with 4394 additions and 157 deletions
@@ -2,15 +2,24 @@
import { useEffect, useState } from 'react'
import Link from 'next/link'
import { PLAN_FEATURES, PLAN_PRICES } from '@rentaldrivego/types'
import { useMarketplacePreferences } from '@/components/MarketplaceShell'
import { marketplaceFetchOrDefault } from '@/lib/api'
import { resolveBrowserAppUrl } from '@/lib/appUrls'
type Billing = 'monthly' | 'annual'
const PRICES: Record<string, { monthly: number; annual: number }> = {
STARTER: { monthly: 299, annual: 2990 },
GROWTH: { monthly: 399, annual: 3990 },
PRO: { monthly: 499, annual: 4990 },
type PricingMatrix = Record<string, Record<string, Record<string, number>>>
type PlanFeatureMap = Record<string, string[]>
type PlatformPricing = {
prices: PricingMatrix
planFeatures: PlanFeatureMap
}
export const DEFAULT_PRICING: PlatformPricing = {
prices: PLAN_PRICES,
planFeatures: PLAN_FEATURES,
}
const PLANS = [
@@ -18,38 +27,18 @@ const PLANS = [
key: 'STARTER',
name: 'Starter',
tagline: 'Launch your fleet online',
features: [
'Up to 10 vehicles',
'1 user account',
'Basic analytics',
'Marketplace listing',
],
highlight: false,
},
{
key: 'GROWTH',
name: 'Growth',
tagline: 'Scale with confidence',
features: [
'Up to 50 vehicles',
'5 user accounts',
'Full analytics',
'Priority marketplace placement',
'Custom branding',
],
highlight: true,
},
{
key: 'PRO',
name: 'Pro',
tagline: 'Enterprise-grade power',
features: [
'Unlimited vehicles',
'Unlimited user accounts',
'Advanced reports',
'API access',
'Dedicated support',
],
highlight: false,
},
]
@@ -93,20 +82,32 @@ const copy = {
},
} as const
function annualSavingsPct(plan: string): number {
const { monthly, annual } = PRICES[plan]
const wouldPay = monthly * 12
return Math.round(((wouldPay - annual) / wouldPay) * 100)
function getPlanPrice(prices: PricingMatrix, plan: string, billingPeriod: 'MONTHLY' | 'ANNUAL') {
return (prices[plan]?.[billingPeriod]?.MAD ?? PLAN_PRICES[plan]?.[billingPeriod]?.MAD ?? 0) / 100
}
export default function PricingClient() {
function annualSavingsPct(prices: PricingMatrix, plan: string): number {
const monthly = getPlanPrice(prices, plan, 'MONTHLY')
const annual = getPlanPrice(prices, plan, 'ANNUAL')
const wouldPay = monthly * 12
if (wouldPay <= 0) return 0
return Math.max(0, Math.round(((wouldPay - annual) / wouldPay) * 100))
}
export default function PricingClient({ initialPricing = DEFAULT_PRICING }: { initialPricing?: PlatformPricing }) {
const { language } = useMarketplacePreferences()
const dict = copy[language]
const [billing, setBilling] = useState<Billing>('monthly')
const [pricing, setPricing] = useState<PlatformPricing>(initialPricing)
const dashboardUrl = resolveBrowserAppUrl(process.env.NEXT_PUBLIC_DASHBOARD_URL ?? 'http://localhost:3000/dashboard')
const savings = annualSavingsPct('STARTER')
useEffect(() => {
marketplaceFetchOrDefault<PlatformPricing>('/site/platform/pricing', DEFAULT_PRICING)
.then(setPricing)
}, [])
const savings = annualSavingsPct(pricing.prices, 'STARTER')
return (
<div className="space-y-10">
@@ -141,10 +142,12 @@ export default function PricingClient() {
{/* Plan cards */}
<div className="grid gap-6 md:grid-cols-3">
{PLANS.map((plan) => {
const price = PRICES[plan.key]
const monthlyPrice = getPlanPrice(pricing.prices, plan.key, 'MONTHLY')
const annualPrice = getPlanPrice(pricing.prices, plan.key, 'ANNUAL')
const displayPrice = billing === 'annual'
? Math.round(price.annual / 12)
: price.monthly
? Math.round(annualPrice / 12)
: monthlyPrice
const features = pricing.planFeatures[plan.key] ?? PLAN_FEATURES[plan.key] ?? []
return (
<div
@@ -177,13 +180,13 @@ export default function PricingClient() {
</div>
{billing === 'annual' && (
<p className="mt-1 text-xs text-stone-400 dark:text-stone-500">
{dict.billedAs} {price.annual} MAD {dict.yearly}
{dict.billedAs} {annualPrice} MAD {dict.yearly}
</p>
)}
</div>
<ul className="mt-8 flex-1 space-y-3">
{plan.features.map((f) => (
{features.map((f) => (
<li key={f} className="flex items-start gap-2.5 text-sm text-stone-700 dark:text-stone-300">
<svg
className="mt-0.5 h-4 w-4 shrink-0 text-orange-500 dark:text-orange-400"
@@ -42,7 +42,7 @@ const copy = {
},
} as const
export default function PricingPageContent() {
export default function PricingPageContent({ initialPricing }: { initialPricing?: React.ComponentProps<typeof PricingClient>['initialPricing'] }) {
const { language } = useMarketplacePreferences()
const dict = copy[language]
@@ -56,7 +56,7 @@ export default function PricingPageContent() {
</div>
<div className="mt-16">
<PricingClient />
<PricingClient initialPricing={initialPricing} />
</div>
<div className="site-panel mx-auto mt-24 max-w-3xl divide-y divide-stone-200/80 dark:divide-stone-800">
@@ -1,4 +1,6 @@
import type { Metadata } from 'next'
import { PLAN_FEATURES, PLAN_PRICES } from '@rentaldrivego/types'
import { marketplaceFetchOrDefault } from '@/lib/api'
import PricingPageContent from './PricingPageContent'
export const metadata: Metadata = {
@@ -6,6 +8,12 @@ export const metadata: Metadata = {
description: 'Simple, transparent pricing for every fleet size.',
}
export default function PricingPage() {
return <PricingPageContent />
const DEFAULT_PRICING = {
prices: PLAN_PRICES,
planFeatures: PLAN_FEATURES,
}
export default async function PricingPage() {
const initialPricing = await marketplaceFetchOrDefault('/site/platform/pricing', DEFAULT_PRICING)
return <PricingPageContent initialPricing={initialPricing} />
}