import React, { useState, useEffect } from 'react'; import { Box, Container, Typography, Button, Grid, Switch, FormControlLabel, Alert, CircularProgress, Dialog, DialogTitle, DialogContent, DialogActions, Modal, Fade, Backdrop, Snackbar, } from '@mui/material'; import { Warning } from '@mui/icons-material'; import { useNavigate } from 'react-router-dom'; import { apiClient } from '../../api/client'; import { restoreNavigationState, saveCurrentPhaseForTool } from '../../utils/navigationState'; import PlanCard from './PricingPage/PlanCard'; export interface SubscriptionPlan { id: number; name: string; tier: string; price_monthly: number; price_yearly: number; description: string; features: string[]; limits: { gemini_calls: number; openai_calls: number; anthropic_calls: number; mistral_calls: number; tavily_calls: number; serper_calls: number; metaphor_calls: number; firecrawl_calls: number; stability_calls: number; monthly_cost: number; // New limit fields (optional for backward compatibility) image_edit_calls?: number; video_calls?: number; audio_calls?: number; ai_text_generation_calls_limit?: number; // Unified limit for Basic tier }; } const PricingPage: React.FC = () => { const navigate = useNavigate(); const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [yearlyBilling, setYearlyBilling] = useState(false); const [selectedPlan, setSelectedPlan] = useState(null); const [subscribing, setSubscribing] = useState(false); const [paymentModalOpen, setPaymentModalOpen] = useState(false); const [showSignInPrompt, setShowSignInPrompt] = useState(false); const [successSnackbar, setSuccessSnackbar] = useState({ open: false, message: '', countdown: 3 }); const [knowMoreModal, setKnowMoreModal] = useState<{ open: boolean; title: string; content: React.ReactNode }>({ open: false, title: '', content: null }); useEffect(() => { fetchPlans(); }, []); const fetchPlans = async () => { try { setLoading(true); const response = await apiClient.get('/api/subscription/plans'); // Filter out any alpha plans and ensure we only show the 4 main tiers const filteredPlans = response.data.data.plans.filter( (plan: SubscriptionPlan) => !plan.name.toLowerCase().includes('alpha') ); setPlans(filteredPlans); } catch (err) { console.error('Error fetching plans:', err); setError('Failed to load subscription plans'); } finally { setLoading(false); } }; const handleSubscribe = async (planId: number) => { console.log('[PricingPage] handleSubscribe called', { planId }); const plan = plans.find(p => p.id === planId); if (!plan) { console.error('[PricingPage] ❌ Plan not found for ID:', planId); return; } console.log('[PricingPage] Selected plan:', { id: plan.id, name: plan.name, tier: plan.tier }); // Get user_id from localStorage (set by Clerk auth) const userId = localStorage.getItem('user_id'); // Check if user is signed in if (!userId || userId === 'anonymous' || userId === '') { // User not signed in, show sign-in prompt console.warn('[PricingPage] User not signed in, showing prompt'); setShowSignInPrompt(true); return; } // For alpha testing, only allow Free and Basic plans (Pro features not ready) if (plan.tier !== 'free' && plan.tier !== 'basic') { console.error('[PricingPage] Plan tier not available:', plan.tier); setError('This plan is not available for alpha testing'); return; } if (plan.tier === 'free') { console.log('[PricingPage] Processing Free plan subscription directly'); // For free plan, just create subscription try { setSubscribing(true); const userId = localStorage.getItem('user_id') || 'anonymous'; await apiClient.post(`/api/subscription/subscribe/${userId}`, { plan_id: planId, billing_cycle: yearlyBilling ? 'yearly' : 'monthly' }); // Refresh subscription status window.dispatchEvent(new CustomEvent('subscription-updated')); // After subscription, check if onboarding is complete // If not complete, redirect to onboarding; otherwise to dashboard const onboardingComplete = localStorage.getItem('onboarding_complete') === 'true'; if (onboardingComplete) { navigate('/dashboard'); } else { navigate('/onboarding'); } } catch (err) { console.error('Error subscribing:', err); setError('Failed to process subscription'); } finally { setSubscribing(false); } } else { // For Basic plan, show payment modal console.log('[PricingPage] Opening payment modal for Basic plan', { planId, planName: plan.name }); setSelectedPlan(planId); // ✅ Set selected plan before opening modal setPaymentModalOpen(true); } }; const handlePaymentConfirm = async () => { console.log('[PricingPage] handlePaymentConfirm called', { selectedPlan, yearlyBilling }); if (!selectedPlan) { console.error('[PricingPage] ❌ No selectedPlan set - cannot proceed with subscription'); setError('No plan selected. Please select a plan and try again.'); return; } // Get selected plan details const plan = plans.find(p => p.id === selectedPlan); if (!plan) return; try { setSubscribing(true); const userId = localStorage.getItem('user_id') || 'anonymous'; // Check if Stripe is configured if (process.env.REACT_APP_STRIPE_PUBLISHABLE_KEY) { console.log('[PricingPage] Initiating Stripe Checkout'); const response = await apiClient.post('/api/subscription/create-checkout-session', { tier: plan.tier, billing_cycle: yearlyBilling ? 'yearly' : 'monthly', success_url: `${window.location.origin}/dashboard?subscription=success`, cancel_url: `${window.location.origin}/pricing?subscription=cancel`, }); if (response.data.url) { window.location.href = response.data.url; return; } } console.log('[PricingPage] Making legacy subscription API call:', { url: `/api/subscription/subscribe/${userId}`, plan_id: selectedPlan, billing_cycle: yearlyBilling ? 'yearly' : 'monthly', userId }); const response = await apiClient.post(`/api/subscription/subscribe/${userId}`, { plan_id: selectedPlan, billing_cycle: yearlyBilling ? 'yearly' : 'monthly' }); console.log('[PricingPage] ✅ Subscription renewed successfully:', response.data); // Refresh subscription status immediately window.dispatchEvent(new CustomEvent('subscription-updated')); // Also trigger user authenticated event to refresh subscription context window.dispatchEvent(new CustomEvent('user-authenticated')); setPaymentModalOpen(false); // Get plan name for success message const planName = plans.find(p => p.id === selectedPlan)?.name || 'subscription'; // Show success message with countdown setSuccessSnackbar({ open: true, message: `🎉 ${planName} plan activated! Your usage limits have been reset. Returning to your work in 3 seconds...`, countdown: 3 }); // Countdown timer let countdown = 3; const countdownInterval = setInterval(() => { countdown -= 1; if (countdown > 0) { setSuccessSnackbar(prev => ({ ...prev, message: `🎉 ${planName} plan activated! Your usage limits have been reset. Returning to your work in ${countdown} second${countdown !== 1 ? 's' : ''}...`, countdown })); } else { clearInterval(countdownInterval); } }, 1000); // Auto-redirect after 3 seconds setTimeout(() => { clearInterval(countdownInterval); // After subscription, check if onboarding is complete // If not complete, redirect to onboarding; otherwise to dashboard const onboardingComplete = localStorage.getItem('onboarding_complete') === 'true'; if (onboardingComplete) { // Restore navigation state (path, phase, tool) if available const navState = restoreNavigationState(); if (navState && navState.path && navState.path !== '/pricing') { // Restore phase if applicable (e.g., Blog Writer) if (navState.tool === 'blog-writer' && navState.phase) { saveCurrentPhaseForTool('blog-writer', navState.phase); console.log('[PricingPage] Restored Blog Writer phase:', navState.phase); } console.log('[PricingPage] Redirecting to saved navigation state:', navState); navigate(navState.path); } else { // Fallback: try legacy referrer const referrer = sessionStorage.getItem('subscription_referrer'); if (referrer && referrer !== '/pricing') { navigate(referrer); } else { navigate('/dashboard'); } } } else { navigate('/onboarding'); } }, 3000); } catch (err) { console.error('Error subscribing:', err); setError('Failed to process subscription'); setSuccessSnackbar({ open: false, message: '', countdown: 0 }); } finally { setSubscribing(false); } }; const openKnowMoreModal = (title: string, content: React.ReactNode) => { setKnowMoreModal({ open: true, title, content }); }; if (loading) { return ( Loading subscription plans... ); } if (error) { return ( {error} ); } return ( Choose Your Plan Select the perfect plan for your AI content creation needs 💡 Perfect for Content Creators, Marketers, Solopreneurs & Startups All plans include access to every ALwrity tool. Limits reset monthly, and you're protected by automatic cost caps. {yearlyBilling && ' Save 20% with yearly billing!'} {/* Billing Toggle */} setYearlyBilling(e.target.checked)} color="primary" /> } label={yearlyBilling ? "Yearly Billing (Save 20%)" : "Monthly Billing"} sx={{ mb: 2 }} /> {plans.map((plan) => ( ))} All plans include our core AI content creation features.
Need a custom plan?
{/* Payment Modal */} setPaymentModalOpen(false)} closeAfterTransition BackdropComponent={Backdrop} BackdropProps={{ timeout: 500, }} > Alpha Testing Subscription {/* Alpha Testing Notice */} ⚠️ Alpha Testing Mode - No Payment Required Payment integration is coming soon. For now, subscriptions are activated without charge. Thank you for participating in our alpha testing! We're crediting the Basic plan ($29 value) to your account. {/* TODO: Payment Integration Notice */} Coming in Production: • Secure Stripe/PayPal payment processing
• Automatic renewal management
• Payment verification & receipts
• Upgrade/downgrade options
{/* Note: Current behavior allows renewal without payment verification */} {/* This is intentional for alpha testing but will be secured in production */}
{/* Know More Modal */} setKnowMoreModal({ open: false, title: '', content: null })} maxWidth="md" fullWidth > {knowMoreModal.title} {knowMoreModal.content} {/* Sign In Prompt Modal */} setShowSignInPrompt(false)} maxWidth="sm" fullWidth > Sign In Required Please sign in to subscribe to a plan and start using ALwrity. If you don't have an account, signing in will automatically create one for you. {/* Success Snackbar */} setSuccessSnackbar({ open: false, message: '', countdown: 0 })} anchorOrigin={{ vertical: 'top', horizontal: 'center' }} sx={{ top: { xs: 16, sm: 24 }, '& .MuiSnackbarContent-root': { minWidth: { xs: '90vw', sm: '500px' } } }} > setSuccessSnackbar({ open: false, message: '', countdown: 0 })} sx={{ width: '100%', fontSize: '1rem', alignItems: 'center', boxShadow: '0 8px 24px rgba(76, 175, 80, 0.4)', '& .MuiAlert-icon': { fontSize: '2rem' } }} > {successSnackbar.message}
); }; export default PricingPage;