"use client"; import { useState, useTransition } from "react"; import { Copy, Link2, Plus, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { createInviteCode, createBulkInviteCodes, deleteInviteCode } from "../actions"; type InviteUser = { id: string; name: string | null; email: string | null; createdAt: string; }; type InviteCode = { id: string; code: string; maxUses: number; uses: number; expiresAt: string | null; createdAt: string; creator: { name: string | null }; usedBy: InviteUser[]; }; export function InviteManager({ inviteCodes, appUrl, }: { inviteCodes: InviteCode[]; appUrl: string; }) { const [maxUses, setMaxUses] = useState(1); const [expiresInDays, setExpiresInDays] = useState(7); const [noExpiry, setNoExpiry] = useState(false); const [bulkCount, setBulkCount] = useState(5); const [isPending, startTransition] = useTransition(); const [copiedId, setCopiedId] = useState(null); const [copiedType, setCopiedType] = useState<"code" | "link" | null>(null); function handleCreate() { startTransition(async () => { await createInviteCode({ maxUses, expiresInDays: noExpiry ? null : expiresInDays, }); }); } function handleBulkCreate() { startTransition(async () => { await createBulkInviteCodes({ count: bulkCount, maxUses, expiresInDays: noExpiry ? null : expiresInDays, }); }); } function handleDelete(id: string) { startTransition(async () => { await deleteInviteCode(id); }); } function copyToClipboard(text: string, id: string, type: "code" | "link") { navigator.clipboard.writeText(text); setCopiedId(id); setCopiedType(type); setTimeout(() => { setCopiedId(null); setCopiedType(null); }, 2000); } function getStatus(invite: InviteCode): "active" | "used" | "expired" { if (invite.uses >= invite.maxUses) return "used"; if (invite.expiresAt && new Date(invite.expiresAt) < new Date()) return "expired"; return "active"; } function formatRelativeDate(dateStr: string) { const date = new Date(dateStr); const now = new Date(); const diffMs = date.getTime() - now.getTime(); const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24)); if (diffDays < 0) return "Expired"; if (diffDays === 0) return "Today"; if (diffDays === 1) return "Tomorrow"; return `${diffDays} days`; } const activeCount = inviteCodes.filter((i) => getStatus(i) === "active").length; const usedCount = inviteCodes.filter((i) => getStatus(i) === "used").length; return (
{/* Create Card */} Generate Invite Codes Create single or bulk invite codes to share with new users
setMaxUses(Number(e.target.value))} className="w-24" />
setExpiresInDays(Number(e.target.value))} disabled={noExpiry} className="w-24" />
setBulkCount(Number(e.target.value))} className="w-20" />
{/* Codes Table */} Invite Codes {inviteCodes.length} total · {activeCount} active · {usedCount} fully used {inviteCodes.length === 0 ? (

No invite codes yet. Create one above.

) : ( Code Status Uses Redeemed By Expires Created Actions {inviteCodes.map((invite) => { const status = getStatus(invite); const isCopiedCode = copiedId === invite.id && copiedType === "code"; const isCopiedLink = copiedId === invite.id && copiedType === "link"; return ( {invite.code} {status} {invite.uses} / {invite.maxUses} {invite.usedBy.length === 0 ? ( -- ) : (
{invite.usedBy.map((user) => (
{user.name ?? user.email ?? "Unknown"}
{user.email &&
{user.email}
}
Joined{" "} {new Date(user.createdAt).toLocaleDateString()}
))}
)}
{invite.expiresAt ? ( {formatRelativeDate(invite.expiresAt)} {new Date(invite.expiresAt).toLocaleString()} ) : ( Never )} {new Date(invite.createdAt).toLocaleDateString()} by {invite.creator.name ?? "Unknown"}
Copy code Copy registration link Delete code Delete invite code? This will permanently delete the invite code{" "} {invite.code} .{" "} {status === "active" && "Anyone with this code will no longer be able to register."} Cancel handleDelete(invite.id)} > Delete
); })}
)}
); }