chore: upgrade dependencies and stabilize prisma/zod migrations

This commit is contained in:
Matti Nannt
2026-03-05 17:09:51 +01:00
parent e9bcbf6e4c
commit ce3b66130a
184 changed files with 4930 additions and 4560 deletions

View File

@@ -12,18 +12,18 @@
},
"devDependencies": {
"@chromatic-com/storybook": "^5.0.1",
"@storybook/addon-a11y": "10.2.14",
"@storybook/addon-links": "10.2.14",
"@storybook/addon-onboarding": "10.2.14",
"@storybook/react-vite": "10.2.14",
"@storybook/addon-a11y": "10.2.15",
"@storybook/addon-links": "10.2.15",
"@storybook/addon-onboarding": "10.2.15",
"@storybook/react-vite": "10.2.15",
"@typescript-eslint/eslint-plugin": "8.56.1",
"@tailwindcss/vite": "4.2.1",
"@typescript-eslint/parser": "8.56.1",
"@vitejs/plugin-react": "5.1.4",
"eslint-plugin-react-refresh": "0.4.26",
"eslint-plugin-storybook": "10.2.14",
"storybook": "10.2.14",
"storybook": "10.2.15",
"vite": "7.3.1",
"@storybook/addon-docs": "10.2.14"
"@storybook/addon-docs": "10.2.15"
}
}

View File

@@ -1,7 +1,7 @@
import { z } from "zod";
export const ZOrganizationTeam = z.object({
id: z.string().cuid2(),
id: z.cuid2(),
name: z.string(),
});

View File

@@ -110,8 +110,8 @@ export const getResponseCountAction = authenticatedActionClient
const ZGetDisplaysWithContactAction = z.object({
surveyId: ZId,
limit: z.number().int().min(1).max(100),
offset: z.number().int().nonnegative(),
limit: z.int().min(1).max(100),
offset: z.int().nonnegative(),
});
export const getDisplaysWithContactAction = authenticatedActionClient

View File

@@ -1095,7 +1095,7 @@ export const getResponsesForSummary = reactCache(
[limit, ZOptionalNumber],
[offset, ZOptionalNumber],
[filterCriteria, ZResponseFilterCriteria.optional()],
[cursor, z.string().cuid2().optional()]
[cursor, z.cuid2().optional()]
);
const queryLimit = limit ?? RESPONSES_PER_PAGE;

View File

