'use client'; import { zodResolver } from '@hookform/resolvers/zod'; import { collectDefaultPermissionIds, collectPermissionIdsByKeys, } from '@/components/permission-tree'; import type { PermissionTreeItem, Plan } from '@/types'; import { useCallback } from 'react'; import { useForm, useWatch } from 'react-hook-form'; import { z } from 'zod'; import { useSavePlanMutation } from './usePlanMutations'; const nonNegativeNumber = z.number().refine( (value) => Number.isFinite(value) && value >= 0, { message: 'Enter a valid number' }, ); const planFormSchema = z.object({ id: z.number().optional(), name: z.string().trim().min(1, 'Plan name is required'), slug: z.string().trim().min(1, 'Slug is required'), description: z.string().trim().min(1, 'Description is required'), price: z .string() .trim() .min(1, 'Price is required') .refine((value) => Number.isFinite(Number(value)) && Number(value) >= 0, { message: 'Enter a valid price', }), billing_cycle: z.string().trim().min(1, 'Billing cycle is required'), trial_days: nonNegativeNumber, max_projects: nonNegativeNumber, max_organizations: nonNegativeNumber, max_users: nonNegativeNumber, max_roles: nonNegativeNumber, permission_ids: z.array(z.number()).min(1, 'Select at least one permission'), is_active: z.boolean(), is_custom: z.boolean(), }); export type PlanFormValues = z.infer; const defaultValues: PlanFormValues = { name: '', slug: '', description: '', price: '', billing_cycle: 'monthly', trial_days: 0, max_projects: 0, max_organizations: 0, max_users: 0, max_roles: 0, permission_ids: [], is_active: true, is_custom: false, }; function toNumber(value: unknown) { const numberValue = Number(value); return Number.isFinite(numberValue) ? numberValue : 0; } export function usePlanForm({ permissionTree, onSaved, }: { permissionTree: PermissionTreeItem[]; onSaved: () => void; }) { const { control, register, handleSubmit, reset, setValue, formState: { errors, isSubmitting, isSubmitted }, } = useForm({ defaultValues, mode: 'onSubmit', reValidateMode: 'onChange', resolver: zodResolver(planFormSchema), }); const saveMutation = useSavePlanMutation({ onSaved: () => { reset(defaultValues); onSaved(); }, }); const planId = useWatch({ control, name: 'id' }); const permissionIds = useWatch({ control, name: 'permission_ids' }) || []; const name = useWatch({ control, name: 'name' }) || ''; const slug = useWatch({ control, name: 'slug' }) || ''; const description = useWatch({ control, name: 'description' }) || ''; const price = useWatch({ control, name: 'price' }) || ''; const canSubmit = name.trim().length > 0 && slug.trim().length > 0 && description.trim().length > 0 && price.trim().length > 0 && permissionIds.length > 0; const onSubmit = handleSubmit((values) => { saveMutation.mutate({ ...values, name: values.name.trim(), slug: values.slug.trim(), description: values.description.trim(), price: values.price.trim(), trial_days: toNumber(values.trial_days), max_projects: toNumber(values.max_projects), max_organizations: toNumber(values.max_organizations), max_users: toNumber(values.max_users), max_roles: toNumber(values.max_roles), }); }); const openCreate = useCallback(() => { reset({ ...defaultValues, permission_ids: collectDefaultPermissionIds(permissionTree), }); }, [permissionTree, reset]); const openEdit = useCallback( (plan: Plan) => { reset({ id: plan.id, name: plan.name || '', slug: plan.slug || '', description: plan.description || '', price: plan.price || '', billing_cycle: plan.billing_cycle || 'monthly', trial_days: plan.trial_days ?? 0, max_projects: plan.max_projects ?? 0, max_organizations: plan.max_organizations ?? 0, max_users: plan.max_users ?? 0, max_roles: plan.max_roles ?? 0, permission_ids: collectPermissionIdsByKeys( permissionTree, plan.permissions || [], ), is_active: plan.is_active, is_custom: plan.is_custom, }); }, [permissionTree, reset], ); const setPermissionIds = useCallback( (ids: number[]) => setValue('permission_ids', ids, { shouldDirty: true, shouldValidate: true, }), [setValue], ); return { register, control, onSubmit, openCreate, openEdit, planId, permissionIds, setPermissionIds, errors, isSubmitted, canSubmit, isSaving: isSubmitting || saveMutation.isPending, }; }