'use client'; import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Mail, Phone, UserCheck, UserPlus } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { usePermissions } from '@/hooks/use-permissions'; import { apiFetch } from '@/lib/api/client'; import { toastError } from '@/lib/api/toast-error'; type ProxyEntityType = 'client' | 'interest' | 'yacht'; interface Proxy { id: string; name: string; email: string | null; phone: string | null; relationship: string | null; notes: string | null; } const RESOURCE: Record = { client: 'clients', interest: 'interests', yacht: 'yachts', }; /** * CM-9: point-of-contact ("proxy") panel for a client / interest / yacht detail * page. Reads + edits the per-entity proxy via the entity's sub-resource route. */ export function ProxyCard({ entityType, entityId, }: { entityType: ProxyEntityType; entityId: string; }) { const { can } = usePermissions(); const canManage = can(RESOURCE[entityType], 'edit'); const qc = useQueryClient(); const base = `/api/v1/${RESOURCE[entityType]}/${entityId}/proxy`; const queryKey = ['proxy', entityType, entityId]; const { data } = useQuery<{ data: Proxy | null }>({ queryKey, queryFn: () => apiFetch(base), }); const proxy = data?.data ?? null; const [open, setOpen] = useState(false); const remove = useMutation({ mutationFn: () => apiFetch(base, { method: 'DELETE' }), onSuccess: () => { toast.success('Point of contact removed'); qc.invalidateQueries({ queryKey }); }, onError: (err) => toastError(err), }); return (

Point of contact

{canManage ? ( ) : null}
{proxy ? (

{proxy.name} {proxy.relationship ? ( {proxy.relationship} ) : null}

{proxy.email ? ( {proxy.email} ) : null} {proxy.phone ? (

{proxy.phone}

) : null} {proxy.notes ?

{proxy.notes}

: null} {canManage ? ( ) : null}
) : (

No proxy set — comms go to the {entityType} directly.

)} {open ? ( qc.invalidateQueries({ queryKey })} /> ) : null}
); } function ProxyDialog({ open, onOpenChange, base, existing, entityType, onSaved, }: { open: boolean; onOpenChange: (v: boolean) => void; base: string; existing: Proxy | null; entityType: ProxyEntityType; onSaved: () => void; }) { const [name, setName] = useState(existing?.name ?? ''); const [email, setEmail] = useState(existing?.email ?? ''); const [phone, setPhone] = useState(existing?.phone ?? ''); const [relationship, setRelationship] = useState(existing?.relationship ?? ''); const [notes, setNotes] = useState(existing?.notes ?? ''); // State seeds from `existing` at mount; the dialog is remounted on each open // (the parent renders it conditionally), so no reseed effect is needed. const save = useMutation({ mutationFn: () => apiFetch(base, { method: 'PUT', body: { name: name.trim(), email, phone, relationship, notes }, }), onSuccess: () => { toast.success('Point of contact saved'); onSaved(); onOpenChange(false); }, onError: (err) => toastError(err), }); return ( Point of contact A person who acts as the point of contact for this {entityType}. Used to address outbound comms.
setName(e.target.value)} autoFocus />
setEmail(e.target.value)} />
setPhone(e.target.value)} />
setRelationship(e.target.value)} />
setNotes(e.target.value)} />
); }