'use client'; import { useState } from 'react'; import { useTranslations } from 'next-intl'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import type { Identity, EmailAddress } from '@/lib/jmap/types'; import { sanitizeSignatureHtml } from '@/lib/email-sanitization'; import { getEmailValidationError, validateEmailList } from '@/lib/validation'; interface IdentityFormData { name: string; email: string; replyTo?: EmailAddress[]; bcc?: EmailAddress[]; textSignature?: string; htmlSignature?: string; } interface IdentityFormProps { identity?: Identity; onSave: (data: IdentityFormData) => Promise; onCancel: () => void; } export function IdentityForm({ identity, onSave, onCancel }: IdentityFormProps) { const t = useTranslations('identities.form'); const tValidation = useTranslations('identities.validation_errors'); const tDisplay = useTranslations('identities.display'); const isEditing = !!identity; const [formData, setFormData] = useState({ name: identity?.name || '', email: identity?.email || '', replyTo: identity?.replyTo, bcc: identity?.bcc, textSignature: identity?.textSignature || '', htmlSignature: identity?.htmlSignature || '', }); const [replyToInput, setReplyToInput] = useState( identity?.replyTo?.map(a => a.email).join(', ') || '' ); const [bccInput, setBccInput] = useState( identity?.bcc?.map(a => a.email).join(', ') || '' ); const [isSubmitting, setIsSubmitting] = useState(false); const [errors, setErrors] = useState>({}); const parseEmailList = (input: string): EmailAddress[] | undefined => { if (!input.trim()) return undefined; const emails = input.split(',').map(e => e.trim()).filter(Boolean); return emails.map(email => ({ email })); }; const validate = (): boolean => { const newErrors: Record = {}; if (!formData.name.trim()) { newErrors.name = t('name_required'); } // Use secure email validation const emailError = getEmailValidationError(formData.email); if (emailError) { newErrors.email = emailError; } // Validate reply-to email list if (replyToInput.trim()) { const validation = validateEmailList(replyToInput); if (!validation.valid) { newErrors.replyTo = tValidation('invalid_emails', { emails: validation.invalidEmails.join(', ') }); } } // Validate bcc email list if (bccInput.trim()) { const validation = validateEmailList(bccInput); if (!validation.valid) { newErrors.bcc = tValidation('invalid_emails', { emails: validation.invalidEmails.join(', ') }); } } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validate()) return; setIsSubmitting(true); try { // Sanitize HTML signature before sending to server const sanitizedData: IdentityFormData = { ...formData, replyTo: parseEmailList(replyToInput), bcc: parseEmailList(bccInput), htmlSignature: formData.htmlSignature ? sanitizeSignatureHtml(formData.htmlSignature) : undefined, }; await onSave(sanitizedData); } finally { setIsSubmitting(false); } }; return (
{/* Name */}
setFormData({ ...formData, name: e.target.value })} placeholder={t('name_placeholder')} disabled={isSubmitting} className={errors.name ? 'border-destructive' : ''} aria-describedby={errors.name ? 'name-error' : undefined} aria-invalid={!!errors.name} /> {errors.name && ( )}
{/* Email */}
setFormData({ ...formData, email: e.target.value })} placeholder={t('email_placeholder')} disabled={isSubmitting || isEditing} className={errors.email ? 'border-destructive' : ''} aria-describedby={errors.email ? 'email-error' : undefined} aria-invalid={!!errors.email} /> {isEditing && (

{t('email_immutable')}

)} {errors.email && ( )}
{/* Reply-To */}
setReplyToInput(e.target.value)} placeholder={t('reply_to_placeholder')} disabled={isSubmitting} className={errors.replyTo ? 'border-destructive' : ''} aria-describedby={errors.replyTo ? 'reply-to-error' : undefined} aria-invalid={!!errors.replyTo} /> {errors.replyTo && ( )}
{/* BCC */}
setBccInput(e.target.value)} placeholder={t('bcc_placeholder')} disabled={isSubmitting} className={errors.bcc ? 'border-destructive' : ''} aria-describedby={errors.bcc ? 'bcc-error' : undefined} aria-invalid={!!errors.bcc} /> {errors.bcc && ( )}
{/* Text Signature */}