Initial commit

This commit is contained in:
belviskhoremk
2026-02-22 21:41:14 +00:00
commit 3ac82d31aa
32 changed files with 7069 additions and 0 deletions

226
src/pages/PricingPage.tsx Normal file
View 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>
)
}