"use client"; import { UserPlus, Save, Loader2, ArrowLeft, DollarSign, FileText, } from "lucide-react"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { toast } from "sonner"; import { Button } from "~/components/ui/button"; 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 { AddressForm } from "~/components/forms/address-form"; import { FloatingActionBar } from "~/components/layout/floating-action-bar"; import { PageHeader } from "~/components/layout/page-header"; import { NumberInput } from "~/components/ui/number-input"; import { api } from "~/trpc/react"; import { formatPhoneNumber, isValidEmail, VALIDATION_MESSAGES, PLACEHOLDERS, } from "~/lib/form-constants"; interface ClientFormProps { clientId?: string; mode: "create" | "edit"; } interface FormData { name: string; email: string; phone: string; addressLine1: string; addressLine2: string; city: string; state: string; postalCode: string; country: string; defaultHourlyRate: number; } interface FormErrors { name?: string; email?: string; phone?: string; addressLine1?: string; city?: string; state?: string; postalCode?: string; country?: string; defaultHourlyRate?: string; } const initialFormData: FormData = { name: "", email: "", phone: "", addressLine1: "", addressLine2: "", city: "", state: "", postalCode: "", country: "United States", defaultHourlyRate: 100, }; export function ClientForm({ clientId, mode }: ClientFormProps) { const router = useRouter(); const [formData, setFormData] = useState(initialFormData); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const [isDirty, setIsDirty] = useState(false); // Fetch client data if editing const { data: client, isLoading: isLoadingClient } = api.clients.getById.useQuery( { id: clientId! }, { enabled: mode === "edit" && !!clientId }, ); const createClient = api.clients.create.useMutation({ onSuccess: () => { toast.success("Client created successfully"); router.push("/dashboard/clients"); }, onError: (error) => { toast.error(error.message || "Failed to create client"); }, }); const updateClient = api.clients.update.useMutation({ onSuccess: () => { toast.success("Client updated successfully"); router.push("/dashboard/clients"); }, onError: (error) => { toast.error(error.message || "Failed to update client"); }, }); // Load client data when editing useEffect(() => { if (client && mode === "edit") { setFormData({ name: client.name, email: client.email ?? "", phone: client.phone ?? "", addressLine1: client.addressLine1 ?? "", addressLine2: client.addressLine2 ?? "", city: client.city ?? "", state: client.state ?? "", postalCode: client.postalCode ?? "", country: client.country ?? "United States", defaultHourlyRate: client.defaultHourlyRate ?? 100, }); } }, [client, mode]); const handleInputChange = (field: string, value: string | number) => { 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 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 === "US") { if (!formData.state) newErrors.state = VALIDATION_MESSAGES.required; if (!formData.postalCode) newErrors.postalCode = VALIDATION_MESSAGES.required; } } 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 { if (mode === "create") { await createClient.mutateAsync(formData); } else { await updateClient.mutateAsync({ id: clientId!, ...formData, }); } } 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/clients"); }; if (mode === "edit" && isLoadingClient) { return (
); } return ( <>
{/* Main Form Container - styled like data table */}
{/* Basic Information */}
Basic Information

Enter the client's primary details

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

{errors.name}

)}
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}

)}
{/* Address */}
Address

Client's physical location

{/* Billing Information */}
Billing Information

Default billing rates for this client

handleInputChange("defaultHourlyRate", value) } min={0} step={1} prefix="$" width="full" disabled={isSubmitting} /> {errors.defaultHourlyRate && (

{errors.defaultHourlyRate}

)}

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

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

} >
); }