"use client"; import { ArrowLeft, Building, Eye, EyeOff, FileText, Globe, Info, Key, Loader2, Mail, Save, Star, User, } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { AddressForm } from "~/components/forms/address-form"; import { FloatingActionBar } from "~/components/layout/floating-action-bar"; import { PageHeader } from "~/components/layout/page-header"; import { Button } from "~/components/ui/button"; import { Alert, AlertDescription } from "~/components/ui/alert"; import { Badge } from "~/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Input } from "~/components/ui/input"; import { Label } from "~/components/ui/label"; import { Skeleton } from "~/components/ui/skeleton"; import { Switch } from "~/components/ui/switch"; import { formatPhoneNumber, formatTaxId, formatWebsiteUrl, isValidEmail, PLACEHOLDERS, VALIDATION_MESSAGES, } from "~/lib/form-constants"; import { api } from "~/trpc/react"; interface BusinessFormProps { businessId?: string; mode: "create" | "edit"; } interface FormData { name: string; email: string; phone: string; addressLine1: string; addressLine2: string; city: string; state: string; postalCode: string; country: string; website: string; taxId: string; isDefault: boolean; resendApiKey: string; resendDomain: string; emailFromName: string; } interface FormErrors { name?: string; email?: string; phone?: string; addressLine1?: string; city?: string; state?: string; postalCode?: string; country?: string; website?: string; taxId?: string; resendApiKey?: string; resendDomain?: string; emailFromName?: string; } const initialFormData: FormData = { name: "", email: "", phone: "", addressLine1: "", addressLine2: "", city: "", state: "", postalCode: "", country: "United States", website: "", taxId: "", isDefault: false, resendApiKey: "", resendDomain: "", emailFromName: "", }; export function BusinessForm({ businessId, mode }: BusinessFormProps) { const router = useRouter(); const [formData, setFormData] = useState(initialFormData); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const [showApiKey, setShowApiKey] = useState(false); const [isDirty, setIsDirty] = useState(false); // Fetch business data if editing const { data: business, isLoading: isLoadingBusiness } = api.businesses.getById.useQuery( { id: businessId! }, { enabled: mode === "edit" && !!businessId }, ); // Fetch email configuration if editing const { data: emailConfig, isLoading: isLoadingEmailConfig } = api.businesses.getEmailConfig.useQuery( { id: businessId! }, { enabled: mode === "edit" && !!businessId }, ); // Update email configuration mutation const updateEmailConfig = api.businesses.updateEmailConfig.useMutation({ onSuccess: () => { toast.success("Email configuration updated successfully"); }, onError: (error) => { toast.error(`Failed to update email configuration: ${error.message}`); }, }); const createBusiness = api.businesses.create.useMutation({ onSuccess: () => { toast.success("Business created successfully"); router.push("/dashboard/businesses"); }, onError: (error) => { toast.error(error.message || "Failed to create business"); }, }); const updateBusiness = api.businesses.update.useMutation({ onSuccess: () => { toast.success("Business updated successfully"); router.push("/dashboard/businesses"); }, onError: (error) => { toast.error(error.message || "Failed to update business"); }, }); // Load business data when editing useEffect(() => { if (business && mode === "edit") { setFormData({ name: business.name, email: business.email ?? "", phone: business.phone ?? "", addressLine1: business.addressLine1 ?? "", addressLine2: business.addressLine2 ?? "", city: business.city ?? "", state: business.state ?? "", postalCode: business.postalCode ?? "", country: business.country ?? "United States", website: business.website ?? "", taxId: business.taxId ?? "", isDefault: business.isDefault ?? false, resendApiKey: "", // Never pre-fill API key for security resendDomain: emailConfig?.resendDomain ?? "", emailFromName: emailConfig?.emailFromName ?? "", }); } }, [business, emailConfig, mode]); const handleInputChange = (field: string, value: string | boolean) => { setFormData((prev) => ({ ...prev, [field]: value })); setIsDirty(true); // Clear error for this field when user starts typing if (errors[field as keyof FormErrors]) { setErrors((prev) => ({ ...prev, [field]: undefined })); } }; const handlePhoneChange = (value: string) => { const formatted = formatPhoneNumber(value); handleInputChange("phone", formatted); }; const handleTaxIdChange = (value: string) => { const formatted = formatTaxId(value, "EIN"); handleInputChange("taxId", formatted); }; const validateForm = (): boolean => { const newErrors: FormErrors = {}; // Required fields if (!formData.name.trim()) { newErrors.name = VALIDATION_MESSAGES.required; } // Email validation if (formData.email && !isValidEmail(formData.email)) { newErrors.email = VALIDATION_MESSAGES.email; } // Phone validation (basic check for US format) if (formData.phone) { const phoneDigits = formData.phone.replace(/\D/g, ""); if (phoneDigits.length > 0 && phoneDigits.length < 10) { newErrors.phone = VALIDATION_MESSAGES.phone; } } // Address validation if any address field is filled const hasAddressData = formData.addressLine1 || formData.city || formData.state || formData.postalCode; if (hasAddressData) { if (!formData.addressLine1) newErrors.addressLine1 = VALIDATION_MESSAGES.required; if (!formData.city) newErrors.city = VALIDATION_MESSAGES.required; if (!formData.country) newErrors.country = VALIDATION_MESSAGES.required; if (formData.country === "United States") { if (!formData.state) newErrors.state = VALIDATION_MESSAGES.required; if (!formData.postalCode) newErrors.postalCode = VALIDATION_MESSAGES.required; } } // Email configuration validation // API Key validation if (formData.resendApiKey && !formData.resendApiKey.startsWith("re_")) { newErrors.resendApiKey = "Resend API key should start with 're_'"; } // Domain validation if (formData.resendDomain) { const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?\.([a-zA-Z]{2,})+$/; if (!domainRegex.test(formData.resendDomain)) { newErrors.resendDomain = "Please enter a valid domain (e.g., yourdomain.com)"; } } // If API key is provided, domain must also be provided if (formData.resendApiKey && !formData.resendDomain) { newErrors.resendDomain = "Domain is required when API key is provided"; } // If domain is provided, API key must also be provided if ( formData.resendDomain && !formData.resendApiKey && !emailConfig?.hasApiKey ) { newErrors.resendApiKey = "API key is required when domain is provided"; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!validateForm()) { toast.error("Please correct the errors in the form"); return; } setIsSubmitting(true); try { // Format website URL before submission const dataToSubmit = { ...formData, website: formData.website ? formatWebsiteUrl(formData.website) : "", }; if (mode === "create") { // Create business data (excluding email config fields) const businessData = { name: dataToSubmit.name, email: dataToSubmit.email, phone: dataToSubmit.phone, addressLine1: dataToSubmit.addressLine1, addressLine2: dataToSubmit.addressLine2, city: dataToSubmit.city, state: dataToSubmit.state, postalCode: dataToSubmit.postalCode, country: dataToSubmit.country, website: dataToSubmit.website, taxId: dataToSubmit.taxId, isDefault: dataToSubmit.isDefault, }; const newBusiness = await createBusiness.mutateAsync(businessData); // Update email configuration separately if any email fields are provided if ( newBusiness && (formData.resendApiKey || formData.resendDomain || formData.emailFromName) ) { await updateEmailConfig.mutateAsync({ id: newBusiness.id, resendApiKey: formData.resendApiKey || undefined, resendDomain: formData.resendDomain || undefined, emailFromName: formData.emailFromName || undefined, }); } } else { // Update business data (excluding email config fields) const businessData = { name: dataToSubmit.name, email: dataToSubmit.email, phone: dataToSubmit.phone, addressLine1: dataToSubmit.addressLine1, addressLine2: dataToSubmit.addressLine2, city: dataToSubmit.city, state: dataToSubmit.state, postalCode: dataToSubmit.postalCode, country: dataToSubmit.country, website: dataToSubmit.website, taxId: dataToSubmit.taxId, isDefault: dataToSubmit.isDefault, }; await updateBusiness.mutateAsync({ id: businessId!, ...businessData, }); // Update email configuration separately if any email fields are provided if ( formData.resendApiKey || formData.resendDomain || formData.emailFromName ) { await updateEmailConfig.mutateAsync({ id: businessId!, resendApiKey: formData.resendApiKey || undefined, resendDomain: formData.resendDomain || undefined, emailFromName: formData.emailFromName || undefined, }); } } } finally { setIsSubmitting(false); } }; const handleCancel = () => { if (isDirty) { const confirmed = window.confirm( "You have unsaved changes. Are you sure you want to leave?", ); if (!confirmed) return; } router.push("/dashboard/businesses"); }; if ( (mode === "edit" && isLoadingBusiness) || (mode === "edit" && isLoadingEmailConfig) ) { return (
); } return ( <>
{/* Main Form Container - styled like data table */}
{/* Basic Information */}
Basic Information

Enter your business details

handleInputChange("name", e.target.value) } placeholder={PLACEHOLDERS.name} className={`${errors.name ? "border-destructive" : ""}`} disabled={isSubmitting} /> {errors.name && (

{errors.name}

)}
handleTaxIdChange(e.target.value)} placeholder={PLACEHOLDERS.taxId} className={`${errors.taxId ? "border-destructive" : ""}`} disabled={isSubmitting} maxLength={10} /> {errors.taxId && (

{errors.taxId}

)}
handleInputChange("email", e.target.value) } placeholder={PLACEHOLDERS.email} className={`${errors.email ? "border-destructive" : ""}`} disabled={isSubmitting} /> {errors.email && (

{errors.email}

)}
handlePhoneChange(e.target.value)} placeholder={PLACEHOLDERS.phone} className={`${errors.phone ? "border-destructive" : ""}`} disabled={isSubmitting} /> {errors.phone && (

{errors.phone}

)}
handleInputChange("website", e.target.value) } placeholder={PLACEHOLDERS.website} className={`${errors.website ? "border-destructive" : ""}`} disabled={isSubmitting} /> {errors.website && (

{errors.website}

)}
{/* Address */}
Business Address

