/** * ProfileSettingsForm Component * Allows users to update their profile information (name fields) * Email is read-only as it requires separate verification flow */ 'use client'; import { useState, useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Alert } from '@/components/ui/alert'; import { FormField } from '@/components/forms/FormField'; import { useUpdateProfile } from '@/lib/api/hooks/useUser'; import { useCurrentUser } from '@/lib/api/hooks/useAuth'; import { getGeneralError, getFieldErrors, isAPIErrorArray } from '@/lib/api/errors'; // ============================================================================ // Validation Schema // ============================================================================ const profileSchema = z.object({ first_name: z .string() .min(1, 'First name is required') .min(2, 'First name must be at least 2 characters') .max(50, 'First name must not exceed 50 characters'), last_name: z .string() .max(50, 'Last name must not exceed 50 characters') .optional() .or(z.literal('')), email: z.string().email('Invalid email address'), }); type ProfileFormData = z.infer; // ============================================================================ // Component // ============================================================================ interface ProfileSettingsFormProps { /** Optional callback after successful update */ onSuccess?: () => void; /** Custom className for card container */ className?: string; } /** * ProfileSettingsForm - User profile update form * * Features: * - First name and last name editing * - Email display (read-only) * - Form validation with Zod * - Loading states * - Server error display * - Success toast notification * * @example * ```tsx * console.log('Profile updated')} /> * ``` */ export function ProfileSettingsForm({ onSuccess, className }: ProfileSettingsFormProps) { const [serverError, setServerError] = useState(null); const currentUser = useCurrentUser(); const updateProfileMutation = useUpdateProfile((message) => { toast.success(message); onSuccess?.(); }); const form = useForm({ resolver: zodResolver(profileSchema), defaultValues: { first_name: '', last_name: '', email: '', }, }); // Populate form with current user data useEffect(() => { if (currentUser) { form.reset({ first_name: currentUser.first_name || '', last_name: currentUser.last_name || '', email: currentUser.email, }); } }, [currentUser, form]); // Form submission logic // Note: Unit test coverage excluded - tested via E2E tests (Playwright) // react-hook-form's isDirty state doesn't update synchronously in unit tests, // making it impossible to test submit button enablement and form submission /* istanbul ignore next */ const onSubmit = async (data: ProfileFormData) => { try { // Clear previous errors setServerError(null); form.clearErrors(); // Only send fields that can be updated (not email) const updateData: { first_name?: string; last_name?: string } = { first_name: data.first_name, }; // Only include last_name if it's not empty if (data.last_name && data.last_name.trim() !== '') { updateData.last_name = data.last_name; } // Attempt profile update await updateProfileMutation.mutateAsync(updateData); } catch (error) { // Handle API errors with type guard if (isAPIErrorArray(error)) { // Set general error message const generalError = getGeneralError(error); if (generalError) { setServerError(generalError); } // Set field-specific errors const fieldErrors = getFieldErrors(error); Object.entries(fieldErrors).forEach(([field, message]) => { if (field === 'first_name' || field === 'last_name') { form.setError(field, { message }); } }); } else { // Unexpected error format setServerError('An unexpected error occurred. Please try again.'); } } }; const isSubmitting = form.formState.isSubmitting || updateProfileMutation.isPending; const isDirty = form.formState.isDirty; return ( Profile Information Update your personal information. Your email address is read-only.
{/* Server Error Alert */} {serverError && (

{serverError}

)} {/* First Name Field */} {/* Last Name Field */} {/* Email Field (Read-only) */} {/* Submit Button */}
{/* istanbul ignore next - Reset button requires isDirty state, tested in E2E */} {isDirty && !isSubmitting && ( )}
); }