'use client' import { useState, useEffect, useCallback } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card' import { UserPlus, Copy, Check, Trash2, Clock, CheckCircle2, Loader2 } from 'lucide-react' interface Invite { id: string token: string email: string created_at: string expires_at: string used_at: string | null } function inviteStatus(invite: Invite): 'used' | 'expired' | 'pending' { if (invite.used_at) return 'used' if (new Date(invite.expires_at) < new Date()) return 'expired' return 'pending' } function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false) const handleCopy = async () => { await navigator.clipboard.writeText(text) setCopied(true) setTimeout(() => setCopied(false), 2000) } return ( ) } export default function AdminPage() { const [invites, setInvites] = useState([]) const [email, setEmail] = useState('') const [isLoading, setIsLoading] = useState(true) const [isCreating, setIsCreating] = useState(false) const [error, setError] = useState(null) const [successEmail, setSuccessEmail] = useState(null) const [newToken, setNewToken] = useState(null) const fetchInvites = useCallback(async () => { try { const res = await fetch('/api/invites') const data = await res.json() if (res.ok) setInvites(data.invites) } finally { setIsLoading(false) } }, []) useEffect(() => { fetchInvites() }, [fetchInvites]) const handleCreate = async (e: React.FormEvent) => { e.preventDefault() setError(null) setSuccessEmail(null) setNewToken(null) setIsCreating(true) try { const res = await fetch('/api/invites', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email }), }) const data = await res.json() if (!res.ok) { setError(data.error) return } setSuccessEmail(email) setNewToken(data.invite.token) setEmail('') await fetchInvites() } catch { setError('Network error. Please try again.') } finally { setIsCreating(false) } } const handleDelete = async (id: string) => { const res = await fetch('/api/invites', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id }), }) if (res.ok) await fetchInvites() } const inviteUrl = (token: string) => `${window.location.origin}/auth/invite/${token}` const statusBadge = (invite: Invite) => { const status = inviteStatus(invite) if (status === 'used') return ( Used ) if (status === 'expired') return ( Expired ) return ( Pending ) } return (

Admin

Manage user invitations

{/* Create invite */} Invite user Send an invite link to a new user. Links expire after 7 days.
setEmail(e.target.value)} className="bg-secondary/50" />
{error && (

{error}

)} {newToken && successEmail && (

Invite created for {successEmail}

{inviteUrl(newToken)}
)}
{/* Invites list */} Invitations All invite links, newest first {isLoading ? (
) : invites.length === 0 ? (

No invitations yet

) : (
{invites.map((invite) => { const status = inviteStatus(invite) return (

{invite.email}

{statusBadge(invite)} {new Date(invite.created_at).toLocaleDateString()}
{status === 'pending' && ( )}
) })}
)}
) }