import type { Currency } from './shared'; import { useEffect, useState } from 'react'; import { Loader2, Send } from 'lucide-react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { useDocumentAction, useSummary } from './useInvoiceShelfData'; // The one dialog in /invoices that causes something irreversible to leave the building: `send` hands the // document to InvoiceShelf's mailer, which posts it to the customer. There is no unsend. // // So it is deliberately not a menu item that fires on click. The recipient is shown, editable, and // pre-filled from the customer record rather than assumed; the button says who it is emailing; and nothing // else in this app calls the send action. Everything that *looks* like sending but isn't — "mark as sent", // which only moves the status — goes through a plain confirm instead, and says so. export type SendTarget = { /** Sidecar resource slug: invoices | estimates | payments. */ resource: string; id: number; /** Shown in the title, e.g. "000145". */ number: string; customerName: string; customerEmail: string | null; /** Whether this is a first send or a resend, purely for wording. */ resend?: boolean; }; const DEFAULT_BODY = [ '

Hi {CUSTOMER_NAME},

', '

Please find the attached document.

', '

Thanks,
{COMPANY_NAME}

', ].join('\n'); const KIND_LABEL: Record = { invoices: 'invoice', estimates: 'estimate', payments: 'payment receipt', }; export const SendDocumentDialog = ({ target, currency: _currency, onClose, }: { target: SendTarget | null; currency?: Currency | null; onClose: () => void; }) => { const { company, summary } = useSummary(); const action = useDocumentAction(target?.resource ?? 'invoices'); const kind = KIND_LABEL[target?.resource ?? ''] ?? 'document'; const [to, setTo] = useState(''); const [from, setFrom] = useState(''); const [subject, setSubject] = useState(''); const [body, setBody] = useState(''); // Re-seed whenever a different document is opened. Keyed on id so reopening the same one keeps nothing // stale from a previous edit of another document's mail. useEffect(() => { if (!target) return; setTo(target.customerEmail ?? ''); setFrom(summary?.me?.email ?? ''); setSubject(`${company?.name ?? 'Invoice'} — ${kind} ${target.number}`); setBody( DEFAULT_BODY.replace('{CUSTOMER_NAME}', target.customerName || 'there').replace( '{COMPANY_NAME}', company?.name ?? '', ), ); }, [target?.id, target?.resource, company?.name, summary?.me?.email, kind, target]); if (!target) return null; const missingRecipient = !to.trim(); const submit = (ev?: React.FormEvent) => { ev?.preventDefault(); action.mutate({ id: target.id, action: 'send', payload: { subject, body, from, to } }, { onSuccess: onClose }); }; return ( !open && onClose()}> {/* `contents` keeps the form invisible to DialogContent's grid layout — it still owns the gap between header, body and footer, the form just carries Enter-to-submit across all three. */}
{target.resend ? 'Resend' : 'Send'} {kind} {target.number} This emails {target.customerName} through InvoiceShelf. It cannot be undone.
setTo(e.target.value)} className="h-8 text-xs" type="email" />
setFrom(e.target.value)} className="h-8 text-xs" type="email" />
setSubject(e.target.value)} className="h-8 text-xs" />