Your business location

{/* Email Configuration */}
Email Configuration

Configure your own Resend API key and domain for sending invoices

{/* Current Status */} {mode === "edit" && (
Current Status: {emailConfig?.hasApiKey && emailConfig?.resendDomain ? ( Custom Configuration Active ) : ( Using System Default )}
{emailConfig?.resendDomain && ( Domain: {emailConfig.resendDomain} )}
)} To use your own email configuration, you'll need to:
  • Create a free account at{" "} resend.com
  • Verify your domain in the Resend dashboard
  • Get your API key from the Resend dashboard
{/* API Key */}
handleInputChange("resendApiKey", e.target.value) } placeholder={ mode === "edit" && emailConfig?.hasApiKey ? "Enter new API key to update" : "re_..." } className={ errors.resendApiKey ? "border-destructive" : "" } />
{errors.resendApiKey && (

{errors.resendApiKey}

)}
{/* Domain */}
handleInputChange("resendDomain", e.target.value) } placeholder="yourdomain.com" className={ errors.resendDomain ? "border-destructive" : "" } /> {errors.resendDomain && (

{errors.resendDomain}

)}

This domain must be verified in your Resend account before emails can be sent.

{/* From Name */}
handleInputChange("emailFromName", e.target.value) } placeholder={formData.name || "Your Business Name"} className={ errors.emailFromName ? "border-destructive" : "" } /> {errors.emailFromName && (

{errors.emailFromName}

)}

This will appear as the sender name in emails. Defaults to your business name.

{/* Settings */}
Settings

Configure business preferences

Set this as your default business for new invoices

handleInputChange("isDefault", checked) } disabled={isSubmitting} />

{mode === "create" ? "Creating a new business" : "Editing business details"}

{mode === "create" ? "Complete the form to create your business" : "Update your business information"}

} >
); }