'use client'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { useCreateUser, useUpdateUser, useRoles } from '@/hooks/use-users'; import { useOrganizations } from '@/hooks/use-master-data'; import { useEffect, useState } from 'react'; import { User } from '@/types/user'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Eye, EyeOff } from 'lucide-react'; const ALL_ORGANIZATIONS_VALUE = 'all'; // Update schema to include confirmPassword const userSchema = z .object({ username: z.string().min(3, 'Username must be at least 3 characters'), email: z.string().email('Invalid email address'), firstName: z.string().min(1, 'First name is required'), lastName: z.string().min(1, 'Last name is required'), password: z.string().optional(), confirmPassword: z.string().optional(), isActive: z.boolean().optional(), lineId: z.string().optional(), primaryOrganizationId: z.string().optional(), roleIds: z.array(z.number()).optional(), }) .refine( (data) => { // If password is provided (creating or resetting), confirmPassword must match if (data.password && data.password !== data.confirmPassword) { return false; } return true; }, { message: 'Passwords do not match', path: ['confirmPassword'], } ) .refine( (data) => { // Password required for creation // We can't easily check "isCreating" here without context, checking length if provided if (data.password && data.password.length < 6) { return false; } return true; }, { message: 'Password must be at least 6 characters', path: ['password'], } ); type UserFormData = z.infer; interface UserDialogProps { open: boolean; onOpenChange: (open: boolean) => void; user?: User | null; } export function UserDialog({ open, onOpenChange, user }: UserDialogProps) { const createUser = useCreateUser(); const updateUser = useUpdateUser(); const { data: roles = [] } = useRoles(); const { data: organizations = [] } = useOrganizations(); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const { register, handleSubmit, setValue, watch, reset, formState: { errors }, } = useForm({ resolver: zodResolver(userSchema), defaultValues: { username: '', email: '', firstName: '', lastName: '', isActive: true, roleIds: [], lineId: '', primaryOrganizationId: ALL_ORGANIZATIONS_VALUE, password: '', confirmPassword: '', }, }); useEffect(() => { if (user) { reset({ username: user.username, email: user.email, firstName: user.firstName, lastName: user.lastName, isActive: user.isActive, lineId: user.lineId || '', primaryOrganizationId: user.primaryOrganizationId || ALL_ORGANIZATIONS_VALUE, roleIds: user.roles?.map((r: { roleId?: number }) => r.roleId).filter((id): id is number => id !== undefined) || [], password: '', confirmPassword: '', }); } else { reset({ username: '', email: '', firstName: '', lastName: '', isActive: true, lineId: '', primaryOrganizationId: ALL_ORGANIZATIONS_VALUE, roleIds: [], password: '', confirmPassword: '', }); } // Also reset visibility setShowPassword(false); setShowConfirmPassword(false); }, [user, reset, open]); const selectedRoleIds = watch('roleIds') || []; const onSubmit = (data: UserFormData) => { // Basic validation for create vs update if (!user && !data.password) { // This should be caught by schema ideally, but refined schema is tricky with conditional // Force error via set error not possible easily here, rely on form state? // Actually the refine check handles length check if provided, but for create it is mandatory. // Let's rely on server side or manual check if schema misses it (zod optional() makes it pass if undefined) // Adjusting schema to be strict string for create is hard with one schema. // Let's trust Zod or add checks. } // Clean up data const payload = { ...data }; delete payload.confirmPassword; // Don't send to API if (!payload.password) delete payload.password; // Don't send empty password on edit if (payload.primaryOrganizationId === ALL_ORGANIZATIONS_VALUE) { delete payload.primaryOrganizationId; } if (user) { updateUser.mutate({ uuid: user.publicId, data: payload }, { onSuccess: () => onOpenChange(false) }); } else { // Create req: Password mandatory if (!payload.password) return; // Should allow Zod to catch or show error createUser.mutate( { username: payload.username, email: payload.email, firstName: payload.firstName, lastName: payload.lastName, password: payload.password, isActive: payload.isActive ?? true, lineId: payload.lineId, primaryOrganizationId: payload.primaryOrganizationId, roleIds: payload.roleIds ?? [], }, { onSuccess: () => onOpenChange(false), } ); } }; return ( {user ? 'Edit User' : 'Create New User'}
{errors.username &&

{errors.username.message}

}
{errors.email &&

{errors.email.message}

}
{errors.firstName &&

{errors.firstName.message}

}
{errors.lastName &&

{errors.lastName.message}

}
{/* Password Section - Show for Create, or Optional for Edit */}

{user ? 'Change Password (Optional)' : 'Password Setup'}

{errors.password &&

{errors.password.message}

}
{errors.confirmPassword &&

{errors.confirmPassword.message}

}
{Array.isArray(roles) && roles.length === 0 && (

Loading roles...

)} {Array.isArray(roles) && roles.map((role: { publicId?: string; roleId?: number; roleName: string; description?: string }) => { const roleId = role.roleId; if (roleId === undefined) return null; return (
{ const current = selectedRoleIds; if (checked) { setValue('roleIds', [...current, roleId]); } else { setValue( 'roleIds', current.filter((id) => id !== roleId) ); } }} />

{role.description}

); })}
{user && (
setValue('isActive', chk === true)} />
)}
); }