- Profile Picture\
Donation Tracking\ Validation Rejection\ Subscription Data Export\ Admin Dashboard Logo\ Admin Navbar Reorganization
This commit is contained in:
@@ -30,8 +30,18 @@ import {
|
||||
Loader2,
|
||||
Calendar,
|
||||
Edit,
|
||||
XCircle
|
||||
XCircle,
|
||||
Download,
|
||||
FileDown,
|
||||
AlertTriangle,
|
||||
Info
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '../../components/ui/dropdown-menu';
|
||||
|
||||
const AdminSubscriptions = () => {
|
||||
const [subscriptions, setSubscriptions] = useState([]);
|
||||
@@ -42,6 +52,7 @@ const AdminSubscriptions = () => {
|
||||
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);
|
||||
@@ -118,6 +129,62 @@ const AdminSubscriptions = () => {
|
||||
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}`, {
|
||||
@@ -151,6 +218,38 @@ const AdminSubscriptions = () => {
|
||||
}
|
||||
};
|
||||
|
||||
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)}`;
|
||||
};
|
||||
@@ -307,8 +406,39 @@ const AdminSubscriptions = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-sm text-[#664fa3]" style={{ fontFamily: "'Nunito Sans', sans-serif" }}>
|
||||
Showing {filteredSubscriptions.length} of {subscriptions.length} subscriptions
|
||||
<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 */}
|
||||
<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>
|
||||
|
||||
@@ -542,6 +672,59 @@ const AdminSubscriptions = () => {
|
||||
<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 */}
|
||||
|
||||
Reference in New Issue
Block a user