@@ -50,7 +50,7 @@ export const GET = withV1ApiWrapper({
{
environmentId: params.environmentId,
url: req.url,
validationError: cuidValidation.error.errors[0]?.message,
validationError: cuidValidation.error.issues[0]?.message,
},
"Invalid CUID v1 format detected"
);

View File

@@ -6,7 +6,7 @@ import { DatabaseError } from "@formbricks/types/errors";
import { validateInputs } from "@/lib/utils/validate";
export const deleteSurvey = async (surveyId: string) => {
validateInputs([surveyId, z.string().cuid2()]);
validateInputs([surveyId, z.cuid2()]);
try {
const deletedSurvey = await prisma.survey.delete({

View File

@@ -101,7 +101,9 @@ describe("verifyRecaptchaToken", () => {
},
signal: {},
};
vi.spyOn(global, "AbortController").mockImplementation(() => abortController as any);
vi.spyOn(global, "AbortController").mockImplementation(function AbortController() {
return abortController as any;
});
(global.fetch as any).mockImplementation(() => new Promise(() => {}));
verifyRecaptchaToken("token", 0.5);
vi.advanceTimersByTime(5000);

View File

@@ -1,4 +1,4 @@
import cuid2 from "@paralleldrive/cuid2";
import * as cuid2 from "@paralleldrive/cuid2";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import * as crypto from "@/lib/crypto";
import { generateSurveySingleUseId, validateSurveySingleUseId } from "./singleUseSurveys";
@@ -20,10 +20,6 @@ vi.mock("@paralleldrive/cuid2", () => {
const isCuidMock = vi.fn();
return {
default: {
createId: createIdMock,
isCuid: isCuidMock,
},
createId: createIdMock,
isCuid: isCuidMock,
};

View File

@@ -1,10 +1,10 @@
import cuid2 from "@paralleldrive/cuid2";
import { createId, isCuid } from "@paralleldrive/cuid2";
import { ENCRYPTION_KEY } from "@/lib/constants";
import { symmetricDecrypt, symmetricEncrypt } from "@/lib/crypto";
// generate encrypted single use id for the survey
export const generateSurveySingleUseId = (isEncrypted: boolean): string => {
const cuid = cuid2.createId();
const cuid = createId();
if (!isEncrypted) {
return cuid;
}
@@ -30,7 +30,7 @@ export const validateSurveySingleUseId = (surveySingleUseId: string): string | u
return undefined;
}
if (cuid2.isCuid(decryptedCuid)) {
if (isCuid(decryptedCuid)) {
return decryptedCuid;
} else {
return undefined;

View File

@@ -159,7 +159,7 @@ export const BREVO_LIST_ID = env.BREVO_LIST_ID;
export const UNSPLASH_ACCESS_KEY = env.UNSPLASH_ACCESS_KEY;
export const UNSPLASH_ALLOWED_DOMAINS = ["api.unsplash.com"];
export const STRIPE_API_VERSION = "2024-06-20";
export const STRIPE_API_VERSION = "2026-02-25.clover";
// Maximum number of attribute classes allowed:
export const MAX_ATTRIBUTE_CLASSES_PER_ENVIRONMENT = 150;

View File

@@ -71,8 +71,8 @@ export const getDisplaysBySurveyIdWithContact = reactCache(
async (surveyId: string, limit?: number, offset?: number): Promise<TDisplayWithContact[]> => {
validateInputs(
[surveyId, ZId],
[limit, z.number().int().min(1).optional()],
[offset, z.number().int().nonnegative().optional()]
[limit, z.int().min(1).optional()],
[offset, z.int().nonnegative().optional()]
);
try {

View File

@@ -14,7 +14,7 @@ export const env = createEnv({
CRON_SECRET: z.string().optional(),
BREVO_API_KEY: z.string().optional(),
BREVO_LIST_ID: z.string().optional(),
DATABASE_URL: z.string().url(),
DATABASE_URL: z.url(),
DEBUG: z.enum(["1", "0"]).optional(),
AUTH_DEFAULT_TEAM_ID: z.string().optional(),
AUTH_SKIP_INVITE_FOR_SSO: z.enum(["1", "0"]).optional(),
@@ -23,7 +23,7 @@ export const env = createEnv({
EMAIL_VERIFICATION_DISABLED: z.enum(["1", "0"]).optional(),
ENCRYPTION_KEY: z.string(),
ENTERPRISE_LICENSE_KEY: z.string().optional(),
ENVIRONMENT: z.enum(["production", "staging"]).default("production"),
ENVIRONMENT: z.enum(["production", "staging"]).prefault("production"),
GITHUB_ID: z.string().optional(),
GITHUB_SECRET: z.string().optional(),
GOOGLE_CLIENT_ID: z.string().optional(),
@@ -31,21 +31,20 @@ export const env = createEnv({
GOOGLE_SHEETS_CLIENT_ID: z.string().optional(),
GOOGLE_SHEETS_CLIENT_SECRET: z.string().optional(),
GOOGLE_SHEETS_REDIRECT_URL: z.string().optional(),
HTTP_PROXY: z.string().url().optional(),
HTTPS_PROXY: z.string().url().optional(),
HTTP_PROXY: z.url().optional(),
HTTPS_PROXY: z.url().optional(),
IMPRINT_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
IMPRINT_ADDRESS: z.string().optional(),
INVITE_DISABLED: z.enum(["1", "0"]).optional(),
CHATWOOT_WEBSITE_TOKEN: z.string().optional(),
CHATWOOT_BASE_URL: z.string().url().optional(),
CHATWOOT_BASE_URL: z.url().optional(),
IS_FORMBRICKS_CLOUD: z.enum(["1", "0"]).optional(),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error", "fatal"]).optional(),
MAIL_FROM: z.string().email().optional(),
NEXTAUTH_URL: z.string().url().optional(),
MAIL_FROM: z.email().optional(),
NEXTAUTH_URL: z.url().optional(),
NEXTAUTH_SECRET: z.string().optional(),
MAIL_FROM_NAME: z.string().optional(),
NOTION_OAUTH_CLIENT_ID: z.string().optional(),
@@ -58,10 +57,9 @@ export const env = createEnv({
REDIS_URL:
process.env.NODE_ENV === "test"
? z.string().optional()
: z.string().url("REDIS_URL is required for caching, rate limiting, and audit logging"),
: z.url("REDIS_URL is required for caching, rate limiting, and audit logging"),
PASSWORD_RESET_DISABLED: z.enum(["1", "0"]).optional(),
PRIVACY_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
@@ -86,7 +84,6 @@ export const env = createEnv({
STRIPE_SECRET_KEY: z.string().optional(),
STRIPE_WEBHOOK_SECRET: z.string().optional(),
PUBLIC_URL: z
.string()
.url()
.refine(
(url) => {
@@ -98,12 +95,11 @@ export const env = createEnv({
}
},
{
message: "PUBLIC_URL must be a valid URL with a proper host (e.g., https://example.com)",
error: "PUBLIC_URL must be a valid URL with a proper host (e.g., https://example.com)",
}
)
.optional(),
TERMS_URL: z
.string()
.url()
.optional()
.or(z.string().refine((str) => str === "")),
@@ -112,7 +108,7 @@ export const env = createEnv({
RECAPTCHA_SITE_KEY: z.string().optional(),
RECAPTCHA_SECRET_KEY: z.string().optional(),
VERCEL_URL: z.string().optional(),
WEBAPP_URL: z.string().url().optional(),
WEBAPP_URL: z.url().optional(),
UNSPLASH_ACCESS_KEY: z.string().optional(),
NODE_ENV: z.enum(["development", "production", "test"]).optional(),

View File

@@ -267,7 +267,7 @@ export const getResponses = reactCache(
[limit, ZOptionalNumber],
[offset, ZOptionalNumber],
[filterCriteria, ZResponseFilterCriteria.optional()],
[cursor, z.string().cuid2().optional()]
[cursor, z.cuid2().optional()]
);
limit = limit ?? RESPONSES_PER_PAGE;

View File

@@ -1,5 +1,5 @@
import "server-only";
import { ActionClass, Prisma } from "@prisma/client";
import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { logger } from "@formbricks/logger";
@@ -114,7 +114,12 @@ export const selectSurvey = {
slug: true,
} satisfies Prisma.SurveySelect;
export const checkTriggersValidity = (triggers: TSurvey["triggers"], actionClasses: ActionClass[]) => {
type TriggerWithActionClassId = { actionClass: { id: string } };
export const checkTriggersValidity = (
triggers: TriggerWithActionClassId[] | null | undefined,
actionClasses: Array<{ id: string }>
) => {
if (!triggers) return;
// check if all the triggers are valid
@@ -133,14 +138,14 @@ export const checkTriggersValidity = (triggers: TSurvey["triggers"], actionClass
};
export const handleTriggerUpdates = (
updatedTriggers: TSurvey["triggers"],
currentTriggers: TSurvey["triggers"],
actionClasses: ActionClass[]
updatedTriggers: TriggerWithActionClassId[] | null | undefined,
currentTriggers: TriggerWithActionClassId[] | null | undefined,
actionClasses: Array<{ id: string }>
) => {
if (!updatedTriggers) return {};
checkTriggersValidity(updatedTriggers, actionClasses);
const currentTriggerIds = currentTriggers.map((trigger) => trigger.actionClass.id);
const currentTriggerIds = (currentTriggers ?? []).map((trigger) => trigger.actionClass.id);
const updatedTriggerIds = updatedTriggers.map((trigger) => trigger.actionClass.id);
// added triggers are triggers that are not in the current triggers and are there in the new triggers
@@ -149,7 +154,7 @@ export const handleTriggerUpdates = (
);
// deleted triggers are triggers that are not in the new triggers and are there in the current triggers
const deletedTriggers = currentTriggers.filter(
const deletedTriggers = (currentTriggers ?? []).filter(
(trigger) => !updatedTriggerIds.includes(trigger.actionClass.id)
);

View File

@@ -52,7 +52,7 @@ export const getUser = reactCache(async (id: string): Promise<TUser | null> => {
});
export const getUserByEmail = reactCache(async (email: string): Promise<TUser | null> => {
validateInputs([email, z.string().email()]);
validateInputs([email, z.email()]);
try {
const user = await prisma.user.findFirst({

View File

@@ -1,4 +1,4 @@
import cuid2 from "@paralleldrive/cuid2";
import * as cuid2 from "@paralleldrive/cuid2";
import { beforeEach, describe, expect, test, vi } from "vitest";
import * as crypto from "@/lib/crypto";
import { env } from "@/lib/env";

View File

@@ -1,10 +1,10 @@
import cuid2 from "@paralleldrive/cuid2";
import { createId } from "@paralleldrive/cuid2";
import { symmetricEncrypt } from "@/lib/crypto";
import { env } from "@/lib/env";
// generate encrypted single use id for the survey
export const generateSurveySingleUseId = (isEncrypted: boolean): string => {
const cuid = cuid2.createId();
const cuid = createId();
if (!isEncrypted) {
return cuid;
}

View File

@@ -154,7 +154,7 @@ export const deleteTagOnResponseAction = authenticatedActionClient.schema(ZDelet
const ZDeleteResponseAction = z.object({
responseId: ZId,
decrementQuotas: z.boolean().default(false),
decrementQuotas: z.boolean().prefault(false),
});
export const deleteResponseAction = authenticatedActionClient.schema(ZDeleteResponseAction).action(

View File

@@ -1,22 +1,23 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const ZOverallHealthStatus = z
.object({
main_database: z.boolean().openapi({
description: "Main database connection status - true if database is reachable and running",
example: true,
}),
cache_database: z.boolean().openapi({
description: "Cache database connection status - true if cache database is reachable and running",
example: true,
}),
main_database: z
.boolean()
.meta({
example: true,
})
.describe("Main database connection status - true if database is reachable and running"),
cache_database: z
.boolean()
.meta({
example: true,
})
.describe("Cache database connection status - true if cache database is reachable and running"),
})
.openapi({
.meta({
title: "Health Check Response",
description: "Health check status for critical application dependencies",
});
})
.describe("Health check status for critical application dependencies");
export type OverallHealthStatus = z.infer<typeof ZOverallHealthStatus>;

View File

@@ -1,26 +1,22 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZContactAttributeKey } from "@formbricks/database/zod/contact-attribute-keys";
extendZodWithOpenApi(z);
export const ZContactAttributeKeyIdSchema = z
.string()
.cuid2()
.openapi({
ref: "contactAttributeKeyId",
description: "The ID of the contact attribute key",
.meta({
id: "contactAttributeKeyId",
param: {
name: "id",
in: "path",
},
});
})
.describe("The ID of the contact attribute key");
export const ZContactAttributeKeyUpdateSchema = ZContactAttributeKey.pick({
name: true,
description: true,
}).openapi({
ref: "contactAttributeKeyUpdate",
}).meta({
id: "contactAttributeKeyUpdate",
description: "A contact attribute key to update. Key cannot be changed.",
});

View File

@@ -17,7 +17,7 @@ export const getContactAttributeKeysEndpoint: ZodOpenApiOperationObject = {
description: "Gets contact attribute keys from the database.",
tags: ["Management API - Contact Attribute Keys"],
requestParams: {
query: ZGetContactAttributeKeysFilter.sourceType(),
query: ZGetContactAttributeKeysFilter,
},
responses: {
"200": {

View File

@@ -17,7 +17,7 @@ export const GET = async (request: NextRequest) =>
authenticatedApiClient({
request,
schemas: {
query: ZGetContactAttributeKeysFilter.sourceType(),
query: ZGetContactAttributeKeysFilter,
},
handler: async ({ authentication, parsedInput }) => {
const { query } = parsedInput;
@@ -49,7 +49,7 @@ export const POST = async (request: NextRequest) =>
authenticatedApiClient({
request,
schemas: {
body: ZContactAttributeKeyInput.sourceType(),
body: ZContactAttributeKeyInput,
},
handler: async ({ authentication, parsedInput, auditLog }) => {
const { body } = parsedInput;

View File

@@ -1,13 +1,10 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZContactAttributeKey } from "@formbricks/database/zod/contact-attribute-keys";
import { isSafeIdentifier } from "@/lib/utils/safe-identifier";
import { ZGetFilter } from "@/modules/api/v2/types/api-filter";
extendZodWithOpenApi(z);
export const ZGetContactAttributeKeysFilter = ZGetFilter.extend({
environmentId: z.string().cuid2().optional().describe("The environment ID to filter by"),
environmentId: z.cuid2().optional().describe("The environment ID to filter by"),
})
.refine(
(data) => {
@@ -37,15 +34,15 @@ export const ZContactAttributeKeyInput = ZContactAttributeKey.pick({
// Enforce safe identifier format for key
if (!isSafeIdentifier(data.key)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message:
"Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter",
path: ["key"],
});
}
})
.openapi({
ref: "contactAttributeKeyInput",
.meta({
id: "contactAttributeKeyInput",
description: "Input data for creating or updating a contact attribute",
});

View File

@@ -1,25 +1,21 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZResponse } from "@formbricks/database/zod/responses";
extendZodWithOpenApi(z);
export const ZResponseIdSchema = z
.string()
.cuid2()
.openapi({
ref: "responseId",
description: "The ID of the response",
.meta({
id: "responseId",
param: {
name: "id",
in: "path",
},
});
})
.describe("The ID of the response");
export const ZResponseUpdateSchema = ZResponse.omit({
id: true,
surveyId: true,
}).openapi({
ref: "responseUpdate",
}).meta({
id: "responseUpdate",
description: "A response to update.",
});

View File

@@ -13,7 +13,7 @@ export const getResponsesEndpoint: ZodOpenApiOperationObject = {
summary: "Get responses",
description: "Gets responses from the database.",
requestParams: {
query: ZGetResponsesFilter.sourceType(),
query: ZGetResponsesFilter,
},
tags: ["Management API - Responses"],
responses: {

View File

@@ -19,7 +19,7 @@ export const GET = async (request: NextRequest) =>
authenticatedApiClient({
request,
schemas: {
query: ZGetResponsesFilter.sourceType(),
query: ZGetResponsesFilter,
},
handler: async ({ authentication, parsedInput }) => {
const { query } = parsedInput;

View File

@@ -3,7 +3,7 @@ import { ZResponse } from "@formbricks/database/zod/responses";
import { ZGetFilter } from "@/modules/api/v2/types/api-filter";
export const ZGetResponsesFilter = ZGetFilter.extend({
surveyId: z.string().cuid2().optional(),
surveyId: z.cuid2().optional(),
contactId: z.string().optional(),
}).refine(
(data) => {

View File

@@ -23,7 +23,7 @@ export const getPersonalizedSurveyLink: ZodOpenApiOperationObject = {
schema: makePartialSchema(
z.object({
data: z.object({
surveyUrl: z.string().url(),
surveyUrl: z.url(),
expiresAt: z
.string()
.nullable()

View File

@@ -1,23 +1,18 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const ZContactLinkParams = z.object({
surveyId: z
.string()
.cuid2()
.openapi({
description: "The ID of the survey",
.meta({
param: { name: "surveyId", in: "path" },
}),
})
.describe("The ID of the survey"),
contactId: z
.string()
.cuid2()
.openapi({
description: "The ID of the contact",
.meta({
param: { name: "contactId", in: "path" },
}),
})
.describe("The ID of the contact"),
});
export const ZContactLinkQuery = z.object({

View File

@@ -1,24 +1,19 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZGetFilter } from "@/modules/api/v2/types/api-filter";
extendZodWithOpenApi(z);
export const ZContactLinksBySegmentParams = z.object({
surveyId: z
.string()
.cuid2()
.openapi({
description: "The ID of the survey",
.meta({
param: { name: "surveyId", in: "path" },
}),
})
.describe("The ID of the survey"),
segmentId: z
.string()
.cuid2()
.openapi({
description: "The ID of the segment",
.meta({
param: { name: "segmentId", in: "path" },
}),
})
.describe("The ID of the segment"),
});
export const ZContactLinksBySegmentQuery = ZGetFilter.pick({
@@ -30,7 +25,7 @@ export const ZContactLinksBySegmentQuery = ZGetFilter.pick({
.min(1)
.max(365)
.nullish()
.default(null)
.prefault(null)
.describe("Number of days until the generated JWT expires. If not provided, there is no expiration."),
attributeKeys: z
.string()
@@ -52,7 +47,7 @@ export type TContactWithAttributes = {
export const ZContactLinkResponse = z.object({
contactId: z.string().describe("The ID of the contact"),
surveyUrl: z.string().url().describe("Personalized survey link"),
surveyUrl: z.url().describe("Personalized survey link"),
expiresAt: z.string().nullable().describe("The date and time the link expires, null if no expiration"),
attributes: z.record(z.string(), z.string()).describe("The attributes of the contact"),
});

View File

@@ -1,16 +1,12 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const surveyIdSchema = z
.string()
.cuid2()
.openapi({
ref: "surveyId",
description: "The ID of the survey",
.meta({
id: "surveyId",
param: {
name: "id",
in: "path",
},
});
})
.describe("The ID of the survey");

View File

@@ -1,15 +1,12 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZSurveyWithoutQuestionType } from "@formbricks/database/zod/surveys";
extendZodWithOpenApi(z);
export const ZGetSurveysFilter = z
.object({
limit: z.coerce.number().positive().min(1).max(100).optional().default(10),
skip: z.coerce.number().nonnegative().optional().default(0),
sortBy: z.enum(["createdAt", "updatedAt"]).optional().default("createdAt"),
order: z.enum(["asc", "desc"]).optional().default("desc"),
limit: z.coerce.number().positive().min(1).max(100).optional().prefault(10),
skip: z.coerce.number().nonnegative().optional().prefault(0),
sortBy: z.enum(["createdAt", "updatedAt"]).optional().prefault("createdAt"),
order: z.enum(["asc", "desc"]).optional().prefault("desc"),
startDate: z.coerce.date().optional(),
endDate: z.coerce.date().optional(),
surveyType: z.enum(["link", "app"]).optional(),
@@ -23,7 +20,7 @@ export const ZGetSurveysFilter = z
return true;
},
{
message: "startDate must be before endDate",
error: "startDate must be before endDate",
}
);
@@ -69,8 +66,8 @@ export const ZSurveyInput = ZSurveyWithoutQuestionType.pick({
inlineTriggers: true,
displayPercentage: true,
})
.openapi({
ref: "surveyInput",
.meta({
id: "surveyInput",
description: "A survey input object for creating or updating surveys",
});

View File

@@ -1,20 +1,16 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZWebhook } from "@formbricks/database/zod/webhooks";
extendZodWithOpenApi(z);
export const ZWebhookIdSchema = z
.string()
.cuid2()
.openapi({
ref: "webhookId",
description: "The ID of the webhook",
.meta({
id: "webhookId",
param: {
name: "id",
in: "path",
},
});
})
.describe("The ID of the webhook");
export const ZWebhookUpdateSchema = ZWebhook.omit({
id: true,
@@ -22,7 +18,7 @@ export const ZWebhookUpdateSchema = ZWebhook.omit({
updatedAt: true,
environmentId: true,
secret: true,
}).openapi({
ref: "webhookUpdate",
}).meta({
id: "webhookUpdate",
description: "A webhook to update.",
});

View File

@@ -13,7 +13,7 @@ export const getWebhooksEndpoint: ZodOpenApiOperationObject = {
summary: "Get webhooks",
description: "Gets webhooks from the database.",
requestParams: {
query: ZGetWebhooksFilter.sourceType(),
query: ZGetWebhooksFilter,
},
tags: ["Management API - Webhooks"],
responses: {

View File

@@ -11,7 +11,7 @@ export const GET = async (request: NextRequest) =>
authenticatedApiClient({
request,
schemas: {
query: ZGetWebhooksFilter.sourceType(),
query: ZGetWebhooksFilter,
},
handler: async ({ authentication, parsedInput }) => {
const { query } = parsedInput;

View File

@@ -3,7 +3,7 @@ import { ZWebhook } from "@formbricks/database/zod/webhooks";
import { ZGetFilter } from "@/modules/api/v2/types/api-filter";
export const ZGetWebhooksFilter = ZGetFilter.extend({
surveyIds: z.array(z.string().cuid2()).optional(),
surveyIds: z.array(z.cuid2()).optional(),
}).refine(
(data) => {
if (data.startDate && data.endDate && data.startDate > data.endDate) {

View File

@@ -1,6 +1,5 @@
import * as yaml from "yaml";
import { z } from "zod";
import { createDocument, extendZodWithOpenApi } from "zod-openapi";
import { createDocument } from "zod-openapi";
import { ZApiKeyData } from "@formbricks/database/zod/api-keys";
import { ZContact } from "@formbricks/database/zod/contact";
import { ZContactAttributeKey } from "@formbricks/database/zod/contact-attribute-keys";
@@ -27,8 +26,6 @@ import { rolePaths } from "@/modules/api/v2/roles/lib/openapi";
import { bulkContactPaths } from "@/modules/ee/contacts/api/v2/management/contacts/bulk/lib/openapi";
import { contactPaths } from "@/modules/ee/contacts/api/v2/management/contacts/lib/openapi";
extendZodWithOpenApi(z);
const document = createDocument({
openapi: "3.1.0",
info: {

View File

@@ -14,7 +14,7 @@ export const getProjectTeamsEndpoint: ZodOpenApiOperationObject = {
summary: "Get project teams",
description: "Gets projectTeams from the database.",
requestParams: {
query: ZGetProjectTeamsFilter.sourceType(),
query: ZGetProjectTeamsFilter,
path: z.object({
organizationId: ZOrganizationIdSchema,
}),

View File

@@ -24,7 +24,7 @@ export async function GET(request: Request, props: { params: Promise<{ organizat
return authenticatedApiClient({
request,
schemas: {
query: ZGetProjectTeamsFilter.sourceType(),
query: ZGetProjectTeamsFilter,
params: z.object({ organizationId: ZOrganizationIdSchema }),
},
externalParams: props.params,

View File

@@ -3,8 +3,8 @@ import { ZProjectTeam } from "@formbricks/database/zod/project-teams";
import { ZGetFilter } from "@/modules/api/v2/types/api-filter";
export const ZGetProjectTeamsFilter = ZGetFilter.extend({
teamId: z.string().cuid2().optional(),
projectId: z.string().cuid2().optional(),
teamId: z.cuid2().optional(),
projectId: z.cuid2().optional(),
}).refine(
(data) => {
if (data.startDate && data.endDate && data.startDate > data.endDate) {
@@ -28,8 +28,8 @@ export const ZProjectTeamInput = ZProjectTeam.pick({
export type TProjectTeamInput = z.infer<typeof ZProjectTeamInput>;
export const ZGetProjectTeamUpdateFilter = z.object({
teamId: z.string().cuid2(),
projectId: z.string().cuid2(),
teamId: z.cuid2(),
projectId: z.cuid2(),
});
export const ZProjectZTeamUpdateSchema = ZProjectTeam.pick({

View File

@@ -1,20 +1,16 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
import { ZTeam } from "@formbricks/database/zod/teams";
extendZodWithOpenApi(z);
export const ZTeamIdSchema = z
.string()
.cuid2()
.openapi({
ref: "teamId",
description: "The ID of the team",
.meta({
id: "teamId",
param: {
name: "id",
in: "path",
},
});
})
.describe("The ID of the team");
export const ZTeamUpdateSchema = ZTeam.omit({
id: true,

View File

@@ -21,7 +21,7 @@ export const getTeamsEndpoint: ZodOpenApiOperationObject = {
path: z.object({
organizationId: ZOrganizationIdSchema,
}),
query: ZGetTeamsFilter.sourceType(),
query: ZGetTeamsFilter,
},
tags: ["Organizations API - Teams"],
responses: {

View File

@@ -16,7 +16,7 @@ export const GET = async (request: NextRequest, props: { params: Promise<{ organ
authenticatedApiClient({
request,
schemas: {
query: ZGetTeamsFilter.sourceType(),
query: ZGetTeamsFilter,
params: z.object({ organizationId: ZOrganizationIdSchema }),
},
externalParams: props.params,

View File

@@ -1,16 +1,12 @@
import { z } from "zod";
import { extendZodWithOpenApi } from "zod-openapi";
extendZodWithOpenApi(z);
export const ZOrganizationIdSchema = z
.string()
.cuid2()
.openapi({
ref: "organizationId",
description: "The ID of the organization",
.meta({
id: "organizationId",
param: {
name: "organizationId",
in: "path",
},
});
})
.describe("The ID of the organization");

View File

@@ -17,7 +17,7 @@ export const getUsersEndpoint: ZodOpenApiOperationObject = {
path: z.object({
organizationId: ZOrganizationIdSchema,
}),
query: ZGetUsersFilter.sourceType(),
query: ZGetUsersFilter,
},
tags: ["Organizations API - Users"],
responses: {

View File

@@ -24,7 +24,7 @@ export const GET = async (request: NextRequest, props: { params: Promise<{ organ
authenticatedApiClient({
request,
schemas: {
query: ZGetUsersFilter.sourceType(),
query: ZGetUsersFilter,
params: z.object({ organizationId: ZOrganizationIdSchema }),
},
externalParams: props.params,

View File

@@ -1,10 +1,10 @@
import { z } from "zod";
export const ZGetFilter = z.object({
limit: z.coerce.number().min(1).max(250).optional().default(50).describe("Number of items to return"),
skip: z.coerce.number().min(0).optional().default(0).describe("Number of items to skip"),
sortBy: z.enum(["createdAt", "updatedAt"]).optional().default("createdAt").describe("Sort by field"),
order: z.enum(["asc", "desc"]).optional().default("desc").describe("Sort order"),
limit: z.coerce.number().min(1).max(250).optional().prefault(50).describe("Number of items to return"),
skip: z.coerce.number().min(0).optional().prefault(0).describe("Number of items to skip"),
sortBy: z.enum(["createdAt", "updatedAt"]).optional().prefault("createdAt").describe("Sort by field"),
order: z.enum(["asc", "desc"]).optional().prefault("desc").describe("Sort order"),
startDate: z.coerce.date().optional().describe("Start date"),
endDate: z.coerce.date().optional().describe("End date"),
filterDateField: z.enum(["createdAt", "updatedAt"]).optional().describe("Date field to filter by"),

View File

@@ -1,6 +1,6 @@
import { z } from "zod";
export function responseWithMetaSchema<T extends z.ZodTypeAny>(contentSchema: T) {
export function responseWithMetaSchema<T extends z.ZodType>(contentSchema: T) {
return z.object({
data: z.array(contentSchema).optional(),
meta: z

View File

@@ -7,7 +7,12 @@ import { getUserByEmail } from "@/lib/user/service";
import { actionClient } from "@/lib/utils/action-client";
const ZCreateEmailTokenAction = z.object({
email: z.string().min(5).max(255).email({ message: "Invalid email" }),
email: z
.email({
error: "Invalid email",
})
.min(5)
.max(255),
});
export const createEmailTokenAction = actionClient

View File

@@ -13,7 +13,7 @@ import { Button } from "@/modules/ui/components/button";
import { FormControl, FormError, FormField, FormItem } from "@/modules/ui/components/form";
const ZForgotPasswordForm = z.object({
email: z.string().email(),
email: z.email(),
});
type TForgotPasswordForm = z.infer<typeof ZForgotPasswordForm>;

View File

@@ -1,63 +1,62 @@
import { Authenticator } from "@otplib/core";
import type { AuthenticatorOptions } from "@otplib/core/authenticator";
import { createDigest, createRandomBytes } from "@otplib/plugin-crypto";
import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two";
import { OTP } from "otplib";
import { describe, expect, test, vi } from "vitest";
import { totpAuthenticatorCheck } from "./totp";
vi.mock("@otplib/core");
vi.mock("@otplib/plugin-crypto");
vi.mock("@otplib/plugin-thirty-two");
vi.mock("otplib", () => ({
OTP: vi.fn(),
}));
describe("totpAuthenticatorCheck", () => {
const token = "123456";
const secret = "JBSWY3DPEHPK3PXP";
const opts: Partial<AuthenticatorOptions> = { window: [1, 0] };
const opts = { window: [1, 0] as [number, number] };
test("should check a TOTP token with a base32-encoded secret", () => {
const checkMock = vi.fn().mockReturnValue(true);
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: checkMock,
}));
const verifySyncMock = vi.fn().mockReturnValue({ valid: true });
(OTP as unknown as vi.Mock).mockImplementation(function OTP() {
return {
verifySync: verifySyncMock,
};
});
const result = totpAuthenticatorCheck(token, secret, opts);
expect(Authenticator).toHaveBeenCalledWith({
createDigest,
createRandomBytes,
keyDecoder,
keyEncoder,
window: [1, 0],
expect(verifySyncMock).toHaveBeenCalledWith({
token,
secret,
period: 30,
epochTolerance: [30, 0],
});
expect(checkMock).toHaveBeenCalledWith(token, secret);
expect(result).toBe(true);
});
test("should use default window if none is provided", () => {
const checkMock = vi.fn().mockReturnValue(true);
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: checkMock,
}));
const verifySyncMock = vi.fn().mockReturnValue({ valid: true });
(OTP as unknown as vi.Mock).mockImplementation(function OTP() {
return {
verifySync: verifySyncMock,
};
});
const result = totpAuthenticatorCheck(token, secret);
expect(Authenticator).toHaveBeenCalledWith({
createDigest,
createRandomBytes,
keyDecoder,
keyEncoder,
window: [1, 0],
expect(verifySyncMock).toHaveBeenCalledWith({
token,
secret,
period: 30,
epochTolerance: [30, 0],
});
expect(checkMock).toHaveBeenCalledWith(token, secret);
expect(result).toBe(true);
});
test("should throw an error for invalid token format", () => {
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: () => {
throw new Error("Invalid token format");
},
}));
(OTP as unknown as vi.Mock).mockImplementation(function OTP() {
return {
verifySync: () => {
throw new Error("Invalid token format");
},
};
});
expect(() => {
totpAuthenticatorCheck("invalidToken", secret);
@@ -65,11 +64,13 @@ describe("totpAuthenticatorCheck", () => {
});
test("should throw an error for invalid secret format", () => {
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: () => {
throw new Error("Invalid secret format");
},
}));
(OTP as unknown as vi.Mock).mockImplementation(function OTP() {
return {
verifySync: () => {
throw new Error("Invalid secret format");
},
};
});
expect(() => {
totpAuthenticatorCheck(token, "invalidSecret");
@@ -77,10 +78,12 @@ describe("totpAuthenticatorCheck", () => {
});
test("should return false if token verification fails", () => {
const checkMock = vi.fn().mockReturnValue(false);
(Authenticator as unknown as vi.Mock).mockImplementation(() => ({
check: checkMock,
}));
const verifySyncMock = vi.fn().mockReturnValue({ valid: false });
(OTP as unknown as vi.Mock).mockImplementation(function OTP() {
return {
verifySync: verifySyncMock,
};
});
const result = totpAuthenticatorCheck(token, secret);
expect(result).toBe(false);

View File

@@ -1,7 +1,15 @@
import { Authenticator } from "@otplib/core";
import type { AuthenticatorOptions } from "@otplib/core/authenticator";
import { createDigest, createRandomBytes } from "@otplib/plugin-crypto";
import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two";
import { OTP, type OTPVerifyOptions } from "otplib";
type TOTPAuthenticatorOptions = {
window?: number | [number, number];
period?: OTPVerifyOptions["period"];
epoch?: OTPVerifyOptions["epoch"];
t0?: OTPVerifyOptions["t0"];
algorithm?: OTPVerifyOptions["algorithm"];
digits?: OTPVerifyOptions["digits"];
};
const createTotp = () => new OTP({ strategy: "totp" });
/**
* Checks the validity of a TOTP token using a base32-encoded secret.
@@ -14,16 +22,19 @@ import { keyDecoder, keyEncoder } from "@otplib/plugin-thirty-two";
export const totpAuthenticatorCheck = (
token: string,
secret: string,
opts: Partial<AuthenticatorOptions> = {}
opts: TOTPAuthenticatorOptions = {}
) => {
const { window = [1, 0], ...rest } = opts;
const authenticator = new Authenticator({
createDigest,
createRandomBytes,
keyDecoder,
keyEncoder,
window,
const { window = [1, 0], period = 30, ...rest } = opts;
const [pastWindow, futureWindow] = Array.isArray(window) ? window : [window, window];
const totp = createTotp();
const result = totp.verifySync({
token,
secret,
period,
epochTolerance: [pastWindow * period, futureWindow * period],
...rest,
});
return authenticator.check(token, secret);
return result.valid;
};

View File

@@ -21,11 +21,15 @@ import { FormControl, FormError, FormField, FormItem } from "@/modules/ui/compon
import { PasswordInput } from "@/modules/ui/components/password-input";
const ZLoginForm = z.object({
email: z.string().email(),
email: z.email(),
password: z
.string()
.min(8, { message: "Password must be at least 8 characters long" })
.max(128, { message: "Password must be 128 characters or less" }),
.min(8, {
error: "Password must be at least 8 characters long",
})
.max(128, {
error: "Password must be 128 characters or less",
}),
totpCode: z.string().optional(),
backupCode: z.string().optional(),
});

View File

@@ -24,7 +24,7 @@ import { PasswordChecks } from "./password-checks";
const ZSignupInput = z.object({
name: ZUserName,
email: z.string().email(),
email: z.email(),
password: ZUserPassword,
});

View File

@@ -2,9 +2,9 @@ import { z } from "zod";
export const ZRateLimitConfig = z.object({
/** Rate limit window in seconds */
interval: z.number().int().positive().describe("Rate limit window in seconds"),
interval: z.int().positive().describe("Rate limit window in seconds"),
/** Maximum allowed requests per interval */
allowedPerInterval: z.number().int().positive().describe("Maximum allowed requests per interval"),
allowedPerInterval: z.int().positive().describe("Maximum allowed requests per interval"),
/** Namespace for grouping rate limit per feature */
namespace: z.string().min(1).describe("Namespace for grouping rate limit per feature"),
});

View File

@@ -73,12 +73,12 @@ export const ZAuditLogEventSchema = z.object({
type: ZAuditTarget,
}),
status: ZAuditStatus,
timestamp: z.string().datetime(),
timestamp: z.iso.datetime(),
organizationId: z.string(),
ipAddress: z.string().optional(), // Not using the .ip() here because if we don't enabled it we want to put UNKNOWN_DATA string, to keep the same pattern as the other fields
changes: z.record(z.any()).optional(),
changes: z.record(z.string(), z.any()).optional(),
eventId: z.string().optional(),
apiUrl: z.string().url().optional(),
apiUrl: z.url().optional(),
});
export type TAuditLogEvent = z.infer<typeof ZAuditLogEventSchema>;

View File

@@ -16,7 +16,7 @@ import { isSubscriptionCancelled } from "@/modules/ee/billing/api/lib/is-subscri
const ZUpgradePlanAction = z.object({
environmentId: ZId,
priceLookupKey: z.nativeEnum(STRIPE_PRICE_LOOKUP_KEYS),
priceLookupKey: z.enum(STRIPE_PRICE_LOOKUP_KEYS),
});
export const upgradePlanAction = authenticatedActionClient.schema(ZUpgradePlanAction).action(

View File

@@ -8,7 +8,8 @@ import { getOrganization, updateOrganization } from "@/lib/organization/service"
export const handleInvoiceFinalized = async (event: Stripe.Event) => {
const invoice = event.data.object as Stripe.Invoice;
const subscriptionId = invoice.subscription as string;
const subscription = invoice.parent?.subscription_details?.subscription;
const subscriptionId = typeof subscription === "string" ? subscription : subscription?.id;
if (!subscriptionId) {
logger.warn({ invoiceId: invoice.id }, "Invoice finalized without subscription ID");
return { status: 400, message: "No subscription ID found in invoice" };

View File

@@ -34,9 +34,11 @@ export const isSubscriptionCancelled = async (
for (const subscription of subscriptions.data) {
if (subscription.cancel_at_period_end) {
const cancellationTimestamp =
subscription.cancel_at ?? subscription.ended_at ?? subscription.canceled_at;
return {
cancelled: true,
date: new Date(subscription.current_period_end * 1000),
date: cancellationTimestamp ? new Date(cancellationTimestamp * 1000) : null,
};
}
}

View File

@@ -23,7 +23,7 @@ import {
const ZGetContactsAction = z.object({
environmentId: ZId,
offset: z.number().int().nonnegative(),
offset: z.int().nonnegative(),
searchValue: z.string().optional(),
});

View File

@@ -64,7 +64,7 @@ export const POST = withV1ApiWrapper({
{
environmentId: params.environmentId,
url: req.url,
validationError: cuidValidation.error.errors[0]?.message,
validationError: cuidValidation.error.issues[0]?.message,
},
"Invalid CUID v1 format detected"
);

View File

@@ -4,7 +4,7 @@ import { isSafeIdentifier } from "@/lib/utils/safe-identifier";
export const ZContactAttributeKeyCreateInput = z.object({
key: z.string().refine((val) => isSafeIdentifier(val), {
message:
error:
"Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter",
}),
description: z.string().optional(),
@@ -21,7 +21,7 @@ export const ZContactAttributeKeyUpdateInput = z.object({
key: z
.string()
.refine((val) => isSafeIdentifier(val), {
message:
error:
"Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter",
})
.optional(),

View File

@@ -20,7 +20,7 @@ import {
const ZCreateContactAttributeKeyAction = z.object({
environmentId: ZId,
key: z.string().refine((val) => isSafeIdentifier(val), {
message:
error:
"Key must be a safe identifier: only lowercase letters, numbers, and underscores, and must start with a letter",
}),
name: z.string().optional(),

View File

@@ -84,7 +84,7 @@ export const UploadContactsCSVButton = ({
const parsedRecords = ZContactCSVUploadResponse.safeParse(records);
if (!parsedRecords.success) {
console.error("Error parsing CSV:", parsedRecords.error);
setError(parsedRecords.error.errors[0].message);
setError(parsedRecords.error.issues[0].message);
return;
}

View File

@@ -6,10 +6,10 @@ import {
} from "@formbricks/types/contact-attribute-key";
export const ZContact = z.object({
id: z.string().cuid2(),
id: z.cuid2(),
createdAt: z.date(),
updatedAt: z.date(),
environmentId: z.string().cuid2(),
environmentId: z.cuid2(),
});
const ZContactTableAttributeData = z.object({
@@ -29,7 +29,7 @@ export const ZContactTableData = z.object({
});
export const ZContactWithAttributes = ZContact.extend({
attributes: z.record(z.string()),
attributes: z.record(z.string(), z.string()),
});
export type TContactWithAttributes = z.infer<typeof ZContactWithAttributes>;
@@ -60,22 +60,26 @@ export const ZContactCSVDuplicateAction = z.enum(["skip", "update", "overwrite"]
export type TContactCSVDuplicateAction = z.infer<typeof ZContactCSVDuplicateAction>;
export const ZContactCSVUploadResponse = z
.array(z.record(z.string()))
.max(10000, { message: "Maximum 10000 records allowed at a time." })
.array(z.record(z.string(), z.string()))
.max(10000, {
error: "Maximum 10000 records allowed at a time.",
})
.superRefine((data, ctx) => {
for (const record of data) {
if (!Object.keys(record).includes("email")) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
ctx.addIssue({
code: "custom",
message: "Missing email field for one or more records",
});
return;
}
if (!record.email) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
ctx.addIssue({
code: "custom",
message: "Email field is empty for one or more records",
});
return;
}
}
@@ -84,10 +88,11 @@ export const ZContactCSVUploadResponse = z
const emailSet = new Set(emails);
if (emails.length !== emailSet.size) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
ctx.addIssue({
code: "custom",
message: "Duplicate emails found in the records",
});
return;
}
// check for duplicate userIds if present
@@ -95,10 +100,11 @@ export const ZContactCSVUploadResponse = z
if (userIds?.length > 0) {
const userIdSet = new Set(userIds);
if (userIds.length !== userIdSet.size) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
ctx.addIssue({
code: "custom",
message: "Duplicate userIds found in the records",
});
return;
}
}
});
@@ -109,10 +115,11 @@ export const ZContactCSVAttributeMap = z.record(z.string(), z.string()).superRef
const values = Object.values(attributeMap);
if (new Set(values).size !== values.length) {
return ctx.addIssue({
code: z.ZodIssueCode.custom,
ctx.addIssue({
code: "custom",
message: "Attribute map contains duplicate values",
});
return;
}
});
export type TContactCSVAttributeMap = z.infer<typeof ZContactCSVAttributeMap>;
@@ -142,17 +149,17 @@ export const validateEmailAttribute = (
if (!emailAttr?.value) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: `Email attribute is required${indexSuffix}`,
});
return { isValid: false };
}
// Check email format
const parsedEmail = z.string().email().safeParse(emailAttr.value);
const parsedEmail = z.email().safeParse(emailAttr.value);
if (!parsedEmail.success) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: `Invalid email format${indexSuffix}`,
});
return { emailAttr, isValid: false };
@@ -183,7 +190,7 @@ export const validateUniqueAttributeKeys = (
if (duplicateKeys.length > 0) {
const indexSuffix = contactIndex !== undefined ? ` for contact at index ${contactIndex}` : "";
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: `Duplicate attribute keys found${indexSuffix}. Please ensure each attribute key is unique`,
params: {
duplicateKeys,
@@ -194,10 +201,12 @@ export const validateUniqueAttributeKeys = (
};
export const ZContactBulkUploadRequest = z.object({
environmentId: z.string().cuid2(),
environmentId: z.cuid2(),
contacts: z
.array(ZContactBulkUploadContact)
.max(250, { message: "Maximum 250 contacts allowed at a time." })
.max(250, {
error: "Maximum 250 contacts allowed at a time.",
})
.superRefine((contacts, ctx) => {
// Track all data in a single pass
const seenEmails = new Set<string>();
@@ -236,7 +245,7 @@ export const ZContactBulkUploadRequest = z.object({
// Report all validation issues after the single pass
if (duplicateEmails.size > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: "Duplicate emails found in the records, please ensure each email is unique.",
params: {
duplicateEmails: Array.from(duplicateEmails),
@@ -246,7 +255,7 @@ export const ZContactBulkUploadRequest = z.object({
if (duplicateUserIds.size > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: "Duplicate userIds found in the records, please ensure each userId is unique.",
params: {
duplicateUserIds: Array.from(duplicateUserIds),
@@ -276,21 +285,21 @@ export type TContactBulkUploadResponseSuccess = TContactBulkUploadResponseBase &
// Schema for single contact creation - simplified with flat attributes
export const ZContactCreateRequest = z.object({
environmentId: z.string().cuid2(),
environmentId: z.cuid2(),
attributes: z.record(z.string(), z.string()).superRefine((attributes, ctx) => {
// Check if email attribute exists and is valid
const email = attributes.email;
if (!email) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: "Email attribute is required",
});
} else {
// Check email format
const parsedEmail = z.string().email().safeParse(email);
const parsedEmail = z.email().safeParse(email);
if (!parsedEmail.success) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: "Invalid email format",
});
}
@@ -302,9 +311,9 @@ export type TContactCreateRequest = z.infer<typeof ZContactCreateRequest>;
// Type for contact response with flattened attributes
export const ZContactResponse = z.object({
id: z.string().cuid2(),
id: z.cuid2(),
createdAt: z.date(),
environmentId: z.string().cuid2(),
environmentId: z.cuid2(),
attributes: z.record(z.string(), z.string()),
});
@@ -336,7 +345,7 @@ export const ZEditContactAttributesForm = z.object({
if (indices.length > 1) {
indices.forEach((index) => {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: `Duplicate key: ${key}`,
path: [index, "key"],
});
@@ -358,13 +367,13 @@ export const ZEditContactAttributesForm = z.object({
// When both are empty, show "Either email or userId is required" on both fields
if (emailIndex !== -1 && userIdIndex !== -1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: "Either email or userId is required",
path: [emailIndex, "value"],
});
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: "Either email or userId is required",
path: [userIdIndex, "value"],
});
@@ -374,10 +383,10 @@ export const ZEditContactAttributesForm = z.object({
// Validate email format if key is "email" and has a value
attributes.forEach((attr, index) => {
if (attr.key === "email" && attr.value && attr.value.trim() !== "") {
const emailResult = z.string().email().safeParse(attr.value);
const emailResult = z.email().safeParse(attr.value);
if (!emailResult.success) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: "Invalid email format",
path: [index, "value"],
});
@@ -416,7 +425,7 @@ export const createEditContactAttributesSchema = (
if (dataType === "date") {
if (!hasValue) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: t("environments.contacts.date_value_required"),
path: ["attributes", index, "value"],
});
@@ -426,7 +435,7 @@ export const createEditContactAttributesSchema = (
const date = new Date(attr.value);
if (Number.isNaN(date.getTime())) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: t("environments.contacts.invalid_date_format"),
path: ["attributes", index, "value"],
});
@@ -434,7 +443,7 @@ export const createEditContactAttributesSchema = (
} else if (dataType === "number") {
if (!hasValue) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: t("environments.contacts.number_value_required"),
path: ["attributes", index, "value"],
});
@@ -443,7 +452,7 @@ export const createEditContactAttributesSchema = (
// Validate number format
if (Number.isNaN(Number(attr.value))) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
message: t("environments.contacts.invalid_number_format"),
path: ["attributes", index, "value"],
});

View File

@@ -120,14 +120,16 @@ export const QuotaModal = ({
defaultValues,
resolver: zodResolver(
quotaResponseCount > 0
? ZSurveyQuotaInput.innerType().extend({
limit: z.number().min(quotaResponseCount, {
message: t(
"environments.surveys.edit.quotas.limit_must_be_greater_than_or_equal_to_the_number_of_responses",
{ value: quotaResponseCount }
),
}),
})
? ZSurveyQuotaInput.and(
z.object({
limit: z.number().min(quotaResponseCount, {
message: t(
"environments.surveys.edit.quotas.limit_must_be_greater_than_or_equal_to_the_number_of_responses",
{ value: quotaResponseCount }
),
}),
})
)
: ZSurveyQuotaInput
),
mode: "onSubmit",

View File

@@ -10,7 +10,7 @@ import { validateInputs } from "@/lib/utils/validate";
import { createTeamMembership } from "@/modules/auth/signup/lib/team";
export const getOrganizationByTeamId = reactCache(async (teamId: string): Promise<Organization | null> => {
validateInputs([teamId, z.string().cuid2()]);
validateInputs([teamId, z.cuid2()]);
try {
const team = await prisma.team.findUnique({

View File

@@ -18,7 +18,7 @@ import {
import { ZTeamSettingsFormSchema } from "@/modules/ee/teams/team-list/types/team";
const ZCreateTeamAction = z.object({
organizationId: z.string().cuid(),
organizationId: z.cuid(),
name: z.string().trim().min(1, "Team name is required"),
});

View File

@@ -24,7 +24,7 @@ export const ZOtherTeam = z.object({
export type TOtherTeam = z.infer<typeof ZOtherTeam>;
export const ZOrganizationTeam = z.object({
id: z.string().cuid2(),
id: z.cuid2(),
name: z.string(),
});
@@ -69,7 +69,9 @@ export const ZTeamSettingsFormSchema = z.object({
role: ZTeamRole,
})
)
.min(1, { message: "Please add at least one member" }),
.min(1, {
error: "Please add at least one member",
}),
projects: z
.array(
z.object({
@@ -77,7 +79,9 @@ export const ZTeamSettingsFormSchema = z.object({
permission: ZTeamPermission,
})
)
.min(1, { message: "Please add at least one project" }),
.min(1, {
error: "Please add at least one project",
}),
});
export type TTeamSettingsFormSchema = z.infer<typeof ZTeamSettingsFormSchema>;
@@ -94,9 +98,14 @@ export type TTeamMember = z.infer<typeof ZTeamMember>;
export const ZTeam = z.object({
id: z.string(),
name: z.string({ message: "Team name is required" }).trim().min(1, {
message: "Team name must be at least 1 character long",
}),
name: z
.string({
error: "Team name is required",
})
.trim()
.min(1, {
error: "Team name must be at least 1 character long",
}),
teamUsers: z.array(ZTeamMember),
});

View File

@@ -32,8 +32,8 @@ const ZDisableTwoFactorFormState = z
backupCode: z.string().optional(),
})
.refine((data) => (!!data.code && !data.backupCode) || (!data.code && !!data.backupCode), {
message: "Please provide either the code OR the backup code",
path: ["code"],
error: "Please provide either the code OR the backup code",
});
type TDisableTwoFactorFormState = z.infer<typeof ZDisableTwoFactorFormState>;

View File

@@ -1,5 +1,5 @@
import crypto from "crypto";
import { authenticator } from "otplib";
import { generateSecret, generateURI } from "otplib";
import qrcode from "qrcode";
import { prisma } from "@formbricks/database";
import { InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
@@ -19,7 +19,7 @@ export const setupTwoFactorAuth = async (
}> => {
// This generates a secret 32 characters in length. Do not modify the number of
// bytes without updating the sanity checks in the enable and login endpoints.
const secret = authenticator.generateSecret(20);
const secret = generateSecret({ length: 20 });
// generate backup codes with 10 character length
const backupCodes = Array.from(Array(10), () => crypto.randomBytes(5).toString("hex"));
@@ -64,7 +64,7 @@ export const setupTwoFactorAuth = async (
});
const name = user.email || user.name || user.id.toString();
const keyUri = authenticator.keyuri(name, "Formbricks", secret);
const keyUri = generateURI({ label: name, issuer: "Formbricks", secret });
const dataUri = await qrcode.toDataURL(keyUri);
return { secret, keyUri, dataUri, backupCodes };

View File

@@ -37,7 +37,7 @@ export const updateProjectBranding = async (
return true;
} catch (error) {
if (error instanceof z.ZodError) {
logger.error(error.errors, "Error updating project branding");
logger.error(error.issues, "Error updating project branding");
}
throw new ValidationError("Data validation of project failed");
}

View File

@@ -7,7 +7,7 @@ import { ZProject } from "@formbricks/types/project";
export const ZApiKeyEnvironmentPermission = z.object({
environmentId: z.string(),
permission: z.nativeEnum(ApiKeyPermission),
permission: z.enum(ApiKeyPermission),
});
export const ZApiKeyCreateInput = ZApiKey.required({
@@ -45,7 +45,7 @@ export type TOrganizationProject = z.infer<typeof OrganizationProject>;
export const TApiKeyEnvironmentPermission = z.object({
environmentId: z.string(),
permission: z.nativeEnum(ApiKeyPermission),
permission: z.enum(ApiKeyPermission),
});
export type TApiKeyEnvironmentPermission = z.infer<typeof TApiKeyEnvironmentPermission>;

View File

@@ -43,10 +43,18 @@ export const IndividualInviteTab = ({
}: IndividualInviteTabProps) => {
const ZFormSchema = z.object({
name: ZUserName,
email: z.string().min(1, { message: "Email is required" }).email({ message: "Invalid email" }),
email: z
.email({
error: "Invalid email",
})
.min(1, {
error: "Email is required",
}),
role: ZOrganizationRole,
teamIds: showTeamAdminRestrictions
? z.array(ZId).min(1, { message: "Team admins must select at least one team" })
? z.array(ZId).min(1, {
error: "Team admins must select at least one team",
})
: z.array(ZId),
});

View File

@@ -64,7 +64,7 @@ export const updateProject = async (
return project;
} catch (error) {
if (error instanceof z.ZodError) {
logger.error(error.errors, "Error updating project");
logger.error(error.issues, "Error updating project");
}
throw new ValidationError("Data validation of project failed");
}

View File

@@ -8,7 +8,7 @@ import { validateInputs } from "@/lib/utils/validate";
export const getProjectByEnvironmentId = reactCache(
async (environmentId: string): Promise<Project | null> => {
validateInputs([environmentId, z.string().cuid2()]);
validateInputs([environmentId, z.cuid2()]);
let projectPrisma;

View File

@@ -12,11 +12,12 @@ export const ZInvitee = ZInvite.pick({
export type TInvitee = z.infer<typeof ZInvitee>;
export const ZInviteMembersFormSchema = z.record(
z.string(),
ZInvite.pick({
email: true,
name: true,
}).extend({
email: z.string().email("Invalid email address"),
email: z.email("Invalid email address"),
name: ZUserName,
})
);

View File

@@ -30,7 +30,9 @@ describe("fileUpload", () => {
beforeEach(() => {
vi.clearAllMocks();
// Mock FileReader
global.FileReader = vi.fn(() => mockFileReader) as any;
global.FileReader = vi.fn(function FileReader() {
return mockFileReader;
}) as any;
global.atob = (base64) => Buffer.from(base64, "base64").toString("binary");
});
@@ -191,7 +193,9 @@ describe("fileUploadModule.toBase64", () => {
result: "data:text/plain;base64,aGVsbG8=",
};
globalThis.FileReader = vi.fn(() => mockFileReaderInstance as unknown as FileReader) as any;
globalThis.FileReader = vi.fn(function FileReader() {
return mockFileReaderInstance as unknown as FileReader;
}) as any;
const promise = fileUploadModule.toBase64(dummyFile);
@@ -213,7 +217,9 @@ describe("fileUploadModule.toBase64", () => {
result: null,
};
globalThis.FileReader = vi.fn(() => mockFileReaderInstance as unknown as FileReader) as any;
globalThis.FileReader = vi.fn(function FileReader() {
return mockFileReaderInstance as unknown as FileReader;
}) as any;
const promise = fileUploadModule.toBase64(dummyFile);

View File

@@ -15,7 +15,7 @@ import { checkSpamProtectionPermission } from "@/modules/survey/lib/permission";
import { getOrganizationBilling } from "@/modules/survey/lib/survey";
const ZCreateSurveyAction = z.object({
environmentId: z.string().cuid2(),
environmentId: z.cuid2(),
surveyBody: ZSurveyCreateInput,
});

View File

@@ -1,4 +1,4 @@
import { ActionClass, Prisma } from "@prisma/client";
import { Prisma } from "@prisma/client";
import { prisma } from "@formbricks/database";
import { logger } from "@formbricks/logger";
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
@@ -134,7 +134,12 @@ export const createSurvey = async (
}
};
const checkTriggersValidity = (triggers: TSurvey["triggers"], actionClasses: ActionClass[]) => {
type TriggerWithActionClassId = { actionClass: { id: string } };
const checkTriggersValidity = (
triggers: TriggerWithActionClassId[] | null | undefined,
actionClasses: Array<{ id: string }>
) => {
if (!triggers) return;
// check if all the triggers are valid
@@ -153,14 +158,14 @@ const checkTriggersValidity = (triggers: TSurvey["triggers"], actionClasses: Act
};
export const handleTriggerUpdates = (
updatedTriggers: TSurvey["triggers"],
currentTriggers: TSurvey["triggers"],
actionClasses: ActionClass[]
updatedTriggers: TriggerWithActionClassId[] | null | undefined,
currentTriggers: TriggerWithActionClassId[] | null | undefined,
actionClasses: Array<{ id: string }>
) => {
if (!updatedTriggers) return {};
checkTriggersValidity(updatedTriggers, actionClasses);
const currentTriggerIds = currentTriggers.map((trigger) => trigger.actionClass.id);
const currentTriggerIds = (currentTriggers ?? []).map((trigger) => trigger.actionClass.id);
const updatedTriggerIds = updatedTriggers.map((trigger) => trigger.actionClass.id);
// added triggers are triggers that are not in the current triggers and are there in the new triggers
@@ -169,7 +174,7 @@ export const handleTriggerUpdates = (
);
// deleted triggers are triggers that are not in the new triggers and are there in the current triggers
const deletedTriggers = currentTriggers.filter(
const deletedTriggers = (currentTriggers ?? []).filter(
(trigger) => !updatedTriggerIds.includes(trigger.actionClass.id)
);

View File

@@ -155,7 +155,7 @@ export const updateSurveyAction = authenticatedActionClient.schema(ZSurvey).acti
);
const ZRefetchProjectAction = z.object({
projectId: z.string().cuid2(),
projectId: z.cuid2(),
});
export const refetchProjectAction = authenticatedActionClient
@@ -236,7 +236,7 @@ const isValidUnsplashUrl = (url: string): boolean => {
};
const ZTriggerDownloadUnsplashImageAction = z.object({
downloadUrl: z.string().url(),
downloadUrl: z.url(),
});
export const triggerDownloadUnsplashImageAction = actionClient

View File

@@ -191,7 +191,7 @@ export const SurveyMenuBar = ({
const validateSurveyWithZod = (): boolean => {
const localSurveyValidation = ZSurvey.safeParse(localSurvey);
if (!localSurveyValidation.success) {
const currentError = localSurveyValidation.error.errors[0];
const currentError = localSurveyValidation.error.issues[0];
if (currentError.path[0] === "blocks") {
const blockIdx = currentError.path[1];

View File

@@ -168,7 +168,7 @@ describe("action-utils", () => {
validateActionNameUniqueness(data, ["existingAction"], ctx, mockT);
expect(ctx.addIssue).toHaveBeenCalledWith({
code: z.ZodIssueCode.custom,
code: "custom",
path: ["name"],
message: 'Action with name "existingAction" already exists',
});
@@ -201,7 +201,7 @@ describe("action-utils", () => {
validateActionKeyUniqueness(data, ["existingKey"], ctx, mockT);
expect(ctx.addIssue).toHaveBeenCalledWith({
code: z.ZodIssueCode.custom,
code: "custom",
path: ["key"],
message: 'Action with key "existingKey" already exists',
});
@@ -251,7 +251,7 @@ describe("action-utils", () => {
validateCssSelector(data, ctx, mockT);
expect(ctx.addIssue).toHaveBeenCalledWith({
code: z.ZodIssueCode.custom,
code: "custom",
path: ["noCodeConfig", "elementSelector", "cssSelector"],
message: "Invalid CSS selector",
});
@@ -309,7 +309,7 @@ describe("action-utils", () => {
validateUrlFilterRegex(data, ctx, mockT);
expect(ctx.addIssue).toHaveBeenCalledWith({
code: z.ZodIssueCode.custom,
code: "custom",
path: ["noCodeConfig", "urlFilters", 0, "value"],
message: "Invalid regex pattern",
});
@@ -368,7 +368,7 @@ describe("action-utils", () => {
expect(ctx.addIssue).toHaveBeenCalledTimes(1);
expect(ctx.addIssue).toHaveBeenCalledWith({
code: z.ZodIssueCode.custom,
code: "custom",
path: ["noCodeConfig", "urlFilters", 1, "value"],
message: "Invalid regex pattern",
});

View File

@@ -36,7 +36,7 @@ export const validateActionNameUniqueness = (
) => {
if (data.name && actionClassNames.includes(data.name)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
path: ["name"],
message: t("environments.actions.action_with_name_already_exists", { name: data.name }),
});
@@ -54,7 +54,7 @@ export const validateActionKeyUniqueness = (
) => {
if (data.type === "code" && data.key && actionClassKeys.includes(data.key)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
path: ["key"],
message: t("environments.actions.action_with_key_already_exists", { key: data.key }),
});
@@ -72,7 +72,7 @@ export const validateCssSelector = (data: TActionClassInput, ctx: z.RefinementCt
!isValidCssSelector(data.noCodeConfig.elementSelector.cssSelector)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
path: ["noCodeConfig", "elementSelector", "cssSelector"],
message: t("environments.actions.invalid_css_selector"),
});
@@ -91,7 +91,7 @@ export const validateUrlFilterRegex = (data: TActionClassInput, ctx: z.Refinemen
new RegExp(urlFilter.value);
} catch {
ctx.addIssue({
code: z.ZodIssueCode.custom,
code: "custom",
path: ["noCodeConfig", "urlFilters", i, "value"],
message: t("environments.actions.invalid_regex"),
});

View File

@@ -40,43 +40,43 @@ describe("getLogicRules", () => {
expect(openTextTextRules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.equals)",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_equal)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: "mockTranslate(environments.surveys.edit.contains)",
value: ZSurveyLogicConditionsOperator.Enum.contains,
value: ZSurveyLogicConditionsOperator.enum.contains,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_contain)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotContain,
value: ZSurveyLogicConditionsOperator.enum.doesNotContain,
},
{
label: "mockTranslate(environments.surveys.edit.starts_with)",
value: ZSurveyLogicConditionsOperator.Enum.startsWith,
value: ZSurveyLogicConditionsOperator.enum.startsWith,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_start_with)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotStartWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotStartWith,
},
{
label: "mockTranslate(environments.surveys.edit.ends_with)",
value: ZSurveyLogicConditionsOperator.Enum.endsWith,
value: ZSurveyLogicConditionsOperator.enum.endsWith,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_end_with)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEndWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotEndWith,
},
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -85,19 +85,19 @@ describe("getLogicRules", () => {
const openTextNumberRules = logicRules.element[TSurveyQuestionTypeEnum.OpenText + ".number"];
expect(openTextNumberRules).toBeDefined();
expect(openTextNumberRules.options).toEqual([
{ label: "=", value: ZSurveyLogicConditionsOperator.Enum.equals },
{ label: "!=", value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual },
{ label: ">", value: ZSurveyLogicConditionsOperator.Enum.isGreaterThan },
{ label: "<", value: ZSurveyLogicConditionsOperator.Enum.isLessThan },
{ label: ">=", value: ZSurveyLogicConditionsOperator.Enum.isGreaterThanOrEqual },
{ label: "<=", value: ZSurveyLogicConditionsOperator.Enum.isLessThanOrEqual },
{ label: "=", value: ZSurveyLogicConditionsOperator.enum.equals },
{ label: "!=", value: ZSurveyLogicConditionsOperator.enum.doesNotEqual },
{ label: ">", value: ZSurveyLogicConditionsOperator.enum.isGreaterThan },
{ label: "<", value: ZSurveyLogicConditionsOperator.enum.isLessThan },
{ label: ">=", value: ZSurveyLogicConditionsOperator.enum.isGreaterThanOrEqual },
{ label: "<=", value: ZSurveyLogicConditionsOperator.enum.isLessThanOrEqual },
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -108,23 +108,23 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.equals)",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_equal)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: "mockTranslate(environments.surveys.edit.equals_one_of)",
value: ZSurveyLogicConditionsOperator.Enum.equalsOneOf,
value: ZSurveyLogicConditionsOperator.enum.equalsOneOf,
},
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -135,35 +135,35 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.equals)",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_equal)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_include_one_of)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeOneOf,
value: ZSurveyLogicConditionsOperator.enum.doesNotIncludeOneOf,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_include_all_of)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeAllOf,
value: ZSurveyLogicConditionsOperator.enum.doesNotIncludeAllOf,
},
{
label: "mockTranslate(environments.surveys.edit.includes_all_of)",
value: ZSurveyLogicConditionsOperator.Enum.includesAllOf,
value: ZSurveyLogicConditionsOperator.enum.includesAllOf,
},
{
label: "mockTranslate(environments.surveys.edit.includes_one_of)",
value: ZSurveyLogicConditionsOperator.Enum.includesOneOf,
value: ZSurveyLogicConditionsOperator.enum.includesOneOf,
},
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -174,35 +174,35 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.equals)",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_equal)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_include_one_of)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeOneOf,
value: ZSurveyLogicConditionsOperator.enum.doesNotIncludeOneOf,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_include_all_of)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeAllOf,
value: ZSurveyLogicConditionsOperator.enum.doesNotIncludeAllOf,
},
{
label: "mockTranslate(environments.surveys.edit.includes_all_of)",
value: ZSurveyLogicConditionsOperator.Enum.includesAllOf,
value: ZSurveyLogicConditionsOperator.enum.includesAllOf,
},
{
label: "mockTranslate(environments.surveys.edit.includes_one_of)",
value: ZSurveyLogicConditionsOperator.Enum.includesOneOf,
value: ZSurveyLogicConditionsOperator.enum.includesOneOf,
},
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -211,19 +211,19 @@ describe("getLogicRules", () => {
const rules = logicRules.element[TSurveyQuestionTypeEnum.Rating];
expect(rules).toBeDefined();
expect(rules.options).toEqual([
{ label: "=", value: ZSurveyLogicConditionsOperator.Enum.equals },
{ label: "!=", value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual },
{ label: ">", value: ZSurveyLogicConditionsOperator.Enum.isGreaterThan },
{ label: "<", value: ZSurveyLogicConditionsOperator.Enum.isLessThan },
{ label: ">=", value: ZSurveyLogicConditionsOperator.Enum.isGreaterThanOrEqual },
{ label: "<=", value: ZSurveyLogicConditionsOperator.Enum.isLessThanOrEqual },
{ label: "=", value: ZSurveyLogicConditionsOperator.enum.equals },
{ label: "!=", value: ZSurveyLogicConditionsOperator.enum.doesNotEqual },
{ label: ">", value: ZSurveyLogicConditionsOperator.enum.isGreaterThan },
{ label: "<", value: ZSurveyLogicConditionsOperator.enum.isLessThan },
{ label: ">=", value: ZSurveyLogicConditionsOperator.enum.isGreaterThanOrEqual },
{ label: "<=", value: ZSurveyLogicConditionsOperator.enum.isLessThanOrEqual },
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -232,19 +232,19 @@ describe("getLogicRules", () => {
const rules = logicRules.element[TSurveyQuestionTypeEnum.NPS];
expect(rules).toBeDefined();
expect(rules.options).toEqual([
{ label: "=", value: ZSurveyLogicConditionsOperator.Enum.equals },
{ label: "!=", value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual },
{ label: ">", value: ZSurveyLogicConditionsOperator.Enum.isGreaterThan },
{ label: "<", value: ZSurveyLogicConditionsOperator.Enum.isLessThan },
{ label: ">=", value: ZSurveyLogicConditionsOperator.Enum.isGreaterThanOrEqual },
{ label: "<=", value: ZSurveyLogicConditionsOperator.Enum.isLessThanOrEqual },
{ label: "=", value: ZSurveyLogicConditionsOperator.enum.equals },
{ label: "!=", value: ZSurveyLogicConditionsOperator.enum.doesNotEqual },
{ label: ">", value: ZSurveyLogicConditionsOperator.enum.isGreaterThan },
{ label: "<", value: ZSurveyLogicConditionsOperator.enum.isLessThan },
{ label: ">=", value: ZSurveyLogicConditionsOperator.enum.isGreaterThanOrEqual },
{ label: "<=", value: ZSurveyLogicConditionsOperator.enum.isLessThanOrEqual },
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -255,11 +255,11 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.is_clicked)",
value: ZSurveyLogicConditionsOperator.Enum.isClicked,
value: ZSurveyLogicConditionsOperator.enum.isClicked,
},
{
label: "mockTranslate(environments.surveys.edit.is_not_clicked)",
value: ZSurveyLogicConditionsOperator.Enum.isNotClicked,
value: ZSurveyLogicConditionsOperator.enum.isNotClicked,
},
]);
});
@@ -270,11 +270,11 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.is_accepted)",
value: ZSurveyLogicConditionsOperator.Enum.isAccepted,
value: ZSurveyLogicConditionsOperator.enum.isAccepted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -285,27 +285,27 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.equals)",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_equal)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: "mockTranslate(environments.surveys.edit.is_before)",
value: ZSurveyLogicConditionsOperator.Enum.isBefore,
value: ZSurveyLogicConditionsOperator.enum.isBefore,
},
{
label: "mockTranslate(environments.surveys.edit.is_after)",
value: ZSurveyLogicConditionsOperator.Enum.isAfter,
value: ZSurveyLogicConditionsOperator.enum.isAfter,
},
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -316,11 +316,11 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -331,11 +331,11 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -346,11 +346,11 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.is_booked)",
value: ZSurveyLogicConditionsOperator.Enum.isBooked,
value: ZSurveyLogicConditionsOperator.enum.isBooked,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -361,15 +361,15 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.is_partially_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isPartiallySubmitted,
value: ZSurveyLogicConditionsOperator.enum.isPartiallySubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_completely_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isCompletelySubmitted,
value: ZSurveyLogicConditionsOperator.enum.isCompletelySubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -380,23 +380,23 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.equals)",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_equal)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: "mockTranslate(environments.surveys.edit.is_empty)",
value: ZSurveyLogicConditionsOperator.Enum.isEmpty,
value: ZSurveyLogicConditionsOperator.enum.isEmpty,
},
{
label: "mockTranslate(environments.surveys.edit.is_not_empty)",
value: ZSurveyLogicConditionsOperator.Enum.isNotEmpty,
value: ZSurveyLogicConditionsOperator.enum.isNotEmpty,
},
{
label: "mockTranslate(environments.surveys.edit.is_any_of)",
value: ZSurveyLogicConditionsOperator.Enum.isAnyOf,
value: ZSurveyLogicConditionsOperator.enum.isAnyOf,
},
]);
});
@@ -407,11 +407,11 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -422,11 +422,11 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.is_submitted)",
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: "mockTranslate(environments.surveys.edit.is_skipped)",
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
]);
});
@@ -439,35 +439,35 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.equals)",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_equal)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: "mockTranslate(environments.surveys.edit.contains)",
value: ZSurveyLogicConditionsOperator.Enum.contains,
value: ZSurveyLogicConditionsOperator.enum.contains,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_contain)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotContain,
value: ZSurveyLogicConditionsOperator.enum.doesNotContain,
},
{
label: "mockTranslate(environments.surveys.edit.starts_with)",
value: ZSurveyLogicConditionsOperator.Enum.startsWith,
value: ZSurveyLogicConditionsOperator.enum.startsWith,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_start_with)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotStartWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotStartWith,
},
{
label: "mockTranslate(environments.surveys.edit.ends_with)",
value: ZSurveyLogicConditionsOperator.Enum.endsWith,
value: ZSurveyLogicConditionsOperator.enum.endsWith,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_end_with)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEndWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotEndWith,
},
]);
});
@@ -476,12 +476,12 @@ describe("getLogicRules", () => {
const rules = logicRules["variable.number"];
expect(rules).toBeDefined();
expect(rules.options).toEqual([
{ label: "=", value: ZSurveyLogicConditionsOperator.Enum.equals },
{ label: "!=", value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual },
{ label: ">", value: ZSurveyLogicConditionsOperator.Enum.isGreaterThan },
{ label: "<", value: ZSurveyLogicConditionsOperator.Enum.isLessThan },
{ label: ">=", value: ZSurveyLogicConditionsOperator.Enum.isGreaterThanOrEqual },
{ label: "<=", value: ZSurveyLogicConditionsOperator.Enum.isLessThanOrEqual },
{ label: "=", value: ZSurveyLogicConditionsOperator.enum.equals },
{ label: "!=", value: ZSurveyLogicConditionsOperator.enum.doesNotEqual },
{ label: ">", value: ZSurveyLogicConditionsOperator.enum.isGreaterThan },
{ label: "<", value: ZSurveyLogicConditionsOperator.enum.isLessThan },
{ label: ">=", value: ZSurveyLogicConditionsOperator.enum.isGreaterThanOrEqual },
{ label: "<=", value: ZSurveyLogicConditionsOperator.enum.isLessThanOrEqual },
]);
});
});
@@ -493,43 +493,43 @@ describe("getLogicRules", () => {
expect(rules.options).toEqual([
{
label: "mockTranslate(environments.surveys.edit.equals)",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_equal)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: "mockTranslate(environments.surveys.edit.contains)",
value: ZSurveyLogicConditionsOperator.Enum.contains,
value: ZSurveyLogicConditionsOperator.enum.contains,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_contain)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotContain,
value: ZSurveyLogicConditionsOperator.enum.doesNotContain,
},
{
label: "mockTranslate(environments.surveys.edit.starts_with)",
value: ZSurveyLogicConditionsOperator.Enum.startsWith,
value: ZSurveyLogicConditionsOperator.enum.startsWith,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_start_with)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotStartWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotStartWith,
},
{
label: "mockTranslate(environments.surveys.edit.ends_with)",
value: ZSurveyLogicConditionsOperator.Enum.endsWith,
value: ZSurveyLogicConditionsOperator.enum.endsWith,
},
{
label: "mockTranslate(environments.surveys.edit.does_not_end_with)",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEndWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotEndWith,
},
{
label: "mockTranslate(environments.surveys.edit.is_set)",
value: ZSurveyLogicConditionsOperator.Enum.isSet,
value: ZSurveyLogicConditionsOperator.enum.isSet,
},
{
label: "mockTranslate(environments.surveys.edit.is_not_set)",
value: ZSurveyLogicConditionsOperator.Enum.isNotSet,
value: ZSurveyLogicConditionsOperator.enum.isNotSet,
},
]);
});
@@ -540,10 +540,10 @@ describe("TLogicRuleOption type", () => {
test("should be compatible with the options structure", () => {
const sampleOption: TLogicRuleOption[number] = {
label: "Test Label",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
};
// This test mainly serves as a type check during compilation
expect(sampleOption.label).toBe("Test Label");
expect(sampleOption.value).toBe(ZSurveyLogicConditionsOperator.Enum.equals);
expect(sampleOption.value).toBe(ZSurveyLogicConditionsOperator.enum.equals);
});
});

View File

@@ -9,43 +9,43 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.equals"),
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: t("environments.surveys.edit.does_not_equal"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: t("environments.surveys.edit.contains"),
value: ZSurveyLogicConditionsOperator.Enum.contains,
value: ZSurveyLogicConditionsOperator.enum.contains,
},
{
label: t("environments.surveys.edit.does_not_contain"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotContain,
value: ZSurveyLogicConditionsOperator.enum.doesNotContain,
},
{
label: t("environments.surveys.edit.starts_with"),
value: ZSurveyLogicConditionsOperator.Enum.startsWith,
value: ZSurveyLogicConditionsOperator.enum.startsWith,
},
{
label: t("environments.surveys.edit.does_not_start_with"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotStartWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotStartWith,
},
{
label: t("environments.surveys.edit.ends_with"),
value: ZSurveyLogicConditionsOperator.Enum.endsWith,
value: ZSurveyLogicConditionsOperator.enum.endsWith,
},
{
label: t("environments.surveys.edit.does_not_end_with"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEndWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotEndWith,
},
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -53,35 +53,35 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: "=",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "!=",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: ">",
value: ZSurveyLogicConditionsOperator.Enum.isGreaterThan,
value: ZSurveyLogicConditionsOperator.enum.isGreaterThan,
},
{
label: "<",
value: ZSurveyLogicConditionsOperator.Enum.isLessThan,
value: ZSurveyLogicConditionsOperator.enum.isLessThan,
},
{
label: ">=",
value: ZSurveyLogicConditionsOperator.Enum.isGreaterThanOrEqual,
value: ZSurveyLogicConditionsOperator.enum.isGreaterThanOrEqual,
},
{
label: "<=",
value: ZSurveyLogicConditionsOperator.Enum.isLessThanOrEqual,
value: ZSurveyLogicConditionsOperator.enum.isLessThanOrEqual,
},
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -89,23 +89,23 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.equals"),
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: t("environments.surveys.edit.does_not_equal"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: t("environments.surveys.edit.equals_one_of"),
value: ZSurveyLogicConditionsOperator.Enum.equalsOneOf,
value: ZSurveyLogicConditionsOperator.enum.equalsOneOf,
},
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -113,35 +113,35 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.equals"),
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: t("environments.surveys.edit.does_not_equal"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: t("environments.surveys.edit.does_not_include_one_of"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeOneOf,
value: ZSurveyLogicConditionsOperator.enum.doesNotIncludeOneOf,
},
{
label: t("environments.surveys.edit.does_not_include_all_of"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeAllOf,
value: ZSurveyLogicConditionsOperator.enum.doesNotIncludeAllOf,
},
{
label: t("environments.surveys.edit.includes_all_of"),
value: ZSurveyLogicConditionsOperator.Enum.includesAllOf,
value: ZSurveyLogicConditionsOperator.enum.includesAllOf,
},
{
label: t("environments.surveys.edit.includes_one_of"),
value: ZSurveyLogicConditionsOperator.Enum.includesOneOf,
value: ZSurveyLogicConditionsOperator.enum.includesOneOf,
},
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -149,35 +149,35 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.equals"),
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: t("environments.surveys.edit.does_not_equal"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: t("environments.surveys.edit.does_not_include_one_of"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeOneOf,
value: ZSurveyLogicConditionsOperator.enum.doesNotIncludeOneOf,
},
{
label: t("environments.surveys.edit.does_not_include_all_of"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotIncludeAllOf,
value: ZSurveyLogicConditionsOperator.enum.doesNotIncludeAllOf,
},
{
label: t("environments.surveys.edit.includes_all_of"),
value: ZSurveyLogicConditionsOperator.Enum.includesAllOf,
value: ZSurveyLogicConditionsOperator.enum.includesAllOf,
},
{
label: t("environments.surveys.edit.includes_one_of"),
value: ZSurveyLogicConditionsOperator.Enum.includesOneOf,
value: ZSurveyLogicConditionsOperator.enum.includesOneOf,
},
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -185,35 +185,35 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: "=",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "!=",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: ">",
value: ZSurveyLogicConditionsOperator.Enum.isGreaterThan,
value: ZSurveyLogicConditionsOperator.enum.isGreaterThan,
},
{
label: "<",
value: ZSurveyLogicConditionsOperator.Enum.isLessThan,
value: ZSurveyLogicConditionsOperator.enum.isLessThan,
},
{
label: ">=",
value: ZSurveyLogicConditionsOperator.Enum.isGreaterThanOrEqual,
value: ZSurveyLogicConditionsOperator.enum.isGreaterThanOrEqual,
},
{
label: "<=",
value: ZSurveyLogicConditionsOperator.Enum.isLessThanOrEqual,
value: ZSurveyLogicConditionsOperator.enum.isLessThanOrEqual,
},
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -221,35 +221,35 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: "=",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "!=",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: ">",
value: ZSurveyLogicConditionsOperator.Enum.isGreaterThan,
value: ZSurveyLogicConditionsOperator.enum.isGreaterThan,
},
{
label: "<",
value: ZSurveyLogicConditionsOperator.Enum.isLessThan,
value: ZSurveyLogicConditionsOperator.enum.isLessThan,
},
{
label: ">=",
value: ZSurveyLogicConditionsOperator.Enum.isGreaterThanOrEqual,
value: ZSurveyLogicConditionsOperator.enum.isGreaterThanOrEqual,
},
{
label: "<=",
value: ZSurveyLogicConditionsOperator.Enum.isLessThanOrEqual,
value: ZSurveyLogicConditionsOperator.enum.isLessThanOrEqual,
},
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -257,11 +257,11 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.is_clicked"),
value: ZSurveyLogicConditionsOperator.Enum.isClicked,
value: ZSurveyLogicConditionsOperator.enum.isClicked,
},
{
label: t("environments.surveys.edit.is_not_clicked"),
value: ZSurveyLogicConditionsOperator.Enum.isNotClicked,
value: ZSurveyLogicConditionsOperator.enum.isNotClicked,
},
],
},
@@ -269,11 +269,11 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.is_accepted"),
value: ZSurveyLogicConditionsOperator.Enum.isAccepted,
value: ZSurveyLogicConditionsOperator.enum.isAccepted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -281,27 +281,27 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.equals"),
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: t("environments.surveys.edit.does_not_equal"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: t("environments.surveys.edit.is_before"),
value: ZSurveyLogicConditionsOperator.Enum.isBefore,
value: ZSurveyLogicConditionsOperator.enum.isBefore,
},
{
label: t("environments.surveys.edit.is_after"),
value: ZSurveyLogicConditionsOperator.Enum.isAfter,
value: ZSurveyLogicConditionsOperator.enum.isAfter,
},
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -309,11 +309,11 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -321,11 +321,11 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -333,11 +333,11 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.is_booked"),
value: ZSurveyLogicConditionsOperator.Enum.isBooked,
value: ZSurveyLogicConditionsOperator.enum.isBooked,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -345,15 +345,15 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.is_partially_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isPartiallySubmitted,
value: ZSurveyLogicConditionsOperator.enum.isPartiallySubmitted,
},
{
label: t("environments.surveys.edit.is_completely_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isCompletelySubmitted,
value: ZSurveyLogicConditionsOperator.enum.isCompletelySubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -361,24 +361,24 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.equals"),
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: t("environments.surveys.edit.does_not_equal"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: t("environments.surveys.edit.is_empty"),
value: ZSurveyLogicConditionsOperator.Enum.isEmpty,
value: ZSurveyLogicConditionsOperator.enum.isEmpty,
},
{
label: t("environments.surveys.edit.is_not_empty"),
value: ZSurveyLogicConditionsOperator.Enum.isNotEmpty,
value: ZSurveyLogicConditionsOperator.enum.isNotEmpty,
},
{
label: t("environments.surveys.edit.is_any_of"),
value: ZSurveyLogicConditionsOperator.Enum.isAnyOf,
value: ZSurveyLogicConditionsOperator.enum.isAnyOf,
},
],
},
@@ -386,11 +386,11 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -398,11 +398,11 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.is_submitted"),
value: ZSurveyLogicConditionsOperator.Enum.isSubmitted,
value: ZSurveyLogicConditionsOperator.enum.isSubmitted,
},
{
label: t("environments.surveys.edit.is_skipped"),
value: ZSurveyLogicConditionsOperator.Enum.isSkipped,
value: ZSurveyLogicConditionsOperator.enum.isSkipped,
},
],
},
@@ -411,35 +411,35 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.equals"),
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: t("environments.surveys.edit.does_not_equal"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: t("environments.surveys.edit.contains"),
value: ZSurveyLogicConditionsOperator.Enum.contains,
value: ZSurveyLogicConditionsOperator.enum.contains,
},
{
label: t("environments.surveys.edit.does_not_contain"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotContain,
value: ZSurveyLogicConditionsOperator.enum.doesNotContain,
},
{
label: t("environments.surveys.edit.starts_with"),
value: ZSurveyLogicConditionsOperator.Enum.startsWith,
value: ZSurveyLogicConditionsOperator.enum.startsWith,
},
{
label: t("environments.surveys.edit.does_not_start_with"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotStartWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotStartWith,
},
{
label: t("environments.surveys.edit.ends_with"),
value: ZSurveyLogicConditionsOperator.Enum.endsWith,
value: ZSurveyLogicConditionsOperator.enum.endsWith,
},
{
label: t("environments.surveys.edit.does_not_end_with"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEndWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotEndWith,
},
],
},
@@ -447,27 +447,27 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: "=",
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: "!=",
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: ">",
value: ZSurveyLogicConditionsOperator.Enum.isGreaterThan,
value: ZSurveyLogicConditionsOperator.enum.isGreaterThan,
},
{
label: "<",
value: ZSurveyLogicConditionsOperator.Enum.isLessThan,
value: ZSurveyLogicConditionsOperator.enum.isLessThan,
},
{
label: ">=",
value: ZSurveyLogicConditionsOperator.Enum.isGreaterThanOrEqual,
value: ZSurveyLogicConditionsOperator.enum.isGreaterThanOrEqual,
},
{
label: "<=",
value: ZSurveyLogicConditionsOperator.Enum.isLessThanOrEqual,
value: ZSurveyLogicConditionsOperator.enum.isLessThanOrEqual,
},
],
},
@@ -475,43 +475,43 @@ export const getLogicRules = (t: TFunction) => {
options: [
{
label: t("environments.surveys.edit.equals"),
value: ZSurveyLogicConditionsOperator.Enum.equals,
value: ZSurveyLogicConditionsOperator.enum.equals,
},
{
label: t("environments.surveys.edit.does_not_equal"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEqual,
value: ZSurveyLogicConditionsOperator.enum.doesNotEqual,
},
{
label: t("environments.surveys.edit.contains"),
value: ZSurveyLogicConditionsOperator.Enum.contains,
value: ZSurveyLogicConditionsOperator.enum.contains,
},
{
label: t("environments.surveys.edit.does_not_contain"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotContain,
value: ZSurveyLogicConditionsOperator.enum.doesNotContain,
},
{
label: t("environments.surveys.edit.starts_with"),
value: ZSurveyLogicConditionsOperator.Enum.startsWith,
value: ZSurveyLogicConditionsOperator.enum.startsWith,
},
{
label: t("environments.surveys.edit.does_not_start_with"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotStartWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotStartWith,
},
{
label: t("environments.surveys.edit.ends_with"),
value: ZSurveyLogicConditionsOperator.Enum.endsWith,
value: ZSurveyLogicConditionsOperator.enum.endsWith,
},
{
label: t("environments.surveys.edit.does_not_end_with"),
value: ZSurveyLogicConditionsOperator.Enum.doesNotEndWith,
value: ZSurveyLogicConditionsOperator.enum.doesNotEndWith,
},
{
label: t("environments.surveys.edit.is_set"),
value: ZSurveyLogicConditionsOperator.Enum.isSet,
value: ZSurveyLogicConditionsOperator.enum.isSet,
},
{
label: t("environments.surveys.edit.is_not_set"),
value: ZSurveyLogicConditionsOperator.Enum.isNotSet,
value: ZSurveyLogicConditionsOperator.enum.isNotSet,
},
],
},

View File

@@ -1,4 +1,4 @@
import { ActionClass, Prisma } from "@prisma/client";
import { Prisma } from "@prisma/client";
import { prisma } from "@formbricks/database";
import { logger } from "@formbricks/logger";
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
@@ -286,7 +286,12 @@ export const updateSurvey = async (updatedSurvey: TSurvey): Promise<TSurvey> =>
}
};
export const checkTriggersValidity = (triggers: TSurvey["triggers"], actionClasses: ActionClass[]) => {
type TriggerWithActionClassId = { actionClass: { id: string } };
export const checkTriggersValidity = (
triggers: TriggerWithActionClassId[] | null | undefined,
actionClasses: Array<{ id: string }>
) => {
if (!triggers) return;
// check if all the triggers are valid
@@ -305,14 +310,14 @@ export const checkTriggersValidity = (triggers: TSurvey["triggers"], actionClass
};
export const handleTriggerUpdates = (
updatedTriggers: TSurvey["triggers"],
currentTriggers: TSurvey["triggers"],
actionClasses: ActionClass[]
updatedTriggers: TriggerWithActionClassId[] | null | undefined,
currentTriggers: TriggerWithActionClassId[] | null | undefined,
actionClasses: Array<{ id: string }>
) => {
if (!updatedTriggers) return {};
checkTriggersValidity(updatedTriggers, actionClasses);
const currentTriggerIds = currentTriggers.map((trigger) => trigger.actionClass.id);
const currentTriggerIds = (currentTriggers ?? []).map((trigger) => trigger.actionClass.id);
const updatedTriggerIds = updatedTriggers.map((trigger) => trigger.actionClass.id);
// added triggers are triggers that are not in the current triggers and are there in the new triggers
@@ -321,7 +326,7 @@ export const handleTriggerUpdates = (
);
// deleted triggers are triggers that are not in the new triggers and are there in the current triggers
const deletedTriggers = currentTriggers.filter(
const deletedTriggers = (currentTriggers ?? []).filter(
(trigger) => !updatedTriggerIds.includes(trigger.actionClass.id)
);

View File

@@ -3,9 +3,9 @@ import { z } from "zod";
export const ZCreateSurveyFollowUpFormSchema = z.object({
followUpName: z.string().trim().min(1, "Name is required"),
triggerType: z.enum(["response", "endings"]),
endingIds: z.array(z.string().cuid2()).nullable(),
endingIds: z.array(z.cuid2()).nullable(),
emailTo: z.string().trim().min(1, "To is required"),
replyTo: z.array(z.string().email()).min(1, "Replies must have at least one email"),
replyTo: z.array(z.email()).min(1, "Replies must have at least one email"),
subject: z.string().trim().min(1, "Subject is required"),
body: z.string().trim().min(1, "Body is required"),
attachResponseData: z.boolean(),

View File

@@ -7,21 +7,19 @@ import { ZSurveyType } from "@formbricks/types/surveys/types";
* Validates essential fields for security/functionality but allows incomplete survey data.
* Full validation (ZSurvey) is enforced when publishing.
*/
export const ZSurveyDraft = z
.object({
// Essential fields - strictly validated
id: ZId,
status: z.literal("draft"),
environmentId: ZId,
type: ZSurveyType,
name: z.string().min(1, "Survey name is required"),
export const ZSurveyDraft = z.looseObject({
// Essential fields - strictly validated
id: ZId,
status: z.literal("draft"),
environmentId: ZId,
type: ZSurveyType,
name: z.string().min(1, "Survey name is required"),
// Required fields for database operations - loosely validated
blocks: z.array(z.record(z.unknown())).optional(),
triggers: z.array(z.record(z.unknown())).optional(),
endings: z.array(z.record(z.unknown())).optional(),
segment: z.record(z.unknown()).nullable().optional(),
})
.passthrough(); // Allow all other fields without validation
// Required fields for database operations - loosely validated
blocks: z.array(z.record(z.string(), z.unknown())).optional(),
triggers: z.array(z.record(z.string(), z.unknown())).optional(),
endings: z.array(z.record(z.string(), z.unknown())).optional(),
segment: z.record(z.string(), z.unknown()).nullable().optional(),
}); // Allow all other fields without validation
export type TSurveyDraft = z.infer<typeof ZSurveyDraft>;

View File

@@ -30,7 +30,7 @@ const evaluateFollowUp = async (
const logoUrl = organization.whitelabel?.logoUrl ?? "";
// Check if 'to' is a direct email address (team member or user email)
const parsedEmailTo = z.string().email().safeParse(to);
const parsedEmailTo = z.email().safeParse(to);
if (parsedEmailTo.success) {
// 'to' is a valid email address, send email directly
await sendFollowUpEmail({
@@ -62,7 +62,7 @@ const evaluateFollowUp = async (
if (typeof toValueFromResponse === "string") {
// parse this string to check for an email:
const parsedResult = z.string().email().safeParse(toValueFromResponse);
const parsedResult = z.email().safeParse(toValueFromResponse);
if (parsedResult.success) {
// send email to this email address
await sendFollowUpEmail({
@@ -98,7 +98,7 @@ const evaluateFollowUp = async (
};
}
const parsedResult = z.string().email().safeParse(emailAddress);
const parsedResult = z.email().safeParse(emailAddress);
if (parsedResult.data) {
await sendFollowUpEmail({
followUp,

View File

@@ -6,7 +6,7 @@ import { DatabaseError } from "@formbricks/types/errors";
import { validateInputs } from "@/lib/utils/validate";
export const getActionClasses = reactCache(async (environmentId: string): Promise<ActionClass[]> => {
validateInputs([environmentId, z.string().cuid2()]);
validateInputs([environmentId, z.cuid2()]);
try {
return await prisma.actionClass.findMany({

View File

@@ -30,7 +30,7 @@ export const sendLinkSurveyEmailAction = actionClient
});
const ZValidateSurveyPinAction = z.object({
surveyId: z.string().cuid2(),
surveyId: z.cuid2(),
pin: z.string(),
});
@@ -55,8 +55,8 @@ export const validateSurveyPinAction = actionClient
});
const ZIsSurveyResponsePresentAction = z.object({
surveyId: z.string().cuid2(),
email: z.string().email(),
surveyId: z.cuid2(),
email: z.email(),
});
export const isSurveyResponsePresentAction = actionClient

View File

@@ -32,7 +32,7 @@ interface VerifyEmailProps {
}
const ZVerifyEmailInput = z.object({
email: z.string().email(),
email: z.email(),
});
type TVerifyEmailInput = z.infer<typeof ZVerifyEmailInput>;

View File

@@ -25,7 +25,7 @@ import {
} from "@/modules/survey/list/lib/survey";
const ZGetSurveyAction = z.object({
surveyId: z.string().cuid2(),
surveyId: z.cuid2(),
});
export const getSurveyAction = authenticatedActionClient
@@ -51,8 +51,8 @@ export const getSurveyAction = authenticatedActionClient
});
const ZCopySurveyToOtherEnvironmentAction = z.object({
surveyId: z.string().cuid2(),
targetEnvironmentId: z.string().cuid2(),
surveyId: z.cuid2(),
targetEnvironmentId: z.cuid2(),
});
export const copySurveyToOtherEnvironmentAction = authenticatedActionClient
@@ -141,7 +141,7 @@ export const copySurveyToOtherEnvironmentAction = authenticatedActionClient
);
const ZGetProjectsByEnvironmentIdAction = z.object({
environmentId: z.string().cuid2(),
environmentId: z.cuid2(),
});
export const getProjectsByEnvironmentIdAction = authenticatedActionClient
@@ -168,7 +168,7 @@ export const getProjectsByEnvironmentIdAction = authenticatedActionClient
});
const ZDeleteSurveyAction = z.object({
surveyId: z.string().cuid2(),
surveyId: z.cuid2(),
});
export const deleteSurveyAction = authenticatedActionClient.schema(ZDeleteSurveyAction).action(
@@ -201,9 +201,9 @@ export const deleteSurveyAction = authenticatedActionClient.schema(ZDeleteSurvey
);
const ZGenerateSingleUseIdAction = z.object({
surveyId: z.string().cuid2(),
surveyId: z.cuid2(),
isEncrypted: z.boolean(),
count: z.number().min(1).max(5000).default(1),
count: z.number().min(1).max(5000).prefault(1),
});
export const generateSingleUseIdsAction = authenticatedActionClient
@@ -229,7 +229,7 @@ export const generateSingleUseIdsAction = authenticatedActionClient
});
const ZGetSurveysAction = z.object({
environmentId: z.string().cuid2(),
environmentId: z.cuid2(),
limit: z.number().optional(),
offset: z.number().optional(),
filterCriteria: ZSurveyFilterCriteria.optional(),

View File

@@ -45,7 +45,7 @@ export const getProjectIdIfEnvironmentExists = reactCache(
export const getEnvironment = reactCache(
async (environmentId: string): Promise<Pick<Environment, "id" | "type"> | null> => {
validateInputs([environmentId, z.string().cuid2()]);
validateInputs([environmentId, z.cuid2()]);
try {
const environment = await prisma.environment.findUnique({

View File

@@ -603,7 +603,7 @@ export const copySurveyToOtherEnvironment = async (
};
export const getSurveyCount = reactCache(async (environmentId: string): Promise<number> => {
validateInputs([environmentId, z.string().cuid2()]);
validateInputs([environmentId, z.cuid2()]);
try {
const surveyCount = await prisma.survey.count({
where: {

View File

@@ -10,7 +10,7 @@ import { getOrganizationIdFromSurveyId, getProjectIdFromSurveyId } from "@/lib/u
import { updateSurveySlug } from "@/modules/survey/lib/slug";
const ZUpdateSurveySlugAction = z.object({
surveyId: z.string().cuid2(),
surveyId: z.cuid2(),
slug: ZSurveySlug,
});
@@ -39,7 +39,7 @@ export const updateSurveySlugAction = authenticatedActionClient
});
const ZRemoveSurveySlugAction = z.object({
surveyId: z.string().cuid2(),
surveyId: z.cuid2(),
});
export const removeSurveySlugAction = authenticatedActionClient

View File

@@ -42,18 +42,18 @@
"@lexical/react": "0.41.0",
"@lexical/rich-text": "0.41.0",
"@lexical/table": "0.41.0",
"@opentelemetry/auto-instrumentations-node": "0.70.1",
"@opentelemetry/exporter-metrics-otlp-http": "0.212.0",
"@opentelemetry/exporter-prometheus": "0.212.0",
"@opentelemetry/exporter-trace-otlp-http": "0.212.0",
"@opentelemetry/resources": "2.5.1",
"@opentelemetry/sdk-metrics": "2.5.1",
"@opentelemetry/sdk-node": "0.212.0",
"@opentelemetry/sdk-trace-base": "2.5.1",
"@opentelemetry/auto-instrumentations-node": "0.71.0",
"@opentelemetry/exporter-metrics-otlp-http": "0.213.0",
"@opentelemetry/exporter-prometheus": "0.213.0",
"@opentelemetry/exporter-trace-otlp-http": "0.213.0",
"@opentelemetry/resources": "2.6.0",
"@opentelemetry/sdk-metrics": "2.6.0",
"@opentelemetry/sdk-node": "0.213.0",
"@opentelemetry/sdk-trace-base": "2.6.0",
"@opentelemetry/semantic-conventions": "1.40.0",
"@paralleldrive/cuid2": "2.3.1",
"@prisma/client": "6.19.2",
"@prisma/instrumentation": "6.19.2",
"@paralleldrive/cuid2": "3.3.0",
"@prisma/client": "7.4.2",
"@prisma/instrumentation": "7.4.2",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-collapsible": "1.1.12",
"@radix-ui/react-dialog": "1.1.15",
@@ -79,55 +79,55 @@
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"csv-parse": "5.6.0",
"csv-parse": "6.1.0",
"date-fns": "4.1.0",
"file-loader": "6.2.0",
"framer-motion": "12.34.4",
"googleapis": "148.0.0",
"framer-motion": "12.35.0",
"googleapis": "171.4.0",
"heic-convert": "2.1.0",
"https-proxy-agent": "7.0.6",
"i18next": "25.8.13",
"i18next": "25.8.14",
"i18next-icu": "2.4.3",
"i18next-resources-to-backend": "1.2.1",
"jiti": "2.6.1",
"jsonwebtoken": "9.0.3",
"lexical": "0.41.0",
"lodash": "4.17.23",
"lucide-react": "0.576.0",
"lucide-react": "0.577.0",
"markdown-it": "14.1.1",
"next": "16.1.6",
"next-auth": "4.24.13",
"next-safe-action": "7.10.8",
"next-safe-action": "8.1.5",
"node-fetch": "3.3.2",
"nodemailer": "7.0.13",
"otplib": "12.0.1",
"nodemailer": "8.0.1",
"otplib": "13.3.0",
"papaparse": "5.5.3",
"prismjs": "1.30.0",
"qr-code-styling": "1.9.2",
"qrcode": "1.5.4",
"react": "19.2.4",
"react-calendar": "5.1.0",
"react-calendar": "6.0.0",
"react-colorful": "5.6.1",
"react-confetti": "6.4.0",
"react-day-picker": "9.14.0",
"react-dom": "19.2.4",
"react-hook-form": "7.71.2",
"react-hot-toast": "2.6.0",
"react-i18next": "15.7.4",
"react-i18next": "16.5.4",
"react-turnstile": "1.1.5",
"react-use": "17.6.0",
"sanitize-html": "2.17.1",
"server-only": "0.0.1",
"sharp": "0.34.5",
"stripe": "16.12.0",
"stripe": "20.4.0",
"tailwind-merge": "3.5.0",
"tailwindcss": "3.4.19",
"ua-parser-js": "2.0.9",
"uuid": "11.1.0",
"webpack": "5.105.3",
"uuid": "13.0.0",
"webpack": "5.105.4",
"xlsx": "file:vendor/xlsx-0.20.3.tgz",
"zod": "3.25.76",
"zod-openapi": "4.2.4"
"zod": "4.3.6",
"zod-openapi": "5.4.6"
},
"devDependencies": {
"@formbricks/config-typescript": "workspace:*",
@@ -143,15 +143,15 @@
"@types/qrcode": "1.5.6",
"@types/sanitize-html": "2.16.0",
"@types/ungap__structured-clone": "1.2.0",
"@vitest/coverage-v8": "3.2.4",
"@vitest/coverage-v8": "4.0.18",
"autoprefixer": "10.4.27",
"cross-env": "10.1.0",
"dotenv": "16.6.1",
"dotenv": "17.3.1",
"postcss": "8.5.8",
"resize-observer-polyfill": "1.5.1",
"vite": "6.4.1",
"vite-tsconfig-paths": "5.1.4",
"vitest": "3.2.4",
"vite": "7.3.1",
"vite-tsconfig-paths": "6.1.1",
"vitest": "4.0.18",
"vitest-mock-extended": "3.1.0"
}
}

View File

@@ -6,9 +6,6 @@
"apps/*",
"packages/*"
],
"prisma": {
"schema": "packages/database/schema.prisma"
},
"scripts": {
"agents:update": "npx @next/codemod@canary agents-md --output AGENTS.md",
"clean:all": "turbo run clean && rimraf node_modules pnpm-lock.yaml .turbo coverage out",
@@ -54,10 +51,10 @@
"@playwright/test": "1.58.2",
"eslint": "8.57.1",
"husky": "9.1.7",
"lint-staged": "16.3.1",
"lint-staged": "16.3.2",
"rimraf": "6.1.3",
"tsx": "4.21.0",
"turbo": "2.8.12"
"turbo": "2.8.13"
},
"lint-staged": {
"(apps|packages)/**/*.{js,ts,jsx,tsx}": [

View File

@@ -39,13 +39,13 @@
"dependencies": {
"@formbricks/logger": "workspace:*",
"redis": "5.11.0",
"zod": "3.25.76"
"zod": "4.3.6"
},
"devDependencies": {
"@formbricks/config-typescript": "workspace:*",
"@formbricks/eslint-config": "workspace:*",
"vite": "6.4.1",
"vitest": "3.2.4",
"@vitest/coverage-v8": "3.2.4"
"vite": "7.3.1",
"vitest": "4.0.18",
"@vitest/coverage-v8": "4.0.18"
}
}

Some files were not shown because too many files have changed in this diff Show More