diff --git a/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx b/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx index 82d70c598d3..e2ec8072884 100644 --- a/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx +++ b/apps/dashboard/src/components/header-navigation/edit-bridge-url-button.tsx @@ -25,7 +25,7 @@ export const EditBridgeUrlButton = () => { handleSubmit, reset, setError, - formState: { isDirty, errors }, + formState: { isDirty }, } = form; const { currentEnvironment, setBridgeUrl } = useEnvironment(); const { status, bridgeURL: envBridgeUrl } = useBridgeHealthCheck(); @@ -86,7 +86,7 @@ export const EditBridgeUrlButton = () => { Bridge Endpoint URL - + diff --git a/apps/dashboard/src/components/primitives/form/avatar-picker.tsx b/apps/dashboard/src/components/primitives/form/avatar-picker.tsx index 98894a04a97..d313545c90f 100644 --- a/apps/dashboard/src/components/primitives/form/avatar-picker.tsx +++ b/apps/dashboard/src/components/primitives/form/avatar-picker.tsx @@ -75,7 +75,7 @@ export const AvatarPicker = forwardRef(({ n
- + ; -const InputField = React.forwardRef(({ children, className, size, state }, ref) => { - return ( -
- {children} -
- ); +const InputFieldPure = React.forwardRef( + ({ children, className, size, state }, ref) => { + return ( +
+ {children} +
+ ); + } +); + +InputFieldPure.displayName = 'InputFieldPure'; + +export type InputFieldProps = Omit; + +const InputField = React.forwardRef(({ ...props }, ref) => { + const { error } = useFormField(); + + return ; }); InputField.displayName = 'InputField'; @@ -80,4 +93,4 @@ const Input = React.forwardRef(({ className, type, }); Input.displayName = 'Input'; -export { Input, InputField }; +export { Input, InputField, InputFieldPure }; diff --git a/apps/dashboard/src/components/primitives/tag-input.tsx b/apps/dashboard/src/components/primitives/tag-input.tsx index 2e0a1a67c04..f90e85deb6a 100644 --- a/apps/dashboard/src/components/primitives/tag-input.tsx +++ b/apps/dashboard/src/components/primitives/tag-input.tsx @@ -20,7 +20,10 @@ const TagInput = forwardRef((props, ref) => { const [tags, setTags] = useState(value); const [inputValue, setInputValue] = useState(''); const [isOpen, setIsOpen] = useState(false); - const validSuggestions = useMemo(() => suggestions.filter((suggestion) => !tags.includes(suggestion)), [tags]); + const validSuggestions = useMemo( + () => suggestions.filter((suggestion) => !tags.includes(suggestion)), + [tags, suggestions] + ); useEffect(() => { setTags(value); @@ -55,7 +58,7 @@ const TagInput = forwardRef((props, ref) => { return ( -
+
& VariantProps; +export type TextareaPureProps = React.TextareaHTMLAttributes & + VariantProps; -const Textarea = React.forwardRef( +const TextareaPure = React.forwardRef( ({ className, state, size, maxLength, ...props }, ref) => { return ( <> @@ -45,6 +47,15 @@ const Textarea = React.forwardRef( ); } ); +TextareaPure.displayName = 'TextareaPure'; + +export type TextareaProps = Omit; + +const Textarea = React.forwardRef(({ ...props }, ref) => { + const { error } = useFormField(); + + return ; +}); Textarea.displayName = 'Textarea'; export { Textarea }; diff --git a/apps/dashboard/src/components/workflow-editor/schema.ts b/apps/dashboard/src/components/workflow-editor/schema.ts index adddaa2e4b1..f5d197978c4 100644 --- a/apps/dashboard/src/components/workflow-editor/schema.ts +++ b/apps/dashboard/src/components/workflow-editor/schema.ts @@ -1,7 +1,6 @@ import * as z from 'zod'; import type { JSONSchemaDefinition } from '@novu/shared'; import { StepTypeEnum } from '@/utils/enums'; -import { capitalize } from '@/utils/string'; const enabledSchema = z.object({ enabled: z.boolean(), @@ -90,12 +89,12 @@ export const buildDynamicFormSchema = ({ const isRequired = requiredFields.includes(key); let zodValue: z.ZodString | z.ZodNumber | z.ZodOptional; if (value.type === 'string') { - zodValue = z.string().min(1, `${capitalize(key)} is required`); + zodValue = z.string().min(1); if (value.format === 'email') { - zodValue = zodValue.email(`${capitalize(key)} must be a valid email`); + zodValue = zodValue.email(); } } else { - zodValue = z.number().min(1, `${capitalize(key)} is required`); + zodValue = z.number().min(1); } if (!isRequired) { zodValue = zodValue.optional(); @@ -114,7 +113,7 @@ export const buildDynamicFormSchema = ({ try { return JSON.parse(str); } catch (e) { - ctx.addIssue({ code: 'custom', message: 'Invalid payload. Payload needs to be a valid JSON.' }); + ctx.addIssue({ code: 'custom', message: 'Payload must be valid JSON' }); return z.NEVER; } }), diff --git a/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx b/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx index 9d472e21e53..4960ed9d64d 100644 --- a/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx +++ b/apps/dashboard/src/components/workflow-editor/steps/controls/text-widget.tsx @@ -9,11 +9,7 @@ import { useFormContext } from 'react-hook-form'; export function TextWidget(props: WidgetProps) { const { label, readonly, name } = props; - - const { - control, - formState: { errors }, - } = useFormContext(); + const { control } = useFormContext(); return ( {capitalize(label)} - + { - const { - control, - formState: { errors }, - } = useFormContext(); + const { control } = useFormContext(); const { step } = useStepEditorContext(); const variables = useMemo(() => (step ? parseStepVariablesToLiquidVariables(step.variables) : []), [step]); @@ -28,7 +25,7 @@ export const InAppBody = () => { render={({ field }) => ( - + { - const { - control, - formState: { errors }, - } = useFormContext(); + const { control } = useFormContext(); const { step } = useStepEditorContext(); const variables = useMemo(() => (step ? parseStepVariablesToLiquidVariables(step.variables) : []), [step]); @@ -26,7 +23,7 @@ export const InAppSubject = () => { control={control} name={subjectKey} render={({ field }) => ( - + }; export const TestWorkflowForm = ({ workflow }: { workflow?: WorkflowResponseDto }) => { - const { - control, - formState: { errors }, - } = useFormContext(); + const { control } = useFormContext(); const [activeSnippetTab, setActiveSnippetTab] = useState(() => workflow?.origin === WorkflowOriginEnum.EXTERNAL ? 'framework' : 'typescript' ); @@ -72,7 +69,7 @@ export const TestWorkflowForm = ({ workflow }: { workflow?: WorkflowResponseDto {capitalize(key)} - + diff --git a/apps/dashboard/src/components/workflow-editor/url-input.tsx b/apps/dashboard/src/components/workflow-editor/url-input.tsx index 12ca34254a1..de09192f4df 100644 --- a/apps/dashboard/src/components/workflow-editor/url-input.tsx +++ b/apps/dashboard/src/components/workflow-editor/url-input.tsx @@ -2,7 +2,7 @@ import { useFormContext } from 'react-hook-form'; import { Editor } from '@/components/primitives/editor'; import { FormControl, FormField, FormItem, FormMessagePure } from '@/components/primitives/form/form'; -import { Input, InputField, InputFieldProps, InputProps } from '@/components/primitives/input'; +import { Input, InputFieldProps, InputFieldPure, InputProps } from '@/components/primitives/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/primitives/select'; import { completions } from '@/utils/liquid-autocomplete'; import { LiquidVariable } from '@/utils/parseStepVariablesToLiquidVariables'; @@ -36,7 +36,7 @@ export const URLInput = ({
- + )} /> - +
diff --git a/apps/dashboard/src/utils/schema.ts b/apps/dashboard/src/utils/schema.ts index c056322720f..7ae35085ff4 100644 --- a/apps/dashboard/src/utils/schema.ts +++ b/apps/dashboard/src/utils/schema.ts @@ -15,7 +15,7 @@ type ZodValue = const handleStringFormat = ({ value, key, format }: { value: z.ZodString; key: string; format: string }) => { if (format === 'email') { - return value.email(`${capitalize(key)} must be a valid email`); + return value.email(); } else if (format === 'uri') { return value .transform((val) => (val === '' ? undefined : val)) @@ -35,10 +35,6 @@ const handleStringPattern = ({ value, key, pattern }: { value: z.ZodString; key: }); }; -const handleStringEnum = ({ key, enumValues }: { key: string; enumValues: [string, ...string[]] }) => { - return z.enum(enumValues, { message: `${capitalize(key)} must be one of ${enumValues.join(', ')}` }); -}; - const handleStringType = ({ key, format, @@ -75,12 +71,9 @@ const handleStringType = ({ pattern, }); } else if (enumValues) { - stringValue = handleStringEnum({ - key, - enumValues: enumValues as [string, ...string[]], - }); + stringValue = z.enum(enumValues as [string, ...string[]]); } else if (isRequired) { - stringValue = stringValue.min(1, `${capitalize(key)} is missing`); + stringValue = stringValue.min(1); } if (defaultValue) { @@ -123,9 +116,9 @@ export const buildDynamicZodSchema = (obj: JSONSchemaDto): z.AnyZodObject => { } else if (type === 'string') { zodValue = handleStringType({ key, requiredFields, format, pattern, enumValues, defaultValue }); } else if (type === 'boolean') { - zodValue = z.boolean(isRequired ? { message: `${capitalize(key)} is missing` } : undefined); + zodValue = z.boolean(); } else { - zodValue = z.number(isRequired ? { message: `${capitalize(key)} is missing` } : undefined); + zodValue = z.number(); if (defaultValue) { zodValue = zodValue.default(defaultValue as number); } diff --git a/apps/dashboard/src/utils/validation.ts b/apps/dashboard/src/utils/validation.ts index cf06ff2e3fb..b5ac9b5900e 100644 --- a/apps/dashboard/src/utils/validation.ts +++ b/apps/dashboard/src/utils/validation.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { capitalize } from './string'; -const getIssueField = (issue: z.ZodIssueBase) => capitalize(issue.path.join(' ')); +const getIssueField = (issue: z.ZodIssueBase) => capitalize(`${issue.path[issue.path.length - 1]}`); const pluralize = (count: number | bigint) => (count === 1 ? '' : 's'); /** @@ -13,6 +13,7 @@ const pluralize = (count: number | bigint) => (count === 1 ? '' : 's'); */ const customErrorMap: z.ZodErrorMap = (issue, ctx) => { const issueField = getIssueField(issue); + if (issue.code === z.ZodIssueCode.too_big) { if (issue.type === 'array') { return { @@ -21,7 +22,6 @@ const customErrorMap: z.ZodErrorMap = (issue, ctx) => { } if (issue.type === 'string') { - console.log({ issue }); return { message: `${issueField} must be at most ${issue.maximum} character${pluralize(issue.maximum)}`, }; @@ -39,6 +39,7 @@ const customErrorMap: z.ZodErrorMap = (issue, ctx) => { }; } } + if (issue.code === z.ZodIssueCode.too_small) { if (issue.type === 'array') { return { @@ -70,6 +71,18 @@ const customErrorMap: z.ZodErrorMap = (issue, ctx) => { }; } } + + if (issue.code === z.ZodIssueCode.invalid_string && issue.validation === 'email') { + return { + message: `${issueField} must be a valid email`, + }; + } + + if (issue.code === z.ZodIssueCode.invalid_enum_value) { + return { + message: `${issueField} must be one of ${issue.options.join(', ')}`, + }; + } return { message: ctx.defaultError }; };