'use client'; import React, { useState, useEffect, useCallback } from 'react'; import Link from 'next/link'; import { useAuth } from '@/contexts/AuthContext'; import { useRouter } from 'next/navigation'; interface SuspensionUser { id: string; email: string; full_name: string; avatar_url: string | null; role: string; } interface Suspension { id: string; user_id: string; suspension_type: 'suspended' | 'banned'; reason: string; duration_days: number | null; expires_at: string | null; is_active: boolean; lifted_at: string | null; lift_reason: string | null; email_sent: boolean; created_at: string; user: SuspensionUser; suspended_by_user: { id: string; full_name: string; email: string }; } interface UserForSuspend { id: string; email: string; full_name: string; avatar_url: string | null; role: string; suspension_status: string | null; suspended_until: string | null; } const DURATION_OPTIONS = [ { label: '1 day', value: 1 }, { label: '3 days', value: 3 }, { label: '7 days', value: 7 }, { label: '14 days', value: 14 }, { label: '30 days', value: 30 }, { label: '90 days', value: 90 }, { label: 'Permanent (Ban)', value: null }, ]; export default function AdminSuspensionsPage() { const { user, loading } = useAuth(); const router = useRouter(); const [suspensions, setSuspensions] = useState([]); const [users, setUsers] = useState([]); const [loadingData, setLoadingData] = useState(true); const [activeTab, setActiveTab] = useState<'active' | 'history' | 'users'>('active'); const [notification, setNotification] = useState<{ type: 'success' | 'error'; message: string } | null>(null); const [processingIds, setProcessingIds] = useState>(new Set()); // Suspend modal state const [showSuspendModal, setShowSuspendModal] = useState(false); const [selectedUser, setSelectedUser] = useState(null); const [suspendReason, setSuspendReason] = useState(''); const [suspendType, setSuspendType] = useState<'suspended' | 'banned'>('suspended'); const [suspendDuration, setSuspendDuration] = useState(7); const [suspending, setSuspending] = useState(false); // Lift modal state const [showLiftModal, setShowLiftModal] = useState(false); const [liftingSuspension, setLiftingSuspension] = useState(null); const [liftReason, setLiftReason] = useState(''); const [lifting, setLifting] = useState(false); // User search const [userSearch, setUserSearch] = useState(''); useEffect(() => { if (!loading && !user) router.replace('/account'); }, [user, loading, router]); const showNotification = (type: 'success' | 'error', message: string) => { setNotification({ type, message }); setTimeout(() => setNotification(null), 4000); }; const fetchSuspensions = useCallback(async (activeOnly: boolean) => { const res = await fetch(`/api/admin/suspensions?active=${activeOnly}`); if (res.ok) { const data = await res.json(); setSuspensions(data.suspensions || []); } }, []); const fetchUsers = useCallback(async () => { const res = await fetch('/api/admin/users?tab=users'); if (res.ok) { const data = await res.json(); setUsers(data.users || []); } }, []); const loadData = useCallback(async () => { setLoadingData(true); try { await Promise.all([ fetchSuspensions(activeTab === 'active'), fetchUsers(), ]); } finally { setLoadingData(false); } }, [activeTab, fetchSuspensions, fetchUsers]); useEffect(() => { if (user) loadData(); }, [user, loadData]); const handleSuspend = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedUser || !suspendReason.trim()) return; setSuspending(true); try { const res = await fetch('/api/admin/suspensions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: selectedUser.id, suspension_type: suspendType, reason: suspendReason.trim(), duration_days: suspendType === 'banned' ? null : suspendDuration, }), }); const data = await res.json(); if (res.ok) { showNotification('success', `User ${suspendType === 'banned' ? 'banned' : 'suspended'} successfully${data.emailSent ? ' — notification email sent' : ''}`); setShowSuspendModal(false); setSuspendReason(''); setSuspendType('suspended'); setSuspendDuration(7); setSelectedUser(null); loadData(); } else { showNotification('error', data.error || 'Failed to suspend user'); } } catch { showNotification('error', 'Failed to suspend user'); } finally { setSuspending(false); } }; const handleLiftSuspension = async (e: React.FormEvent) => { e.preventDefault(); if (!liftingSuspension) return; setLifting(true); try { const res = await fetch('/api/admin/suspensions', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ suspension_id: liftingSuspension.id, lift_reason: liftReason.trim() || undefined, }), }); if (res.ok) { showNotification('success', 'Suspension lifted successfully'); setShowLiftModal(false); setLiftReason(''); setLiftingSuspension(null); loadData(); } else { const data = await res.json(); showNotification('error', data.error || 'Failed to lift suspension'); } } catch { showNotification('error', 'Failed to lift suspension'); } finally { setLifting(false); } }; const openSuspendModal = (u: UserForSuspend) => { setSelectedUser(u); setSuspendType('suspended'); setSuspendDuration(7); setSuspendReason(''); setShowSuspendModal(true); }; const openLiftModal = (s: Suspension) => { setLiftingSuspension(s); setLiftReason(''); setShowLiftModal(true); }; const formatDate = (d: string) => new Date(d).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); const formatExpiry = (s: Suspension) => { if (s.suspension_type === 'banned') return 'Permanent'; if (!s.expires_at) return 'Indefinite'; const exp = new Date(s.expires_at); const now = new Date(); if (exp < now) return 'Expired'; const days = Math.ceil((exp.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); return `${days}d remaining`; }; const filteredUsers = users.filter((u) => { if (!userSearch) return true; return ( u.email.toLowerCase().includes(userSearch.toLowerCase()) || (u.full_name || '').toLowerCase().includes(userSearch.toLowerCase()) ); }); const activeSuspensions = suspensions.filter((s) => s.is_active); const suspendedUserIds = new Set(activeSuspensions.map((s) => s.user_id)); if (loading) { return (
); } return (
{/* Notification */} {notification && (
{notification.message}
)} {/* Header */}
← Admin /

Suspensions & Bans

{activeSuspensions.length} active
{/* Stats */}

Active Suspensions

{activeSuspensions.filter(s => s.suspension_type === 'suspended').length}

Active Bans

{activeSuspensions.filter(s => s.suspension_type === 'banned').length}

Total Users

{users.length}

Restricted Users

{suspendedUserIds.size}

{/* Tabs */}
{(['active', 'history', 'users'] as const).map((tab) => ( ))}
{loadingData ? (
) : ( <> {/* Active Restrictions Tab */} {activeTab === 'active' && (
{activeSuspensions.length === 0 ? (

No active suspensions or bans

) : (
{activeSuspensions.map((s) => ( ))}
User Type Reason Expires Email Actions
{s.user?.avatar_url ? ( {s.user.full_name ) : ( (s.user?.full_name?.[0] || s.user?.email?.[0] || '?').toUpperCase() )}

{s.user?.full_name || '—'}

{s.user?.email}

{s.suspension_type === 'banned' ? '🚫 Banned' : '⏸ Suspended'}

{s.reason}

{formatExpiry(s)} {s.email_sent ? '✓ Sent' : '✗ Not sent'}
)}
)} {/* History Tab */} {activeTab === 'history' && (
{suspensions.length === 0 ? (

No suspension history

) : (
{suspensions.map((s) => ( ))}
User Type Reason Status Date
{(s.user?.full_name?.[0] || s.user?.email?.[0] || '?').toUpperCase()}

{s.user?.full_name || s.user?.email || '—'}

{s.suspension_type}

{s.reason}

{s.is_active ? 'Active' : s.lifted_at ? 'Lifted' : 'Expired'} {formatDate(s.created_at)}
)}
)} {/* Manage Users Tab */} {activeTab === 'users' && (
setUserSearch(e.target.value)} className="w-full bg-[#111827] border border-[#1f2937] text-white text-sm rounded-lg pl-9 pr-3 py-2 placeholder-[#444] focus:outline-none focus:border-[#3b82f6]" />
{filteredUsers.map((u) => { const isSuspended = suspendedUserIds.has(u.id); const activeSusp = activeSuspensions.find(s => s.user_id === u.id); return ( ); })}
User Role Status Actions
{u.avatar_url ? ( {u.full_name ) : ( (u.full_name?.[0] || u.email?.[0] || '?').toUpperCase() )}

{u.full_name || '—'}

{u.email}

{u.role} {isSuspended ? ( {activeSusp?.suspension_type === 'banned' ? 'Banned' : 'Suspended'} ) : ( Active )}
{isSuspended && activeSusp ? ( ) : ( u.role !== 'admin' && ( ) )}
)} )}
{/* Suspend Modal */} {showSuspendModal && selectedUser && (

Suspend / Ban User

{/* User info */}
{selectedUser.avatar_url ? ( {selectedUser.full_name ) : ( (selectedUser.full_name?.[0] || selectedUser.email?.[0] || '?').toUpperCase() )}

{selectedUser.full_name || '—'}

{selectedUser.email}

{/* Type */}
{(['suspended', 'banned'] as const).map((t) => ( ))}
{/* Duration (only for suspend) */} {suspendType === 'suspended' && (
)} {/* Reason */}