'use client' import { useState } from 'react' import { toast } from 'sonner' import { Ticket } from 'lucide-react' import { createInvite, updateInvite, deleteInvite } from '@/lib/actions' import { Card } from './card' import { useConfirm } from '@/components/ui/confirm' import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/components/ui/empty' import type { Invite } from './types' import { INVITE_ROLES, canAssignInviteRole } from '@/lib/invite-logic' export function InvitesPanel({ isAdmin, invites: initial, creatorRole, spaceSlug, }: { isAdmin: boolean invites: Invite[] creatorRole: string spaceSlug: string }) { const confirm = useConfirm() const [invites, setInvites] = useState(initial) const [showNew, setShowNew] = useState(false) const [d, setD] = useState({ code: '', label: '', expires_at: '', max_uses: '', role: 'member', singleUse: false }) const assignableRoles = INVITE_ROLES.filter(r => canAssignInviteRole(creatorRole, r)) function inviteLink(code: string) { const origin = typeof window !== 'undefined' ? window.location.origin : '' return `${origin}/join/${spaceSlug}?code=${encodeURIComponent(code)}` } return ( setShowNew(v => !v)} className="font-mono text-[10px] border border-border px-3 py-1.5 rounded hover:border-primary hover:text-primary transition whitespace-nowrap">{showNew ? 'Cancel' : '+ New invite'} : undefined} > {showNew && (
{ e.preventDefault() const maxUses = d.singleUse ? 1 : (d.max_uses ? Number(d.max_uses) : undefined) const result = await createInvite({ code: d.code.trim().toUpperCase() || undefined, label: d.label.trim() || undefined, expires_at: d.expires_at || undefined, max_uses: maxUses, role: d.role as 'admin' | 'board' | 'treasurer' | 'member' | 'associate' }) if ('error' in result && result.error) { toast.error(result.error); return } const c = result as { id: string; code: string } toast.success(`Invite created: ${c.code}`) setInvites(prev => [{ id: c.id, code: c.code, label: d.label.trim() || null, expires_at: d.expires_at || null, max_uses: maxUses ?? null, uses_count: 0, is_enabled: true, role: d.role, created_at: new Date().toISOString() }, ...prev]) setShowNew(false); setD({ code: '', label: '', expires_at: '', max_uses: '', role: 'member', singleUse: false }) }} className="mb-4 p-4 border border-border rounded bg-background grid grid-cols-1 md:grid-cols-2 gap-3" > setD({ ...d, code: e.target.value.toUpperCase().replace(/[^A-Z0-9-]/g, '') })} placeholder="CODE (blank = auto)" className="bg-background border border-border text-foreground font-mono text-xs rounded px-2 py-1.5 focus:outline-none focus:border-primary" /> setD({ ...d, label: e.target.value })} placeholder="Label (optional)" className="bg-background border border-border text-foreground font-sans text-sm rounded px-2 py-1.5 focus:outline-none focus:border-primary" /> setD({ ...d, expires_at: e.target.value })} className="bg-background border border-border text-foreground font-mono text-xs rounded px-2 py-1.5 focus:outline-none focus:border-primary" /> setD({ ...d, max_uses: e.target.value })} placeholder="Max uses (blank = unlimited)" className="bg-background border border-border text-foreground font-mono text-xs rounded px-2 py-1.5 focus:outline-none focus:border-primary disabled:opacity-50" />
)} {invites.length === 0 && ( No invite codes yet Create a code to let new members join, with an optional expiry or use cap. )}
) }