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 } from 'lucide-react'; import { toast } from 'sonner'; const Plans = () => { const { user } = useAuth(); const navigate = useNavigate(); const [plans, setPlans] = useState([]); const [loading, setLoading] = useState(true); const [processingPlanId, setProcessingPlanId] = useState(null); // Amount selection dialog state const [amountDialogOpen, setAmountDialogOpen] = useState(false); const [selectedPlan, setSelectedPlan] = useState(null); const [amountInput, setAmountInput] = useState(''); useEffect(() => { fetchPlans(); }, []); 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.
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.