import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import api from '../utils/api'; import { Card } from '../components/ui/card'; import { Button } from '../components/ui/button'; import { Input } from '../components/ui/input'; import { Label } from '../components/ui/label'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '../components/ui/dialog'; import Navbar from '../components/Navbar'; import { CheckCircle, CreditCard, Loader2, Heart, AlertCircle } from 'lucide-react'; import { toast } from 'sonner'; const Plans = () => { const { user, loading: authLoading } = useAuth(); const navigate = useNavigate(); const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(true); const [processingPlanId, setProcessingPlanId] = useState(null); const [statusInfo, setStatusInfo] = useState(null); // Amount selection dialog state const [amountDialogOpen, setAmountDialogOpen] = useState(false); const [selectedPlan, setSelectedPlan] = useState(null); const [amountInput, setAmountInput] = useState(''); useEffect(() => { fetchPlans(); }, []); // Status-based access control useEffect(() => { if (!authLoading && user) { // Define status-to-message mapping const statusMessages = { pending_email: { title: "Email Verification Required", message: "Please verify your email address before selecting a membership plan. Check your inbox for the verification link.", action: null, canView: true, canSubscribe: false }, pending_validation: { title: "Application Under Review", message: "Your application is being reviewed by our admin team. You'll receive an email once validated to proceed with payment.", action: null, canView: true, canSubscribe: false }, pre_validated: { title: "Application Under Review", message: "Your application is being reviewed by our admin team. You'll receive an email once validated to proceed with payment.", action: null, canView: true, canSubscribe: false }, payment_pending: { title: null, message: null, canView: true, canSubscribe: true }, active: { title: "Already a Member", message: "You already have an active membership. Visit your dashboard to view your membership details.", action: "Go to Dashboard", actionLink: "/dashboard", canView: true, canSubscribe: false }, inactive: { title: "Membership Inactive", message: "Your membership is currently inactive. Please contact support for assistance.", action: null, canView: true, canSubscribe: false }, canceled: { title: "Membership Canceled", message: "Your membership was canceled. You can rejoin by selecting a plan below.", action: null, canView: true, canSubscribe: true }, expired: { title: "Membership Expired", message: "Your membership has expired. Please renew by selecting a plan below.", action: null, canView: true, canSubscribe: true }, abandoned: { title: "Application Incomplete", message: "Your application was not completed. Please contact support to restart the registration process.", action: null, canView: true, canSubscribe: false } }; const userStatus = statusMessages[user.status]; setStatusInfo(userStatus || { title: "Access Restricted", message: "Please contact support for assistance with your account.", canView: true, canSubscribe: false }); } }, [user, authLoading]); const fetchPlans = async () => { try { const response = await api.get('/subscriptions/plans'); setPlans(response.data); } catch (error) { console.error('Failed to fetch plans:', error); toast.error('Failed to load subscription plans'); } finally { setLoading(false); } }; const handleSelectPlan = (plan) => { if (!user) { navigate('/login'); return; } setSelectedPlan(plan); // Pre-fill with suggested price or minimum price const suggestedAmount = (plan.suggested_price_cents || plan.minimum_price_cents) / 100; setAmountInput(suggestedAmount.toFixed(2)); setAmountDialogOpen(true); }; const handleCheckout = async () => { const amountCents = Math.round(parseFloat(amountInput) * 100); const minimumCents = selectedPlan.minimum_price_cents || 3000; // Validate amount if (!amountInput || isNaN(amountCents) || amountCents < minimumCents) { toast.error(`Amount must be at least $${(minimumCents / 100).toFixed(2)}`); return; } // Check if plan allows donations const donationCents = amountCents - minimumCents; if (donationCents > 0 && !selectedPlan.allow_donation) { toast.error('This plan does not accept donations above the minimum price'); return; } setProcessingPlanId(selectedPlan.id); setAmountDialogOpen(false); try { const response = await api.post('/subscriptions/checkout', { plan_id: selectedPlan.id, amount_cents: amountCents }); // Redirect to Stripe Checkout window.location.href = response.data.checkout_url; } catch (error) { console.error('Failed to create checkout session:', error); toast.error(error.response?.data?.detail || 'Failed to start checkout process'); setProcessingPlanId(null); } }; const formatPrice = (cents) => { return `$${(cents / 100).toFixed(2)}`; }; const getBillingCycleLabel = (billingCycle) => { const labels = { yearly: 'per year', monthly: 'per month', quarterly: 'per quarter', lifetime: 'one-time', custom: 'custom period' }; return labels[billingCycle] || billingCycle; }; // Calculate donation breakdown const getAmountBreakdown = () => { if (!selectedPlan || !amountInput) return null; const totalCents = Math.round(parseFloat(amountInput) * 100); const minimumCents = selectedPlan.minimum_price_cents || 3000; const donationCents = Math.max(0, totalCents - minimumCents); return { total: totalCents, base: minimumCents, donation: donationCents }; }; const breakdown = getAmountBreakdown(); return (
Choose the membership plan that works best for you and become part of our vibrant community.
{statusInfo.message}
{statusInfo.action && statusInfo.actionLink && ( )}Loading plans...
{plan.description}
)}{getBillingCycleLabel(plan.billing_cycle)}
{plan.allow_donation && (Membership plans are not currently available. Please check back later!
If you have any questions about our membership plans or need assistance, please contact us.