refactor: update validation rule types for survey elements

- Replace TValidationRule with specific validation rule types for each survey element in their respective forms.
- Ensure type safety by introducing TValidationRulesFor* types for Address, Cal, Consent, Contact Info, CTA, Date, File Upload, Matrix, Multiple Choice, NPS, Open Text, Picture Selection, Ranking, and Rating elements.
- Update the ZSurveyElement definitions to include the new validation rules schemas.
This commit is contained in:
Dhruwang
2026-01-08 15:14:29 +05:30
parent a32241d7c8
commit 9b0cf5f532
16 changed files with 301 additions and 46 deletions

View File

@@ -4,9 +4,9 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon } from "lucide-react";
import { type JSX, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { TSurveyElementTypeEnum, TSurveyAddressElement } from "@formbricks/types/surveys/elements";
import { TSurveyAddressElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForAddress } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -165,7 +165,7 @@ export const AddressElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.Address}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForAddress) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -3,9 +3,9 @@
import { PlusIcon } from "lucide-react";
import { type JSX, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { TSurveyElementTypeEnum, TSurveyCalElement } from "@formbricks/types/surveys/elements";
import { TSurveyCalElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForCal } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -149,7 +149,7 @@ export const CalElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.Cal}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForCal) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -4,9 +4,9 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon } from "lucide-react";
import { type JSX } from "react";
import { useTranslation } from "react-i18next";
import { TSurveyElementTypeEnum, TSurveyConsentElement } from "@formbricks/types/surveys/elements";
import { TSurveyConsentElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForConsent } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -108,7 +108,7 @@ export const ConsentElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.Consent}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForConsent) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -4,9 +4,9 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon } from "lucide-react";
import { type JSX, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { TSurveyElementTypeEnum, TSurveyContactInfoElement } from "@formbricks/types/surveys/elements";
import { TSurveyContactInfoElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForContactInfo } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -162,7 +162,7 @@ export const ContactInfoElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.ContactInfo}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForContactInfo) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -4,9 +4,9 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon } from "lucide-react";
import { type JSX } from "react";
import { useTranslation } from "react-i18next";
import { TSurveyElementTypeEnum, TSurveyCTAElement } from "@formbricks/types/surveys/elements";
import { TSurveyCTAElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForCTA } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -148,7 +148,7 @@ export const CTAElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.CTA}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForCTA) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -4,9 +4,9 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { PlusIcon } from "lucide-react";
import { type JSX } from "react";
import { useTranslation } from "react-i18next";
import { TSurveyElementTypeEnum, TSurveyDateElement } from "@formbricks/types/surveys/elements";
import { TSurveyDateElement, TSurveyElementTypeEnum } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForDate } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -132,7 +132,7 @@ export const DateElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.Date}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForDate) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -10,7 +10,7 @@ import { useTranslation } from "react-i18next";
import { TAllowedFileExtension, ZAllowedFileExtension } from "@formbricks/types/storage";
import { TSurveyElementTypeEnum, TSurveyFileUploadElement } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForFileUpload } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -231,7 +231,7 @@ export const FileUploadElementForm = ({
updateElement(elementIdx, { maxSizeInMB: parseInt(e.target.value, 10) });
}}
className="ml-2 mr-2 inline w-20 bg-white text-center text-sm"
className="mr-2 ml-2 inline w-20 bg-white text-center text-sm"
/>
MB
</p>
@@ -296,7 +296,7 @@ export const FileUploadElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.FileUpload}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForFileUpload) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -11,7 +11,7 @@ import { useTranslation } from "react-i18next";
import { TI18nString } from "@formbricks/types/i18n";
import { TSurveyElementTypeEnum, TSurveyMatrixElement } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForMatrix } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -353,7 +353,7 @@ export const MatrixElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.Matrix}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForMatrix) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -12,7 +12,10 @@ import { getLanguageLabel } from "@formbricks/i18n-utils/src/utils";
import { TI18nString } from "@formbricks/types/i18n";
import { TSurveyElementTypeEnum, TSurveyMultipleChoiceElement } from "@formbricks/types/surveys/elements";
import { TShuffleOption, TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import {
TValidationRulesForMultipleChoiceMulti,
TValidationRulesForMultipleChoiceSingle,
} from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -405,7 +408,7 @@ export const MultipleChoiceElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.MultipleChoiceMulti}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForMultipleChoiceMulti) => {
updateElement(elementIdx, {
validationRules: rules,
});
@@ -415,7 +418,7 @@ export const MultipleChoiceElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.MultipleChoiceSingle}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForMultipleChoiceSingle) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -6,7 +6,7 @@ import { type JSX } from "react";
import { useTranslation } from "react-i18next";
import { TSurveyElementTypeEnum, TSurveyNPSElement } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForNPS } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -146,7 +146,7 @@ export const NPSElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.NPS}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForNPS) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -10,7 +10,7 @@ import {
TSurveyOpenTextElementInputType,
} from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForOpenText } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -146,7 +146,7 @@ export const OpenElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.OpenText}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForOpenText) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -7,7 +7,7 @@ import { type JSX } from "react";
import { useTranslation } from "react-i18next";
import { TSurveyElementTypeEnum, TSurveyPictureSelectionElement } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForPictureSelection } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { cn } from "@/lib/cn";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
@@ -176,7 +176,7 @@ export const PictureSelectionForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.PictureSelection}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForPictureSelection) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -10,7 +10,7 @@ import { useTranslation } from "react-i18next";
import { TI18nString } from "@formbricks/types/i18n";
import { TSurveyElementTypeEnum, TSurveyRankingElement } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForRanking } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -252,7 +252,7 @@ export const RankingElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.Ranking}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForRanking) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -5,7 +5,7 @@ import { HashIcon, PlusIcon, SmileIcon, StarIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import { TSurveyElementTypeEnum, TSurveyRatingElement } from "@formbricks/types/surveys/elements";
import { TSurvey } from "@formbricks/types/surveys/types";
import { TValidationRule } from "@formbricks/types/surveys/validation-rules";
import { TValidationRulesForRating } from "@formbricks/types/surveys/validation-rules";
import { TUserLocale } from "@formbricks/types/user";
import { createI18nString, extractLanguageCodes } from "@/lib/i18n/utils";
import { ElementFormInput } from "@/modules/survey/components/element-form-input";
@@ -195,7 +195,7 @@ export const RatingElementForm = ({
<ValidationRulesEditor
elementType={TSurveyElementTypeEnum.Rating}
validationRules={element.validationRules ?? []}
onUpdateRules={(rules: TValidationRule[]) => {
onUpdateRules={(rules: TValidationRulesForRating) => {
updateElement(elementIdx, {
validationRules: rules,
});

View File

@@ -3,7 +3,22 @@ import { ZUrl } from "../common";
import { ZI18nString } from "../i18n";
import { ZAllowedFileExtension } from "../storage";
import { FORBIDDEN_IDS } from "./validation";
import { TValidationRule, ZValidationRules } from "./validation-rules";
import {
ZValidationRulesForAddress,
ZValidationRulesForCal,
ZValidationRulesForConsent,
ZValidationRulesForContactInfo,
ZValidationRulesForDate,
ZValidationRulesForFileUpload,
ZValidationRulesForMatrix,
ZValidationRulesForMultipleChoiceMulti,
ZValidationRulesForMultipleChoiceSingle,
ZValidationRulesForNPS,
ZValidationRulesForOpenText,
ZValidationRulesForPictureSelection,
ZValidationRulesForRanking,
ZValidationRulesForRating,
} from "./validation-rules";
// Element Type Enum (same as question types)
export enum TSurveyElementTypeEnum {
@@ -50,9 +65,8 @@ export const ZSurveyElementId = z.string().superRefine((id, ctx) => {
export type TSurveyElementId = z.infer<typeof ZSurveyElementId>;
const ZValidationRulesSafe: z.ZodType<TValidationRule[]> = z.lazy(() => ZValidationRules);
// Base element (like ZSurveyQuestionBase but WITHOUT logic, buttonLabel, backButtonLabel)
// Note: validationRules is not included in base - each element type will add its own narrowed schema
export const ZSurveyElementBase = z.object({
id: ZSurveyElementId,
type: z.nativeEnum(TSurveyElementTypeEnum),
@@ -64,7 +78,6 @@ export const ZSurveyElementBase = z.object({
scale: z.enum(["number", "smiley", "star"]).optional(),
range: z.union([z.literal(5), z.literal(3), z.literal(4), z.literal(7), z.literal(10)]).optional(),
isDraft: z.boolean().optional(),
validationRules: ZValidationRulesSafe.optional(),
});
// OpenText Element
@@ -84,6 +97,7 @@ export const ZSurveyOpenTextElement = ZSurveyElementBase.extend({
max: z.number().optional(),
})
.default({ enabled: false }),
validationRules: ZValidationRulesForOpenText.optional(),
}).superRefine((data, ctx) => {
if (data.charLimit.enabled && data.charLimit.min === undefined && data.charLimit.max === undefined) {
ctx.addIssue({
@@ -120,6 +134,7 @@ export type TSurveyOpenTextElement = z.infer<typeof ZSurveyOpenTextElement>;
export const ZSurveyConsentElement = ZSurveyElementBase.extend({
type: z.literal(TSurveyElementTypeEnum.Consent),
label: ZI18nString,
validationRules: ZValidationRulesForConsent.optional(),
});
export type TSurveyConsentElement = z.infer<typeof ZSurveyConsentElement>;
@@ -135,18 +150,34 @@ export type TSurveyElementChoice = z.infer<typeof ZSurveyElementChoice>;
export const ZShuffleOption = z.enum(["none", "all", "exceptLast"]);
export type TShuffleOption = z.infer<typeof ZShuffleOption>;
export const ZSurveyMultipleChoiceElement = ZSurveyElementBase.extend({
type: z.union([
z.literal(TSurveyElementTypeEnum.MultipleChoiceSingle),
z.literal(TSurveyElementTypeEnum.MultipleChoiceMulti),
]),
// Multiple Choice Single Element
export const ZSurveyMultipleChoiceSingleElement = ZSurveyElementBase.extend({
type: z.literal(TSurveyElementTypeEnum.MultipleChoiceSingle),
choices: z
.array(ZSurveyElementChoice)
.min(2, { message: "Multiple Choice Element must have at least two choices" }),
shuffleOption: ZShuffleOption.optional(),
otherOptionPlaceholder: ZI18nString.optional(),
validationRules: ZValidationRulesForMultipleChoiceSingle.optional(),
});
// Multiple Choice Multi Element
export const ZSurveyMultipleChoiceMultiElement = ZSurveyElementBase.extend({
type: z.literal(TSurveyElementTypeEnum.MultipleChoiceMulti),
choices: z
.array(ZSurveyElementChoice)
.min(2, { message: "Multiple Choice Element must have at least two choices" }),
shuffleOption: ZShuffleOption.optional(),
otherOptionPlaceholder: ZI18nString.optional(),
validationRules: ZValidationRulesForMultipleChoiceMulti.optional(),
});
// Union type for Multiple Choice Elements
export const ZSurveyMultipleChoiceElement = z.union([
ZSurveyMultipleChoiceSingleElement,
ZSurveyMultipleChoiceMultiElement,
]);
export type TSurveyMultipleChoiceElement = z.infer<typeof ZSurveyMultipleChoiceElement>;
// NPS Element
@@ -155,6 +186,7 @@ export const ZSurveyNPSElement = ZSurveyElementBase.extend({
lowerLabel: ZI18nString.optional(),
upperLabel: ZI18nString.optional(),
isColorCodingEnabled: z.boolean().optional().default(false),
validationRules: ZValidationRulesForNPS.optional(),
});
export type TSurveyNPSElement = z.infer<typeof ZSurveyNPSElement>;
@@ -206,6 +238,7 @@ export const ZSurveyRatingElement = ZSurveyElementBase.extend({
lowerLabel: ZI18nString.optional(),
upperLabel: ZI18nString.optional(),
isColorCodingEnabled: z.boolean().optional().default(false),
validationRules: ZValidationRulesForRating.optional(),
});
export type TSurveyRatingElement = z.infer<typeof ZSurveyRatingElement>;
@@ -224,6 +257,7 @@ export const ZSurveyPictureSelectionElement = ZSurveyElementBase.extend({
choices: z
.array(ZSurveyPictureChoice)
.min(2, { message: "Picture Selection element must have a minimum of 2 choices" }),
validationRules: ZValidationRulesForPictureSelection.optional(),
});
export type TSurveyPictureSelectionElement = z.infer<typeof ZSurveyPictureSelectionElement>;
@@ -233,6 +267,7 @@ export const ZSurveyDateElement = ZSurveyElementBase.extend({
type: z.literal(TSurveyElementTypeEnum.Date),
html: ZI18nString.optional(),
format: z.enum(["M-d-y", "d-M-y", "y-M-d"]),
validationRules: ZValidationRulesForDate.optional(),
});
export type TSurveyDateElement = z.infer<typeof ZSurveyDateElement>;
@@ -243,6 +278,7 @@ export const ZSurveyFileUploadElement = ZSurveyElementBase.extend({
allowMultipleFiles: z.boolean(),
maxSizeInMB: z.number().optional(),
allowedFileExtensions: z.array(ZAllowedFileExtension).optional(),
validationRules: ZValidationRulesForFileUpload.optional(),
});
export type TSurveyFileUploadElement = z.infer<typeof ZSurveyFileUploadElement>;
@@ -252,6 +288,7 @@ export const ZSurveyCalElement = ZSurveyElementBase.extend({
type: z.literal(TSurveyElementTypeEnum.Cal),
calUserName: z.string().min(1, { message: "Cal user name is required" }),
calHost: z.string().optional(),
validationRules: ZValidationRulesForCal.optional(),
});
export type TSurveyCalElement = z.infer<typeof ZSurveyCalElement>;
@@ -269,6 +306,7 @@ export const ZSurveyMatrixElement = ZSurveyElementBase.extend({
rows: z.array(ZSurveyMatrixElementChoice),
columns: z.array(ZSurveyMatrixElementChoice),
shuffleOption: ZShuffleOption.optional().default("none"),
validationRules: ZValidationRulesForMatrix.optional(),
});
export type TSurveyMatrixElement = z.infer<typeof ZSurveyMatrixElement>;
@@ -290,6 +328,7 @@ export const ZSurveyAddressElement = ZSurveyElementBase.extend({
state: ZToggleInputConfig,
zip: ZToggleInputConfig,
country: ZToggleInputConfig,
validationRules: ZValidationRulesForAddress.optional(),
});
export type TSurveyAddressElement = z.infer<typeof ZSurveyAddressElement>;
@@ -303,6 +342,7 @@ export const ZSurveyRankingElement = ZSurveyElementBase.extend({
.max(25, { message: "Ranking Element can have at most 25 options" }),
otherOptionPlaceholder: ZI18nString.optional(),
shuffleOption: ZShuffleOption.optional(),
validationRules: ZValidationRulesForRanking.optional(),
});
export type TSurveyRankingElement = z.infer<typeof ZSurveyRankingElement>;
@@ -315,6 +355,7 @@ export const ZSurveyContactInfoElement = ZSurveyElementBase.extend({
email: ZToggleInputConfig,
phone: ZToggleInputConfig,
company: ZToggleInputConfig,
validationRules: ZValidationRulesForContactInfo.optional(),
});
export type TSurveyContactInfoElement = z.infer<typeof ZSurveyContactInfoElement>;
@@ -323,7 +364,8 @@ export type TSurveyContactInfoElement = z.infer<typeof ZSurveyContactInfoElement
export const ZSurveyElement = z.union([
ZSurveyOpenTextElement,
ZSurveyConsentElement,
ZSurveyMultipleChoiceElement,
ZSurveyMultipleChoiceSingleElement,
ZSurveyMultipleChoiceMultiElement,
ZSurveyNPSElement,
ZSurveyCTAElement,
ZSurveyRatingElement,

View File

@@ -248,3 +248,213 @@ export type TValidationRulesForAddress = TValidationRulesForElementType<typeof A
export type TValidationRulesForContactInfo = TValidationRulesForElementType<typeof CONTACT_INFO_RULES>;
export type TValidationRulesForCal = TValidationRulesForElementType<typeof CAL_RULES>;
export type TValidationRulesForCTA = TValidationRulesForElementType<typeof CTA_RULES>;
// Element-specific validation rules schemas (manually created for type safety)
// These are narrowed versions of ZValidationRule that only include applicable rule types
export const ZValidationRulesForOpenText: z.ZodType<TValidationRulesForOpenText> = z.array(
z.discriminatedUnion("type", [
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("minLength"),
params: ZValidationRuleParamsMinLength,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("maxLength"),
params: ZValidationRuleParamsMaxLength,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("pattern"),
params: ZValidationRuleParamsPattern,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("email"),
params: ZValidationRuleParamsEmail,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("url"),
params: ZValidationRuleParamsUrl,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("phone"),
params: ZValidationRuleParamsPhone,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("minValue"),
params: ZValidationRuleParamsMinValue,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("maxValue"),
params: ZValidationRuleParamsMaxValue,
customErrorMessage: ZI18nString.optional(),
}),
])
);
export const ZValidationRulesForMultipleChoiceSingle: z.ZodType<TValidationRulesForMultipleChoiceSingle> =
z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForMultipleChoiceMulti: z.ZodType<TValidationRulesForMultipleChoiceMulti> =
z.array(
z.discriminatedUnion("type", [
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("minSelections"),
params: ZValidationRuleParamsMinSelections,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("maxSelections"),
params: ZValidationRuleParamsMaxSelections,
customErrorMessage: ZI18nString.optional(),
}),
])
);
export const ZValidationRulesForRating: z.ZodType<TValidationRulesForRating> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForNPS: z.ZodType<TValidationRulesForNPS> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForDate: z.ZodType<TValidationRulesForDate> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForConsent: z.ZodType<TValidationRulesForConsent> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForMatrix: z.ZodType<TValidationRulesForMatrix> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForRanking: z.ZodType<TValidationRulesForRanking> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForFileUpload: z.ZodType<TValidationRulesForFileUpload> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForPictureSelection: z.ZodType<TValidationRulesForPictureSelection> = z.array(
z.discriminatedUnion("type", [
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("minSelections"),
params: ZValidationRuleParamsMinSelections,
customErrorMessage: ZI18nString.optional(),
}),
z.object({
id: z.string(),
type: z.literal("maxSelections"),
params: ZValidationRuleParamsMaxSelections,
customErrorMessage: ZI18nString.optional(),
}),
])
);
export const ZValidationRulesForAddress: z.ZodType<TValidationRulesForAddress> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForContactInfo: z.ZodType<TValidationRulesForContactInfo> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForCal: z.ZodType<TValidationRulesForCal> = z.array(
z.object({
id: z.string(),
type: z.literal("required"),
params: ZValidationRuleParamsRequired,
customErrorMessage: ZI18nString.optional(),
})
);
export const ZValidationRulesForCTA: z.ZodType<TValidationRulesForCTA> = z.array(z.never());