Files
membership-fe/src/pages/admin/AdminSubscriptions.js
Koncept Kit 40a0e3f342 feat(frontend): Comprehensive RBAC implementation across admin pages
**Option 3 Implementation (Latest):**
- InviteStaffDialog: Use /admin/roles/assignable endpoint
- AdminStaff: Enable admin users to see 'Invite Staff' button

**Permission Checks Added (8 admin pages):**
- AdminNewsletters: newsletters.create/edit/delete
- AdminFinancials: financials.create/edit/delete
- AdminBylaws: bylaws.create/edit/delete
- AdminValidations: users.approve, subscriptions.activate
- AdminSubscriptions: subscriptions.export/edit/cancel
- AdminDonations: donations.export
- AdminGallery: gallery.upload/edit/delete
- AdminPlans: subscriptions.plans

**Pattern Established:**
All admin action buttons now wrapped with hasPermission() checks.
UI hides what users can't access, backend enforces rules.

**Files Modified:** 10 files, 100+ permission checks added
2026-01-06 14:45:15 +07:00

789 lines
31 KiB
JavaScript

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 (
<div className="flex items-center justify-center min-h-screen">
<Loader2 className="h-12 w-12 animate-spin text-[#664fa3]" />
</div>
);
}
return (
<div className="space-y-8">
{/* Header */}
<div>
<h1 className="text-3xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
Subscription Management
</h1>
<p className="text-[#664fa3] mt-2" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
View and manage all member subscriptions
</p>
</div>
{/* Stats Cards */}
<div className="grid md:grid-cols-4 gap-6">
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Total Subscriptions
</p>
<p className="text-3xl font-bold text-[#422268] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
{stats.total || 0}
</p>
</div>
<div className="p-3 bg-[#DDD8EB]/20 rounded-full">
<CreditCard className="h-6 w-6 text-[#664fa3]" />
</div>
</div>
</Card>
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Active Members
</p>
<p className="text-3xl font-bold text-[#81B29A] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
{stats.active || 0}
</p>
</div>
<div className="p-3 bg-[#81B29A]/10 rounded-full">
<TrendingUp className="h-6 w-6 text-[#81B29A]" />
</div>
</div>
</Card>
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Total Revenue
</p>
<p className="text-3xl font-bold text-[#422268] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
{formatPrice(stats.total_revenue || 0)}
</p>
</div>
<div className="p-3 bg-[#DDD8EB]/20 rounded-full">
<DollarSign className="h-6 w-6 text-[#664fa3]" />
</div>
</div>
</Card>
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
<div className="flex items-center justify-between">
<div>
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Total Donations
</p>
<p className="text-3xl font-bold text-[#ff9e77] mt-2" style={{ fontFamily: "'Inter', sans-serif" }}>
{formatPrice(stats.total_donations || 0)}
</p>
</div>
<div className="p-3 bg-[#ff9e77]/10 rounded-full">
<Heart className="h-6 w-6 text-[#ff9e77]" />
</div>
</div>
</Card>
</div>
{/* Search & Filter Bar */}
<Card className="p-6 bg-white rounded-2xl border-2 border-[#ddd8eb]">
<div className="grid md:grid-cols-3 gap-4">
{/* Search */}
<div className="md:col-span-1">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
<Input
placeholder="Search by name or email..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
/>
</div>
</div>
{/* Status Filter */}
<div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
<SelectValue placeholder="All Statuses" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Statuses</SelectItem>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="cancelled">Cancelled</SelectItem>
<SelectItem value="expired">Expired</SelectItem>
</SelectContent>
</Select>
</div>
{/* Plan Filter */}
<div>
<Select value={planFilter} onValueChange={setPlanFilter}>
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
<SelectValue placeholder="All Plans" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Plans</SelectItem>
{plans.map(plan => (
<SelectItem key={plan.id} value={plan.id}>{plan.name}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="mt-4 flex items-center justify-between">
<div className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Showing {filteredSubscriptions.length} of {subscriptions.length} subscriptions
</div>
{/* Export Dropdown */}
{hasPermission('subscriptions.export') && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
disabled={exporting}
className="bg-[#81B29A] text-white hover:bg-[#6a9680] rounded-full px-6 py-2 flex items-center gap-2"
>
<Download className="h-4 w-4" />
{exporting ? 'Exporting...' : 'Export'}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56 bg-white rounded-xl border-2 border-[#ddd8eb] shadow-lg">
<DropdownMenuItem
onClick={() => handleExport('all')}
className="cursor-pointer hover:bg-[#f1eef9] rounded-lg p-3"
>
<FileDown className="h-4 w-4 mr-2 text-[#664fa3]" />
<span className="text-[#422268]">Export All Subscriptions</span>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => handleExport('current')}
className="cursor-pointer hover:bg-[#f1eef9] rounded-lg p-3"
>
<FileDown className="h-4 w-4 mr-2 text-[#664fa3]" />
<span className="text-[#422268]">Export Current View</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
</Card>
{/* Subscriptions Table */}
<Card className="bg-white rounded-2xl border-2 border-[#ddd8eb] overflow-hidden">
{/* Mobile Card View */}
<div className="md:hidden p-4 space-y-4">
{filteredSubscriptions.length > 0 ? (
filteredSubscriptions.map((sub) => (
<Card key={sub.id} className="p-4 border border-[#ddd8eb] bg-[#f9f5ff]/30">
<div className="space-y-3">
{/* Member Info */}
<div className="flex justify-between items-start border-b border-[#ddd8eb] pb-3">
<div className="flex-1">
<p className="font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
{sub.user.first_name} {sub.user.last_name}
</p>
<p className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{sub.user.email}
</p>
</div>
<Badge variant={getStatusBadgeVariant(sub.status)}>{sub.status}</Badge>
</div>
{/* Plan & Period */}
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<p className="text-xs text-[#664fa3] mb-1">Plan</p>
<p className="font-medium text-[#422268]">{sub.plan.name}</p>
<p className="text-xs text-[#664fa3]">{sub.plan.billing_cycle}</p>
</div>
<div>
<p className="text-xs text-[#664fa3] mb-1">Period</p>
<p className="text-[#422268]">
{new Date(sub.current_period_start).toLocaleDateString()} -
{new Date(sub.current_period_end).toLocaleDateString()}
</p>
</div>
</div>
{/* Pricing */}
<div className="grid grid-cols-3 gap-2 text-sm bg-white/50 p-3 rounded">
<div>
<p className="text-xs text-[#664fa3] mb-1">Base Fee</p>
<p className="font-medium text-[#422268]">
${(sub.base_fee_cents / 100).toFixed(2)}
</p>
</div>
<div>
<p className="text-xs text-[#664fa3] mb-1">Donation</p>
<p className="font-medium text-[#422268]">
${(sub.donation_cents / 100).toFixed(2)}
</p>
</div>
<div>
<p className="text-xs text-[#664fa3] mb-1">Total</p>
<p className="font-semibold text-[#422268]">
${(sub.total_cents / 100).toFixed(2)}
</p>
</div>
</div>
{/* Actions */}
<div className="flex gap-2 pt-2">
{hasPermission('subscriptions.edit') && (
<Button
size="sm"
variant="outline"
onClick={() => handleEdit(sub)}
className="flex-1 text-[#664fa3] hover:bg-[#DDD8EB]"
>
<Edit className="h-4 w-4 mr-2" />
Edit
</Button>
)}
{sub.status === 'active' && hasPermission('subscriptions.cancel') && (
<Button
size="sm"
variant="outline"
onClick={() => handleCancelSubscription(sub.id)}
className="flex-1 text-red-600 hover:bg-red-50"
>
<XCircle className="h-4 w-4 mr-2" />
Cancel
</Button>
)}
</div>
</div>
</Card>
))
) : (
<div className="p-12 text-center text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
No subscriptions found
</div>
)}
</div>
{/* Desktop Table View */}
<div className="hidden md:block overflow-x-auto">
<table className="w-full">
<thead>
<tr className="bg-[#DDD8EB]/20 border-b border-[#ddd8eb]">
<th className="text-left p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
Member
</th>
<th className="text-left p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
Plan
</th>
<th className="text-left p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
Status
</th>
<th className="text-left p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
Period
</th>
<th className="text-right p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
Base Fee
</th>
<th className="text-right p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
Donation
</th>
<th className="text-right p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
Total
</th>
<th className="text-center p-4 text-[#422268] font-semibold" style={{ fontFamily: "'Inter', sans-serif" }}>
Actions
</th>
</tr>
</thead>
<tbody>
{filteredSubscriptions.length > 0 ? (
filteredSubscriptions.map((sub) => (
<tr key={sub.id} className="border-b border-[#ddd8eb] hover:bg-[#f9f5ff] transition-colors">
<td className="p-4">
<div className="font-medium text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
{sub.user.first_name} {sub.user.last_name}
</div>
<div className="text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{sub.user.email}
</div>
</td>
<td className="p-4">
<div className="text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{sub.plan.name}
</div>
<div className="text-xs text-[#664fa3]">
{sub.plan.billing_cycle}
</div>
</td>
<td className="p-4">
<Badge variant={getStatusBadgeVariant(sub.status)}>
{sub.status}
</Badge>
</td>
<td className="p-4">
<div className="text-sm text-[#422268]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
<div>{formatDate(sub.start_date)}</div>
<div className="text-xs text-[#664fa3]">to {formatDate(sub.end_date)}</div>
</div>
</td>
<td className="p-4 text-right text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
{formatPrice(sub.base_subscription_cents || 0)}
</td>
<td className="p-4 text-right text-[#ff9e77]" style={{ fontFamily: "'Inter', sans-serif" }}>
{formatPrice(sub.donation_cents || 0)}
</td>
<td className="p-4 text-right font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
{formatPrice(sub.amount_paid_cents || 0)}
</td>
<td className="p-4">
<div className="flex items-center justify-center gap-2">
{hasPermission('subscriptions.edit') && (
<Button
size="sm"
variant="outline"
onClick={() => handleEdit(sub)}
className="text-[#664fa3] hover:bg-[#DDD8EB]"
>
<Edit className="h-4 w-4" />
</Button>
)}
{sub.status === 'active' && hasPermission('subscriptions.cancel') && (
<Button
size="sm"
variant="outline"
onClick={() => handleCancelSubscription(sub.id)}
className="text-red-600 hover:bg-red-50"
>
<XCircle className="h-4 w-4" />
</Button>
)}
</div>
</td>
</tr>
))
) : (
<tr>
<td colSpan="8" className="p-12 text-center text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
No subscriptions found
</td>
</tr>
)}
</tbody>
</table>
</div>
</Card>
{/* Edit Subscription Dialog */}
<Dialog open={editDialogOpen} onOpenChange={setEditDialogOpen}>
<DialogContent className="sm:max-w-[500px] bg-white rounded-2xl">
<DialogHeader>
<DialogTitle className="text-2xl font-semibold text-[#422268]" style={{ fontFamily: "'Inter', sans-serif" }}>
Edit Subscription
</DialogTitle>
<DialogDescription className="text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
Update subscription status or end date for {selectedSubscription?.user.first_name} {selectedSubscription?.user.last_name}
</DialogDescription>
</DialogHeader>
<div className="space-y-6 py-4">
{/* Status */}
<div className="space-y-2">
<Label htmlFor="status" className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
Status
</Label>
<Select
value={editFormData.status}
onValueChange={(value) => setEditFormData({ ...editFormData, status: value })}
>
<SelectTrigger className="rounded-xl border-2 border-[#ddd8eb]">
<SelectValue placeholder="Select status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="cancelled">Cancelled</SelectItem>
<SelectItem value="expired">Expired</SelectItem>
</SelectContent>
</Select>
{/* Warning Box - Show when status is different */}
{selectedSubscription && editFormData.status !== selectedSubscription.status && (
<div className={`mt-3 p-4 rounded-xl border-2 ${
editFormData.status === 'cancelled'
? 'bg-red-50 border-red-300'
: editFormData.status === 'expired'
? 'bg-orange-50 border-orange-300'
: 'bg-green-50 border-green-300'
}`}>
<div className="flex items-start gap-3">
{editFormData.status === 'cancelled' || editFormData.status === 'expired' ? (
<AlertTriangle className="h-5 w-5 text-red-600 flex-shrink-0 mt-0.5" />
) : (
<Info className="h-5 w-5 text-green-600 flex-shrink-0 mt-0.5" />
)}
<div className="flex-1">
<p className="font-semibold text-sm mb-2" style={{ fontFamily: "'Inter', sans-serif" }}>
{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'}
</p>
<ul className="text-xs space-y-1 list-disc list-inside" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
{editFormData.status === 'cancelled' && (
<>
<li>User status will be set to INACTIVE</li>
<li>Member access will be removed immediately</li>
<li>All future billing will be stopped</li>
</>
)}
{editFormData.status === 'expired' && (
<>
<li>User status will be set to INACTIVE</li>
<li>Member access will be removed</li>
<li>Subscription will be marked as ended</li>
</>
)}
{editFormData.status === 'active' && (
<>
<li>User status will be set to ACTIVE</li>
<li>Full member access will be granted</li>
<li>Billing will resume if applicable</li>
</>
)}
</ul>
<p className="text-xs mt-2 font-medium">
Current: <span className="font-bold">{selectedSubscription.status.toUpperCase()}</span>
New: <span className="font-bold">{editFormData.status.toUpperCase()}</span>
</p>
</div>
</div>
</div>
)}
</div>
{/* End Date */}
<div className="space-y-2">
<Label htmlFor="end_date" className="text-[#422268] font-medium" style={{ fontFamily: "'Inter', sans-serif" }}>
End Date
</Label>
<div className="relative">
<Calendar className="absolute left-4 top-1/2 transform -translate-y-1/2 h-5 w-5 text-[#664fa3]" />
<Input
id="end_date"
type="date"
value={editFormData.end_date}
onChange={(e) => setEditFormData({ ...editFormData, end_date: e.target.value })}
className="pl-12 rounded-xl border-2 border-[#ddd8eb] focus:border-[#664fa3]"
/>
</div>
</div>
</div>
<DialogFooter className="gap-2">
<Button
type="button"
variant="outline"
onClick={() => setEditDialogOpen(false)}
className="rounded-full border-2 border-[#ddd8eb]"
disabled={isUpdating}
>
Cancel
</Button>
<Button
type="button"
onClick={handleSaveSubscription}
disabled={isUpdating}
className="bg-[#81B29A] text-white hover:bg-[#6FA087] rounded-full"
>
{isUpdating ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Saving...
</>
) : (
'Save Changes'
)}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
};
export default AdminSubscriptions;