Files
contexta_fe/src/pages/PricingPage.tsx
belviskhoremk 3ac82d31aa Initial commit
2026-02-22 21:41:14 +00:00

227 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
)
}