From c108cd4780637b568b34dcb0d327e7b5f8688c4f Mon Sep 17 00:00:00 2001
From: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com>
Date: Wed, 21 Aug 2024 16:42:30 +0530
Subject: [PATCH 01/19] fix: js command queue support for late initialization
(#2974)
---
apps/demo/pages/website/index.tsx | 1 +
packages/js/src/method-queue.ts | 38 -------
packages/js/src/shared/load-formbricks.ts | 117 ++++++++--------------
turbo.json | 6 ++
4 files changed, 51 insertions(+), 111 deletions(-)
delete mode 100644 packages/js/src/method-queue.ts
diff --git a/apps/demo/pages/website/index.tsx b/apps/demo/pages/website/index.tsx
index 40ea72dac5..28a01898f6 100644
--- a/apps/demo/pages/website/index.tsx
+++ b/apps/demo/pages/website/index.tsx
@@ -128,6 +128,7 @@ const AppPage = ({}) => {
}}>
Reset
+
If you made a change in Formbricks app and it does not seem to work, hit 'Reset' and
try again.
diff --git a/packages/js/src/method-queue.ts b/packages/js/src/method-queue.ts
deleted file mode 100644
index a2fab1ce6d..0000000000
--- a/packages/js/src/method-queue.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-// Simple queue for formbricks methods
-
-export class MethodQueue {
- private queue: (() => Promise)[] = [];
- private isExecuting = false;
-
- add = (method: () => Promise): void => {
- this.queue.push(method);
- void this.run();
- };
-
- private runNext = async (): Promise => {
- if (this.isExecuting) return;
-
- const method = this.queue.shift();
- if (method) {
- this.isExecuting = true;
- try {
- await method();
- } finally {
- this.isExecuting = false;
- if (this.queue.length > 0) {
- void this.runNext();
- }
- }
- }
- };
-
- run = async (): Promise => {
- if (!this.isExecuting && this.queue.length > 0) {
- await this.runNext();
- }
- };
-
- clear = (): void => {
- this.queue = [];
- };
-}
diff --git a/packages/js/src/shared/load-formbricks.ts b/packages/js/src/shared/load-formbricks.ts
index 12b2cbd598..6e89e2b96a 100644
--- a/packages/js/src/shared/load-formbricks.ts
+++ b/packages/js/src/shared/load-formbricks.ts
@@ -1,26 +1,16 @@
-/* eslint-disable @typescript-eslint/no-unsafe-member-access --
- * Required for dynamic function calls
- */
-
-/* eslint-disable @typescript-eslint/no-unsafe-call --
- * Required for dynamic function calls
- */
-
/*
- eslint-disable no-console --
+ eslint-disable no-console --
* Required for logging errors
*/
-import { type Result, wrapThrowsAsync } from "@formbricks/types/error-handlers";
-import { MethodQueue } from "../method-queue";
+import { type Result } from "@formbricks/types/error-handlers";
let isInitializing = false;
let isInitialized = false;
-const methodQueue = new MethodQueue();
// Load the SDK, return the result
-const loadFormbricksSDK = async (apiHost: string, sdkType: "app" | "website"): Promise> => {
+const loadFormbricksSDK = async (apiHostParam: string, sdkType: "app" | "website"): Promise> => {
if (!window.formbricks) {
- const res = await fetch(`${apiHost}/api/packages/${sdkType}`);
+ const res = await fetch(`${apiHostParam}/api/packages/${sdkType}`);
// Failed to fetch the app package
if (!res.ok) {
@@ -63,81 +53,62 @@ const loadFormbricksSDK = async (apiHost: string, sdkType: "app" | "website"): P
return { ok: true, data: undefined };
};
-// TODO: @pandeymangg - Fix these types
-// type FormbricksAppMethods = {
-// [K in keyof TFormbricksApp]: TFormbricksApp[K] extends Function ? K : never;
-// }[keyof TFormbricksApp];
-
-// type FormbricksWebsiteMethods = {
-// [K in keyof TFormbricksWebsite]: TFormbricksWebsite[K] extends Function ? K : never;
-// }[keyof TFormbricksWebsite];
+const functionsToProcess: { prop: string; args: unknown[] }[] = [];
export const loadFormbricksToProxy = async (
prop: string,
sdkType: "app" | "website",
...args: unknown[]
- // eslint-disable-next-line @typescript-eslint/require-await -- Required for dynamic function calls
): Promise => {
- const executeMethod = async (): Promise => {
- try {
- if (window.formbricks) {
- // @ts-expect-error -- window.formbricks is a dynamic function
- return (await window.formbricks[prop](...args)) as unknown;
- }
- } catch (error: unknown) {
- console.error("🧱 Formbricks - Global error: ", error);
- throw error;
- }
- };
-
+ // all of this should happen when not initialized:
if (!isInitialized) {
- if (isInitializing) {
- methodQueue.add(executeMethod);
- } else if (prop === "init") {
+ if (prop === "init") {
+ if (isInitializing) {
+ console.warn("🧱 Formbricks - Warning: Formbricks is already initializing.");
+ return;
+ }
+
+ // reset the initialization state
isInitializing = true;
- const initialize = async (): Promise => {
- const { apiHost } = args[0] as { apiHost: string };
- const loadSDKResult = (await wrapThrowsAsync(loadFormbricksSDK)(apiHost, sdkType)) as unknown as {
- ok: boolean;
- error: Error;
- };
+ const apiHost = (args[0] as { apiHost: string }).apiHost;
+ const loadSDKResult = await loadFormbricksSDK(apiHost, sdkType);
+
+ if (loadSDKResult.ok) {
+ if (window.formbricks) {
+ // @ts-expect-error -- Required for dynamic function calls
+ void window.formbricks.init(...args);
- if (!loadSDKResult.ok) {
isInitializing = false;
- console.error(`🧱 Formbricks - Global error: ${loadSDKResult.error.message}`);
- return;
- }
+ isInitialized = true;
- try {
- if (window.formbricks) {
- // @ts-expect-error -- args is an array
- await window.formbricks[prop](...args);
- isInitialized = true;
- isInitializing = false;
+ // process the queued functions
+ for (const { prop: functionProp, args: functionArgs } of functionsToProcess) {
+ type FormbricksProp = keyof typeof window.formbricks;
+
+ if (typeof window.formbricks[functionProp as FormbricksProp] !== "function") {
+ console.error(`🧱 Formbricks - Error: Method ${functionProp} does not exist on formbricks`);
+ continue;
+ }
+
+ // @ts-expect-error -- Required for dynamic function calls
+ (window.formbricks[functionProp] as unknown)(...functionArgs);
}
- } catch (error) {
- isInitializing = false;
- console.error("🧱 Formbricks - Global error: ", error);
- throw error;
}
- };
-
- methodQueue.add(initialize);
+ }
} else {
- console.error(
- "🧱 Formbricks - Global error: You need to call formbricks.init before calling any other method"
+ console.warn(
+ "🧱 Formbricks - Warning: Formbricks not initialized. This method will be queued and executed after initialization."
);
- }
- } else {
- // @ts-expect-error -- window.formbricks is a dynamic function
- if (window.formbricks && typeof window.formbricks[prop] !== "function") {
- console.error(
- `🧱 Formbricks - Global error: Formbricks ${sdkType} SDK does not support method ${String(prop)}`
- );
- return;
- }
- methodQueue.add(executeMethod);
+ functionsToProcess.push({ prop, args });
+ }
+ } else if (window.formbricks) {
+ type Formbricks = typeof window.formbricks;
+ type FunctionProp = keyof Formbricks;
+ const functionPropTyped = prop as FunctionProp;
+
+ // @ts-expect-error -- Required for dynamic function calls
+ await window.formbricks[functionPropTyped](...args);
}
};
diff --git a/turbo.json b/turbo.json
index 1e854de355..c1c6d95cf6 100644
--- a/turbo.json
+++ b/turbo.json
@@ -47,6 +47,12 @@
"persistent": true,
"dependsOn": ["@formbricks/api#build"]
},
+ "@formbricks/js#lint": {
+ "dependsOn": ["@formbricks/js-core#build"]
+ },
+ "@formbricks/database#lint": {
+ "dependsOn": ["@formbricks/database#build"]
+ },
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"],
From afe042ecfc0ab99f69c856b5e00017ea1ae55b19 Mon Sep 17 00:00:00 2001
From: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com>
Date: Thu, 22 Aug 2024 14:25:44 +0530
Subject: [PATCH 02/19] fix: member invite issues (#3028)
Co-authored-by: Matti Nannt
---
apps/web/app/(app)/(onboarding)/organizations/actions.ts | 2 +-
.../[environmentId]/surveys/[surveyId]/edit/actions.ts | 2 +-
.../[environmentId]/(people)/attributes/actions.ts | 2 +-
.../[environmentId]/(people)/people/[personId]/actions.ts | 2 +-
.../[environmentId]/(people)/segments/actions.ts | 2 +-
apps/web/app/(app)/environments/[environmentId]/actions.ts | 2 +-
.../(app)/environments/[environmentId]/actions/actions.ts | 2 +-
.../environments/[environmentId]/integrations/actions.ts | 2 +-
.../[environmentId]/integrations/slack/actions.ts | 2 +-
.../[environmentId]/integrations/webhooks/actions.ts | 2 +-
.../(app)/environments/[environmentId]/product/actions.ts | 2 +-
.../[environmentId]/product/api-keys/actions.ts | 2 +-
.../environments/[environmentId]/product/general/actions.ts | 2 +-
.../environments/[environmentId]/product/tags/actions.ts | 2 +-
.../[environmentId]/settings/(account)/profile/actions.ts | 2 +-
.../settings/(organization)/billing/actions.ts | 2 +-
.../settings/(organization)/members/actions.ts | 6 +++---
.../surveys/[surveyId]/(analysis)/actions.ts | 2 +-
.../surveys/[surveyId]/(analysis)/summary/actions.ts | 2 +-
.../[environmentId]/surveys/[surveyId]/actions.ts | 2 +-
.../app/api/v1/client/[environmentId]/responses/route.ts | 2 +-
apps/web/app/s/[surveyId]/actions.ts | 2 +-
apps/web/app/s/[surveyId]/page.tsx | 2 +-
.../setup/organization/[organizationId]/invite/actions.ts | 2 +-
apps/web/app/share/[sharingKey]/actions.ts | 2 +-
packages/ee/advanced-targeting/lib/actions.ts | 2 +-
packages/ee/multi-language/lib/actions.ts | 2 +-
packages/ee/role-management/lib/actions.ts | 4 ++--
packages/lib/action/service.ts | 2 +-
packages/lib/actionClass/auth.ts | 2 +-
packages/lib/actionClass/service.ts | 2 +-
packages/lib/apiKey/auth.ts | 2 +-
packages/lib/apiKey/service.ts | 2 +-
packages/lib/attribute/service.ts | 2 +-
packages/lib/attributeClass/auth.ts | 2 +-
packages/lib/attributeClass/service.ts | 2 +-
packages/lib/display/service.ts | 2 +-
packages/lib/environment/auth.ts | 2 +-
packages/lib/environment/service.ts | 2 +-
packages/lib/integration/auth.ts | 2 +-
packages/lib/integration/service.ts | 2 +-
packages/lib/language/service.ts | 2 +-
packages/lib/organization/auth.ts | 2 +-
packages/lib/organization/hooks/actions.ts | 2 +-
packages/lib/organization/service.ts | 2 +-
packages/lib/person/auth.ts | 2 +-
packages/lib/person/service.ts | 2 +-
packages/lib/product/auth.ts | 2 +-
packages/lib/product/service.ts | 2 +-
packages/lib/response/auth.ts | 2 +-
packages/lib/response/service.ts | 2 +-
packages/lib/responseNote/auth.ts | 2 +-
packages/lib/responseNote/service.ts | 2 +-
packages/lib/segment/service.ts | 2 +-
packages/lib/storage/service.ts | 2 +-
packages/lib/survey/auth.ts | 2 +-
packages/lib/survey/service.ts | 3 ++-
packages/lib/tag/auth.ts | 2 +-
packages/lib/tag/service.ts | 2 +-
packages/lib/tagOnResponse/auth.ts | 2 +-
packages/lib/tagOnResponse/service.ts | 2 +-
packages/lib/user/service.ts | 2 +-
packages/lib/webhook/auth.ts | 2 +-
packages/lib/webhook/service.ts | 2 +-
packages/types/actions.ts | 2 +-
packages/types/common.ts | 4 ++++
packages/types/environment.ts | 2 --
packages/types/responses.ts | 2 +-
packages/types/surveys/types.ts | 3 +--
packages/ui/ShareSurveyLink/actions.ts | 2 +-
packages/ui/SingleResponseCard/actions.ts | 2 +-
packages/ui/SurveysList/actions.ts | 2 +-
packages/ui/TemplateList/actions.ts | 2 +-
73 files changed, 79 insertions(+), 77 deletions(-)
diff --git a/apps/web/app/(app)/(onboarding)/organizations/actions.ts b/apps/web/app/(app)/(onboarding)/organizations/actions.ts
index 9d165e84e1..a3e3befaa8 100644
--- a/apps/web/app/(app)/(onboarding)/organizations/actions.ts
+++ b/apps/web/app/(app)/(onboarding)/organizations/actions.ts
@@ -6,7 +6,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { INVITE_DISABLED } from "@formbricks/lib/constants";
import { inviteUser } from "@formbricks/lib/invite/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { AuthenticationError } from "@formbricks/types/errors";
import { ZMembershipRole } from "@formbricks/types/memberships";
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts
index c54d52cffb..47ed02dc8d 100644
--- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/actions.ts
@@ -21,7 +21,7 @@ import {
import { surveyCache } from "@formbricks/lib/survey/cache";
import { loadNewSegmentInSurvey, updateSurvey } from "@formbricks/lib/survey/service";
import { ZActionClassInput } from "@formbricks/types/action-classes";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZBaseFilters, ZSegmentFilters, ZSegmentUpdateInput } from "@formbricks/types/segment";
import { ZSurvey } from "@formbricks/types/surveys/types";
diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/attributes/actions.ts b/apps/web/app/(app)/environments/[environmentId]/(people)/attributes/actions.ts
index 2ccdc320af..a9ebeacc9d 100644
--- a/apps/web/app/(app)/environments/[environmentId]/(people)/attributes/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/(people)/attributes/actions.ts
@@ -9,7 +9,7 @@ import {
} from "@formbricks/lib/organization/utils";
import { getSegmentsByAttributeClassName } from "@formbricks/lib/segment/service";
import { ZAttributeClass } from "@formbricks/types/attribute-classes";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
const ZGetSegmentsByAttributeClassAction = z.object({
environmentId: ZId,
diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/people/[personId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/(people)/people/[personId]/actions.ts
index 6174f60f57..071378add9 100644
--- a/apps/web/app/(app)/environments/[environmentId]/(people)/people/[personId]/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/(people)/people/[personId]/actions.ts
@@ -5,7 +5,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromPersonId } from "@formbricks/lib/organization/utils";
import { deletePerson } from "@formbricks/lib/person/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
const ZPersonDeleteAction = z.object({
personId: ZId,
diff --git a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/actions.ts b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/actions.ts
index fac8a1012c..a998ef92fc 100644
--- a/apps/web/app/(app)/environments/[environmentId]/(people)/segments/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/(people)/segments/actions.ts
@@ -5,7 +5,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromSegmentId } from "@formbricks/lib/organization/utils";
import { deleteSegment, updateSegment } from "@formbricks/lib/segment/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZSegmentFilters, ZSegmentUpdateInput } from "@formbricks/types/segment";
const ZDeleteBasicSegmentAction = z.object({
diff --git a/apps/web/app/(app)/environments/[environmentId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/actions.ts
index a5d1fc8a86..76e7b33e6c 100644
--- a/apps/web/app/(app)/environments/[environmentId]/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/actions.ts
@@ -8,7 +8,7 @@ import { createMembership } from "@formbricks/lib/membership/service";
import { createOrganization } from "@formbricks/lib/organization/service";
import { createProduct } from "@formbricks/lib/product/service";
import { updateUser } from "@formbricks/lib/user/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { OperationNotAllowedError } from "@formbricks/types/errors";
import { ZProductUpdateInput } from "@formbricks/types/product";
import { TUserNotificationSettings } from "@formbricks/types/user";
diff --git a/apps/web/app/(app)/environments/[environmentId]/actions/actions.ts b/apps/web/app/(app)/environments/[environmentId]/actions/actions.ts
index 953797f02e..3ff5965856 100644
--- a/apps/web/app/(app)/environments/[environmentId]/actions/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/actions/actions.ts
@@ -7,7 +7,7 @@ import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromActionClassId } from "@formbricks/lib/organization/utils";
import { getSurveysByActionClassId } from "@formbricks/lib/survey/service";
import { ZActionClassInput } from "@formbricks/types/action-classes";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ResourceNotFoundError } from "@formbricks/types/errors";
const ZDeleteActionClassAction = z.object({
diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/actions.ts b/apps/web/app/(app)/environments/[environmentId]/integrations/actions.ts
index a962579a6f..beeba66026 100644
--- a/apps/web/app/(app)/environments/[environmentId]/integrations/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/integrations/actions.ts
@@ -5,7 +5,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { createOrUpdateIntegration, deleteIntegration } from "@formbricks/lib/integration/service";
import { getOrganizationIdFromEnvironmentId } from "@formbricks/lib/organization/utils";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZIntegrationInput } from "@formbricks/types/integration";
const ZCreateOrUpdateIntegrationAction = z.object({
diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/slack/actions.ts b/apps/web/app/(app)/environments/[environmentId]/integrations/slack/actions.ts
index 3bbbb6a732..b96fcb68e0 100644
--- a/apps/web/app/(app)/environments/[environmentId]/integrations/slack/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/integrations/slack/actions.ts
@@ -5,7 +5,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromEnvironmentId } from "@formbricks/lib/organization/utils";
import { getSlackChannels } from "@formbricks/lib/slack/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
const ZRefreshChannelsAction = z.object({
environmentId: ZId,
diff --git a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts
index 380ceaa288..4d0d0f1c40 100644
--- a/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/integrations/webhooks/actions.ts
@@ -9,7 +9,7 @@ import {
} from "@formbricks/lib/organization/utils";
import { createWebhook, deleteWebhook, updateWebhook } from "@formbricks/lib/webhook/service";
import { testEndpoint } from "@formbricks/lib/webhook/utils";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZWebhookInput } from "@formbricks/types/webhooks";
const ZCreateWebhookAction = z.object({
diff --git a/apps/web/app/(app)/environments/[environmentId]/product/actions.ts b/apps/web/app/(app)/environments/[environmentId]/product/actions.ts
index c9ec85c4c2..d610f773a7 100644
--- a/apps/web/app/(app)/environments/[environmentId]/product/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/product/actions.ts
@@ -5,7 +5,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromProductId } from "@formbricks/lib/organization/utils";
import { updateProduct } from "@formbricks/lib/product/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZProductUpdateInput } from "@formbricks/types/product";
const ZUpdateProductAction = z.object({
diff --git a/apps/web/app/(app)/environments/[environmentId]/product/api-keys/actions.ts b/apps/web/app/(app)/environments/[environmentId]/product/api-keys/actions.ts
index 0ee2a1c848..225f5785ec 100644
--- a/apps/web/app/(app)/environments/[environmentId]/product/api-keys/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/product/api-keys/actions.ts
@@ -9,7 +9,7 @@ import {
getOrganizationIdFromEnvironmentId,
} from "@formbricks/lib/organization/utils";
import { ZApiKeyCreateInput } from "@formbricks/types/api-keys";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
const ZDeleteApiKeyAction = z.object({
id: ZId,
diff --git a/apps/web/app/(app)/environments/[environmentId]/product/general/actions.ts b/apps/web/app/(app)/environments/[environmentId]/product/general/actions.ts
index 75818d75a3..712518245d 100644
--- a/apps/web/app/(app)/environments/[environmentId]/product/general/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/product/general/actions.ts
@@ -5,7 +5,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromProductId } from "@formbricks/lib/organization/utils";
import { deleteProduct, getProducts } from "@formbricks/lib/product/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
const ZProductDeleteAction = z.object({
productId: ZId,
diff --git a/apps/web/app/(app)/environments/[environmentId]/product/tags/actions.ts b/apps/web/app/(app)/environments/[environmentId]/product/tags/actions.ts
index a3b821a9f5..cb37fa90da 100644
--- a/apps/web/app/(app)/environments/[environmentId]/product/tags/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/product/tags/actions.ts
@@ -5,7 +5,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromTagId } from "@formbricks/lib/organization/utils";
import { deleteTag, mergeTags, updateTagName } from "@formbricks/lib/tag/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
const ZDeleteTagAction = z.object({
tagId: ZId,
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/actions.ts
index c6ea517f46..9fe04d1d0f 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(account)/profile/actions.ts
@@ -8,7 +8,7 @@ import { getOrganizationIdFromEnvironmentId } from "@formbricks/lib/organization
import { deleteFile } from "@formbricks/lib/storage/service";
import { getFileNameWithIdFromUrl } from "@formbricks/lib/storage/utils";
import { updateUser } from "@formbricks/lib/user/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZUserUpdateInput } from "@formbricks/types/user";
export const updateUserAction = authenticatedActionClient
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts
index 980fe8d3bb..14bdc9d988 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/billing/actions.ts
@@ -9,7 +9,7 @@ import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { STRIPE_PRICE_LOOKUP_KEYS } from "@formbricks/lib/constants";
import { WEBAPP_URL } from "@formbricks/lib/constants";
import { getOrganization } from "@formbricks/lib/organization/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { AuthorizationError, ResourceNotFoundError } from "@formbricks/types/errors";
const ZUpgradePlanAction = z.object({
diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/actions.ts
index a737a9ebb8..d1e7f29e80 100644
--- a/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/settings/(organization)/members/actions.ts
@@ -15,7 +15,7 @@ import {
} from "@formbricks/lib/membership/service";
import { deleteOrganization, updateOrganization } from "@formbricks/lib/organization/service";
import { getOrganizationIdFromInviteId } from "@formbricks/lib/organization/utils";
-import { ZId } from "@formbricks/types/environment";
+import { ZId, ZUuid } from "@formbricks/types/common";
import { AuthenticationError, OperationNotAllowedError, ValidationError } from "@formbricks/types/errors";
import { ZMembershipRole } from "@formbricks/types/memberships";
import { ZOrganizationUpdateInput } from "@formbricks/types/organizations";
@@ -39,7 +39,7 @@ export const updateOrganizationNameAction = authenticatedActionClient
});
const ZDeleteInviteAction = z.object({
- inviteId: ZId,
+ inviteId: ZUuid,
organizationId: ZId,
});
@@ -130,7 +130,7 @@ export const createInviteTokenAction = authenticatedActionClient
});
const ZResendInviteAction = z.object({
- inviteId: ZId,
+ inviteId: ZUuid,
organizationId: ZId,
});
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions.ts
index 05095a3ae4..6edf88643d 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/actions.ts
@@ -6,7 +6,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromSurveyId } from "@formbricks/lib/organization/utils";
import { getResponseCountBySurveyId, getResponses, getSurveySummary } from "@formbricks/lib/response/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZResponseFilterCriteria } from "@formbricks/types/responses";
export const revalidateSurveyIdPath = async (environmentId: string, surveyId: string) => {
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/actions.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/actions.ts
index 1b14aa6a31..7c1b0b4f19 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/summary/actions.ts
@@ -8,7 +8,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromSurveyId } from "@formbricks/lib/organization/utils";
import { getSurvey, updateSurvey } from "@formbricks/lib/survey/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ResourceNotFoundError } from "@formbricks/types/errors";
const ZSendEmbedSurveyPreviewEmailAction = z.object({
diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.ts b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.ts
index fd4952bd72..9bbe3a2c9a 100644
--- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.ts
+++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/actions.ts
@@ -7,7 +7,7 @@ import { getOrganizationIdFromSurveyId } from "@formbricks/lib/organization/util
import { getResponseDownloadUrl, getResponseFilteringValues } from "@formbricks/lib/response/service";
import { getSurvey, updateSurvey } from "@formbricks/lib/survey/service";
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ResourceNotFoundError } from "@formbricks/types/errors";
import { ZResponseFilterCriteria } from "@formbricks/types/responses";
import { ZSurvey } from "@formbricks/types/surveys/types";
diff --git a/apps/web/app/api/v1/client/[environmentId]/responses/route.ts b/apps/web/app/api/v1/client/[environmentId]/responses/route.ts
index ffd086f65e..b619a06dae 100644
--- a/apps/web/app/api/v1/client/[environmentId]/responses/route.ts
+++ b/apps/web/app/api/v1/client/[environmentId]/responses/route.ts
@@ -7,7 +7,7 @@ import { getPerson } from "@formbricks/lib/person/service";
import { capturePosthogEnvironmentEvent } from "@formbricks/lib/posthogServer";
import { createResponse } from "@formbricks/lib/response/service";
import { getSurvey } from "@formbricks/lib/survey/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { InvalidInputError } from "@formbricks/types/errors";
import { TResponse, TResponseInput, ZResponseInput } from "@formbricks/types/responses";
diff --git a/apps/web/app/s/[surveyId]/actions.ts b/apps/web/app/s/[surveyId]/actions.ts
index e5258ef046..0825735348 100644
--- a/apps/web/app/s/[surveyId]/actions.ts
+++ b/apps/web/app/s/[surveyId]/actions.ts
@@ -6,8 +6,8 @@ import { sendLinkSurveyToVerifiedEmail } from "@formbricks/email";
import { actionClient } from "@formbricks/lib/actionClient";
import { verifyTokenForLinkSurvey } from "@formbricks/lib/jwt";
import { getSurvey } from "@formbricks/lib/survey/service";
+import { ZId } from "@formbricks/types/common";
import { ZLinkSurveyEmailData } from "@formbricks/types/email";
-import { ZId } from "@formbricks/types/environment";
export const sendLinkSurveyEmailAction = actionClient
.schema(ZLinkSurveyEmailData)
diff --git a/apps/web/app/s/[surveyId]/page.tsx b/apps/web/app/s/[surveyId]/page.tsx
index 5b2cff989f..2ebf817c83 100644
--- a/apps/web/app/s/[surveyId]/page.tsx
+++ b/apps/web/app/s/[surveyId]/page.tsx
@@ -13,7 +13,7 @@ import { createPerson, getPersonByUserId } from "@formbricks/lib/person/service"
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { getResponseBySingleUseId, getResponseCountBySurveyId } from "@formbricks/lib/response/service";
import { getSurvey } from "@formbricks/lib/survey/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { TResponse } from "@formbricks/types/responses";
import { getEmailVerificationDetails } from "./lib/helpers";
diff --git a/apps/web/app/setup/organization/[organizationId]/invite/actions.ts b/apps/web/app/setup/organization/[organizationId]/invite/actions.ts
index ac869c07c3..c7bb5e6fee 100644
--- a/apps/web/app/setup/organization/[organizationId]/invite/actions.ts
+++ b/apps/web/app/setup/organization/[organizationId]/invite/actions.ts
@@ -7,7 +7,7 @@ import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { INVITE_DISABLED } from "@formbricks/lib/constants";
import { inviteUser } from "@formbricks/lib/invite/service";
import { getOrganizationsByUserId } from "@formbricks/lib/organization/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { AuthenticationError } from "@formbricks/types/errors";
const ZInviteOrganizationMemberAction = z.object({
diff --git a/apps/web/app/share/[sharingKey]/actions.ts b/apps/web/app/share/[sharingKey]/actions.ts
index fdbe5e2a60..1d9add65a7 100644
--- a/apps/web/app/share/[sharingKey]/actions.ts
+++ b/apps/web/app/share/[sharingKey]/actions.ts
@@ -10,7 +10,7 @@ import {
} from "@formbricks/lib/response/service";
import { getSurveyIdByResultShareKey } from "@formbricks/lib/survey/service";
import { getTagsByEnvironmentId } from "@formbricks/lib/tag/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { AuthorizationError } from "@formbricks/types/errors";
import { ZResponseFilterCriteria } from "@formbricks/types/responses";
diff --git a/packages/ee/advanced-targeting/lib/actions.ts b/packages/ee/advanced-targeting/lib/actions.ts
index db571b6cfa..2366330b0d 100644
--- a/packages/ee/advanced-targeting/lib/actions.ts
+++ b/packages/ee/advanced-targeting/lib/actions.ts
@@ -16,7 +16,7 @@ import {
updateSegment,
} from "@formbricks/lib/segment/service";
import { loadNewSegmentInSurvey } from "@formbricks/lib/survey/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZSegmentCreateInput, ZSegmentFilters, ZSegmentUpdateInput } from "@formbricks/types/segment";
export const createSegmentAction = authenticatedActionClient
diff --git a/packages/ee/multi-language/lib/actions.ts b/packages/ee/multi-language/lib/actions.ts
index a22fd16054..9455aec55d 100644
--- a/packages/ee/multi-language/lib/actions.ts
+++ b/packages/ee/multi-language/lib/actions.ts
@@ -14,7 +14,7 @@ import {
getOrganizationIdFromLanguageId,
getOrganizationIdFromProductId,
} from "@formbricks/lib/organization/utils";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ResourceNotFoundError } from "@formbricks/types/errors";
import { ZLanguageInput } from "@formbricks/types/product";
diff --git a/packages/ee/role-management/lib/actions.ts b/packages/ee/role-management/lib/actions.ts
index dbcfd4910f..a3c32b7968 100644
--- a/packages/ee/role-management/lib/actions.ts
+++ b/packages/ee/role-management/lib/actions.ts
@@ -10,7 +10,7 @@ import {
transferOwnership,
updateMembership,
} from "@formbricks/lib/membership/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId, ZUuid } from "@formbricks/types/common";
import { AuthorizationError, ValidationError } from "@formbricks/types/errors";
import { ZInviteUpdateInput } from "@formbricks/types/invites";
import { ZMembershipUpdateInput } from "@formbricks/types/memberships";
@@ -44,7 +44,7 @@ export const transferOwnershipAction = authenticatedActionClient
});
const ZUpdateInviteAction = z.object({
- inviteId: ZId,
+ inviteId: ZUuid,
organizationId: ZId,
data: ZInviteUpdateInput,
});
diff --git a/packages/lib/action/service.ts b/packages/lib/action/service.ts
index 81393cb7c2..ef594b3468 100644
--- a/packages/lib/action/service.ts
+++ b/packages/lib/action/service.ts
@@ -5,7 +5,7 @@ import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { TAction, TActionInput, ZActionInput } from "@formbricks/types/actions";
import { ZOptionalNumber } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, OperationNotAllowedError } from "@formbricks/types/errors";
import { actionClassCache } from "../actionClass/cache";
import { getActionClassByEnvironmentIdAndName } from "../actionClass/service";
diff --git a/packages/lib/actionClass/auth.ts b/packages/lib/actionClass/auth.ts
index 0be6a21859..358311f85e 100644
--- a/packages/lib/actionClass/auth.ts
+++ b/packages/lib/actionClass/auth.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getMembershipByUserIdOrganizationId } from "../membership/service";
diff --git a/packages/lib/actionClass/service.ts b/packages/lib/actionClass/service.ts
index d69bfbe60a..c87b09795c 100644
--- a/packages/lib/actionClass/service.ts
+++ b/packages/lib/actionClass/service.ts
@@ -6,7 +6,7 @@ import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { TActionClass, TActionClassInput, ZActionClassInput } from "@formbricks/types/action-classes";
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { cache } from "../cache";
import { ITEMS_PER_PAGE } from "../constants";
diff --git a/packages/lib/apiKey/auth.ts b/packages/lib/apiKey/auth.ts
index 40ed5ace37..89b525c12f 100644
--- a/packages/lib/apiKey/auth.ts
+++ b/packages/lib/apiKey/auth.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { validateInputs } from "../utils/validate";
diff --git a/packages/lib/apiKey/service.ts b/packages/lib/apiKey/service.ts
index 31dd253aaf..285430e90f 100644
--- a/packages/lib/apiKey/service.ts
+++ b/packages/lib/apiKey/service.ts
@@ -5,7 +5,7 @@ import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { TApiKey, TApiKeyCreateInput, ZApiKeyCreateInput } from "@formbricks/types/api-keys";
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, InvalidInputError } from "@formbricks/types/errors";
import { cache } from "../cache";
import { ITEMS_PER_PAGE } from "../constants";
diff --git a/packages/lib/attribute/service.ts b/packages/lib/attribute/service.ts
index 6d0961f0d4..3f6a15fd86 100644
--- a/packages/lib/attribute/service.ts
+++ b/packages/lib/attribute/service.ts
@@ -4,7 +4,7 @@ import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { TAttributes, ZAttributes } from "@formbricks/types/attributes";
import { ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, OperationNotAllowedError } from "@formbricks/types/errors";
import { attributeCache } from "../attribute/cache";
import { attributeClassCache } from "../attributeClass/cache";
diff --git a/packages/lib/attributeClass/auth.ts b/packages/lib/attributeClass/auth.ts
index f25c2e5cba..78e8bb1631 100644
--- a/packages/lib/attributeClass/auth.ts
+++ b/packages/lib/attributeClass/auth.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { validateInputs } from "../utils/validate";
diff --git a/packages/lib/attributeClass/service.ts b/packages/lib/attributeClass/service.ts
index e464245384..1317349c96 100644
--- a/packages/lib/attributeClass/service.ts
+++ b/packages/lib/attributeClass/service.ts
@@ -12,7 +12,7 @@ import {
ZAttributeClassUpdateInput,
} from "@formbricks/types/attribute-classes";
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, OperationNotAllowedError } from "@formbricks/types/errors";
import { cache } from "../cache";
import { ITEMS_PER_PAGE, MAX_ATTRIBUTE_CLASSES_PER_ENVIRONMENT } from "../constants";
diff --git a/packages/lib/display/service.ts b/packages/lib/display/service.ts
index 95c3a0b807..a925bf61a0 100644
--- a/packages/lib/display/service.ts
+++ b/packages/lib/display/service.ts
@@ -3,6 +3,7 @@ import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { ZOptionalNumber } from "@formbricks/types/common";
+import { ZId } from "@formbricks/types/common";
import {
TDisplay,
TDisplayCreateInput,
@@ -11,7 +12,6 @@ import {
ZDisplayCreateInput,
ZDisplayUpdateInput,
} from "@formbricks/types/displays";
-import { ZId } from "@formbricks/types/environment";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { TPerson } from "@formbricks/types/people";
import { cache } from "../cache";
diff --git a/packages/lib/environment/auth.ts b/packages/lib/environment/auth.ts
index 7bee822adb..8ae4a2b371 100644
--- a/packages/lib/environment/auth.ts
+++ b/packages/lib/environment/auth.ts
@@ -1,6 +1,6 @@
import { Prisma } from "@prisma/client";
import { prisma } from "@formbricks/database";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError } from "@formbricks/types/errors";
import { cache } from "../cache";
import { organizationCache } from "../organization/cache";
diff --git a/packages/lib/environment/service.ts b/packages/lib/environment/service.ts
index 201e8e8f2c..8a07284748 100644
--- a/packages/lib/environment/service.ts
+++ b/packages/lib/environment/service.ts
@@ -3,6 +3,7 @@ import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { z } from "zod";
import { prisma } from "@formbricks/database";
+import { ZId } from "@formbricks/types/common";
import type {
TEnvironment,
TEnvironmentCreateInput,
@@ -12,7 +13,6 @@ import {
ZEnvironment,
ZEnvironmentCreateInput,
ZEnvironmentUpdateInput,
- ZId,
} from "@formbricks/types/environment";
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
import { cache } from "../cache";
diff --git a/packages/lib/integration/auth.ts b/packages/lib/integration/auth.ts
index 5183113ead..852e7f6b34 100644
--- a/packages/lib/integration/auth.ts
+++ b/packages/lib/integration/auth.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { validateInputs } from "../utils/validate";
diff --git a/packages/lib/integration/service.ts b/packages/lib/integration/service.ts
index 2e6cc7198a..a5f06e1135 100644
--- a/packages/lib/integration/service.ts
+++ b/packages/lib/integration/service.ts
@@ -3,7 +3,7 @@ import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError } from "@formbricks/types/errors";
import { TIntegration, TIntegrationInput, ZIntegrationType } from "@formbricks/types/integration";
import { cache } from "../cache";
diff --git a/packages/lib/language/service.ts b/packages/lib/language/service.ts
index 2fedf490bb..081dd689ca 100644
--- a/packages/lib/language/service.ts
+++ b/packages/lib/language/service.ts
@@ -1,7 +1,7 @@
import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, ResourceNotFoundError, ValidationError } from "@formbricks/types/errors";
import {
TLanguage,
diff --git a/packages/lib/organization/auth.ts b/packages/lib/organization/auth.ts
index da227de64a..6cb7731057 100644
--- a/packages/lib/organization/auth.ts
+++ b/packages/lib/organization/auth.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { getMembershipByUserIdOrganizationId } from "../membership/service";
import { getAccessFlags } from "../membership/utils";
diff --git a/packages/lib/organization/hooks/actions.ts b/packages/lib/organization/hooks/actions.ts
index 63db66fb14..f2aac75d3e 100644
--- a/packages/lib/organization/hooks/actions.ts
+++ b/packages/lib/organization/hooks/actions.ts
@@ -2,7 +2,7 @@
import "server-only";
import { z } from "zod";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { authenticatedActionClient } from "../../actionClient";
import { checkAuthorization } from "../../actionClient/utils";
import { getOrganization } from "../service";
diff --git a/packages/lib/organization/service.ts b/packages/lib/organization/service.ts
index 72da377f94..6ef87ff5b1 100644
--- a/packages/lib/organization/service.ts
+++ b/packages/lib/organization/service.ts
@@ -3,7 +3,7 @@ import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import {
TOrganization,
diff --git a/packages/lib/person/auth.ts b/packages/lib/person/auth.ts
index eee3de32b6..2dc7ebbbbe 100644
--- a/packages/lib/person/auth.ts
+++ b/packages/lib/person/auth.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { validateInputs } from "../utils/validate";
diff --git a/packages/lib/person/service.ts b/packages/lib/person/service.ts
index 0666068944..3ad71cf4d5 100644
--- a/packages/lib/person/service.ts
+++ b/packages/lib/person/service.ts
@@ -3,7 +3,7 @@ import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError } from "@formbricks/types/errors";
import { TPerson } from "@formbricks/types/people";
import { cache } from "../cache";
diff --git a/packages/lib/product/auth.ts b/packages/lib/product/auth.ts
index ab9926f4f1..7d4212d895 100644
--- a/packages/lib/product/auth.ts
+++ b/packages/lib/product/auth.ts
@@ -1,4 +1,4 @@
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { getMembershipByUserIdOrganizationId } from "../membership/service";
import { getAccessFlags } from "../membership/utils";
diff --git a/packages/lib/product/service.ts b/packages/lib/product/service.ts
index 7bc988f087..de43d89ec4 100644
--- a/packages/lib/product/service.ts
+++ b/packages/lib/product/service.ts
@@ -4,7 +4,7 @@ import { cache as reactCache } from "react";
import { z } from "zod";
import { prisma } from "@formbricks/database";
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, ValidationError } from "@formbricks/types/errors";
import type { TProduct, TProductUpdateInput } from "@formbricks/types/product";
import { ZProduct, ZProductUpdateInput } from "@formbricks/types/product";
diff --git a/packages/lib/response/auth.ts b/packages/lib/response/auth.ts
index a5f7122f0e..689c3c64af 100644
--- a/packages/lib/response/auth.ts
+++ b/packages/lib/response/auth.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getSurvey } from "../survey/service";
diff --git a/packages/lib/response/service.ts b/packages/lib/response/service.ts
index 6dabaadbae..bed519ebd6 100644
--- a/packages/lib/response/service.ts
+++ b/packages/lib/response/service.ts
@@ -4,7 +4,7 @@ import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { TAttributes } from "@formbricks/types/attributes";
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { TPerson } from "@formbricks/types/people";
import {
diff --git a/packages/lib/responseNote/auth.ts b/packages/lib/responseNote/auth.ts
index 7ad756aa5e..289c162ca6 100644
--- a/packages/lib/responseNote/auth.ts
+++ b/packages/lib/responseNote/auth.ts
@@ -1,4 +1,4 @@
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { canUserAccessResponse } from "../response/auth";
import { getResponse } from "../response/service";
diff --git a/packages/lib/responseNote/service.ts b/packages/lib/responseNote/service.ts
index 4afb548663..8882777c0a 100644
--- a/packages/lib/responseNote/service.ts
+++ b/packages/lib/responseNote/service.ts
@@ -3,7 +3,7 @@ import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { TResponseNote } from "@formbricks/types/responses";
import { cache } from "../cache";
diff --git a/packages/lib/segment/service.ts b/packages/lib/segment/service.ts
index a7f0996596..ac77666a23 100644
--- a/packages/lib/segment/service.ts
+++ b/packages/lib/segment/service.ts
@@ -2,7 +2,7 @@ import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import {
DatabaseError,
OperationNotAllowedError,
diff --git a/packages/lib/storage/service.ts b/packages/lib/storage/service.ts
index 461efceec3..510584065f 100644
--- a/packages/lib/storage/service.ts
+++ b/packages/lib/storage/service.ts
@@ -20,12 +20,12 @@ import {
S3_ACCESS_KEY,
S3_BUCKET_NAME,
S3_ENDPOINT_URL,
+ S3_FORCE_PATH_STYLE,
S3_REGION,
S3_SECRET_KEY,
UPLOADS_DIR,
WEBAPP_URL,
isS3Configured,
- S3_FORCE_PATH_STYLE,
} from "../constants";
import { generateLocalSignedUrl } from "../crypto";
import { env } from "../env";
diff --git a/packages/lib/survey/auth.ts b/packages/lib/survey/auth.ts
index 951da37ea3..810573a8a7 100644
--- a/packages/lib/survey/auth.ts
+++ b/packages/lib/survey/auth.ts
@@ -1,4 +1,4 @@
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getMembershipByUserIdOrganizationId } from "../membership/service";
diff --git a/packages/lib/survey/service.ts b/packages/lib/survey/service.ts
index 2078f08ecb..77cbc3dba6 100644
--- a/packages/lib/survey/service.ts
+++ b/packages/lib/survey/service.ts
@@ -5,7 +5,8 @@ import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { TActionClass } from "@formbricks/types/action-classes";
import { ZOptionalNumber } from "@formbricks/types/common";
-import { TEnvironment, ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
+import { TEnvironment } from "@formbricks/types/environment";
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
import { TPerson } from "@formbricks/types/people";
import { TProduct } from "@formbricks/types/product";
diff --git a/packages/lib/tag/auth.ts b/packages/lib/tag/auth.ts
index f2b9b06d9e..1e75bd7d58 100644
--- a/packages/lib/tag/auth.ts
+++ b/packages/lib/tag/auth.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { getMembershipByUserIdOrganizationId } from "../membership/service";
import { getAccessFlags } from "../membership/utils";
diff --git a/packages/lib/tag/service.ts b/packages/lib/tag/service.ts
index 44a9351851..df6ff44175 100644
--- a/packages/lib/tag/service.ts
+++ b/packages/lib/tag/service.ts
@@ -2,7 +2,7 @@ import "server-only";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
import { ZOptionalNumber, ZString } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { TTag } from "@formbricks/types/tags";
import { cache } from "../cache";
import { ITEMS_PER_PAGE } from "../constants";
diff --git a/packages/lib/tagOnResponse/auth.ts b/packages/lib/tagOnResponse/auth.ts
index 67e8cb3cf7..15be2c7ddf 100644
--- a/packages/lib/tagOnResponse/auth.ts
+++ b/packages/lib/tagOnResponse/auth.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { getMembershipByUserIdOrganizationId } from "../membership/service";
import { getAccessFlags } from "../membership/utils";
diff --git a/packages/lib/tagOnResponse/service.ts b/packages/lib/tagOnResponse/service.ts
index 7db0a254ae..3406543ee0 100644
--- a/packages/lib/tagOnResponse/service.ts
+++ b/packages/lib/tagOnResponse/service.ts
@@ -2,7 +2,7 @@ import "server-only";
import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { prisma } from "@formbricks/database";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError } from "@formbricks/types/errors";
import { TTagsCount, TTagsOnResponses } from "@formbricks/types/tags";
import { cache } from "../cache";
diff --git a/packages/lib/user/service.ts b/packages/lib/user/service.ts
index 5b8adcbdf0..241a8b8154 100644
--- a/packages/lib/user/service.ts
+++ b/packages/lib/user/service.ts
@@ -3,7 +3,7 @@ import { Prisma } from "@prisma/client";
import { cache as reactCache } from "react";
import { z } from "zod";
import { prisma } from "@formbricks/database";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
import { TMembership } from "@formbricks/types/memberships";
import { TUser, TUserCreateInput, TUserUpdateInput, ZUserUpdateInput } from "@formbricks/types/user";
diff --git a/packages/lib/webhook/auth.ts b/packages/lib/webhook/auth.ts
index f3b4626118..f88839aa61 100644
--- a/packages/lib/webhook/auth.ts
+++ b/packages/lib/webhook/auth.ts
@@ -1,4 +1,4 @@
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { cache } from "../cache";
import { hasUserEnvironmentAccess } from "../environment/auth";
import { validateInputs } from "../utils/validate";
diff --git a/packages/lib/webhook/service.ts b/packages/lib/webhook/service.ts
index 4a10edc198..c80b47ae72 100644
--- a/packages/lib/webhook/service.ts
+++ b/packages/lib/webhook/service.ts
@@ -2,7 +2,7 @@ import "server-only";
import { Prisma } from "@prisma/client";
import { prisma } from "@formbricks/database";
import { ZOptionalNumber } from "@formbricks/types/common";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
import { TWebhook, TWebhookInput, ZWebhookInput } from "@formbricks/types/webhooks";
import { cache } from "../cache";
diff --git a/packages/types/actions.ts b/packages/types/actions.ts
index 8524228f85..7418abd1e1 100644
--- a/packages/types/actions.ts
+++ b/packages/types/actions.ts
@@ -1,6 +1,6 @@
import { z } from "zod";
import { ZActionClass } from "./action-classes";
-import { ZId } from "./environment";
+import { ZId } from "./common";
export const ZAction = z.object({
id: ZId,
diff --git a/packages/types/common.ts b/packages/types/common.ts
index e6b7397fef..eb45cff676 100644
--- a/packages/types/common.ts
+++ b/packages/types/common.ts
@@ -37,3 +37,7 @@ export const ZAllowedFileExtension = z.enum([
]);
export type TAllowedFileExtension = z.infer;
+
+export const ZId = z.string().cuid2();
+
+export const ZUuid = z.string().uuid();
diff --git a/packages/types/environment.ts b/packages/types/environment.ts
index aa45c2500c..9111ad3901 100644
--- a/packages/types/environment.ts
+++ b/packages/types/environment.ts
@@ -25,8 +25,6 @@ export const ZEnvironmentUpdateInput = z.object({
websiteSetupCompleted: z.boolean(),
});
-export const ZId = z.string().cuid2();
-
export const ZEnvironmentCreateInput = z.object({
type: z.enum(["development", "production"]).optional(),
appSetupCompleted: z.boolean().optional(),
diff --git a/packages/types/responses.ts b/packages/types/responses.ts
index 2e7c637e0b..d31a5fda07 100644
--- a/packages/types/responses.ts
+++ b/packages/types/responses.ts
@@ -1,6 +1,6 @@
import { z } from "zod";
import { ZAttributes } from "./attributes";
-import { ZId } from "./environment";
+import { ZId } from "./common";
import { ZSurvey, ZSurveyLogicCondition } from "./surveys/types";
import { ZTag } from "./tags";
diff --git a/packages/types/surveys/types.ts b/packages/types/surveys/types.ts
index 8b279b748a..48671fea35 100644
--- a/packages/types/surveys/types.ts
+++ b/packages/types/surveys/types.ts
@@ -1,8 +1,7 @@
import { z } from "zod";
import { ZActionClass, ZActionClassNoCodeConfig } from "../action-classes";
import { ZAttributes } from "../attributes";
-import { ZAllowedFileExtension, ZColor, ZPlacement } from "../common";
-import { ZId } from "../environment";
+import { ZAllowedFileExtension, ZColor, ZPlacement , ZId } from "../common";
import { ZLanguage } from "../product";
import { ZSegment } from "../segment";
import { ZBaseStyling } from "../styling";
diff --git a/packages/ui/ShareSurveyLink/actions.ts b/packages/ui/ShareSurveyLink/actions.ts
index c68331e0d7..f176441958 100644
--- a/packages/ui/ShareSurveyLink/actions.ts
+++ b/packages/ui/ShareSurveyLink/actions.ts
@@ -5,7 +5,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromSurveyId } from "@formbricks/lib/organization/utils";
import { generateSurveySingleUseId } from "@formbricks/lib/utils/singleUseSurveys";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
const ZGenerateSingleUseIdAction = z.object({
surveyId: ZId,
diff --git a/packages/ui/SingleResponseCard/actions.ts b/packages/ui/SingleResponseCard/actions.ts
index 7643f02f31..029a40d9a9 100644
--- a/packages/ui/SingleResponseCard/actions.ts
+++ b/packages/ui/SingleResponseCard/actions.ts
@@ -17,7 +17,7 @@ import {
} from "@formbricks/lib/responseNote/service";
import { createTag } from "@formbricks/lib/tag/service";
import { addTagToRespone, deleteTagOnResponse } from "@formbricks/lib/tagOnResponse/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
const ZCreateTagAction = z.object({
environmentId: ZId,
diff --git a/packages/ui/SurveysList/actions.ts b/packages/ui/SurveysList/actions.ts
index abd917f8b4..7404bf541f 100644
--- a/packages/ui/SurveysList/actions.ts
+++ b/packages/ui/SurveysList/actions.ts
@@ -15,7 +15,7 @@ import {
getSurveys,
} from "@formbricks/lib/survey/service";
import { generateSurveySingleUseId } from "@formbricks/lib/utils/singleUseSurveys";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZSurveyFilterCriteria } from "@formbricks/types/surveys/types";
const ZGetSurveyAction = z.object({
diff --git a/packages/ui/TemplateList/actions.ts b/packages/ui/TemplateList/actions.ts
index 182c7a4284..2fb8c92e63 100644
--- a/packages/ui/TemplateList/actions.ts
+++ b/packages/ui/TemplateList/actions.ts
@@ -5,7 +5,7 @@ import { authenticatedActionClient } from "@formbricks/lib/actionClient";
import { checkAuthorization } from "@formbricks/lib/actionClient/utils";
import { getOrganizationIdFromEnvironmentId } from "@formbricks/lib/organization/utils";
import { createSurvey } from "@formbricks/lib/survey/service";
-import { ZId } from "@formbricks/types/environment";
+import { ZId } from "@formbricks/types/common";
import { ZSurveyCreateInput } from "@formbricks/types/surveys/types";
const ZCreateSurveyAction = z.object({
From f4a367d2de6290f632288c9f3586d4086ce274ab Mon Sep 17 00:00:00 2001
From: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com>
Date: Thu, 22 Aug 2024 15:13:39 +0530
Subject: [PATCH 03/19] feat: survey variables (#3013)
Co-authored-by: Matti Nannt
---
.../edit/components/HiddenFieldsCard.tsx | 31 ++-
.../edit/components/QuestionsView.tsx | 24 +-
.../edit/components/SurveyVariablesCard.tsx | 79 ++++++
.../components/SurveyVariablesCardItem.tsx | 224 ++++++++++++++++++
.../surveys/lib/minimalSurvey.ts | 1 +
.../lib/notificationResponse.ts | 8 +-
packages/database/json-types.ts | 2 +
.../migration.sql | 2 +
packages/database/schema.prisma | 3 +
packages/database/zod-utils.ts | 1 +
packages/lib/styling/constants.ts | 1 +
packages/lib/survey/service.ts | 1 +
.../lib/survey/tests/__mock__/survey.mock.ts | 1 +
packages/lib/utils/recall.ts | 48 +++-
.../src/components/general/EndingCard.tsx | 15 +-
.../surveys/src/components/general/Survey.tsx | 3 +-
.../src/components/general/WelcomeCard.tsx | 16 +-
packages/surveys/src/lib/recall.ts | 25 +-
packages/types/surveys/types.ts | 55 ++++-
packages/ui/Input/index.tsx | 2 +-
packages/ui/PreviewSurvey/index.tsx | 8 +-
.../components/RecallItemSelect.tsx | 50 +++-
.../components/SingleResponseCardBody.tsx | 2 +
23 files changed, 554 insertions(+), 48 deletions(-)
create mode 100644 apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCard.tsx
create mode 100644 apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCardItem.tsx
create mode 100644 packages/database/migrations/20240813094711_added_variables_to_survey_model/migration.sql
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx
index c4472acf0b..b21fafaba9 100644
--- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/HiddenFieldsCard.tsx
@@ -4,6 +4,7 @@ import * as Collapsible from "@radix-ui/react-collapsible";
import { useState } from "react";
import { toast } from "react-hot-toast";
import { cn } from "@formbricks/lib/cn";
+import { extractRecallInfo } from "@formbricks/lib/utils/recall";
import { TSurvey, TSurveyHiddenFields } from "@formbricks/types/surveys/types";
import { validateId } from "@formbricks/types/surveys/validation";
import { Button } from "@formbricks/ui/Button";
@@ -36,9 +37,26 @@ export const HiddenFieldsCard = ({
}
};
- const updateSurvey = (data: TSurveyHiddenFields) => {
+ const updateSurvey = (data: TSurveyHiddenFields, currentFieldId?: string) => {
+ const questions = [...localSurvey.questions];
+
+ // Remove recall info from question headlines
+ if (currentFieldId) {
+ questions.forEach((question) => {
+ for (const [languageCode, headline] of Object.entries(question.headline)) {
+ if (headline.includes(`recall:${currentFieldId}`)) {
+ const recallInfo = extractRecallInfo(headline);
+ if (recallInfo) {
+ question.headline[languageCode] = headline.replace(recallInfo, "");
+ }
+ }
+ }
+ });
+ }
+
setLocalSurvey({
...localSurvey,
+ questions,
hiddenFields: {
...localSurvey.hiddenFields,
...data,
@@ -93,10 +111,13 @@ export const HiddenFieldsCard = ({
{
- updateSurvey({
- enabled: true,
- fieldIds: localSurvey.hiddenFields?.fieldIds?.filter((q) => q !== fieldId),
- });
+ updateSurvey(
+ {
+ enabled: true,
+ fieldIds: localSurvey.hiddenFields?.fieldIds?.filter((q) => q !== fieldId),
+ },
+ fieldId
+ );
}}
tagId={fieldId}
tagName={fieldId}
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx
index 3212b17b72..446984c12d 100644
--- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/QuestionsView.tsx
@@ -14,7 +14,7 @@ import { createId } from "@paralleldrive/cuid2";
import React, { SetStateAction, useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";
import { MultiLanguageCard } from "@formbricks/ee/multi-language/components/multi-language-card";
-import { addMultiLanguageLabels, extractLanguageCodes, getLocalizedValue } from "@formbricks/lib/i18n/utils";
+import { addMultiLanguageLabels, extractLanguageCodes } from "@formbricks/lib/i18n/utils";
import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone";
import { getDefaultEndingCard } from "@formbricks/lib/templates";
import { checkForEmptyFallBackValue, extractRecallInfo } from "@formbricks/lib/utils/recall";
@@ -209,15 +209,14 @@ export const QuestionsView = ({
const activeQuestionIdTemp = activeQuestionId ?? localSurvey.questions[0].id;
let updatedSurvey: TSurvey = { ...localSurvey };
- // check if we are recalling from this question
+ // check if we are recalling from this question for every language
updatedSurvey.questions.forEach((question) => {
- if (question.headline[selectedLanguageCode].includes(`recall:${questionId}`)) {
- const recallInfo = extractRecallInfo(getLocalizedValue(question.headline, selectedLanguageCode));
- if (recallInfo) {
- question.headline[selectedLanguageCode] = question.headline[selectedLanguageCode].replace(
- recallInfo,
- ""
- );
+ for (const [languageCode, headline] of Object.entries(question.headline)) {
+ if (headline.includes(`recall:${questionId}`)) {
+ const recallInfo = extractRecallInfo(headline);
+ if (recallInfo) {
+ question.headline[languageCode] = headline.replace(recallInfo, "");
+ }
}
}
});
@@ -434,6 +433,13 @@ export const QuestionsView = ({
activeQuestionId={activeQuestionId}
/>
+ {/* */}
+
void;
+ activeQuestionId: string | null;
+ setActiveQuestionId: (id: string | null) => void;
+}
+
+const variablesCardId = `fb-variables-${Date.now()}`;
+
+export const SurveyVariablesCard = ({
+ localSurvey,
+ setLocalSurvey,
+ activeQuestionId,
+ setActiveQuestionId,
+}: SurveyVariablesCardProps) => {
+ const open = activeQuestionId === variablesCardId;
+
+ const setOpenState = (state: boolean) => {
+ if (state) {
+ setActiveQuestionId(variablesCardId);
+ } else {
+ setActiveQuestionId(null);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {localSurvey.variables.length > 0 ? (
+ localSurvey.variables.map((variable) => (
+
+ ))
+ ) : (
+
No variables yet. Add the first one below.
+ )}
+
+
+
+
+
+
+ );
+};
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCardItem.tsx b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCardItem.tsx
new file mode 100644
index 0000000000..63820f46fa
--- /dev/null
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/[surveyId]/edit/components/SurveyVariablesCardItem.tsx
@@ -0,0 +1,224 @@
+"use client";
+
+import { createId } from "@paralleldrive/cuid2";
+import { TrashIcon } from "lucide-react";
+import React, { useCallback, useEffect } from "react";
+import { useForm } from "react-hook-form";
+import { extractRecallInfo } from "@formbricks/lib/utils/recall";
+import { TSurvey, TSurveyVariable } from "@formbricks/types/surveys/types";
+import { Button } from "@formbricks/ui/Button";
+import { FormControl, FormField, FormItem, FormProvider } from "@formbricks/ui/Form";
+import { Input } from "@formbricks/ui/Input";
+import { Label } from "@formbricks/ui/Label";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@formbricks/ui/Select";
+
+interface SurveyVariablesCardItemProps {
+ variable?: TSurveyVariable;
+ localSurvey: TSurvey;
+ setLocalSurvey: React.Dispatch>;
+ mode: "create" | "edit";
+}
+
+export const SurveyVariablesCardItem = ({
+ variable,
+ localSurvey,
+ setLocalSurvey,
+ mode,
+}: SurveyVariablesCardItemProps) => {
+ const form = useForm({
+ defaultValues: variable ?? {
+ id: createId(),
+ name: "",
+ type: "number",
+ value: 0,
+ },
+ mode: "onChange",
+ });
+
+ const { errors } = form.formState;
+ const isNameError = !!errors.name?.message;
+ const variableType = form.watch("type");
+
+ const editSurveyVariable = useCallback(
+ (data: TSurveyVariable) => {
+ setLocalSurvey((prevSurvey) => {
+ const updatedVariables = prevSurvey.variables.map((v) => (v.id === data.id ? data : v));
+ return { ...prevSurvey, variables: updatedVariables };
+ });
+ },
+ [setLocalSurvey]
+ );
+
+ const createSurveyVariable = (data: TSurveyVariable) => {
+ setLocalSurvey({
+ ...localSurvey,
+ variables: [...localSurvey.variables, data],
+ });
+
+ form.reset({
+ id: createId(),
+ name: "",
+ type: "number",
+ value: 0,
+ });
+ };
+
+ useEffect(() => {
+ if (mode === "create") {
+ return;
+ }
+
+ const subscription = form.watch(() => form.handleSubmit(editSurveyVariable)());
+ return () => subscription.unsubscribe();
+ }, [form, mode, editSurveyVariable]);
+
+ const onVaribleDelete = (variable: TSurveyVariable) => {
+ const questions = [...localSurvey.questions];
+
+ // find if this variable is used in any question's recall and remove it for every language
+
+ questions.forEach((question) => {
+ for (const [languageCode, headline] of Object.entries(question.headline)) {
+ if (headline.includes(`recall:${variable.id}`)) {
+ const recallInfo = extractRecallInfo(headline);
+ if (recallInfo) {
+ question.headline[languageCode] = headline.replace(recallInfo, "");
+ }
+ }
+ }
+ });
+
+ setLocalSurvey((prevSurvey) => {
+ const updatedVariables = prevSurvey.variables.filter((v) => v.id !== variable.id);
+ return { ...prevSurvey, variables: updatedVariables, questions };
+ });
+ };
+
+ if (mode === "edit" && !variable) {
+ return null;
+ }
+
+ return (
+
+ );
+};
diff --git a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/lib/minimalSurvey.ts b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/lib/minimalSurvey.ts
index 428eab5de9..4e4920f56e 100644
--- a/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/lib/minimalSurvey.ts
+++ b/apps/web/app/(app)/(survey-editor)/environments/[environmentId]/surveys/lib/minimalSurvey.ts
@@ -37,4 +37,5 @@ export const minimalSurvey: TSurvey = {
languages: [],
showLanguageSwitch: false,
isVerifyEmailEnabled: false,
+ variables: [],
};
diff --git a/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.ts b/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.ts
index 80c6f67fd1..9841b81955 100644
--- a/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.ts
+++ b/apps/web/app/api/cron/weekly-summary/lib/notificationResponse.ts
@@ -1,7 +1,9 @@
import { getLocalizedValue } from "@formbricks/lib/i18n/utils";
import { convertResponseValue } from "@formbricks/lib/responses";
import { replaceHeadlineRecall } from "@formbricks/lib/utils/recall";
+import { TSurvey } from "@formbricks/types/surveys/types";
import {
+ TWeeklyEmailResponseData,
TWeeklySummaryEnvironmentData,
TWeeklySummaryNotificationDataSurvey,
TWeeklySummaryNotificationResponse,
@@ -23,7 +25,11 @@ export const getNotificationResponse = (
const surveys: TWeeklySummaryNotificationDataSurvey[] = [];
// iterate through the surveys and calculate the overall insights
for (const survey of environment.surveys) {
- const parsedSurvey = replaceHeadlineRecall(survey, "default", environment.attributeClasses);
+ const parsedSurvey = replaceHeadlineRecall(
+ survey as unknown as TSurvey,
+ "default",
+ environment.attributeClasses
+ ) as TSurvey & { responses: TWeeklyEmailResponseData[] };
const surveyData: TWeeklySummaryNotificationDataSurvey = {
id: parsedSurvey.id,
name: parsedSurvey.name,
diff --git a/packages/database/json-types.ts b/packages/database/json-types.ts
index 16e95507f2..cd5cf51b04 100644
--- a/packages/database/json-types.ts
+++ b/packages/database/json-types.ts
@@ -17,6 +17,7 @@ import {
type TSurveyQuestions,
type TSurveySingleUse,
type TSurveyStyling,
+ type TSurveyVariables,
type TSurveyWelcomeCard,
} from "@formbricks/types/surveys/types";
import { type TUserNotificationSettings } from "@formbricks/types/user";
@@ -34,6 +35,7 @@ declare global {
export type SurveyQuestions = TSurveyQuestions;
export type SurveyEnding = TSurveyEnding;
export type SurveyHiddenFields = TSurveyHiddenFields;
+ export type SurveyVariables = TSurveyVariables;
export type SurveyProductOverwrites = TSurveyProductOverwrites;
export type SurveyStyling = TSurveyStyling;
export type SurveyClosedMessage = TSurveyClosedMessage;
diff --git a/packages/database/migrations/20240813094711_added_variables_to_survey_model/migration.sql b/packages/database/migrations/20240813094711_added_variables_to_survey_model/migration.sql
new file mode 100644
index 0000000000..3a0b4bd5e5
--- /dev/null
+++ b/packages/database/migrations/20240813094711_added_variables_to_survey_model/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Survey" ADD COLUMN "variables" JSONB NOT NULL DEFAULT '[]';
diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma
index 32d544a3cd..2920c6a53c 100644
--- a/packages/database/schema.prisma
+++ b/packages/database/schema.prisma
@@ -278,6 +278,9 @@ model Survey {
/// @zod.custom(imports.ZSurveyHiddenFields)
/// [SurveyHiddenFields]
hiddenFields Json @default("{\"enabled\": false}")
+ /// @zod.custom(imports.ZSurveyVariables)
+ /// [SurveyVariables]
+ variables Json @default("[]")
responses Response[]
displayOption displayOptions @default(displayOnce)
recontactDays Int?
diff --git a/packages/database/zod-utils.ts b/packages/database/zod-utils.ts
index 21a869b445..376c8d3e74 100644
--- a/packages/database/zod-utils.ts
+++ b/packages/database/zod-utils.ts
@@ -15,6 +15,7 @@ export {
ZSurveyWelcomeCard,
ZSurveyQuestions,
ZSurveyHiddenFields,
+ ZSurveyVariables,
ZSurveyClosedMessage,
ZSurveyProductOverwrites,
ZSurveyStyling,
diff --git a/packages/lib/styling/constants.ts b/packages/lib/styling/constants.ts
index 74ead8b6d2..61c3767ff2 100644
--- a/packages/lib/styling/constants.ts
+++ b/packages/lib/styling/constants.ts
@@ -104,6 +104,7 @@ export const PREVIEW_SURVEY = {
enabled: true,
fieldIds: [],
},
+ variables: [],
displayOption: "displayOnce",
recontactDays: null,
displayLimit: null,
diff --git a/packages/lib/survey/service.ts b/packages/lib/survey/service.ts
index 77cbc3dba6..8bd8a7185a 100644
--- a/packages/lib/survey/service.ts
+++ b/packages/lib/survey/service.ts
@@ -66,6 +66,7 @@ export const selectSurvey = {
questions: true,
endings: true,
hiddenFields: true,
+ variables: true,
displayOption: true,
recontactDays: true,
displayLimit: true,
diff --git a/packages/lib/survey/tests/__mock__/survey.mock.ts b/packages/lib/survey/tests/__mock__/survey.mock.ts
index f8852e9026..6e4ec19e17 100644
--- a/packages/lib/survey/tests/__mock__/survey.mock.ts
+++ b/packages/lib/survey/tests/__mock__/survey.mock.ts
@@ -277,6 +277,7 @@ export const updateSurveyInput: TSurvey = {
segment: null,
languages: [],
showLanguageSwitch: null,
+ variables: [],
...commonMockProperties,
...baseSurveyProperties,
};
diff --git a/packages/lib/utils/recall.ts b/packages/lib/utils/recall.ts
index daecc7840f..65423e68d6 100644
--- a/packages/lib/utils/recall.ts
+++ b/packages/lib/utils/recall.ts
@@ -5,8 +5,8 @@ import {
TI18nString,
TSurvey,
TSurveyQuestion,
- TSurveyQuestionsObject,
TSurveyRecallItem,
+ TSurveyVariables,
} from "@formbricks/types/surveys/types";
import { getLocalizedValue } from "../i18n/utils";
import { structuredClone } from "../pollyfills/structuredClone";
@@ -60,7 +60,7 @@ export const findRecallInfoById = (text: string, id: string): string | null => {
return match ? match[0] : null;
};
-const getRecallItemLabel = (
+const getRecallItemLabel = (
recallItemId: string,
survey: T,
languageCode: string,
@@ -75,11 +75,14 @@ const getRecallItemLabel = (
const attributeClass = attributeClasses.find(
(attributeClass) => attributeClass.name.replaceAll(" ", "nbsp") === recallItemId
);
- return attributeClass?.name;
+ if (attributeClass) return attributeClass?.name;
+
+ const variable = survey.variables?.find((variable) => variable.id === recallItemId);
+ if (variable) return variable.name;
};
// Converts recall information in a headline to a corresponding recall question headline, with or without a slash.
-export const recallToHeadline = (
+export const recallToHeadline = (
headline: TI18nString,
survey: T,
withSlash: boolean,
@@ -149,7 +152,7 @@ export const checkForEmptyFallBackValue = (survey: TSurvey, language: string): T
};
// Processes each question in a survey to ensure headlines are formatted correctly for recall and return the modified survey.
-export const replaceHeadlineRecall = (
+export const replaceHeadlineRecall = (
survey: T,
language: string,
attributeClasses: TAttributeClass[]
@@ -181,15 +184,24 @@ export const getRecallItems = (
ids.forEach((recallItemId) => {
const isHiddenField = survey.hiddenFields.fieldIds?.includes(recallItemId);
const isSurveyQuestion = survey.questions.find((question) => question.id === recallItemId);
+ const isVariable = survey.variables.find((variable) => variable.id === recallItemId);
const recallItemLabel = getRecallItemLabel(recallItemId, survey, languageCode, attributeClasses);
+
+ const getRecallItemType = () => {
+ if (isHiddenField) return "hiddenField";
+ if (isSurveyQuestion) return "question";
+ if (isVariable) return "variable";
+ return "attributeClass";
+ };
+
if (recallItemLabel) {
let recallItemLabelTemp = recallItemLabel;
recallItemLabelTemp = replaceRecallInfoWithUnderline(recallItemLabelTemp);
recallItems.push({
id: recallItemId,
label: recallItemLabelTemp,
- type: isHiddenField ? "hiddenField" : isSurveyQuestion ? "question" : "attributeClass",
+ type: getRecallItemType(),
});
}
});
@@ -228,6 +240,7 @@ export const parseRecallInfo = (
text: string,
attributes?: TAttributes,
responseData?: TResponseData,
+ variables?: TSurveyVariables,
withSlash: boolean = false
) => {
let modifiedText = text;
@@ -253,6 +266,29 @@ export const parseRecallInfo = (
}
});
}
+
+ if (variables && variables.length > 0) {
+ variables.forEach((variable) => {
+ const recallPattern = `#recall:`;
+ while (modifiedText.includes(recallPattern)) {
+ const recallInfo = extractRecallInfo(modifiedText, variable.id);
+ if (!recallInfo) break; // Exit the loop if no recall info is found
+
+ const recallItemId = extractId(recallInfo);
+ if (!recallItemId) continue; // Skip to the next iteration if no ID could be extracted
+
+ const fallback = extractFallbackValue(recallInfo).replaceAll("nbsp", " ");
+
+ let value = variable.value?.toString() || fallback;
+ if (withSlash) {
+ modifiedText = modifiedText.replace(recallInfo, "#/" + value + "\\#");
+ } else {
+ modifiedText = modifiedText.replace(recallInfo, value);
+ }
+ }
+ });
+ }
+
if (responseData && questionIds.length > 0) {
while (modifiedText.includes("recall:")) {
const recallInfo = extractRecallInfo(modifiedText);
diff --git a/packages/surveys/src/components/general/EndingCard.tsx b/packages/surveys/src/components/general/EndingCard.tsx
index a52f775102..086a08594f 100644
--- a/packages/surveys/src/components/general/EndingCard.tsx
+++ b/packages/surveys/src/components/general/EndingCard.tsx
@@ -96,7 +96,11 @@ export const EndingCard = ({
alignTextCenter={true}
headline={
endingCard.type === "endScreen"
- ? replaceRecallInfo(getLocalizedValue(endingCard.headline, languageCode), responseData)
+ ? replaceRecallInfo(
+ getLocalizedValue(endingCard.headline, languageCode),
+ responseData,
+ survey.variables
+ )
: "Respondants will not see this card"
}
questionId="EndingCard"
@@ -104,7 +108,11 @@ export const EndingCard = ({
q.id === questionId);
}
}, [questionId, survey, history]);
+
const contentRef = useRef(null);
const showProgressBar = !styling.hideProgressBar;
const getShowSurveyCloseButton = (offset: number) => {
@@ -297,7 +298,7 @@ export const Survey = ({
string;
+ replaceRecallInfo: (text: string, responseData: TResponseData, variables: TSurveyVariables) => string;
isCurrent: boolean;
responseData: TResponseData;
}
@@ -142,11 +142,19 @@ export const WelcomeCard = ({
)}
diff --git a/packages/surveys/src/lib/recall.ts b/packages/surveys/src/lib/recall.ts
index 5782b367d6..621be79f12 100644
--- a/packages/surveys/src/lib/recall.ts
+++ b/packages/surveys/src/lib/recall.ts
@@ -3,9 +3,13 @@ import { structuredClone } from "@formbricks/lib/pollyfills/structuredClone";
import { formatDateWithOrdinal, isValidDateString } from "@formbricks/lib/utils/datetime";
import { extractFallbackValue, extractId, extractRecallInfo } from "@formbricks/lib/utils/recall";
import { TResponseData } from "@formbricks/types/responses";
-import { TSurveyQuestion } from "@formbricks/types/surveys/types";
+import { TSurveyQuestion, TSurveyVariables } from "@formbricks/types/surveys/types";
-export const replaceRecallInfo = (text: string, responseData: TResponseData): string => {
+export const replaceRecallInfo = (
+ text: string,
+ responseData: TResponseData,
+ variables: TSurveyVariables
+): string => {
let modifiedText = text;
while (modifiedText.includes("recall:")) {
@@ -16,7 +20,13 @@ export const replaceRecallInfo = (text: string, responseData: TResponseData): st
if (!recallItemId) return modifiedText; // Return the text if no ID could be extracted
const fallback = extractFallbackValue(recallInfo).replaceAll("nbsp", " ");
- let value = null;
+ let value: string | null = null;
+
+ // Fetching value from variables based on recallItemId
+ if (variables.length) {
+ const variable = variables.find((variable) => variable.id === recallItemId);
+ value = variable?.value?.toString() ?? fallback;
+ }
// Fetching value from responseData or attributes based on recallItemId
if (responseData[recallItemId]) {
@@ -42,13 +52,15 @@ export const replaceRecallInfo = (text: string, responseData: TResponseData): st
export const parseRecallInformation = (
question: TSurveyQuestion,
languageCode: string,
- responseData: TResponseData
+ responseData: TResponseData,
+ variables: TSurveyVariables
) => {
const modifiedQuestion = structuredClone(question);
if (question.headline && question.headline[languageCode]?.includes("recall:")) {
modifiedQuestion.headline[languageCode] = replaceRecallInfo(
getLocalizedValue(modifiedQuestion.headline, languageCode),
- responseData
+ responseData,
+ variables
);
}
if (
@@ -58,7 +70,8 @@ export const parseRecallInformation = (
) {
modifiedQuestion.subheader[languageCode] = replaceRecallInfo(
getLocalizedValue(modifiedQuestion.subheader, languageCode),
- responseData
+ responseData,
+ variables
);
}
return modifiedQuestion;
diff --git a/packages/types/surveys/types.ts b/packages/types/surveys/types.ts
index 48671fea35..f2b7686c06 100644
--- a/packages/types/surveys/types.ts
+++ b/packages/types/surveys/types.ts
@@ -142,6 +142,36 @@ export const ZSurveyHiddenFields = z.object({
export type TSurveyHiddenFields = z.infer;
+export const ZSurveyVariable = z
+ .discriminatedUnion("type", [
+ z.object({
+ id: z.string().cuid2(),
+ name: z.string(),
+ type: z.literal("number"),
+ value: z.number().default(0),
+ }),
+ z.object({
+ id: z.string().cuid2(),
+ name: z.string(),
+ type: z.literal("text"),
+ value: z.string().default(""),
+ }),
+ ])
+ .superRefine((data, ctx) => {
+ // variable name can only contain lowercase letters, numbers, and underscores
+ if (!/^[a-z0-9_]+$/.test(data.name)) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Variable name can only contain lowercase letters, numbers, and underscores",
+ path: ["variables"],
+ });
+ }
+ });
+export const ZSurveyVariables = z.array(ZSurveyVariable);
+
+export type TSurveyVariable = z.infer;
+export type TSurveyVariables = z.infer;
+
export const ZSurveyProductOverwrites = z.object({
brandColor: ZColor.nullish(),
highlightBorderColor: ZColor.nullish(),
@@ -603,6 +633,29 @@ export const ZSurvey = z
}
}),
hiddenFields: ZSurveyHiddenFields,
+ variables: ZSurveyVariables.superRefine((variables, ctx) => {
+ // variable ids must be unique
+ const variableIds = variables.map((v) => v.id);
+ const uniqueVariableIds = new Set(variableIds);
+ if (uniqueVariableIds.size !== variableIds.length) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Variable IDs must be unique",
+ path: ["variables"],
+ });
+ }
+
+ // variable names must be unique
+ const variableNames = variables.map((v) => v.name);
+ const uniqueVariableNames = new Set(variableNames);
+ if (uniqueVariableNames.size !== variableNames.length) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Variable names must be unique",
+ path: ["variables"],
+ });
+ }
+ }),
delay: z.number(),
autoComplete: z.number().min(1, { message: "Response limit must be greater than 0" }).nullable(),
runOnDate: z.date().nullable(),
@@ -1389,7 +1442,7 @@ export type TSurveySummary = z.infer;
export const ZSurveyRecallItem = z.object({
id: z.string(),
label: z.string(),
- type: z.enum(["question", "hiddenField", "attributeClass"]),
+ type: z.enum(["question", "hiddenField", "attributeClass", "variable"]),
});
export type TSurveyRecallItem = z.infer;
diff --git a/packages/ui/Input/index.tsx b/packages/ui/Input/index.tsx
index 6c2abcb975..cf391afc5e 100644
--- a/packages/ui/Input/index.tsx
+++ b/packages/ui/Input/index.tsx
@@ -16,7 +16,7 @@ const Input = React.forwardRef(({ className, isInv
className={cn(
"focus:border-brand-dark flex h-10 w-full rounded-md border border-slate-300 bg-transparent px-3 py-2 text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-500 dark:text-slate-300",
className,
- isInvalid && "border-error focus:border-error border"
+ isInvalid && "border border-red-500 focus:border-red-500"
)}
ref={ref}
{...props}
diff --git a/packages/ui/PreviewSurvey/index.tsx b/packages/ui/PreviewSurvey/index.tsx
index 8ef4872039..c563d7814d 100644
--- a/packages/ui/PreviewSurvey/index.tsx
+++ b/packages/ui/PreviewSurvey/index.tsx
@@ -145,7 +145,13 @@ export const PreviewSurvey = ({
const updateQuestionId = useCallback(
(newQuestionId: string) => {
- if (!newQuestionId || newQuestionId === "hidden" || newQuestionId === "multiLanguage") return;
+ if (
+ !newQuestionId ||
+ newQuestionId === "hidden" ||
+ newQuestionId === "multiLanguage" ||
+ newQuestionId.includes("fb-variables-")
+ )
+ return;
if (newQuestionId === "start" && !survey.welcomeCard.enabled) return;
setQuestionId(newQuestionId);
},
diff --git a/packages/ui/QuestionFormInput/components/RecallItemSelect.tsx b/packages/ui/QuestionFormInput/components/RecallItemSelect.tsx
index 1903d549e8..685ded56dd 100644
--- a/packages/ui/QuestionFormInput/components/RecallItemSelect.tsx
+++ b/packages/ui/QuestionFormInput/components/RecallItemSelect.tsx
@@ -2,6 +2,8 @@ import { DropdownMenuItem } from "@radix-ui/react-dropdown-menu";
import {
CalendarDaysIcon,
EyeOffIcon,
+ FileDigitIcon,
+ FileTextIcon,
HomeIcon,
ListIcon,
MessageSquareTextIcon,
@@ -98,6 +100,22 @@ export const RecallItemSelect = ({
});
}, [attributeClasses]);
+ const variableRecallItems = useMemo(() => {
+ if (localSurvey.variables.length) {
+ return localSurvey.variables
+ .filter((variable) => !recallItemIds.includes(variable.id))
+ .map((variable) => {
+ return {
+ id: variable.id,
+ label: variable.name,
+ type: "variable" as const,
+ };
+ });
+ }
+
+ return [];
+ }, [localSurvey.variables, recallItemIds]);
+
const surveyQuestionRecallItems = useMemo(() => {
const isEndingCard = !localSurvey.questions.map((question) => question.id).includes(questionId);
const idx = isEndingCard
@@ -118,22 +136,31 @@ export const RecallItemSelect = ({
}, [localSurvey.questions, questionId, recallItemIds]);
const filteredRecallItems: TSurveyRecallItem[] = useMemo(() => {
- return [...surveyQuestionRecallItems, ...hiddenFieldRecallItems, ...attributeClassRecallItems].filter(
- (recallItems) => {
- if (searchValue.trim() === "") return true;
- else {
- return recallItems.label.toLowerCase().startsWith(searchValue.toLowerCase());
- }
+ return [
+ ...surveyQuestionRecallItems,
+ ...hiddenFieldRecallItems,
+ ...attributeClassRecallItems,
+ ...variableRecallItems,
+ ].filter((recallItems) => {
+ if (searchValue.trim() === "") return true;
+ else {
+ return recallItems.label.toLowerCase().startsWith(searchValue.toLowerCase());
}
- );
- }, [surveyQuestionRecallItems, hiddenFieldRecallItems, attributeClassRecallItems, searchValue]);
+ });
+ }, [
+ surveyQuestionRecallItems,
+ hiddenFieldRecallItems,
+ attributeClassRecallItems,
+ variableRecallItems,
+ searchValue,
+ ]);
// function to modify headline (recallInfo to corresponding headline)
const getRecallLabel = (label: string): string => {
return replaceRecallInfoWithUnderline(label);
};
- const getQuestionIcon = (recallItem: TSurveyRecallItem) => {
+ const getRecallItemIcon = (recallItem: TSurveyRecallItem) => {
switch (recallItem.type) {
case "question":
const question = localSurvey.questions.find((question) => question.id === recallItem.id);
@@ -144,6 +171,9 @@ export const RecallItemSelect = ({
return EyeOffIcon;
case "attributeClass":
return TagIcon;
+ case "variable":
+ const variable = localSurvey.variables.find((variable) => variable.id === recallItem.id);
+ return variable?.type === "number" ? FileDigitIcon : FileTextIcon;
}
};
@@ -170,7 +200,7 @@ export const RecallItemSelect = ({
/>
{filteredRecallItems.map((recallItem, index) => {
- const IconComponent = getQuestionIcon(recallItem);
+ const IconComponent = getRecallItemIcon(recallItem);
return (
{survey.welcomeCard.enabled && (
@@ -161,6 +162,7 @@ export const SingleResponseCardBody = ({
getLocalizedValue(question.headline, "default"),
{},
response.data,
+ survey.variables,
true
)
)}
From 5689c36b12903b7a61508c6c614228099b10e4e3 Mon Sep 17 00:00:00 2001
From: Piyush Gupta <56182734+gupta-piyush19@users.noreply.github.com>
Date: Thu, 22 Aug 2024 15:25:13 +0530
Subject: [PATCH 04/19] feat: adds management endpoint to generate multiple
suId (#3022)
Co-authored-by: Matti Nannt
---
.../docs/app/developer-docs/rest-api/page.mdx | 2 +-
.../surveys/[surveyId]/singleUseIds/route.ts | 47 +++++++++++++++++++
packages/lib/utils/singleUseSurveys.ts | 10 ++++
3 files changed, 58 insertions(+), 1 deletion(-)
create mode 100644 apps/web/app/api/v1/management/surveys/[surveyId]/singleUseIds/route.ts
diff --git a/apps/docs/app/developer-docs/rest-api/page.mdx b/apps/docs/app/developer-docs/rest-api/page.mdx
index 7bb30057da..7783a07063 100644
--- a/apps/docs/app/developer-docs/rest-api/page.mdx
+++ b/apps/docs/app/developer-docs/rest-api/page.mdx
@@ -39,7 +39,7 @@ We currently have the following Management API methods exposed and below is thei
- [Me API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#79e08365-641d-4b2d-aea2-9a855e0438ec) - Retrieve Account Information
- [People API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#cffc27a6-dafb-428f-8ea7-5165bedb911e) - List and Delete People
- [Response API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#e544ec0d-8b30-4e33-8d35-2441cb40d676) - List, List by Survey, Update, and Delete Responses
-- [Survey API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#953189b2-37b5-4429-a7bd-f4d01ceae242) - List, Create, Update, and Delete Surveys
+- [Survey API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#953189b2-37b5-4429-a7bd-f4d01ceae242) - List, Create, Update, generate multiple suId and Delete Surveys
- [Webhook API](https://documenter.getpostman.com/view/11026000/2sA3Bq5XEh#62e6ec65-021b-42a4-ac93-d1434b393c6c) - List, Create, and Delete Webhooks
## How to Generate an API key
diff --git a/apps/web/app/api/v1/management/surveys/[surveyId]/singleUseIds/route.ts b/apps/web/app/api/v1/management/surveys/[surveyId]/singleUseIds/route.ts
new file mode 100644
index 0000000000..9fa9ef30b2
--- /dev/null
+++ b/apps/web/app/api/v1/management/surveys/[surveyId]/singleUseIds/route.ts
@@ -0,0 +1,47 @@
+import { authenticateRequest, handleErrorResponse } from "@/app/api/v1/auth";
+import { responses } from "@/app/lib/api/response";
+import { NextRequest } from "next/server";
+import { getSurvey } from "@formbricks/lib/survey/service";
+import { generateSurveySingleUseIds } from "@formbricks/lib/utils/singleUseSurveys";
+
+export const GET = async (
+ request: NextRequest,
+ { params }: { params: { surveyId: string } }
+): Promise => {
+ try {
+ const authentication = await authenticateRequest(request);
+ if (!authentication) return responses.notAuthenticatedResponse();
+ const survey = await getSurvey(params.surveyId);
+ if (!survey) {
+ return responses.notFoundResponse("Survey", params.surveyId);
+ }
+ if (survey.environmentId !== authentication.environmentId) {
+ throw new Error("Unauthorized");
+ }
+
+ if (!survey.singleUse || !survey.singleUse.enabled) {
+ return responses.badRequestResponse("Single use links are not enabled for this survey");
+ }
+ const searchParams = request.nextUrl.searchParams;
+ const limit = searchParams.get("limit") ? Number(searchParams.get("limit")) : 10;
+
+ if (limit < 1) {
+ return responses.badRequestResponse("Limit cannot be less than 1");
+ }
+
+ if (limit > 5000) {
+ return responses.badRequestResponse("Limit cannot be more than 5000");
+ }
+
+ const singleUseIds = generateSurveySingleUseIds(limit, survey.singleUse.isEncrypted);
+
+ // map single use ids to survey links
+ const surveyLinks = singleUseIds.map(
+ (singleUseId) => `${process.env.WEBAPP_URL}/s/${survey.id}?suId=${singleUseId}`
+ );
+
+ return responses.successResponse(surveyLinks);
+ } catch (error) {
+ return handleErrorResponse(error);
+ }
+};
diff --git a/packages/lib/utils/singleUseSurveys.ts b/packages/lib/utils/singleUseSurveys.ts
index f6d75d0387..378f88db72 100644
--- a/packages/lib/utils/singleUseSurveys.ts
+++ b/packages/lib/utils/singleUseSurveys.ts
@@ -13,6 +13,16 @@ export const generateSurveySingleUseId = (isEncrypted: boolean): string => {
return encryptedCuid;
};
+export const generateSurveySingleUseIds = (count: number, isEncrypted: boolean): string[] => {
+ const singleUseIds: string[] = [];
+
+ for (let i = 0; i < count; i++) {
+ singleUseIds.push(generateSurveySingleUseId(isEncrypted));
+ }
+
+ return singleUseIds;
+};
+
// validate the survey single use id
export const validateSurveySingleUseId = (surveySingleUseId: string): string | undefined => {
try {
From 2bbeb040c2265a1b1e96f1961d4e11af52c8ad47 Mon Sep 17 00:00:00 2001
From: Matti Nannt
Date: Thu, 22 Aug 2024 13:14:08 +0200
Subject: [PATCH 05/19] fix: use createdAt instead of updatedAt in responseCard
(#3040)
---
packages/lib/response/service.ts | 2 +-
.../components/SingleResponseCardHeader.tsx | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/lib/response/service.ts b/packages/lib/response/service.ts
index bed519ebd6..509d3193c4 100644
--- a/packages/lib/response/service.ts
+++ b/packages/lib/response/service.ts
@@ -114,7 +114,7 @@ export const getResponsesByPersonId = reactCache(
take: page ? ITEMS_PER_PAGE : undefined,
skip: page ? ITEMS_PER_PAGE * (page - 1) : undefined,
orderBy: {
- updatedAt: "asc",
+ createdAt: "desc",
},
});
diff --git a/packages/ui/SingleResponseCard/components/SingleResponseCardHeader.tsx b/packages/ui/SingleResponseCard/components/SingleResponseCardHeader.tsx
index 877de0683b..f705cf3c57 100644
--- a/packages/ui/SingleResponseCard/components/SingleResponseCardHeader.tsx
+++ b/packages/ui/SingleResponseCard/components/SingleResponseCardHeader.tsx
@@ -168,8 +168,8 @@ export const SingleResponseCardHeader = ({
-
);
From 89ffe99dccfc5d3cebfc1b27625b142104ba0011 Mon Sep 17 00:00:00 2001
From: ty kerr
Date: Fri, 23 Aug 2024 03:43:49 -0700
Subject: [PATCH 08/19] feat: react native sdk (#2565)
Co-authored-by: tykerr
Co-authored-by: Dhruwang
Co-authored-by: Dhruwang Jariwala <67850763+Dhruwang@users.noreply.github.com>
Co-authored-by: pandeymangg
Co-authored-by: Matthias Nannt
---
apps/demo-react-native/.env.example | 2 +
apps/demo-react-native/.eslintrc.js | 7 +
apps/demo-react-native/.gitignore | 35 +
apps/demo-react-native/.npmrc | 0
apps/demo-react-native/app.json | 34 +
.../assets/adaptive-icon.png | Bin 0 -> 17547 bytes
apps/demo-react-native/assets/favicon.png | Bin 0 -> 1466 bytes
apps/demo-react-native/assets/icon.png | Bin 0 -> 22380 bytes
apps/demo-react-native/assets/splash.png | Bin 0 -> 47346 bytes
apps/demo-react-native/babel.config.js | 6 +
apps/demo-react-native/index.js | 7 +
apps/demo-react-native/metro.config.js | 21 +
apps/demo-react-native/package.json | 28 +
apps/demo-react-native/src/app.tsx | 52 +
apps/demo-react-native/tsconfig.json | 6 +
.../framework-guides/components/Libraries.tsx | 8 +-
.../app/app-surveys/framework-guides/page.mdx | 60 +
apps/docs/app/app-surveys/quickstart/page.mdx | 8 +-
.../developer-docs/app-survey-rn-sdk/page.mdx | 127 +
.../contributing/get-started/page.mdx | 10 +-
apps/docs/lib/navigation.ts | 5 +-
.../product/look/components/EditLogo.tsx | 3 +-
.../components/ThemeStylingPreviewSurvey.tsx | 6 +-
.../app/sync/[userId]/route.ts | 2 -
.../people/[userId]/attributes/route.ts | 1 +
.../[environmentId]/storage/local/route.ts | 8 +-
.../s/[surveyId]/components/LinkSurvey.tsx | 3 +-
packages/api/src/api/client/storage.ts | 42 +-
packages/config-typescript/react-library.json | 2 +-
.../react-native-library.json | 13 +
packages/js-core/src/app/lib/actions.ts | 17 +-
packages/js-core/src/app/lib/attributes.ts | 17 +-
packages/js-core/src/app/lib/config.ts | 72 +-
.../js-core/src/app/lib/eventListeners.ts | 4 +-
packages/js-core/src/app/lib/initialize.ts | 36 +-
packages/js-core/src/app/lib/sync.ts | 32 +-
packages/js-core/src/app/lib/widget.ts | 63 +-
packages/js-core/src/shared/commandQueue.ts | 2 +-
packages/js-core/src/shared/constants.ts | 3 +
packages/js-core/src/shared/errors.ts | 10 +-
packages/js-core/src/shared/utils.ts | 74 +-
packages/js-core/src/website/lib/config.ts | 14 +-
.../js-core/src/website/lib/initialize.ts | 7 +-
packages/js-core/src/website/lib/widget.ts | 14 +-
packages/js/index.html | 2 +-
packages/react-native/.eslintrc.cjs | 11 +
packages/react-native/.gitignore | 28 +
packages/react-native/LICENSE | 9 +
packages/react-native/README.md | 47 +
packages/react-native/package.json | 59 +
packages/react-native/src/formbricks.tsx | 44 +
packages/react-native/src/index.ts | 6 +
packages/react-native/src/lib/actions.ts | 70 +
.../react-native/src/lib/command-queue.ts | 77 +
packages/react-native/src/lib/config.ts | 11 +
packages/react-native/src/lib/index.ts | 21 +
packages/react-native/src/lib/initialize.ts | 154 +
packages/react-native/src/lib/person.ts | 28 +
packages/react-native/src/lib/survey-store.ts | 46 +
packages/react-native/src/survey-web-view.tsx | 389 ++
packages/react-native/tsconfig.json | 8 +
packages/react-native/vite.config.ts | 28 +
.../src/components/general/FileInput.tsx | 100 +-
.../general/QuestionConditional.tsx | 3 +-
.../questions/FileUploadQuestion.tsx | 3 +-
packages/types/formbricks-surveys.ts | 9 +-
packages/types/js.ts | 22 +-
packages/types/surveys/types.ts | 2 +-
packages/ui/PreviewSurvey/index.tsx | 3 +-
packages/ui/Survey/index.tsx | 2 +-
pnpm-lock.yaml | 5313 ++++++++++++++---
turbo.json | 15 +-
72 files changed, 6300 insertions(+), 1071 deletions(-)
create mode 100644 apps/demo-react-native/.env.example
create mode 100644 apps/demo-react-native/.eslintrc.js
create mode 100644 apps/demo-react-native/.gitignore
create mode 100644 apps/demo-react-native/.npmrc
create mode 100644 apps/demo-react-native/app.json
create mode 100644 apps/demo-react-native/assets/adaptive-icon.png
create mode 100644 apps/demo-react-native/assets/favicon.png
create mode 100644 apps/demo-react-native/assets/icon.png
create mode 100644 apps/demo-react-native/assets/splash.png
create mode 100644 apps/demo-react-native/babel.config.js
create mode 100644 apps/demo-react-native/index.js
create mode 100644 apps/demo-react-native/metro.config.js
create mode 100644 apps/demo-react-native/package.json
create mode 100644 apps/demo-react-native/src/app.tsx
create mode 100644 apps/demo-react-native/tsconfig.json
create mode 100644 apps/docs/app/developer-docs/app-survey-rn-sdk/page.mdx
create mode 100644 packages/config-typescript/react-native-library.json
create mode 100644 packages/js-core/src/shared/constants.ts
create mode 100644 packages/react-native/.eslintrc.cjs
create mode 100644 packages/react-native/.gitignore
create mode 100644 packages/react-native/LICENSE
create mode 100644 packages/react-native/README.md
create mode 100644 packages/react-native/package.json
create mode 100644 packages/react-native/src/formbricks.tsx
create mode 100644 packages/react-native/src/index.ts
create mode 100644 packages/react-native/src/lib/actions.ts
create mode 100644 packages/react-native/src/lib/command-queue.ts
create mode 100644 packages/react-native/src/lib/config.ts
create mode 100644 packages/react-native/src/lib/index.ts
create mode 100644 packages/react-native/src/lib/initialize.ts
create mode 100644 packages/react-native/src/lib/person.ts
create mode 100644 packages/react-native/src/lib/survey-store.ts
create mode 100644 packages/react-native/src/survey-web-view.tsx
create mode 100644 packages/react-native/tsconfig.json
create mode 100644 packages/react-native/vite.config.ts
diff --git a/apps/demo-react-native/.env.example b/apps/demo-react-native/.env.example
new file mode 100644
index 0000000000..3a2d97bdc4
--- /dev/null
+++ b/apps/demo-react-native/.env.example
@@ -0,0 +1,2 @@
+EXPO_PUBLIC_API_HOST=http://192.168.178.20:3000
+EXPO_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=clzr04nkd000bcdl110j0ijyq
diff --git a/apps/demo-react-native/.eslintrc.js b/apps/demo-react-native/.eslintrc.js
new file mode 100644
index 0000000000..4d8dbbccec
--- /dev/null
+++ b/apps/demo-react-native/.eslintrc.js
@@ -0,0 +1,7 @@
+module.exports = {
+ extends: ["@formbricks/eslint-config/react.js"],
+ parserOptions: {
+ project: "tsconfig.json",
+ tsconfigRootDir: __dirname,
+ },
+};
diff --git a/apps/demo-react-native/.gitignore b/apps/demo-react-native/.gitignore
new file mode 100644
index 0000000000..05647d55c7
--- /dev/null
+++ b/apps/demo-react-native/.gitignore
@@ -0,0 +1,35 @@
+# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
+
+# dependencies
+node_modules/
+
+# Expo
+.expo/
+dist/
+web-build/
+
+# Native
+*.orig.*
+*.jks
+*.p8
+*.p12
+*.key
+*.mobileprovision
+
+# Metro
+.metro-health-check*
+
+# debug
+npm-debug.*
+yarn-debug.*
+yarn-error.*
+
+# macOS
+.DS_Store
+*.pem
+
+# local env files
+.env*.local
+
+# typescript
+*.tsbuildinfo
diff --git a/apps/demo-react-native/.npmrc b/apps/demo-react-native/.npmrc
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/apps/demo-react-native/app.json b/apps/demo-react-native/app.json
new file mode 100644
index 0000000000..b7701d49b3
--- /dev/null
+++ b/apps/demo-react-native/app.json
@@ -0,0 +1,34 @@
+{
+ "expo": {
+ "name": "react-native-demo",
+ "slug": "react-native-demo",
+ "version": "1.0.0",
+ "orientation": "portrait",
+ "icon": "./assets/icon.png",
+ "userInterfaceStyle": "light",
+ "splash": {
+ "image": "./assets/splash.png",
+ "resizeMode": "contain",
+ "backgroundColor": "#ffffff"
+ },
+ "jsEngine": "hermes",
+ "assetBundlePatterns": ["**/*"],
+ "ios": {
+ "supportsTablet": true,
+ "infoPlist": {
+ "NSCameraUsageDescription": "Take pictures for certain activities.",
+ "NSPhotoLibraryUsageDescription": "Select pictures for certain activities.",
+ "NSMicrophoneUsageDescription": "Need microphone access for recording videos."
+ }
+ },
+ "android": {
+ "adaptiveIcon": {
+ "foregroundImage": "./assets/adaptive-icon.png",
+ "backgroundColor": "#ffffff"
+ }
+ },
+ "web": {
+ "favicon": "./assets/favicon.png"
+ }
+ }
+}
diff --git a/apps/demo-react-native/assets/adaptive-icon.png b/apps/demo-react-native/assets/adaptive-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..03d6f6b6c6727954aec1d8206222769afd178d8d
GIT binary patch
literal 17547
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
literal 0
HcmV?d00001
diff --git a/apps/demo-react-native/assets/icon.png b/apps/demo-react-native/assets/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..a0b1526fc7b78680fd8d733dbc6113e1af695487
GIT binary patch
literal 22380
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
z