import type { Contact } from '@nylas-labs/cli-kit/v3' import { Plus, X } from 'lucide-react' import { useEffect, useRef, useState } from 'react' import { Dialog, DialogContent, DialogTitle } from '#shared/components/ui/dialog' import { Input } from '#shared/components/ui/input' import { Textarea } from '#shared/components/ui/textarea' import { cn } from '#shared/lib/utils' import { type ContactForm, type ContactFormValidation, contactFormsEqual, contactToForm, emptyContactForm, formToFields, removeAt, replaceAt, validateContactForm, } from '../lib/contacts-model.js' import { useCreateContactMutation, useUpdateContactMutation } from '../state/contacts-state.js' const FIELD_TYPES = ['', 'work', 'home', 'other'] as const export const CONTACT_DIALOG_PANEL_CLASS = 'flex flex-col overflow-hidden bg-card sm:max-h-[85vh] sm:max-w-lg' /** Create (contact = null) or edit a contact. Resolves with whether it changed. */ export function ContactModal({ contact, onClose, }: { contact: Contact | null onClose: (changed: boolean, contactId?: string) => void }) { const [initialForm] = useState(() => (contact ? contactToForm(contact) : emptyContactForm())) const [form, setForm] = useState(initialForm) const [busy, setBusy] = useState(false) const [confirmingDiscard, setConfirmingDiscard] = useState(false) const [error, setError] = useState(null) const [validation, setValidation] = useState(null) const busyRef = useRef(false) const closedRef = useRef(false) const restoreFocusTargetRef = useRef(null) const cancelButtonRef = useRef(null) const continueEditingButtonRef = useRef(null) const givenNameRef = useRef(null) const emailRefs = useRef>([]) const createMutation = useCreateContactMutation() const updateMutation = useUpdateContactMutation(contact) const dirty = !contactFormsEqual(form, initialForm) useEffect(() => { if (confirmingDiscard) { continueEditingButtonRef.current?.focus() } else if (restoreFocusTargetRef.current) { const target = restoreFocusTargetRef.current restoreFocusTargetRef.current = null target.focus() } }, [confirmingDiscard]) function finishClose(changed: boolean, contactId?: string) { if (closedRef.current) return closedRef.current = true if (contactId === undefined) onClose(changed) else onClose(changed, contactId) } function requestClose(focusTarget?: HTMLElement) { if (busyRef.current || closedRef.current) return if (dirty) { const activeElement = document.activeElement as HTMLElement restoreFocusTargetRef.current = focusTarget ?? activeElement setConfirmingDiscard(true) return } finishClose(false) } function continueEditing() { setConfirmingDiscard(false) } function patch(next: Partial) { setError(null) setForm((current) => ({ ...current, ...next })) } function clearIdentityValidation() { setValidation((current) => (current?.field === 'identity' ? null : current)) } function clearEmailValidation(index: number) { setValidation((current) => current?.field === 'identity' || (current?.field === 'email' && current.index === index) ? null : current, ) } async function save() { /* v8 ignore next -- native disabled state prevents repeated UI submission; the ref closes same-batch gaps -- @preserve */ if (busyRef.current || closedRef.current) return setError(null) const nextValidation = validateContactForm(form) if (nextValidation) { setValidation(nextValidation) if (nextValidation.field === 'identity') givenNameRef.current?.focus() else emailRefs.current[nextValidation.index]?.focus() return } setValidation(null) busyRef.current = true setBusy(true) try { const fields = formToFields(form) if (contact) { await updateMutation.mutateAsync(fields) finishClose(true, contact.id) } else { const created = await createMutation.mutateAsync(fields) finishClose(true, created.contactId) } } catch { setError('Could not save contact. Check your connection, then try again.') busyRef.current = false setBusy(false) } } return ( { /* v8 ignore next -- this controlled, always-open dialog only emits false dismissal requests -- @preserve */ if (next) return if (confirmingDiscard) { continueEditing() return } requestClose() }} > event.preventDefault()} onBackdropClick={confirmingDiscard ? continueEditing : () => requestClose()} > {confirmingDiscard ? ( finishClose(false)} /> ) : null}