import React, { useState, useEffect } from 'react'; import { useAuth } from '../../context/AuthContext'; 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../components/ui/select'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '../../components/ui/dialog'; import { Badge } from '../../components/ui/badge'; import api from '../../utils/api'; import { toast } from 'sonner'; import { DollarSign, CreditCard, TrendingUp, Heart, Search, Loader2, Calendar, Edit, XCircle, Download, FileDown, AlertTriangle, Info } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '../../components/ui/dropdown-menu'; const AdminSubscriptions = () => { const { hasPermission } = useAuth(); const [subscriptions, setSubscriptions] = useState([]); const [filteredSubscriptions, setFilteredSubscriptions] = useState([]); const [plans, setPlans] = useState([]); const [stats, setStats] = useState({}); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [planFilter, setPlanFilter] = useState('all'); const [exporting, setExporting] = useState(false); // Edit subscription dialog state const [editDialogOpen, setEditDialogOpen] = useState(false); const [selectedSubscription, setSelectedSubscription] = useState(null); const [editFormData, setEditFormData] = useState({ status: '', end_date: '' }); const [isUpdating, setIsUpdating] = useState(false); useEffect(() => { fetchData(); }, []); useEffect(() => { filterSubscriptions(); }, [searchQuery, statusFilter, planFilter, subscriptions]); const fetchData = async () => { setLoading(true); try { const [subsResponse, statsResponse, plansResponse] = await Promise.all([ api.get('/admin/subscriptions'), api.get('/admin/subscriptions/stats'), api.get('/admin/subscriptions/plans') ]); setSubscriptions(subsResponse.data); setStats(statsResponse.data); setPlans(plansResponse.data); } catch (error) { console.error('Failed to fetch subscription data:', error); toast.error('Failed to load subscription data'); } finally { setLoading(false); } }; const filterSubscriptions = () => { let filtered = [...subscriptions]; // Search filter (member name or email) if (searchQuery.trim()) { const query = searchQuery.toLowerCase(); filtered = filtered.filter(sub => sub.user.first_name.toLowerCase().includes(query) || sub.user.last_name.toLowerCase().includes(query) || sub.user.email.toLowerCase().includes(query) ); } // Status filter if (statusFilter !== 'all') { filtered = filtered.filter(sub => sub.status === statusFilter); } // Plan filter if (planFilter !== 'all') { filtered = filtered.filter(sub => sub.plan.id === planFilter); } setFilteredSubscriptions(filtered); }; const handleEdit = (subscription) => { setSelectedSubscription(subscription); setEditFormData({ status: subscription.status, end_date: subscription.end_date ? new Date(subscription.end_date).toISOString().split('T')[0] : '' }); setEditDialogOpen(true); }; const handleSaveSubscription = async () => { if (!selectedSubscription) return; // Check if status is changing const statusChanged = editFormData.status !== selectedSubscription.status; if (statusChanged) { // Get status change consequences let warningMessage = ''; let confirmText = ''; if (editFormData.status === 'cancelled') { warningMessage = `⚠️ CRITICAL: Cancelling this subscription will: • Set the user's status to INACTIVE • Remove their member access immediately • Stop all future billing • This action affects: ${selectedSubscription.user.first_name} ${selectedSubscription.user.last_name} (${selectedSubscription.user.email}) Current Status: ${selectedSubscription.status.toUpperCase()} New Status: CANCELLED Are you absolutely sure you want to proceed?`; confirmText = 'Yes, Cancel Subscription'; } else if (editFormData.status === 'expired') { warningMessage = `⚠️ WARNING: Setting this subscription to EXPIRED will: • Set the user's status to INACTIVE • Remove their member access • Mark the subscription as ended • This action affects: ${selectedSubscription.user.first_name} ${selectedSubscription.user.last_name} (${selectedSubscription.user.email}) Current Status: ${selectedSubscription.status.toUpperCase()} New Status: EXPIRED Are you sure you want to proceed?`; confirmText = 'Yes, Mark as Expired'; } else if (editFormData.status === 'active') { warningMessage = `✓ Activating this subscription will: • Set the user's status to ACTIVE • Grant full member access • Resume billing if applicable • This action affects: ${selectedSubscription.user.first_name} ${selectedSubscription.user.last_name} (${selectedSubscription.user.email}) Current Status: ${selectedSubscription.status.toUpperCase()} New Status: ACTIVE Proceed with activation?`; confirmText = 'Yes, Activate Subscription'; } // Show confirmation dialog const confirmed = window.confirm(warningMessage); if (!confirmed) { return; // User cancelled } } setIsUpdating(true); try { await api.put(`/admin/subscriptions/${selectedSubscription.id}`, { status: editFormData.status, end_date: editFormData.end_date ? new Date(editFormData.end_date).toISOString() : null }); toast.success('Subscription updated successfully'); setEditDialogOpen(false); fetchData(); // Refresh data } catch (error) { console.error('Failed to update subscription:', error); toast.error(error.response?.data?.detail || 'Failed to update subscription'); } finally { setIsUpdating(false); } }; const handleCancelSubscription = async (subscriptionId) => { if (!window.confirm('Are you sure you want to cancel this subscription? This will also set the user status to inactive.')) { return; } try { await api.post(`/admin/subscriptions/${subscriptionId}/cancel`); toast.success('Subscription cancelled successfully'); fetchData(); // Refresh data } catch (error) { console.error('Failed to cancel subscription:', error); toast.error(error.response?.data?.detail || 'Failed to cancel subscription'); } }; const handleExport = async (exportType) => { setExporting(true); try { const params = exportType === 'current' ? { status: statusFilter !== 'all' ? statusFilter : undefined, plan_id: planFilter !== 'all' ? planFilter : undefined, search: searchQuery || undefined } : {}; const response = await api.get('/admin/subscriptions/export', { params, responseType: 'blob' }); const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', `subscriptions_export_${new Date().toISOString().split('T')[0]}.csv`); document.body.appendChild(link); link.click(); link.remove(); window.URL.revokeObjectURL(url); toast.success('Subscriptions exported successfully'); } catch (error) { console.error('Failed to export subscriptions:', error); toast.error('Failed to export subscriptions'); } finally { setExporting(false); } }; const formatPrice = (cents) => { return `$${(cents / 100).toFixed(2)}`; }; const formatDate = (dateString) => { if (!dateString) return 'N/A'; return new Date(dateString).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); }; const getStatusBadgeVariant = (status) => { const variants = { active: 'default', cancelled: 'destructive', expired: 'secondary' }; return variants[status] || 'outline'; }; if (loading) { return (
); } return (
{/* Header */}

Subscription Management

View and manage all member subscriptions

{/* Stats Cards */}

Total Subscriptions

{stats.total || 0}

Active Members

{stats.active || 0}

Total Revenue

{formatPrice(stats.total_revenue || 0)}

Total Donations

{formatPrice(stats.total_donations || 0)}

{/* Search & Filter Bar */}
{/* Search */}
setSearchQuery(e.target.value)} className="pl-10 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" />
{/* Status Filter */}
{/* Plan Filter */}
Showing {filteredSubscriptions.length} of {subscriptions.length} subscriptions
{/* Export Dropdown */} {hasPermission('subscriptions.export') && ( handleExport('all')} className="cursor-pointer hover:bg-[#f1eef9] rounded-lg p-3" > Export All Subscriptions handleExport('current')} className="cursor-pointer hover:bg-[#f1eef9] rounded-lg p-3" > Export Current View )}
{/* Subscriptions Table */} {/* Mobile Card View */}
{filteredSubscriptions.length > 0 ? ( filteredSubscriptions.map((sub) => (
{/* Member Info */}

{sub.user.first_name} {sub.user.last_name}

{sub.user.email}

{sub.status}
{/* Plan & Period */}

Plan

{sub.plan.name}

{sub.plan.billing_cycle}

Period

{new Date(sub.current_period_start).toLocaleDateString()} - {new Date(sub.current_period_end).toLocaleDateString()}

{/* Pricing */}

Base Fee

${(sub.base_fee_cents / 100).toFixed(2)}

Donation

${(sub.donation_cents / 100).toFixed(2)}

Total

${(sub.total_cents / 100).toFixed(2)}

{/* Actions */}
{hasPermission('subscriptions.edit') && ( )} {sub.status === 'active' && hasPermission('subscriptions.cancel') && ( )}
)) ) : (
No subscriptions found
)}
{/* Desktop Table View */}
{filteredSubscriptions.length > 0 ? ( filteredSubscriptions.map((sub) => ( )) ) : ( )}
Member Plan Status Period Base Fee Donation Total Actions
{sub.user.first_name} {sub.user.last_name}
{sub.user.email}
{sub.plan.name}
{sub.plan.billing_cycle}
{sub.status}
{formatDate(sub.start_date)}
to {formatDate(sub.end_date)}
{formatPrice(sub.base_subscription_cents || 0)} {formatPrice(sub.donation_cents || 0)} {formatPrice(sub.amount_paid_cents || 0)}
{hasPermission('subscriptions.edit') && ( )} {sub.status === 'active' && hasPermission('subscriptions.cancel') && ( )}
No subscriptions found
{/* Edit Subscription Dialog */} Edit Subscription Update subscription status or end date for {selectedSubscription?.user.first_name} {selectedSubscription?.user.last_name}
{/* Status */}
{/* Warning Box - Show when status is different */} {selectedSubscription && editFormData.status !== selectedSubscription.status && (
{editFormData.status === 'cancelled' || editFormData.status === 'expired' ? ( ) : ( )}

{editFormData.status === 'cancelled' && 'Critical: This will cancel the subscription'} {editFormData.status === 'expired' && 'Warning: This will mark subscription as expired'} {editFormData.status === 'active' && 'This will activate the subscription'}

    {editFormData.status === 'cancelled' && ( <>
  • User status will be set to INACTIVE
  • Member access will be removed immediately
  • All future billing will be stopped
  • )} {editFormData.status === 'expired' && ( <>
  • User status will be set to INACTIVE
  • Member access will be removed
  • Subscription will be marked as ended
  • )} {editFormData.status === 'active' && ( <>
  • User status will be set to ACTIVE
  • Full member access will be granted
  • Billing will resume if applicable
  • )}

Current: {selectedSubscription.status.toUpperCase()} → New: {editFormData.status.toUpperCase()}

)}
{/* End Date */}
setEditFormData({ ...editFormData, end_date: e.target.value })} className="pl-12 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]" />
); }; export default AdminSubscriptions;