'use client' /** * useUserForm Hook * * Manages form state for creating and editing users. * Handles validation, submission, and error states. * * @example * const { formData, errors, loading, handlers } = useUserForm({ * initialData: existingUser * }) * * const handleSubmit = async () => { * const isValid = handlers.validateForm() * if (!isValid) return * * if (isCreate) { * await handlers.submitCreate(formData) * } else { * await handlers.submitUpdate(userId, formData) * } * } */ import { useCallback, useState } from 'react' import type { User } from '@/lib/level-types' import { validateUserForm, type UserFormData, type UserFormErrors } from '@/lib/validation/user-validation' interface UseUserFormOptions { baseUrl?: string initialData?: Partial onSuccess?: (user: User) => void onError?: (error: string) => void } interface UseUserFormState { formData: UserFormData errors: UserFormErrors loading: boolean submitError: string | null isValid: boolean isDirty: boolean } interface UseUserFormHandlers { setField: (name: keyof UserFormData, value: unknown) => void setErrors: (errors: Partial) => void validateForm: () => boolean validateField: (field: keyof UserFormData) => boolean submitCreate: (data?: UserFormData) => Promise submitUpdate: (userId: string, data?: UserFormData) => Promise reset: () => void } interface UseUserFormReturn extends UseUserFormState { handlers: UseUserFormHandlers } // Default form data structure const DEFAULT_FORM_DATA: UserFormData = { username: '', email: '', role: 'user', bio: '', profilePicture: '', } /** * Hook for managing user form state and submission */ export function useUserForm(options?: UseUserFormOptions): UseUserFormReturn { // Initialize form data from provided initial data or defaults const baseUrl = options?.baseUrl ?? '' const initialFormData: UserFormData = { username: options?.initialData?.username ?? '', email: options?.initialData?.email ?? '', role: (options?.initialData?.role as any) ?? 'user', bio: options?.initialData?.bio ?? '', profilePicture: options?.initialData?.profilePicture ?? '', } const [formData, setFormData] = useState(initialFormData) const [errors, setErrors] = useState({}) const [loading, setLoading] = useState(false) const [submitError, setSubmitError] = useState(null) // Determine if form is dirty (has changes from initial data) const isDirty = JSON.stringify(formData) !== JSON.stringify(initialFormData) // Determine if form is valid (no errors and all required fields filled) const isValid = Object.keys(errors).length === 0 && !!formData.username && !!formData.email /** * Update a single form field */ const setField = useCallback((name: keyof UserFormData, value: unknown) => { setFormData((prev) => ({ ...prev, [name]: value, })) // Clear error for this field when user starts typing setErrors((prev) => { const next = { ...prev } delete next[name] return next }) }, []) /** * Validate a single field */ const validateField = useCallback((field: keyof UserFormData): boolean => { const fieldData: Partial = { [field]: formData[field], } const fieldErrors = validateUserForm(fieldData, { singleField: field }) if (Object.keys(fieldErrors).length > 0) { setErrors((prev) => ({ ...prev, ...fieldErrors, })) return false } // Clear field error if validation passes setErrors((prev) => { const next = { ...prev } delete next[field] return next }) return true }, [formData]) /** * Validate entire form */ const validateForm = useCallback((): boolean => { const formErrors = validateUserForm(formData) setErrors(formErrors) return Object.keys(formErrors).length === 0 }, [formData]) /** * Submit form for creating a new user */ const submitCreate = useCallback( async (data?: UserFormData): Promise => { const submitData = data ?? formData // Validate form if (!validateForm()) { setSubmitError('Please fix validation errors') return null } setLoading(true) setSubmitError(null) try { // Prepare request data const requestData = { username: submitData.username.trim(), email: submitData.email.trim(), role: submitData.role.toUpperCase(), ...(submitData.bio && { bio: submitData.bio.trim() }), ...(submitData.profilePicture && { profilePicture: submitData.profilePicture.trim() }), } // Make API request const response = await fetch(`${baseUrl}/api/v1/default/user_manager/users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData), }) const result = await response.json() if (!response.ok) { // Handle validation errors if (response.status === 422 && result.error?.details) { const fieldErrors: UserFormErrors = {} for (const [field, message] of Object.entries(result.error.details)) { fieldErrors[field as keyof UserFormData] = message as string } setErrors(fieldErrors) throw new Error('Please fix validation errors') } // Handle conflict errors (duplicate username/email) if (response.status === 409) { throw new Error(result.error?.message ?? 'This username or email already exists') } throw new Error( result.error?.message ?? `HTTP ${response.status}: ${response.statusText}` ) } const newUser: User = result.data // Call success callback options?.onSuccess?.(newUser) // Reset form on success setFormData(DEFAULT_FORM_DATA) return newUser } catch (err) { const message = err instanceof Error ? err.message : 'Failed to create user' setSubmitError(message) options?.onError?.(message) return null } finally { setLoading(false) } }, [formData, validateForm, options] ) /** * Submit form for updating an existing user */ const submitUpdate = useCallback( async (userId: string, data?: UserFormData): Promise => { const submitData = data ?? formData // Validate form if (!validateForm()) { setSubmitError('Please fix validation errors') return null } setLoading(true) setSubmitError(null) try { // Prepare request data const requestData = { username: submitData.username.trim(), email: submitData.email.trim(), role: submitData.role.toUpperCase(), ...(submitData.bio !== undefined && { bio: submitData.bio.trim() }), ...(submitData.profilePicture !== undefined && { profilePicture: submitData.profilePicture.trim() }), } // Make API request const response = await fetch(`${baseUrl}/api/v1/default/user_manager/users/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestData), }) const result = await response.json() if (!response.ok) { // Handle validation errors if (response.status === 422 && result.error?.details) { const fieldErrors: UserFormErrors = {} for (const [field, message] of Object.entries(result.error.details)) { fieldErrors[field as keyof UserFormData] = message as string } setErrors(fieldErrors) throw new Error('Please fix validation errors') } // Handle conflict errors (duplicate username/email) if (response.status === 409) { throw new Error(result.error?.message ?? 'This username or email already exists') } // Handle not found if (response.status === 404) { throw new Error('User not found') } throw new Error( result.error?.message ?? `HTTP ${response.status}: ${response.statusText}` ) } const updatedUser: User = result.data // Call success callback options?.onSuccess?.(updatedUser) // Update form data to match server response (new initial state) setFormData({ username: updatedUser.username ?? '', email: updatedUser.email ?? '', role: (updatedUser.role as any) ?? 'user', bio: updatedUser.bio ?? '', profilePicture: updatedUser.profilePicture ?? '', }) return updatedUser } catch (err) { const message = err instanceof Error ? err.message : 'Failed to update user' setSubmitError(message) options?.onError?.(message) return null } finally { setLoading(false) } }, [formData, validateForm, options] ) /** * Reset form to initial state */ const reset = useCallback(() => { setFormData(initialFormData) setErrors({}) setSubmitError(null) }, [initialFormData]) return { formData, errors, loading, submitError, isValid, isDirty, handlers: { setField, setErrors, validateForm, validateField, submitCreate, submitUpdate, reset, }, } } export default useUserForm