'use client' import { useEffect, useState, useCallback } from 'react' import { toast } from 'sonner' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { useConfirm } from '@/components/ui/confirm' import { listMemberCards, addMemberCard, updateMemberCard, deleteMemberCard, } from '@/lib/actions' type Card = { id: string card_uid: string card_type: string label: string | null is_active: boolean created_at: string } export function MemberCardsDialog({ member, onClose, }: { member: { id: string; display_name: string | null } | null onClose: () => void }) { const confirm = useConfirm() const open = !!member const [loading, setLoading] = useState(false) const [cards, setCards] = useState([]) const [uid, setUid] = useState('') const [type, setType] = useState<'rfid' | 'nfc'>('rfid') const [label, setLabel] = useState('') const [busy, setBusy] = useState(false) const load = useCallback(async (memberId: string) => { setLoading(true) const res = await listMemberCards({ memberId }) setLoading(false) if ('error' in res && res.error) { toast.error(res.error) return } setCards(((res as { data: Card[] }).data) ?? []) }, []) useEffect(() => { if (member) { setUid('') setType('rfid') setLabel('') load(member.id) } }, [member, load]) async function onAdd(e: React.FormEvent) { e.preventDefault() if (!member) return if (!uid.trim()) { toast.error('Card UID is required') return } setBusy(true) const res = await addMemberCard({ memberId: member.id, card_uid: uid.trim(), card_type: type, label: label.trim() || null, }) setBusy(false) if ('error' in res && res.error) { toast.error(res.error) return } toast.success('Card added') setUid('') setLabel('') load(member.id) } async function onToggle(c: Card) { const res = await updateMemberCard({ cardId: c.id, is_active: !c.is_active }) if ('error' in res && res.error) { toast.error(res.error) return } setCards(prev => prev.map(x => (x.id === c.id ? { ...x, is_active: !x.is_active } : x))) } async function onDelete(c: Card) { const ok = await confirm({ title: 'Delete card', description: 'This removes the card association. If a controller is connected, revoke it there too.', confirmText: 'Delete', destructive: true, }) if (!ok || !member) return const res = await deleteMemberCard({ cardId: c.id }) if ('error' in res && res.error) { toast.error(res.error) return } setCards(prev => prev.filter(x => x.id !== c.id)) toast.success('Deleted') } return ( { if (!o) onClose() }}> Access cards — {member?.display_name ?? 'Member'}

Add a card

setUid(e.target.value)} placeholder="Card UID (the number from the card/reader)" className="w-full bg-background border border-border text-foreground font-mono text-sm rounded px-2 py-2 focus:outline-none focus:border-primary" />
setLabel(e.target.value)} placeholder="Label (optional)" className="bg-background border border-border text-foreground font-sans text-sm rounded px-2 py-2 focus:outline-none focus:border-primary" />
{loading ? (

Loading…

) : cards.length === 0 ? (

No cards registered.

) : (
    {cards.map(c => (
  • {c.card_uid} {c.card_type} {c.label && {c.label}} {!c.is_active && inactive}
  • ))}
)}
) }