From 4f53733422afe0dedfa48f251f71355730b9a6b5 Mon Sep 17 00:00:00 2001 From: Callum Morris Date: Mon, 28 Jul 2025 22:19:18 +0100 Subject: [PATCH] feat: migrate to new version of auto form --- apps/web/package.json | 2 + .../forms/create-component-version.tsx | 30 +-- .../dashboard/forms/create-deployments.tsx | 51 +++-- .../dashboard/forms/create-environment.tsx | 32 +-- .../forms/create-release-component.tsx | 51 +++-- .../dashboard/forms/create-release-step.tsx | 51 +++-- .../forms/create-release-strategy-step.tsx | 93 +++++---- .../dashboard/forms/create-release.tsx | 51 +++-- .../components/dashboard/forms/generic.tsx | 33 ++-- .../src/components/dashboard/workspace.tsx | 45 ++--- .../components/ui/auto-form/common/label.tsx | 23 --- .../ui/auto-form/common/tooltip.tsx | 13 -- .../web/src/components/ui/auto-form/config.ts | 35 ---- .../components/ui/auto-form/dependencies.ts | 57 ------ .../components/ui/auto-form/fields/array.tsx | 75 ------- .../ui/auto-form/fields/checkbox.tsx | 31 --- .../components/ui/auto-form/fields/date.tsx | 29 --- .../components/ui/auto-form/fields/enum.tsx | 71 ------- .../components/ui/auto-form/fields/file.tsx | 62 ------ .../components/ui/auto-form/fields/input.tsx | 29 --- .../components/ui/auto-form/fields/number.tsx | 26 --- .../components/ui/auto-form/fields/object.tsx | 183 ------------------ .../ui/auto-form/fields/radio-group.tsx | 60 ------ .../components/ui/auto-form/fields/switch.tsx | 31 --- .../ui/auto-form/fields/textarea.tsx | 25 --- .../web/src/components/ui/auto-form/index.tsx | 106 ---------- apps/web/src/components/ui/auto-form/types.ts | 76 -------- apps/web/src/components/ui/auto-form/utils.ts | 176 ----------------- .../src/components/ui/autoform/AutoForm.tsx | 51 +++++ .../components/ArrayElementWrapper.tsx | 24 +++ .../ui/autoform/components/ArrayWrapper.tsx | 20 ++ .../ui/autoform/components/BooleanField.tsx | 32 +++ .../ui/autoform/components/DateField.tsx | 20 ++ .../ui/autoform/components/ErrorMessage.tsx | 11 ++ .../ui/autoform/components/FieldWrapper.tsx | 33 ++++ .../ui/autoform/components/Form.tsx | 12 ++ .../ui/autoform/components/NumberField.tsx | 20 ++ .../ui/autoform/components/ObjectWrapper.tsx | 14 ++ .../ui/autoform/components/SelectField.tsx | 45 +++++ .../ui/autoform/components/StringField.tsx | 15 ++ .../ui/autoform/components/SubmitButton.tsx | 6 + apps/web/src/components/ui/autoform/index.ts | 3 + apps/web/src/components/ui/autoform/types.ts | 5 + apps/web/src/components/ui/autoform/utils.ts | 9 + apps/web/src/lib/validation.ts | 37 ++++ apps/web/src/validation/workspace.ts | 11 +- yarn.lock | 31 +++ 47 files changed, 636 insertions(+), 1310 deletions(-) delete mode 100644 apps/web/src/components/ui/auto-form/common/label.tsx delete mode 100644 apps/web/src/components/ui/auto-form/common/tooltip.tsx delete mode 100644 apps/web/src/components/ui/auto-form/config.ts delete mode 100644 apps/web/src/components/ui/auto-form/dependencies.ts delete mode 100644 apps/web/src/components/ui/auto-form/fields/array.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/checkbox.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/date.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/enum.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/file.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/input.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/number.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/object.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/radio-group.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/switch.tsx delete mode 100644 apps/web/src/components/ui/auto-form/fields/textarea.tsx delete mode 100644 apps/web/src/components/ui/auto-form/index.tsx delete mode 100644 apps/web/src/components/ui/auto-form/types.ts delete mode 100644 apps/web/src/components/ui/auto-form/utils.ts create mode 100644 apps/web/src/components/ui/autoform/AutoForm.tsx create mode 100644 apps/web/src/components/ui/autoform/components/ArrayElementWrapper.tsx create mode 100644 apps/web/src/components/ui/autoform/components/ArrayWrapper.tsx create mode 100644 apps/web/src/components/ui/autoform/components/BooleanField.tsx create mode 100644 apps/web/src/components/ui/autoform/components/DateField.tsx create mode 100644 apps/web/src/components/ui/autoform/components/ErrorMessage.tsx create mode 100644 apps/web/src/components/ui/autoform/components/FieldWrapper.tsx create mode 100644 apps/web/src/components/ui/autoform/components/Form.tsx create mode 100644 apps/web/src/components/ui/autoform/components/NumberField.tsx create mode 100644 apps/web/src/components/ui/autoform/components/ObjectWrapper.tsx create mode 100644 apps/web/src/components/ui/autoform/components/SelectField.tsx create mode 100644 apps/web/src/components/ui/autoform/components/StringField.tsx create mode 100644 apps/web/src/components/ui/autoform/components/SubmitButton.tsx create mode 100644 apps/web/src/components/ui/autoform/index.ts create mode 100644 apps/web/src/components/ui/autoform/types.ts create mode 100644 apps/web/src/components/ui/autoform/utils.ts create mode 100644 apps/web/src/lib/validation.ts diff --git a/apps/web/package.json b/apps/web/package.json index 8843d334..d52957d6 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -18,6 +18,8 @@ "stripe:listen": "stripe listen --forward-to=https://localhost:3000/api/billing/webhooks/stripe" }, "dependencies": { + "@autoform/react": "^4.0.0", + "@autoform/zod": "^4.0.0", "@clerk/backend": "^2.6.0", "@clerk/nextjs": "^6.27.1", "@clerk/themes": "2.4.2", diff --git a/apps/web/src/components/dashboard/forms/create-component-version.tsx b/apps/web/src/components/dashboard/forms/create-component-version.tsx index c09185e4..593661e3 100644 --- a/apps/web/src/components/dashboard/forms/create-component-version.tsx +++ b/apps/web/src/components/dashboard/forms/create-component-version.tsx @@ -1,8 +1,10 @@ 'use client'; import { createComponentVersionAction } from '@/actions/components'; import { InputForm } from '@/components/dashboard/forms/generic'; +import { fieldConfig } from '@/components/ui/autoform'; import { Navigation } from '@/config/navigation'; import { Component } from '@/database/schema'; +import { enhanceFields } from '@/lib/validation'; import { createComponentVersionSchema } from '@/validation/component'; export function CreateComponentVersion({ @@ -10,23 +12,27 @@ export function CreateComponentVersion({ }: { components: Component[]; }) { + const enhancedSchema = enhanceFields(createComponentVersionSchema, { + componentId: { + define: 'Component to create version for', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: components.map((component) => ({ + value: component.id, + label: component.name, + })), + }, + }), + }, + }); + return ( ({ - value: component.id, - label: component.name, - })), - }, - }, - }} /> ); } diff --git a/apps/web/src/components/dashboard/forms/create-deployments.tsx b/apps/web/src/components/dashboard/forms/create-deployments.tsx index 8653f5ce..cfe4c13b 100644 --- a/apps/web/src/components/dashboard/forms/create-deployments.tsx +++ b/apps/web/src/components/dashboard/forms/create-deployments.tsx @@ -1,8 +1,10 @@ 'use client'; import { createDeploymentAction } from '@/actions/deployment'; import { InputForm } from '@/components/dashboard/forms/generic'; +import { fieldConfig } from '@/components/ui/autoform'; import { Navigation } from '@/config/navigation'; import { Environment, ReleaseStep } from '@/database/schema'; +import { enhanceFields } from '@/lib/validation'; import { createDeploymentsSchema } from '@/validation/deployment'; export function CreateDeploymentForm({ @@ -12,32 +14,39 @@ export function CreateDeploymentForm({ environments: Environment[]; releaseSteps: ReleaseStep[]; }) { + const enhancedSchema = enhanceFields(createDeploymentsSchema, { + environmentId: { + define: 'Environment to deploy to', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: environments.map((environment) => ({ + value: environment.id, + label: environment.name, + })), + }, + }), + }, + releaseStepId: { + define: 'Release step to deploy', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: releaseSteps.map((step) => ({ + value: step.id, + label: `${step.id} (${step.status})`, + })), + }, + }), + }, + }); + return ( ({ - value: environment.id, - label: environment.name, - })), - }, - }, - releaseStepId: { - fieldType: 'select', - inputProps: { - values: releaseSteps.map((step) => ({ - value: step.id, - label: `${step.id} (${step.status})`, - })), - }, - }, - }} /> ); } diff --git a/apps/web/src/components/dashboard/forms/create-environment.tsx b/apps/web/src/components/dashboard/forms/create-environment.tsx index a321c25e..3cbfc082 100644 --- a/apps/web/src/components/dashboard/forms/create-environment.tsx +++ b/apps/web/src/components/dashboard/forms/create-environment.tsx @@ -1,8 +1,10 @@ 'use client'; import { createEnvironmentAction } from '@/actions/environment'; import { InputForm } from '@/components/dashboard/forms/generic'; +import { fieldConfig } from '@/components/ui/autoform'; import { Navigation } from '@/config/navigation'; import { EnvironmentType } from '@/database/schema'; +import { enhanceFields } from '@/lib/validation'; import { createEnvironmentSchema } from '@/validation/environment'; export function CreateEnvironmentForm({ @@ -10,25 +12,27 @@ export function CreateEnvironmentForm({ }: { environmentTypes: EnvironmentType[]; }) { + const enhancedSchema = enhanceFields(createEnvironmentSchema, { + typeId: { + define: 'Type of environment we are creating', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: environmentTypes.map((type) => ({ + value: type.id, + label: type.label, + })), + }, + }), + }, + }); + return ( ({ - value: type.id, - label: type.label, - })), - }, - }, - }} /> ); } diff --git a/apps/web/src/components/dashboard/forms/create-release-component.tsx b/apps/web/src/components/dashboard/forms/create-release-component.tsx index 41552604..4d3f6c3a 100644 --- a/apps/web/src/components/dashboard/forms/create-release-component.tsx +++ b/apps/web/src/components/dashboard/forms/create-release-component.tsx @@ -1,8 +1,10 @@ 'use client'; import { createReleaseComponentAction } from '@/actions/components'; import { InputForm } from '@/components/dashboard/forms/generic'; +import { fieldConfig } from '@/components/ui/autoform'; import { Navigation } from '@/config/navigation'; import { ComponentVersion, Release } from '@/database/schema'; +import { enhanceFields } from '@/lib/validation'; import { createReleaseComponentSchema } from '@/validation/component'; export function CreateReleaseComponent({ @@ -12,32 +14,39 @@ export function CreateReleaseComponent({ componentVersions: ComponentVersion[]; releases: Release[]; }) { + const enhancedSchema = enhanceFields(createReleaseComponentSchema, { + componentVersionId: { + define: 'Component version to include in release', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: componentVersions.map((componentVersion) => ({ + value: componentVersion.id, + label: componentVersion.version, + })), + }, + }), + }, + releaseId: { + define: 'Release to add component to', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: releases.map((release) => ({ + value: release.id, + label: release.version, + })), + }, + }), + }, + }); + return ( ({ - value: componentVersion.id, - label: componentVersion.version, - })), - }, - }, - releaseId: { - fieldType: 'select', - inputProps: { - values: releases.map((release) => ({ - value: release.id, - label: release.version, - })), - }, - }, - }} /> ); } diff --git a/apps/web/src/components/dashboard/forms/create-release-step.tsx b/apps/web/src/components/dashboard/forms/create-release-step.tsx index 810beff1..6f130854 100644 --- a/apps/web/src/components/dashboard/forms/create-release-step.tsx +++ b/apps/web/src/components/dashboard/forms/create-release-step.tsx @@ -1,8 +1,10 @@ 'use client'; import { createReleaseStepAction } from '@/actions/release'; import { InputForm } from '@/components/dashboard/forms/generic'; +import { fieldConfig } from '@/components/ui/autoform'; import { Navigation } from '@/config/navigation'; import { Release, ReleaseStrategyStep } from '@/database/schema'; +import { enhanceFields } from '@/lib/validation'; import { createReleaseStepSchema } from '@/validation/release'; export function CreateReleaseStepForm({ @@ -12,32 +14,39 @@ export function CreateReleaseStepForm({ releases: Release[]; releaseStrategySteps: ReleaseStrategyStep[]; }) { + const enhancedSchema = enhanceFields(createReleaseStepSchema, { + releaseId: { + define: 'Release for this step', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: releases.map((release) => ({ + value: release.id, + label: release.version, + })), + }, + }), + }, + releaseStrategyStepId: { + define: 'Strategy step to execute', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: releaseStrategySteps.map((step) => ({ + value: step.id, + label: step.name, + })), + }, + }), + }, + }); + return ( ({ - value: release.id, - label: release.version, - })), - }, - }, - releaseStrategyStepId: { - fieldType: 'select', - inputProps: { - values: releaseStrategySteps.map((step) => ({ - value: step.id, - label: step.name, - })), - }, - }, - }} /> ); } diff --git a/apps/web/src/components/dashboard/forms/create-release-strategy-step.tsx b/apps/web/src/components/dashboard/forms/create-release-strategy-step.tsx index 0b9c0c97..bc91d8db 100644 --- a/apps/web/src/components/dashboard/forms/create-release-strategy-step.tsx +++ b/apps/web/src/components/dashboard/forms/create-release-strategy-step.tsx @@ -1,6 +1,7 @@ 'use client'; import { createReleaseStrategyStepAction } from '@/actions/release'; import { InputForm } from '@/components/dashboard/forms/generic'; +import { fieldConfig } from '@/components/ui/autoform'; import { Navigation } from '@/config/navigation'; import { ApprovalGroup, @@ -8,6 +9,7 @@ import { ReleaseStrategy, ReleaseStrategyStep, } from '@/database/schema'; +import { enhanceFields } from '@/lib/validation'; import { createReleaseStrategyStepSchema } from '@/validation/release'; export function CreateReleaseStrategyStepForm({ @@ -21,50 +23,63 @@ export function CreateReleaseStrategyStepForm({ approvalGroups: ApprovalGroup[]; releaseStrategies: ReleaseStrategy[]; }) { + const enhancedSchema = enhanceFields(createReleaseStrategyStepSchema, { + strategyId: { + define: 'Release strategy for this step', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: releaseStrategies.map((step) => ({ + value: step.id, + label: step.name, + })), + }, + }), + }, + approvalGroupId: { + define: 'Approval group required for this step', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: approvalGroups.map((group) => ({ + value: group.id, + label: group.name, + })), + }, + }), + }, + environmentId: { + define: 'Environment for this release step', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: environments.map((environment) => ({ + value: environment.id, + label: environment.name, + })), + }, + }), + }, + parentId: { + define: 'Parent step (optional dependency)', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: releaseStrategySteps.map((step) => ({ + value: step.id, + label: step.name, + })), + }, + }), + }, + }); + return ( ({ - value: step.id, - label: step.name, - })), - }, - }, - approvalGroupId: { - fieldType: 'select', - inputProps: { - values: approvalGroups.map((group) => ({ - value: group.id, - label: group.name, - })), - }, - }, - environmentId: { - fieldType: 'select', - inputProps: { - values: environments.map((environment) => ({ - value: environment.id, - label: environment.name, - })), - }, - }, - parentId: { - fieldType: 'select', - inputProps: { - values: releaseStrategySteps.map((step) => ({ - value: step.id, - label: step.name, - })), - }, - }, - }} /> ); } diff --git a/apps/web/src/components/dashboard/forms/create-release.tsx b/apps/web/src/components/dashboard/forms/create-release.tsx index 4b71f294..bcb7a6c0 100644 --- a/apps/web/src/components/dashboard/forms/create-release.tsx +++ b/apps/web/src/components/dashboard/forms/create-release.tsx @@ -1,9 +1,11 @@ 'use client'; import { createReleaseAction } from '@/actions/release'; import { InputForm } from '@/components/dashboard/forms/generic'; +import { fieldConfig } from '@/components/ui/autoform'; import { Navigation } from '@/config/navigation'; import { ReleaseStatus, ReleaseStrategy } from '@/database/schema'; import { capitalizeFirstLetter } from '@/lib/utils'; +import { enhanceFields } from '@/lib/validation'; import { createReleaseSchema } from '@/validation/release'; export function CreateReleaseForm({ @@ -11,32 +13,39 @@ export function CreateReleaseForm({ }: { releaseStrategies: ReleaseStrategy[]; }) { + const enhancedSchema = enhanceFields(createReleaseSchema, { + strategyId: { + define: 'Strategy for the release', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: releaseStrategies.map((strategy) => ({ + value: strategy.id, + label: strategy.name, + })), + }, + }), + }, + status: { + define: 'Status of the release', + superRefine: fieldConfig({ + fieldType: 'select', + inputProps: { + values: ReleaseStatus.map((status) => ({ + value: status, + label: capitalizeFirstLetter(status), + })), + }, + }), + }, + }); + return ( ({ - value: strategy.id, - label: strategy.name, - })), - }, - }, - status: { - fieldType: 'select', - inputProps: { - values: ReleaseStatus.map((status) => ({ - value: status, - label: capitalizeFirstLetter(status), - })), - }, - }, - }} /> ); } diff --git a/apps/web/src/components/dashboard/forms/generic.tsx b/apps/web/src/components/dashboard/forms/generic.tsx index 3e36e00d..fec5c395 100644 --- a/apps/web/src/components/dashboard/forms/generic.tsx +++ b/apps/web/src/components/dashboard/forms/generic.tsx @@ -1,7 +1,6 @@ 'use client'; -import AutoForm, { AutoFormSubmit } from '@/components/ui/auto-form'; -import { FieldConfig } from '@/components/ui/auto-form/types'; +import { AutoForm } from '@/components/ui/autoform'; import { NavigationItem, dashboardRoute } from '@/config/navigation'; import { parseServerError } from '@/lib/actions/parse-server-error'; import { AppErrorJson } from '@/lib/error/app.error'; @@ -11,8 +10,10 @@ import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { toast } from 'sonner'; import { z } from 'zod'; +import { ZodProvider } from '@autoform/zod'; +import { FieldConfig } from '@autoform/react'; -export function InputForm({ +export function InputForm({ schema, action, resource = 'Resource', @@ -29,7 +30,6 @@ export function InputForm({ const { slug } = useParams<{ slug: string }>(); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(); - const [values, setValues] = useState>>({}); const onSubmit = async (values: z.infer) => { setSubmitting(true); @@ -64,30 +64,25 @@ export function InputForm({ if (data) { toast.success(`${resource} created`); setError(undefined); - setValues({}); console.log('postSubmitLink', postSubmitLink); router.push(dashboardRoute(slug, postSubmitLink)); router.refresh(); } }; + const schemaProvider = new ZodProvider(schema); + return ( <>

Create {resource}

- - - {submitting ? ( - - ) : ( - `Create ${resource}` - )} - + + {/* */} + {submitting ? ( + + ) : ( + `Create ${resource}` + )} + {/* */} {error &&

{error?.message}

}
diff --git a/apps/web/src/components/dashboard/workspace.tsx b/apps/web/src/components/dashboard/workspace.tsx index ac7a0c00..23fa3d76 100644 --- a/apps/web/src/components/dashboard/workspace.tsx +++ b/apps/web/src/components/dashboard/workspace.tsx @@ -1,11 +1,12 @@ 'use client'; import { changeSlugAction } from '@/actions/workspace'; -import AutoForm, { AutoFormSubmit } from '@/components/ui/auto-form'; +import { AutoForm } from '@/components/ui/autoform'; import { Navigation, dashboardRoute } from '@/config/navigation'; import { parseServerError } from '@/lib/actions/parse-server-error'; import { AppErrorJson } from '@/lib/error/app.error'; import { changeSlugSchema } from '@/validation/workspace'; +import { ZodProvider } from '@autoform/zod'; import { Loader2 } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; @@ -16,20 +17,12 @@ export function ChangeSlugForm() { const router = useRouter(); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(); - const [values, setValues] = useState< - Partial> - >({}); - - const updateValues = (values: Partial>) => { - setValues({ - ...values, - slug: values.slug?.toLowerCase(), - }); - }; const onSubmit = async (values: z.infer) => { setSubmitting(true); + values.slug = values.slug.toLowerCase(); + const loadingToast = toast.loading( `Changing workspace slug to "${values.slug}"`, { @@ -64,35 +57,21 @@ export function ChangeSlugForm() { if (updatedWorkspace) { toast.success(`Workspace slug updated to "${updatedWorkspace.slug}"`); setError(undefined); - setValues({}); router.push( dashboardRoute(updatedWorkspace.slug, Navigation.DASHBOARD_SETTINGS), ); } }; + const schemaProvider = new ZodProvider(changeSlugSchema); + return ( - - - {submitting ? ( - - ) : ( - 'Change slug' - )} - + + {submitting ? ( + + ) : ( + 'Change slug' + )} {error &&

{error?.message}

}
); diff --git a/apps/web/src/components/ui/auto-form/common/label.tsx b/apps/web/src/components/ui/auto-form/common/label.tsx deleted file mode 100644 index 8bc6e2d0..00000000 --- a/apps/web/src/components/ui/auto-form/common/label.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { FormLabel } from '@/components/ui/form'; -import { cn } from '@/lib/utils'; - -function AutoFormLabel({ - label, - isRequired, - className, -}: { - label: string; - isRequired: boolean; - className?: string; -}) { - return ( - <> - - {label} - {isRequired && *} - - - ); -} - -export default AutoFormLabel; diff --git a/apps/web/src/components/ui/auto-form/common/tooltip.tsx b/apps/web/src/components/ui/auto-form/common/tooltip.tsx deleted file mode 100644 index cafdb79a..00000000 --- a/apps/web/src/components/ui/auto-form/common/tooltip.tsx +++ /dev/null @@ -1,13 +0,0 @@ -function AutoFormTooltip({ fieldConfigItem }: { fieldConfigItem: any }) { - return ( - <> - {fieldConfigItem?.description && ( -

- {fieldConfigItem.description} -

- )} - - ); -} - -export default AutoFormTooltip; diff --git a/apps/web/src/components/ui/auto-form/config.ts b/apps/web/src/components/ui/auto-form/config.ts deleted file mode 100644 index 31539f38..00000000 --- a/apps/web/src/components/ui/auto-form/config.ts +++ /dev/null @@ -1,35 +0,0 @@ -import AutoFormCheckbox from './fields/checkbox'; -import AutoFormDate from './fields/date'; -import AutoFormEnum from './fields/enum'; -import AutoFormFile from './fields/file'; -import AutoFormInput from './fields/input'; -import AutoFormNumber from './fields/number'; -import AutoFormRadioGroup from './fields/radio-group'; -import AutoFormSwitch from './fields/switch'; -import AutoFormTextarea from './fields/textarea'; - -export const INPUT_COMPONENTS = { - checkbox: AutoFormCheckbox, - date: AutoFormDate, - select: AutoFormEnum, - radio: AutoFormRadioGroup, - switch: AutoFormSwitch, - textarea: AutoFormTextarea, - number: AutoFormNumber, - file: AutoFormFile, - fallback: AutoFormInput, -}; - -/** - * Define handlers for specific Zod types. - * You can expand this object to support more types. - */ -export const DEFAULT_ZOD_HANDLERS: { - [key: string]: keyof typeof INPUT_COMPONENTS; -} = { - ZodBoolean: 'checkbox', - ZodDate: 'date', - ZodEnum: 'select', - ZodNativeEnum: 'select', - ZodNumber: 'number', -}; diff --git a/apps/web/src/components/ui/auto-form/dependencies.ts b/apps/web/src/components/ui/auto-form/dependencies.ts deleted file mode 100644 index 32dd1338..00000000 --- a/apps/web/src/components/ui/auto-form/dependencies.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { FieldValues, UseFormWatch } from 'react-hook-form'; -import * as z from 'zod'; -import { Dependency, DependencyType, EnumValues } from './types'; - -export default function resolveDependencies< - SchemaType extends z.infer> ->( - dependencies: Dependency[], - currentFieldName: keyof SchemaType, - watch: UseFormWatch -) { - let isDisabled = false; - let isHidden = false; - let isRequired = false; - let overrideOptions: EnumValues | undefined; - - const currentFieldValue = watch(currentFieldName as string); - - const currentFieldDependencies = dependencies.filter( - (dependency) => dependency.targetField === currentFieldName - ); - for (const dependency of currentFieldDependencies) { - const watchedValue = watch(dependency.sourceField as string); - - const conditionMet = dependency.when(watchedValue, currentFieldValue); - - switch (dependency.type) { - case DependencyType.DISABLES: - if (conditionMet) { - isDisabled = true; - } - break; - case DependencyType.REQUIRES: - if (conditionMet) { - isRequired = true; - } - break; - case DependencyType.HIDES: - if (conditionMet) { - isHidden = true; - } - break; - case DependencyType.SETS_OPTIONS: - if (conditionMet) { - overrideOptions = dependency.options; - } - break; - } - } - - return { - isDisabled, - isHidden, - isRequired, - overrideOptions, - }; -} diff --git a/apps/web/src/components/ui/auto-form/fields/array.tsx b/apps/web/src/components/ui/auto-form/fields/array.tsx deleted file mode 100644 index 0d9f9aa8..00000000 --- a/apps/web/src/components/ui/auto-form/fields/array.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { - AccordionContent, - AccordionItem, - AccordionTrigger, -} from '@/components/ui/accordion'; -import { Button } from '@/components/ui/button'; -import { Separator } from '@/components/ui/separator'; -import { Plus, Trash } from 'lucide-react'; -import { useFieldArray, useForm } from 'react-hook-form'; -import * as z from 'zod'; -import { beautifyObjectName } from '../utils'; -import AutoFormObject from './object'; - -export default function AutoFormArray({ - name, - item, - form, - path = [], - fieldConfig, -}: { - name: string; - item: z.ZodArray; - form: ReturnType; - path?: string[]; - fieldConfig?: any; -}) { - const { fields, append, remove } = useFieldArray({ - control: form.control, - name, - }); - const title = item._def.description ?? beautifyObjectName(name); - - return ( - - {title} - - {fields.map((_field, index) => { - const key = [...path, index.toString()].join('.'); - return ( -
- } - form={form} - fieldConfig={fieldConfig} - path={[...path, index.toString()]} - /> -
- -
- - -
- ); - })} - -
-
- ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/checkbox.tsx b/apps/web/src/components/ui/auto-form/fields/checkbox.tsx deleted file mode 100644 index 2946f48f..00000000 --- a/apps/web/src/components/ui/auto-form/fields/checkbox.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Checkbox } from '@/components/ui/checkbox'; -import { FormControl, FormItem } from '@/components/ui/form'; -import AutoFormLabel from '../common/label'; -import AutoFormTooltip from '../common/tooltip'; -import { AutoFormInputComponentProps } from '../types'; - -export default function AutoFormCheckbox({ - label, - isRequired, - field, - fieldConfigItem, - fieldProps, -}: AutoFormInputComponentProps) { - return ( -
- -
- - - - -
-
- -
- ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/date.tsx b/apps/web/src/components/ui/auto-form/fields/date.tsx deleted file mode 100644 index 28f60866..00000000 --- a/apps/web/src/components/ui/auto-form/fields/date.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { DatePicker } from '@/components/ui/date-picker'; -import { FormControl, FormItem, FormMessage } from '@/components/ui/form'; -import AutoFormLabel from '../common/label'; -import AutoFormTooltip from '../common/tooltip'; -import { AutoFormInputComponentProps } from '../types'; - -export default function AutoFormDate({ - label, - isRequired, - field, - fieldConfigItem, - fieldProps, -}: AutoFormInputComponentProps) { - return ( - - - - - - - - - - ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/enum.tsx b/apps/web/src/components/ui/auto-form/fields/enum.tsx deleted file mode 100644 index e2424f08..00000000 --- a/apps/web/src/components/ui/auto-form/fields/enum.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { FormControl, FormItem, FormMessage } from '@/components/ui/form'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import * as z from 'zod'; -import AutoFormLabel from '../common/label'; -import AutoFormTooltip from '../common/tooltip'; -import { AutoFormInputComponentProps } from '../types'; -import { getBaseSchema } from '../utils'; - -const isLabelValue = (value: any): value is { label: string; value: string } => - typeof value === 'object' && 'label' in value && 'value' in value; - -export default function AutoFormEnum({ - label, - isRequired, - field, - fieldConfigItem, - zodItem, - fieldProps, -}: AutoFormInputComponentProps) { - const baseValues = - fieldConfigItem?.inputProps?.values ?? - (getBaseSchema(zodItem) as unknown as z.ZodEnum)._def.values; - - let values: [string, string][] = []; - if (!Array.isArray(baseValues)) { - values = Object.entries(baseValues); - } else { - values = baseValues.map((value) => [value, value]); - } - - function findItem(value: any) { - return values.find((item) => item[0] === value); - } - - return ( - - - - - - - - - ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/file.tsx b/apps/web/src/components/ui/auto-form/fields/file.tsx deleted file mode 100644 index d51c1ef5..00000000 --- a/apps/web/src/components/ui/auto-form/fields/file.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { FormControl, FormItem, FormMessage } from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import { Trash2 } from 'lucide-react'; -import { ChangeEvent, useState } from 'react'; -import AutoFormLabel from '../common/label'; -import AutoFormTooltip from '../common/tooltip'; -import { AutoFormInputComponentProps } from '../types'; -export default function AutoFormFile({ - label, - isRequired, - fieldConfigItem, - fieldProps, - field, -}: AutoFormInputComponentProps) { - const { showLabel: _showLabel, ...fieldPropsWithoutShowLabel } = fieldProps; - const showLabel = _showLabel === undefined ? true : _showLabel; - const [file, setFile] = useState(null); - const [fileName, setFileName] = useState(null); - const handleFileChange = (e: ChangeEvent) => { - const file = e.target.files?.[0]; - - if (file) { - const reader = new FileReader(); - reader.onloadend = () => { - setFile(reader.result as string); - setFileName(file.name); - field.onChange(reader.result as string); - }; - reader.readAsDataURL(file); - } - }; - - const handleRemoveClick = () => { - setFile(null); - }; - - return ( - - {showLabel && } - {!file && ( - - - - )} - {file && ( -
-

{fileName}

- -
- )} - - -
- ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/input.tsx b/apps/web/src/components/ui/auto-form/fields/input.tsx deleted file mode 100644 index 10f01e26..00000000 --- a/apps/web/src/components/ui/auto-form/fields/input.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { FormControl, FormItem, FormMessage } from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import AutoFormLabel from '../common/label'; -import AutoFormTooltip from '../common/tooltip'; -import { AutoFormInputComponentProps } from '../types'; - -export default function AutoFormInput({ - label, - isRequired, - fieldConfigItem, - fieldProps, -}: AutoFormInputComponentProps) { - const { showLabel: _showLabel, ...fieldPropsWithoutShowLabel } = fieldProps; - const showLabel = _showLabel === undefined ? true : _showLabel; - const type = fieldProps.type || 'text'; - - return ( -
- - {showLabel && } - - - - - - -
- ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/number.tsx b/apps/web/src/components/ui/auto-form/fields/number.tsx deleted file mode 100644 index bdf21e54..00000000 --- a/apps/web/src/components/ui/auto-form/fields/number.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { FormControl, FormItem, FormMessage } from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; -import AutoFormLabel from '../common/label'; -import AutoFormTooltip from '../common/tooltip'; -import { AutoFormInputComponentProps } from '../types'; - -export default function AutoFormNumber({ - label, - isRequired, - fieldConfigItem, - fieldProps, -}: AutoFormInputComponentProps) { - const { showLabel: _showLabel, ...fieldPropsWithoutShowLabel } = fieldProps; - const showLabel = _showLabel === undefined ? true : _showLabel; - - return ( - - {showLabel && } - - - - - - - ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/object.tsx b/apps/web/src/components/ui/auto-form/fields/object.tsx deleted file mode 100644 index b6948e63..00000000 --- a/apps/web/src/components/ui/auto-form/fields/object.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from '@/components/ui/accordion'; -import { FormField } from '@/components/ui/form'; -import { useForm, useFormContext } from 'react-hook-form'; -import * as z from 'zod'; -import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from '../config'; -import resolveDependencies from '../dependencies'; -import { Dependency, FieldConfig, FieldConfigItem } from '../types'; -import { - beautifyObjectName, - getBaseSchema, - getBaseType, - zodToHtmlInputProps, -} from '../utils'; -import AutoFormArray from './array'; - -function DefaultParent({ children }: { children: React.ReactNode }) { - return <>{children}; -} - -export default function AutoFormObject< - SchemaType extends z.ZodObject, ->({ - schema, - form, - fieldConfig, - path = [], - dependencies = [], -}: { - schema: SchemaType | z.ZodEffects; - form: ReturnType; - fieldConfig?: FieldConfig>; - path?: string[]; - dependencies?: Dependency>[]; -}) { - const { watch } = useFormContext(); // Use useFormContext to access the watch function - - if (!schema) { - return null; - } - const { shape } = getBaseSchema(schema) || {}; - - if (!shape) { - return null; - } - - const handleIfZodNumber = (item: z.ZodAny) => { - const isZodNumber = (item as any)._def.typeName === 'ZodNumber'; - const isInnerZodNumber = - (item._def as any).innerType?._def?.typeName === 'ZodNumber'; - - if (isZodNumber) { - (item as any)._def.coerce = true; - } else if (isInnerZodNumber) { - (item._def as any).innerType._def.coerce = true; - } - - return item; - }; - - return ( - - {Object.keys(shape).map((name) => { - let item = shape[name] as z.ZodAny; - item = handleIfZodNumber(item) as z.ZodAny; - const zodBaseType = getBaseType(item); - const itemName = item._def.description ?? beautifyObjectName(name); - const key = [...path, name].join('.'); - - const { - isHidden, - isDisabled, - isRequired: isRequiredByDependency, - overrideOptions, - } = resolveDependencies(dependencies, name, watch); - if (isHidden) { - return null; - } - - if (zodBaseType === 'ZodObject') { - return ( - - {itemName} - - } - form={form} - fieldConfig={ - (fieldConfig?.[name] ?? {}) as FieldConfig< - z.infer - > - } - path={[...path, name]} - /> - - - ); - } - if (zodBaseType === 'ZodArray') { - return ( - } - form={form} - fieldConfig={fieldConfig?.[name] ?? {}} - path={[...path, name]} - /> - ); - } - - const fieldConfigItem: FieldConfigItem = fieldConfig?.[name] ?? {}; - const zodInputProps = zodToHtmlInputProps(item); - const isRequired = - isRequiredByDependency || - zodInputProps.required || - fieldConfigItem.inputProps?.required || - false; - - if (overrideOptions) { - item = z.enum(overrideOptions) as unknown as z.ZodAny; - } - - return ( - { - const inputType = - fieldConfigItem.fieldType ?? - DEFAULT_ZOD_HANDLERS[zodBaseType] ?? - 'fallback'; - - const InputComponent = - typeof inputType === 'function' - ? inputType - : INPUT_COMPONENTS[inputType]; - - const ParentElement = - fieldConfigItem.renderParent ?? DefaultParent; - - const defaultValue = fieldConfigItem.inputProps?.defaultValue; - const value = field.value ?? defaultValue ?? ''; - - const fieldProps = { - ...zodToHtmlInputProps(item), - ...field, - ...fieldConfigItem.inputProps, - disabled: isDisabled, - ref: undefined, - value: value, - }; - - if (InputComponent === undefined) { - return <>; - } - - return ( - - - - ); - }} - /> - ); - })} - - ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/radio-group.tsx b/apps/web/src/components/ui/auto-form/fields/radio-group.tsx deleted file mode 100644 index a983e6f5..00000000 --- a/apps/web/src/components/ui/auto-form/fields/radio-group.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { - FormControl, - FormItem, - FormLabel, - FormMessage, -} from '@/components/ui/form'; -import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; -import * as z from 'zod'; -import AutoFormLabel from '../common/label'; -import AutoFormTooltip from '../common/tooltip'; -import { AutoFormInputComponentProps } from '../types'; -import { getBaseSchema } from '../utils'; - -export default function AutoFormRadioGroup({ - label, - isRequired, - field, - zodItem, - fieldProps, - fieldConfigItem, -}: AutoFormInputComponentProps) { - const baseValues = (getBaseSchema(zodItem) as unknown as z.ZodEnum)._def - .values; - - let values: string[] = []; - if (!Array.isArray(baseValues)) { - values = Object.entries(baseValues).map((item) => item[0]); - } else { - values = baseValues; - } - - return ( -
- - - - - {values?.map((value: any) => ( - - - - - {value} - - ))} - - - - - -
- ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/switch.tsx b/apps/web/src/components/ui/auto-form/fields/switch.tsx deleted file mode 100644 index 7ced7d9d..00000000 --- a/apps/web/src/components/ui/auto-form/fields/switch.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { FormControl, FormItem } from '@/components/ui/form'; -import { Switch } from '@/components/ui/switch'; -import AutoFormLabel from '../common/label'; -import AutoFormTooltip from '../common/tooltip'; -import { AutoFormInputComponentProps } from '../types'; - -export default function AutoFormSwitch({ - label, - isRequired, - field, - fieldConfigItem, - fieldProps, -}: AutoFormInputComponentProps) { - return ( -
- -
- - - - -
-
- -
- ); -} diff --git a/apps/web/src/components/ui/auto-form/fields/textarea.tsx b/apps/web/src/components/ui/auto-form/fields/textarea.tsx deleted file mode 100644 index 8f14248f..00000000 --- a/apps/web/src/components/ui/auto-form/fields/textarea.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { FormControl, FormItem, FormMessage } from '@/components/ui/form'; -import { Textarea } from '@/components/ui/textarea'; -import AutoFormLabel from '../common/label'; -import AutoFormTooltip from '../common/tooltip'; -import { AutoFormInputComponentProps } from '../types'; - -export default function AutoFormTextarea({ - label, - isRequired, - fieldConfigItem, - fieldProps, -}: AutoFormInputComponentProps) { - const { showLabel: _showLabel, ...fieldPropsWithoutShowLabel } = fieldProps; - const showLabel = _showLabel === undefined ? true : _showLabel; - return ( - - {showLabel && } - -