"use client"; import { useState } from "react"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; 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 { Copy, Trash2 } from "lucide-react"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; type Invite = { id: string; token: string; email: string | null; role: "user" | "moderator" | "admin"; tier: "free" | "pro" | "family"; createdAt: string; expiresAt: string | null; }; export function InvitesManager({ invites, appUrl }: { invites: Invite[]; appUrl: string }) { const router = useRouter(); const [email, setEmail] = useState(""); const [role, setRole] = useState<"user" | "moderator" | "admin">("user"); const [tier, setTier] = useState<"free" | "pro" | "family">("free"); const [creating, setCreating] = useState(false); const [revokeId, setRevokeId] = useState(null); async function handleCreate() { setCreating(true); try { const res = await fetch("/api/v1/admin/invites", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: email || undefined, role, tier }), }); if (!res.ok) throw new Error("Failed to create invite"); setEmail(""); toast.success("Invite created"); router.refresh(); } catch { toast.error("Failed to create invite"); } finally { setCreating(false); } } async function handleRevoke(id: string) { try { const res = await fetch(`/api/v1/admin/invites/${id}`, { method: "DELETE" }); if (!res.ok) throw new Error("Failed to revoke"); toast.success("Invite revoked"); router.refresh(); } catch { toast.error("Failed to revoke invite"); } } function copyLink(token: string) { const url = `${appUrl}/signup?invite=${token}`; void navigator.clipboard.writeText(url); toast.success("Link copied"); } return (

New invite

setEmail(e.target.value)} />
{invites.length === 0 && ( )} {invites.map((invite) => ( ))}
Email Role Tier Expires
No active invites.
{invite.email ?? Anyone} {invite.role} {invite.tier} {invite.expiresAt ? new Date(invite.expiresAt).toLocaleDateString() : "Never"}
!open && setRevokeId(null)}> Revoke invite? The link will stop working. Cancel { if (revokeId) void handleRevoke(revokeId); setRevokeId(null); }} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > Revoke
); }