Initial commit
This commit is contained in:
226
src/pages/PricingPage.tsx
Normal file
226
src/pages/PricingPage.tsx
Normal file
@@ -0,0 +1,226 @@
|
||||
import React, { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { billingAPI } from '@/services/api'
|
||||
import { useAuthStore } from '@/store/authStore'
|
||||
import { Button, Card } from '@/components/ui'
|
||||
import { Check, Zap, Building2, Star } from 'lucide-react'
|
||||
|
||||
const PLANS = [
|
||||
{
|
||||
id: 'free',
|
||||
name: 'Free',
|
||||
price: 0,
|
||||
description: 'Perfect for testing and development',
|
||||
icon: '🆓',
|
||||
color: 'gray',
|
||||
features: [
|
||||
{ text: 'Unlimited chatbot creation', included: true },
|
||||
{ text: 'Upload PDF, DOCX, CSV, XLSX', included: true },
|
||||
{ text: 'Unlimited preview testing', included: true },
|
||||
{ text: 'Shareable preview links', included: true },
|
||||
{ text: 'Publish to marketplace', included: false },
|
||||
{ text: 'Premium AI models', included: false },
|
||||
{ text: 'Code export', included: false },
|
||||
{ text: 'Analytics dashboard', included: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'starter',
|
||||
name: 'Starter',
|
||||
price: 39,
|
||||
description: 'For small businesses launching their first chatbot',
|
||||
icon: '🚀',
|
||||
color: 'blue',
|
||||
badge: 'Popular',
|
||||
features: [
|
||||
{ text: 'Everything in Free', included: true },
|
||||
{ text: 'Publish 1 chatbot to marketplace', included: true },
|
||||
{ text: 'Fireworks AI models (Llama, Mixtral)', included: true },
|
||||
{ text: '5,000 conversations/month', included: true },
|
||||
{ text: 'Analytics dashboard', included: true },
|
||||
{ text: 'Custom branding', included: true },
|
||||
{ text: 'Email support', included: true },
|
||||
{ text: 'Premium AI models (GPT-4, Claude)', included: false },
|
||||
{ text: 'Code export', included: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'pro',
|
||||
name: 'Pro',
|
||||
price: 119,
|
||||
description: 'For growing businesses with multiple products',
|
||||
icon: '⚡',
|
||||
color: 'purple',
|
||||
highlighted: true,
|
||||
features: [
|
||||
{ text: 'Everything in Starter', included: true },
|
||||
{ text: 'Build & publish 3 chatbots', included: true },
|
||||
{ text: 'GPT-4o, Claude 3.5, Gemini 1.5', included: true },
|
||||
{ text: '20,000 conversations/month', included: true },
|
||||
{ text: 'Code export (FastAPI + React widget)', included: true },
|
||||
{ text: 'Advanced analytics', included: true },
|
||||
{ text: 'Remove "Powered by" badge', included: true },
|
||||
{ text: 'Priority support', included: true },
|
||||
{ text: 'Custom domain', included: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'enterprise',
|
||||
name: 'Enterprise',
|
||||
price: null,
|
||||
description: 'For large organizations with custom needs',
|
||||
icon: '🏢',
|
||||
color: 'orange',
|
||||
features: [
|
||||
{ text: 'Everything in Pro', included: true },
|
||||
{ text: 'Unlimited chatbots', included: true },
|
||||
{ text: 'Unlimited conversations', included: true },
|
||||
{ text: 'Custom model fine-tuning', included: true },
|
||||
{ text: 'White-label platform', included: true },
|
||||
{ text: 'SSO (SAML)', included: true },
|
||||
{ text: 'SLA guarantees', included: true },
|
||||
{ text: 'Dedicated account manager', included: true },
|
||||
{ text: '24/7 phone support', included: true },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const PricingPage: React.FC = () => {
|
||||
const { user } = useAuthStore()
|
||||
const navigate = useNavigate()
|
||||
const [loading, setLoading] = useState<string | null>(null)
|
||||
|
||||
const handleSubscribe = async (planId: string) => {
|
||||
if (!user) { navigate('/login'); return }
|
||||
if (planId === 'enterprise') {
|
||||
window.open('mailto:enterprise@contexta.ai?subject=Enterprise Inquiry', '_blank')
|
||||
return
|
||||
}
|
||||
if (planId === 'free') {
|
||||
navigate('/dashboard')
|
||||
return
|
||||
}
|
||||
|
||||
setLoading(planId)
|
||||
try {
|
||||
const { checkout_url } = await billingAPI.createCheckout(
|
||||
planId,
|
||||
`${window.location.origin}/settings/billing?success=true`,
|
||||
`${window.location.origin}/pricing`
|
||||
)
|
||||
window.location.href = checkout_url
|
||||
} catch (err: any) {
|
||||
alert(err.response?.data?.detail || 'Failed to create checkout session')
|
||||
} finally {
|
||||
setLoading(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-6 max-w-6xl mx-auto">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-3">Simple, transparent pricing</h1>
|
||||
<p className="text-gray-500 max-w-xl mx-auto">
|
||||
Start free and build as many chatbots as you want. Upgrade when you're ready to publish and go live.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6">
|
||||
{PLANS.map((plan) => (
|
||||
<div
|
||||
key={plan.id}
|
||||
className={`relative rounded-2xl border p-6 flex flex-col ${
|
||||
plan.highlighted
|
||||
? 'border-primary-400 bg-gradient-to-b from-primary-50 to-white shadow-lg shadow-primary-100'
|
||||
: 'border-gray-200 bg-white'
|
||||
}`}
|
||||
>
|
||||
{plan.badge && (
|
||||
<div className="absolute -top-3 left-1/2 -translate-x-1/2">
|
||||
<span className="bg-primary-600 text-white text-xs font-semibold px-3 py-1 rounded-full">
|
||||
{plan.badge}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-6">
|
||||
<div className="text-3xl mb-2">{plan.icon}</div>
|
||||
<h2 className="text-xl font-bold text-gray-900">{plan.name}</h2>
|
||||
<p className="text-sm text-gray-500 mt-1 min-h-[40px]">{plan.description}</p>
|
||||
<div className="mt-4">
|
||||
{plan.price !== null ? (
|
||||
<div className="flex items-baseline gap-1">
|
||||
<span className="text-3xl font-bold text-gray-900">${plan.price}</span>
|
||||
<span className="text-gray-500 text-sm">/month</span>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-2xl font-bold text-gray-900">Custom</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="space-y-2.5 flex-1 mb-6">
|
||||
{plan.features.map(({ text, included }) => (
|
||||
<li key={text} className="flex items-start gap-2.5">
|
||||
<div className={`w-4 h-4 rounded-full flex-shrink-0 mt-0.5 flex items-center justify-center ${
|
||||
included ? 'bg-green-100 text-green-600' : 'bg-gray-100 text-gray-400'
|
||||
}`}>
|
||||
{included ? <Check className="w-2.5 h-2.5" /> : <span className="text-xs">–</span>}
|
||||
</div>
|
||||
<span className={`text-sm ${included ? 'text-gray-700' : 'text-gray-400'}`}>{text}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<Button
|
||||
variant={plan.highlighted ? 'primary' : 'outline'}
|
||||
className="w-full"
|
||||
loading={loading === plan.id}
|
||||
onClick={() => handleSubscribe(plan.id)}
|
||||
disabled={user?.plan === plan.id}
|
||||
>
|
||||
{user?.plan === plan.id
|
||||
? 'Current Plan'
|
||||
: plan.price === null
|
||||
? 'Contact Sales'
|
||||
: plan.price === 0
|
||||
? 'Get Started Free'
|
||||
: `Subscribe – $${plan.price}/mo`}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* FAQ */}
|
||||
<div className="mt-16 max-w-2xl mx-auto">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-6 text-center">Frequently Asked Questions</h2>
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{
|
||||
q: 'What is preview mode?',
|
||||
a: 'Preview mode lets you build and test your chatbot for free with unlimited conversations. Only you (and people you share the link with) can access it until you publish.'
|
||||
},
|
||||
{
|
||||
q: 'Can I cancel anytime?',
|
||||
a: 'Yes, you can cancel anytime. Your chatbots will remain in preview mode but will be removed from the marketplace.'
|
||||
},
|
||||
{
|
||||
q: 'What is code export?',
|
||||
a: 'Pro plan users can export their chatbot as a complete, production-ready package including a FastAPI backend and React TypeScript widget — giving you full control to self-host.'
|
||||
},
|
||||
{
|
||||
q: 'Do I need my own API keys?',
|
||||
a: 'No! Your API keys are handled by Contexta. However, if you export the code, you\'ll need your own API keys for self-hosted deployment.'
|
||||
},
|
||||
].map(({ q, a }) => (
|
||||
<div key={q} className="bg-white border border-gray-200 rounded-xl p-4">
|
||||
<h3 className="font-semibold text-gray-900 text-sm mb-1">{q}</h3>
|
||||
<p className="text-sm text-gray-500">{a}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user