"use client" import * as React from "react" import { IconCopy, IconTrash, IconPlus } from "@tabler/icons-react" import { toast } from "sonner" import { getOrgInvites, revokeInvite } from "@/app/actions/invites" import { Button } from "@/components/ui/button" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Badge } from "@/components/ui/badge" import { cn } from "@/lib/utils" import { CreateInviteDialog } from "./create-invite-dialog" type InviteRow = { readonly id: string readonly code: string readonly role: string readonly maxUses: number | null readonly useCount: number readonly expiresAt: string | null readonly isActive: boolean readonly createdAt: string readonly createdByName: string | null } function isExpired(expiresAt: string | null): boolean { if (!expiresAt) return false return new Date(expiresAt) < new Date() } function isExhausted(invite: InviteRow): boolean { return invite.maxUses !== null && invite.useCount >= invite.maxUses } function formatExpiry(expiresAt: string | null): string { if (!expiresAt) return "Never" const date = new Date(expiresAt) return date.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit", }) } export function InviteLinksSection() { const [invites, setInvites] = React.useState([]) const [loading, setLoading] = React.useState(true) const [createOpen, setCreateOpen] = React.useState(false) const loadInvites = React.useCallback(async () => { try { const result = await getOrgInvites() if (result.success && result.data) { setInvites(result.data as InviteRow[]) } } catch (error) { console.error("Failed to load invites:", error) } finally { setLoading(false) } }, []) React.useEffect(() => { loadInvites() }, [loadInvites]) const handleCopyLink = (code: string) => { const url = `${window.location.origin}/join/${code}` navigator.clipboard.writeText(url) toast.success("Invite link copied") } const handleRevoke = async (inviteId: string) => { const result = await revokeInvite(inviteId) if (result.success) { toast.success("Invite revoked") await loadInvites() } else { toast.error(result.error ?? "Failed to revoke invite") } } const handleCreated = () => { loadInvites() } if (loading) { return (
Loading...
) } return (

Invite Links

Shareable links that let anyone join your organization

{invites.length === 0 ? (
No invite links yet
) : (
Code Role Uses Expires Created by {invites.map((invite) => { const expired = isExpired(invite.expiresAt) const exhausted = isExhausted(invite) const dimmed = !invite.isActive || expired || exhausted return ( {invite.code} {invite.role} {invite.useCount} / {invite.maxUses ?? "∞"} {expired ? ( Expired ) : ( formatExpiry(invite.expiresAt) )} {invite.createdByName ?? "Unknown"}
{!dimmed && ( )} {invite.isActive && !expired && !exhausted && ( )}
) })}
)}
) }