import React, { useEffect, useState } from 'react'; import api from '../../utils/api'; import { Card } from '../../components/ui/card'; import { Button } from '../../components/ui/button'; import { Badge } from '../../components/ui/badge'; import { Input } from '../../components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../components/ui/select'; import PlanDialog from '../../components/PlanDialog'; import { toast } from 'sonner'; import { CreditCard, Plus, Edit, Trash2, Users, Search, DollarSign } from 'lucide-react'; const AdminPlans = () => { const [plans, setPlans] = useState([]); const [filteredPlans, setFilteredPlans] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [activeFilter, setActiveFilter] = useState('all'); const [planDialogOpen, setPlanDialogOpen] = useState(false); const [selectedPlan, setSelectedPlan] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [planToDelete, setPlanToDelete] = useState(null); useEffect(() => { fetchPlans(); }, []); useEffect(() => { filterPlans(); }, [plans, searchQuery, activeFilter]); const fetchPlans = async () => { try { const response = await api.get('/admin/subscriptions/plans'); setPlans(response.data); } catch (error) { toast.error('Failed to fetch plans'); } finally { setLoading(false); } }; const filterPlans = () => { let filtered = plans; if (activeFilter !== 'all') { filtered = filtered.filter(plan => activeFilter === 'active' ? plan.active : !plan.active ); } if (searchQuery) { const query = searchQuery.toLowerCase(); filtered = filtered.filter(plan => plan.name.toLowerCase().includes(query) || plan.description?.toLowerCase().includes(query) ); } setFilteredPlans(filtered); }; const handleCreatePlan = () => { setSelectedPlan(null); setPlanDialogOpen(true); }; const handleEditPlan = (plan) => { setSelectedPlan(plan); setPlanDialogOpen(true); }; const handleDeleteClick = (plan) => { setPlanToDelete(plan); setDeleteDialogOpen(true); }; const handleDeleteConfirm = async () => { try { await api.delete(`/admin/subscriptions/plans/${planToDelete.id}`); toast.success('Plan deleted successfully'); fetchPlans(); setDeleteDialogOpen(false); } catch (error) { toast.error(error.response?.data?.detail || 'Failed to delete plan'); } }; const formatPrice = (cents) => { return `$${(cents / 100).toFixed(2)}`; }; const getBillingCycleLabel = (cycle) => { const labels = { monthly: 'Monthly', quarterly: 'Quarterly', yearly: 'Yearly', lifetime: 'Lifetime' }; return labels[cycle] || cycle; }; return ( <>
Manage membership plans and pricing.
Total Plans
{plans.length}
Active Plans
{plans.filter(p => p.active).length}
Total Subscribers
{plans.reduce((sum, p) => sum + (p.subscriber_count || 0), 0)}
Revenue (Annual Est.)
{formatPrice( plans.reduce((sum, p) => { const annualPrice = p.billing_cycle === 'yearly' ? p.price_cents : p.price_cents * 12; return sum + (annualPrice * (p.subscriber_count || 0)); }, 0) )}
Loading plans...
{plan.description}
)} {/* Price */}{getBillingCycleLabel(plan.billing_cycle)}
Cannot delete plan with active subscribers
)}{searchQuery || activeFilter !== 'all' ? 'Try adjusting your filters' : 'Create your first subscription plan to get started'}
{!searchQuery && activeFilter === 'all' && ( )}Are you sure you want to delete "{planToDelete?.name}"? This action will deactivate the plan and it won't be available for new subscriptions.