import { useState, useEffect } from 'react'; import { Users, Mail, Plus, Trash2, Copy, Check, X, KeyRound, ChevronRight, Shield, ShieldAlert } from 'lucide-react'; import { api } from '@/lib/api'; import { useAuthStore } from '@/stores/auth'; import type { User, Invite } from '@/types'; import { cn } from '@/lib/utils'; export default function AdminPage() { const { user: currentUser } = useAuthStore(); const [users, setUsers] = useState([]); const [invites, setInvites] = useState([]); const [isLoading, setIsLoading] = useState(true); const [activeTab, setActiveTab] = useState<'users' | 'invites'>('users'); // User detail panel const [selectedUser, setSelectedUser] = useState(null); // Invite form const [showInviteForm, setShowInviteForm] = useState(false); const [inviteEmail, setInviteEmail] = useState(''); const [inviteName, setInviteName] = useState(''); const [inviteRole, setInviteRole] = useState<'admin' | 'user'>('user'); const [inviteError, setInviteError] = useState(''); const [inviteUrl, setInviteUrl] = useState(''); const [copied, setCopied] = useState(false); // Password reset (in detail panel) const [resetUrl, setResetUrl] = useState(''); const [resetCopied, setResetCopied] = useState(false); const [resetLoading, setResetLoading] = useState(false); useEffect(() => { loadData(); }, []); const loadData = async () => { setIsLoading(true); try { const [usersData, invitesData] = await Promise.all([ api.getUsers(), api.getInvites(), ]); setUsers(usersData); setInvites(invitesData); } catch (error) { console.error('Failed to load admin data:', error); } finally { setIsLoading(false); } }; const handleCreateInvite = async (e: React.FormEvent) => { e.preventDefault(); setInviteError(''); try { const result = await api.createInvite({ email: inviteEmail, name: inviteName, role: inviteRole }); setInviteUrl(result.setupUrl); setInvites([result, ...invites]); } catch (error) { setInviteError(error instanceof Error ? error.message : 'Failed to create invite'); } }; const handleCopyUrl = () => { navigator.clipboard.writeText(inviteUrl); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const handleChangeRole = async (userId: string, role: 'admin' | 'user') => { try { await api.updateUserRole(userId, role); const updated = users.map(u => u.id === userId ? { ...u, role } : u); setUsers(updated); if (selectedUser?.id === userId) { setSelectedUser({ ...selectedUser, role }); } } catch (error) { console.error('Failed to update role:', error); } }; const handleGenerateResetLink = async (userId: string) => { setResetLoading(true); try { const result = await api.createPasswordReset(userId); setResetUrl(result.resetUrl); } catch (error) { console.error('Failed to generate reset link:', error); } finally { setResetLoading(false); } }; const handleCopyResetUrl = () => { navigator.clipboard.writeText(resetUrl); setResetCopied(true); setTimeout(() => setResetCopied(false), 2000); }; const closeUserPanel = () => { setSelectedUser(null); setResetUrl(''); setResetCopied(false); }; const handleDeleteUser = async (userId: string) => { if (!confirm('Are you sure you want to delete this user? All their data will be lost.')) return; try { await api.deleteUser(userId); setUsers(users.filter(u => u.id !== userId)); if (selectedUser?.id === userId) closeUserPanel(); } catch (error) { console.error('Failed to delete user:', error); } }; const handleDeleteInvite = async (inviteId: string) => { try { await api.deleteInvite(inviteId); setInvites(invites.filter(i => i.id !== inviteId)); } catch (error) { console.error('Failed to delete invite:', error); } }; const resetInviteForm = () => { setShowInviteForm(false); setInviteEmail(''); setInviteName(''); setInviteRole('user'); setInviteError(''); setInviteUrl(''); }; if (currentUser?.role !== 'admin') { return (

Access denied. Admin only.

); } return (

Admin

{/* Tabs */}
{isLoading ? (
Loading...
) : activeTab === 'users' ? (
{users.map((user) => ( setSelectedUser(user)} className="border-b border-slate-100 dark:border-slate-700 last:border-0 cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors" > ))}
Name Email Role Joined
{user.name} {user.email} {user.role} {user.createdAt ? new Date(user.createdAt).toLocaleDateString() : '—'}
) : (
{/* Create invite button/form */} {!showInviteForm ? ( ) : (

Invite New User

{inviteUrl ? (

✓ Invite created! Share this link:

) : (
{inviteError && (

{inviteError}

)}
setInviteName(e.target.value)} required className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg text-sm bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="John Doe" />
setInviteEmail(e.target.value)} required className="w-full px-3 py-2 border border-slate-300 dark:border-slate-600 rounded-lg text-sm bg-white dark:bg-slate-800 text-slate-900 dark:text-slate-100 focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="john@example.com" />
)}
)} {/* Invites list */}
{invites.length === 0 ? ( ) : ( invites.map((invite) => ( )) )}
Name Email Role Status Expires
No invites yet
{invite.name} {invite.email} {invite.role} {invite.status} {new Date(invite.expiresAt).toLocaleDateString()} {invite.status === 'pending' && ( )}
)} {/* User Detail Slide-Over Panel */} {selectedUser && ( <> {/* Backdrop */}
{/* Panel */}
{/* Header */}

User Settings

{/* Content */}
{/* User info */}
{selectedUser.name?.charAt(0).toUpperCase() || '?'}

{selectedUser.name}

{selectedUser.email}

{/* Details */}

{selectedUser.createdAt ? new Date(selectedUser.createdAt).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) : '—'}

{/* Role */}
{selectedUser.id === currentUser?.id ? (
{selectedUser.role} (your account)
) : (
)}
{/* Actions — only for non-self users */} {selectedUser.id !== currentUser?.id && (

Actions

{/* Password Reset */}
Password Reset
{resetUrl ? (

✓ Reset link generated

) : ( )}
{/* Delete User */}
Danger Zone

Permanently delete this user and all their data.

)}
)}
); }