File size: 11,088 Bytes
e1ef9fc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | 'use client';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import {
Sparkles,
ArrowRight,
ArrowLeft,
Check,
Zap,
Crown,
Building2,
HelpCircle,
} from 'lucide-react';
import { useState } from 'react';
import { cn } from '@/lib/utils';
const plans = [
{
name: 'Free',
price: 0,
yearlyPrice: 0,
description: 'Try ScanMenu for free with basic features.',
icon: Sparkles,
features: [
'1 restaurant',
'Up to 15 menu items',
'2 QR codes',
'Basic ordering',
'50 orders/month',
'Community support',
],
cta: 'Get Started',
popular: false,
},
{
name: 'Starter',
price: 29,
yearlyPrice: 24,
description: 'Perfect for small restaurants just getting started.',
icon: Zap,
features: [
'1 restaurant',
'Up to 50 menu items',
'10 QR codes',
'Dine-in + Takeaway',
'Basic analytics',
'Email support',
'500 orders/month',
'Custom branding',
],
cta: 'Start Trial',
popular: false,
},
{
name: 'Pro',
price: 79,
yearlyPrice: 66,
description: 'For growing restaurants with advanced needs.',
icon: Crown,
features: [
'Unlimited restaurants',
'Unlimited menu items',
'Unlimited QR codes',
'Dine-in + Takeaway + Delivery',
'Advanced analytics',
'Priority support',
'Unlimited orders',
'Custom branding',
'API access',
'Team members (up to 10)',
'Real-time order tracking',
'Product options & extras',
],
cta: 'Start Trial',
popular: true,
},
{
name: 'Enterprise',
price: 199,
yearlyPrice: 166,
description: 'For multi-location chains and franchises.',
icon: Building2,
features: [
'Everything in Pro',
'Multi-location dashboard',
'Unlimited team members',
'Dedicated account manager',
'Custom integrations',
'99.9% SLA guarantee',
'White-label solution',
'Onboarding training',
'Advanced security',
'Custom API limits',
'Phone support',
'Invoice billing',
],
cta: 'Contact Sales',
popular: false,
},
];
const faq = [
{ q: 'Can I try ScanMenu for free?', a: 'Yes! Our Free plan lets you try ScanMenu with basic features at no cost. We also offer a 14-day free trial on all paid plans — no credit card required.' },
{ q: 'How do QR codes work?', a: 'Each QR code is linked to a specific table or order type (takeaway/delivery). Customers scan with their phone camera and instantly see your menu — no app download needed.' },
{ q: 'Can I switch plans later?', a: 'Absolutely! You can upgrade or downgrade your plan at any time. Changes take effect at the start of your next billing cycle, and we prorate any differences.' },
{ q: 'Do customers need to install an app?', a: 'No! That\'s the beauty of ScanMenu. Customers simply scan a QR code and your menu opens in their phone\'s browser. Zero friction, zero downloads.' },
{ q: 'What payment methods do you accept?', a: 'We accept all major credit cards (Visa, Mastercard, Amex, Discover) through Stripe. Enterprise customers can also pay via invoice.' },
{ q: 'Is my data secure?', a: 'Yes. We use Supabase (built on PostgreSQL) with row-level security, SSL encryption, and regular backups. Your data is safe and compliant.' },
];
export default function PricingPage() {
const [annual, setAnnual] = useState(false);
const [openFaq, setOpenFaq] = useState<number | null>(0);
return (
<div className="min-h-screen bg-white dark:bg-zinc-950">
{/* Nav */}
<nav className="border-b border-zinc-200/50 bg-white/80 backdrop-blur-xl dark:border-zinc-800/50 dark:bg-zinc-950/80">
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4 sm:px-6">
<Link href="/" className="flex items-center gap-2.5">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-emerald-500 to-cyan-500 shadow-sm">
<Sparkles className="h-4 w-4 text-white" />
</div>
<span className="text-lg font-bold tracking-tight">Scan<span className="text-emerald-600">Menu</span></span>
</Link>
<div className="flex items-center gap-3">
<Link href="/login"><Button variant="ghost" size="sm">Sign In</Button></Link>
<Link href="/register"><Button variant="primary" size="sm">Get Started</Button></Link>
</div>
</div>
</nav>
{/* Header */}
<section className="pt-16 pb-8 text-center">
<div className="mx-auto max-w-3xl px-4">
<Badge variant="outline" className="mb-4">Pricing</Badge>
<h1 className="text-4xl font-bold tracking-tight text-zinc-900 sm:text-5xl dark:text-white">
Simple, transparent pricing
</h1>
<p className="mt-4 text-lg text-zinc-600 dark:text-zinc-400">
Choose the plan that fits your restaurant. Upgrade, downgrade, or cancel anytime.
</p>
</div>
</section>
{/* Toggle */}
<div className="flex items-center justify-center gap-3 pb-12">
<span className={cn('text-sm font-medium transition-colors', !annual ? 'text-zinc-900' : 'text-zinc-500')}>Monthly</span>
<button
onClick={() => setAnnual(!annual)}
className={cn('relative h-6 w-11 rounded-full transition-colors', annual ? 'bg-emerald-500' : 'bg-zinc-300')}
>
<span className={cn('absolute left-0.5 top-0.5 h-5 w-5 rounded-full bg-white shadow transition-transform', annual && 'translate-x-5')} />
</button>
<span className={cn('text-sm font-medium transition-colors', annual ? 'text-zinc-900' : 'text-zinc-500')}>
Annual <Badge variant="success" className="ml-1 text-[10px]">Save 17%</Badge>
</span>
</div>
{/* Plans */}
<section className="pb-20">
<div className="mx-auto max-w-6xl px-4 sm:px-6">
<div className="grid gap-6 lg:grid-cols-4">
{plans.map((plan) => (
<div
key={plan.name}
className={cn(
'relative flex flex-col rounded-2xl border bg-white p-6 transition-all hover:shadow-lg dark:bg-zinc-900',
plan.popular
? 'border-emerald-500 shadow-lg ring-1 ring-emerald-500/20'
: 'border-zinc-200 dark:border-zinc-800'
)}
>
{plan.popular && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
<Badge className="bg-emerald-500 text-white border-0 px-3 py-1">Most Popular</Badge>
</div>
)}
<div className="mb-6">
<div className="flex items-center gap-2 mb-3">
<div className={cn(
'flex h-9 w-9 items-center justify-center rounded-xl',
plan.popular ? 'bg-emerald-100 text-emerald-600' : 'bg-zinc-100 text-zinc-600 dark:bg-zinc-800'
)}>
<plan.icon className="h-4 w-4" />
</div>
<h3 className="text-lg font-bold">{plan.name}</h3>
</div>
<p className="text-sm text-zinc-500 mb-4">{plan.description}</p>
<div className="flex items-baseline gap-1">
<span className="text-4xl font-bold text-zinc-900 dark:text-white">
${annual ? plan.yearlyPrice : plan.price}
</span>
{plan.price > 0 && <span className="text-zinc-500">/month</span>}
</div>
{annual && plan.price > 0 && (
<p className="mt-1 text-xs text-emerald-600">
${plan.yearlyPrice * 12}/year (save ${(plan.price - plan.yearlyPrice) * 12})
</p>
)}
</div>
<Link href="/register" className="block mb-6">
<Button
variant={plan.popular ? 'primary' : 'outline'}
className="w-full"
>
{plan.cta} {plan.price > 0 && <ArrowRight className="h-4 w-4" />}
</Button>
</Link>
<div className="flex-1 space-y-3">
{plan.features.map((feature) => (
<div key={feature} className="flex items-start gap-2 text-sm">
<Check className={cn('h-4 w-4 shrink-0 mt-0.5', plan.popular ? 'text-emerald-500' : 'text-zinc-400')} />
<span className="text-zinc-600 dark:text-zinc-400">{feature}</span>
</div>
))}
</div>
</div>
))}
</div>
</div>
</section>
{/* FAQ */}
<section className="border-t border-zinc-200 bg-zinc-50 py-20 dark:border-zinc-800 dark:bg-zinc-900/50">
<div className="mx-auto max-w-3xl px-4 sm:px-6">
<div className="text-center mb-12">
<Badge variant="outline" className="mb-4">FAQ</Badge>
<h2 className="text-3xl font-bold tracking-tight text-zinc-900 dark:text-white">Frequently asked questions</h2>
</div>
<div className="space-y-3">
{faq.map((item, i) => (
<div key={i} className="rounded-2xl border border-zinc-200 bg-white overflow-hidden dark:border-zinc-800 dark:bg-zinc-900">
<button
onClick={() => setOpenFaq(openFaq === i ? null : i)}
className="flex w-full items-center justify-between p-5 text-left"
>
<span className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{item.q}</span>
<HelpCircle className={cn('h-4 w-4 shrink-0 text-zinc-400 transition-transform', openFaq === i && 'rotate-180')} />
</button>
{openFaq === i && (
<div className="px-5 pb-5 pt-0">
<p className="text-sm leading-relaxed text-zinc-500">{item.a}</p>
</div>
)}
</div>
))}
</div>
</div>
</section>
{/* Footer CTA */}
<section className="py-16 text-center">
<div className="mx-auto max-w-2xl px-4">
<h2 className="text-2xl font-bold text-zinc-900 dark:text-white">Still have questions?</h2>
<p className="mt-2 text-zinc-500">We're here to help. Contact our team for a personalized demo.</p>
<div className="mt-6 flex justify-center gap-3">
<Link href="/register"><Button variant="primary">Start Free Trial</Button></Link>
<Button variant="outline">Contact Sales</Button>
</div>
</div>
</section>
</div>
);
}
|