- {(survey.type === "link" ||
- environment.appSetupCompleted ||
- environment.websiteSetupCompleted) && (
+ {(survey.type === "link" || environment.appSetupCompleted) && (
)}
diff --git a/apps/web/app/api/packages/[package]/route.ts b/apps/web/app/api/packages/[package]/route.ts
index 0cd496b3c6..6c96e9d837 100644
--- a/apps/web/app/api/packages/[package]/route.ts
+++ b/apps/web/app/api/packages/[package]/route.ts
@@ -7,11 +7,8 @@ export const GET = async (_: NextRequest, { params }: { params: { package: strin
const packageRequested = params.package;
switch (packageRequested) {
- case "app":
- path = `../../packages/js-core/dist/app.umd.cjs`;
- break;
- case "website":
- path = `../../packages/js-core/dist/website.umd.cjs`;
+ case "js":
+ path = `../../packages/js-core/dist/index.umd.cjs`;
break;
case "surveys":
path = `../../packages/surveys/dist/index.umd.cjs`;
diff --git a/apps/web/app/api/v1/client/[environmentId]/app/environment/lib/environmentState.ts b/apps/web/app/api/v1/client/[environmentId]/environment/lib/environmentState.ts
similarity index 92%
rename from apps/web/app/api/v1/client/[environmentId]/app/environment/lib/environmentState.ts
rename to apps/web/app/api/v1/client/[environmentId]/environment/lib/environmentState.ts
index fb34f8a81a..ca99392067 100644
--- a/apps/web/app/api/v1/client/[environmentId]/app/environment/lib/environmentState.ts
+++ b/apps/web/app/api/v1/client/[environmentId]/environment/lib/environmentState.ts
@@ -18,7 +18,7 @@ import { productCache } from "@formbricks/lib/product/cache";
import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
import { surveyCache } from "@formbricks/lib/survey/cache";
import { getSurveys } from "@formbricks/lib/survey/service";
-import { InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
+import { ResourceNotFoundError } from "@formbricks/types/errors";
import { TJsEnvironmentState } from "@formbricks/types/js";
/**
@@ -26,7 +26,6 @@ import { TJsEnvironmentState } from "@formbricks/types/js";
* @param environmentId
* @returns The environment state
* @throws ResourceNotFoundError if the environment or organization does not exist
- * @throws InvalidInputError if the channel is not "app"
*/
export const getEnvironmentState = async (
environmentId: string
@@ -52,10 +51,6 @@ export const getEnvironmentState = async (
throw new ResourceNotFoundError("product", null);
}
- if (product.config.channel && product.config.channel !== "app") {
- throw new InvalidInputError("Invalid channel");
- }
-
if (!environment.appSetupCompleted) {
await Promise.all([
prisma.environment.update({
@@ -117,7 +112,7 @@ export const getEnvironmentState = async (
revalidateEnvironment,
};
},
- [`environmentState-app-${environmentId}`],
+ [`environmentState-${environmentId}`],
{
...(IS_FORMBRICKS_CLOUD && { revalidate: 24 * 60 * 60 }),
tags: [
diff --git a/apps/web/app/api/v1/client/[environmentId]/app/environment/route.ts b/apps/web/app/api/v1/client/[environmentId]/environment/route.ts
similarity index 97%
rename from apps/web/app/api/v1/client/[environmentId]/app/environment/route.ts
rename to apps/web/app/api/v1/client/[environmentId]/environment/route.ts
index 919fc350f9..3ec1823f38 100644
--- a/apps/web/app/api/v1/client/[environmentId]/app/environment/route.ts
+++ b/apps/web/app/api/v1/client/[environmentId]/environment/route.ts
@@ -1,4 +1,4 @@
-import { getEnvironmentState } from "@/app/api/v1/client/[environmentId]/app/environment/lib/environmentState";
+import { getEnvironmentState } from "@/app/api/v1/client/[environmentId]/environment/lib/environmentState";
import { responses } from "@/app/lib/api/response";
import { transformErrorToDetails } from "@/app/lib/api/validator";
import { NextRequest } from "next/server";
diff --git a/apps/web/app/api/v1/client/[environmentId]/app/people/[userId]/lib/personState.ts b/apps/web/app/api/v1/client/[environmentId]/identify/people/[userId]/lib/personState.ts
similarity index 97%
rename from apps/web/app/api/v1/client/[environmentId]/app/people/[userId]/lib/personState.ts
rename to apps/web/app/api/v1/client/[environmentId]/identify/people/[userId]/lib/personState.ts
index ae3531a6ac..9ced049337 100644
--- a/apps/web/app/api/v1/client/[environmentId]/app/people/[userId]/lib/personState.ts
+++ b/apps/web/app/api/v1/client/[environmentId]/identify/people/[userId]/lib/personState.ts
@@ -1,4 +1,3 @@
-import { getPersonSegmentIds } from "@/app/api/v1/client/[environmentId]/app/people/[userId]/lib/segments";
import { prisma } from "@formbricks/database";
import { attributeCache } from "@formbricks/lib/attribute/cache";
import { getAttributesByUserId } from "@formbricks/lib/attribute/service";
@@ -17,6 +16,7 @@ import { getResponsesByUserId } from "@formbricks/lib/response/service";
import { segmentCache } from "@formbricks/lib/segment/cache";
import { ResourceNotFoundError } from "@formbricks/types/errors";
import { TJsPersonState } from "@formbricks/types/js";
+import { getPersonSegmentIds } from "./segments";
/**
*
diff --git a/apps/web/app/api/v1/client/[environmentId]/app/people/[userId]/lib/segments.ts b/apps/web/app/api/v1/client/[environmentId]/identify/people/[userId]/lib/segments.ts
similarity index 100%
rename from apps/web/app/api/v1/client/[environmentId]/app/people/[userId]/lib/segments.ts
rename to apps/web/app/api/v1/client/[environmentId]/identify/people/[userId]/lib/segments.ts
diff --git a/apps/web/app/api/v1/client/[environmentId]/app/people/[userId]/route.ts b/apps/web/app/api/v1/client/[environmentId]/identify/people/[userId]/route.ts
similarity index 100%
rename from apps/web/app/api/v1/client/[environmentId]/app/people/[userId]/route.ts
rename to apps/web/app/api/v1/client/[environmentId]/identify/people/[userId]/route.ts
diff --git a/apps/web/app/api/v1/client/[environmentId]/website/environment/lib/environmentState.ts b/apps/web/app/api/v1/client/[environmentId]/website/environment/lib/environmentState.ts
deleted file mode 100644
index 0e1d2d73cf..0000000000
--- a/apps/web/app/api/v1/client/[environmentId]/website/environment/lib/environmentState.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-import { prisma } from "@formbricks/database";
-import { actionClassCache } from "@formbricks/lib/actionClass/cache";
-import { getActionClasses } from "@formbricks/lib/actionClass/service";
-import { cache } from "@formbricks/lib/cache";
-import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
-import { environmentCache } from "@formbricks/lib/environment/cache";
-import { getEnvironment } from "@formbricks/lib/environment/service";
-import { organizationCache } from "@formbricks/lib/organization/cache";
-import {
- getMonthlyOrganizationResponseCount,
- getOrganizationByEnvironmentId,
-} from "@formbricks/lib/organization/service";
-import {
- capturePosthogEnvironmentEvent,
- sendPlanLimitsReachedEventToPosthogWeekly,
-} from "@formbricks/lib/posthogServer";
-import { productCache } from "@formbricks/lib/product/cache";
-import { getProductByEnvironmentId } from "@formbricks/lib/product/service";
-import { surveyCache } from "@formbricks/lib/survey/cache";
-import { getSurveys } from "@formbricks/lib/survey/service";
-import { InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
-import { TJsEnvironmentState } from "@formbricks/types/js";
-
-/**
- * Get the environment state
- * @param environmentId
- * @returns The environment state
- * @throws ResourceNotFoundError if the organization, environment or product is not found
- * @throws InvalidInputError if the product channel is not website
- */
-export const getEnvironmentState = async (
- environmentId: string
-): Promise<{ state: TJsEnvironmentState["data"]; revalidateEnvironment?: boolean }> =>
- cache(
- async () => {
- let revalidateEnvironment = false;
- const [environment, organization, product] = await Promise.all([
- getEnvironment(environmentId),
- getOrganizationByEnvironmentId(environmentId),
- getProductByEnvironmentId(environmentId),
- ]);
-
- if (!environment) {
- throw new ResourceNotFoundError("environment", environmentId);
- }
-
- if (!organization) {
- throw new ResourceNotFoundError("organization", null);
- }
-
- if (!product) {
- throw new ResourceNotFoundError("product", null);
- }
-
- if (product.config.channel && product.config.channel !== "website") {
- throw new InvalidInputError("Product channel is not website");
- }
-
- // check if response limit is reached
- let isWebsiteSurveyResponseLimitReached = false;
- if (IS_FORMBRICKS_CLOUD) {
- const currentResponseCount = await getMonthlyOrganizationResponseCount(organization.id);
- const monthlyResponseLimit = organization.billing.limits.monthly.responses;
-
- isWebsiteSurveyResponseLimitReached =
- monthlyResponseLimit !== null && currentResponseCount >= monthlyResponseLimit;
-
- if (isWebsiteSurveyResponseLimitReached) {
- try {
- await sendPlanLimitsReachedEventToPosthogWeekly(environmentId, {
- plan: organization.billing.plan,
- limits: { monthly: { responses: monthlyResponseLimit, miu: null } },
- });
- } catch (error) {
- console.error(`Error sending plan limits reached event to Posthog: ${error}`);
- }
- }
- }
-
- if (!environment?.websiteSetupCompleted) {
- await Promise.all([
- await prisma.environment.update({
- where: {
- id: environmentId,
- },
- data: { websiteSetupCompleted: true },
- }),
- capturePosthogEnvironmentEvent(environmentId, "website setup completed"),
- ]);
-
- revalidateEnvironment = true;
- }
-
- const [surveys, actionClasses] = await Promise.all([
- getSurveys(environmentId),
- getActionClasses(environmentId),
- ]);
-
- // Common filter condition for selecting surveys that are in progress, are of type 'website' and have no active segment filtering.
- const filteredSurveys = surveys.filter(
- (survey) => survey.status === "inProgress" && survey.type === "website"
- );
-
- const state: TJsEnvironmentState["data"] = {
- surveys: filteredSurveys,
- actionClasses,
- product,
- };
-
- return {
- state,
- revalidateEnvironment,
- };
- },
- [`environmentState-website-${environmentId}`],
- {
- ...(IS_FORMBRICKS_CLOUD && { revalidate: 24 * 60 * 60 }),
- tags: [
- environmentCache.tag.byId(environmentId),
- organizationCache.tag.byEnvironmentId(environmentId),
- productCache.tag.byEnvironmentId(environmentId),
- surveyCache.tag.byEnvironmentId(environmentId),
- actionClassCache.tag.byEnvironmentId(environmentId),
- ],
- }
- )();
diff --git a/apps/web/app/api/v1/client/[environmentId]/website/environment/route.ts b/apps/web/app/api/v1/client/[environmentId]/website/environment/route.ts
deleted file mode 100644
index e5b20a0bf4..0000000000
--- a/apps/web/app/api/v1/client/[environmentId]/website/environment/route.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { responses } from "@/app/lib/api/response";
-import { transformErrorToDetails } from "@/app/lib/api/validator";
-import { NextRequest } from "next/server";
-import { environmentCache } from "@formbricks/lib/environment/cache";
-import { ResourceNotFoundError } from "@formbricks/types/errors";
-import { ZJsSyncInput } from "@formbricks/types/js";
-import { getEnvironmentState } from "./lib/environmentState";
-
-export const OPTIONS = async (): Promise => {
- return responses.successResponse({}, true);
-};
-
-export const GET = async (
- _: NextRequest,
- { params }: { params: { environmentId: string } }
-): Promise => {
- try {
- const syncInputValidation = ZJsSyncInput.safeParse({
- environmentId: params.environmentId,
- });
-
- if (!syncInputValidation.success) {
- return responses.badRequestResponse(
- "Fields are missing or incorrectly formatted",
- transformErrorToDetails(syncInputValidation.error),
- true
- );
- }
-
- const { environmentId } = syncInputValidation.data;
-
- try {
- const environmentState = await getEnvironmentState(environmentId);
-
- if (environmentState.revalidateEnvironment) {
- environmentCache.revalidate({
- id: syncInputValidation.data.environmentId,
- productId: environmentState.state.product.id,
- });
- }
-
- return responses.successResponse(
- environmentState.state,
- true,
- "public, s-maxage=600, max-age=840, stale-while-revalidate=600, stale-if-error=600"
- );
- } catch (err) {
- if (err instanceof ResourceNotFoundError) {
- return responses.notFoundResponse(err.resourceType, err.resourceId);
- }
-
- console.error(err);
- return responses.internalServerErrorResponse(err.message ?? "Unable to complete response", true);
- }
- } catch (error) {
- console.error(error);
- return responses.internalServerErrorResponse(`Unable to complete response: ${error.message}`, true);
- }
-};
diff --git a/apps/web/app/api/v1/management/me/route.ts b/apps/web/app/api/v1/management/me/route.ts
index 378d452ec6..13ab9d9433 100644
--- a/apps/web/app/api/v1/management/me/route.ts
+++ b/apps/web/app/api/v1/management/me/route.ts
@@ -24,7 +24,6 @@ export const GET = async () => {
},
},
appSetupCompleted: true,
- websiteSetupCompleted: true,
},
},
},
diff --git a/apps/web/app/lib/formbricks.ts b/apps/web/app/lib/formbricks.ts
index 73927d2dee..c80b553cd9 100644
--- a/apps/web/app/lib/formbricks.ts
+++ b/apps/web/app/lib/formbricks.ts
@@ -1,4 +1,4 @@
-import formbricks from "@formbricks/js/app";
+import formbricks from "@formbricks/js";
import { env } from "@formbricks/lib/env";
export const formbricksEnabled =
diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs
index 5545d63325..d5a90c3550 100644
--- a/apps/web/next.config.mjs
+++ b/apps/web/next.config.mjs
@@ -165,6 +165,30 @@ const nextConfig = {
},
];
},
+ async rewrites() {
+ return [
+ {
+ source: "/api/packages/website",
+ destination: "/api/packages/js",
+ },
+ {
+ source: "/api/packages/app",
+ destination: "/api/packages/js",
+ },
+ {
+ source: "/api/v1/client/:environmentId/website/environment",
+ destination: "/api/v1/client/:environmentId/environment",
+ },
+ {
+ source: "/api/v1/client/:environmentId/app/environment",
+ destination: "/api/v1/client/:environmentId/environment",
+ },
+ {
+ source: "/api/v1/client/:environmentId/app/people/:userId",
+ destination: "/api/v1/client/:environmentId/identify/people/:userId",
+ },
+ ];
+ },
env: {
NEXTAUTH_URL: process.env.WEBAPP_URL,
},
diff --git a/apps/web/playwright/js.spec.ts b/apps/web/playwright/js.spec.ts
index a23a19135c..4f83234cdd 100644
--- a/apps/web/playwright/js.spec.ts
+++ b/apps/web/playwright/js.spec.ts
@@ -59,7 +59,7 @@ test.describe("JS Package Test", async () => {
// Formbricks In App Sync has happened
const syncApi = await page.waitForResponse(
(response) => {
- return response.url().includes("/app/environment");
+ return response.url().includes("/environment");
},
{
timeout: 120000,
diff --git a/packages/database/data-migrations/20241002123456_migrate_survey_types/data-migration.ts b/packages/database/data-migrations/20241002123456_migrate_survey_types/data-migration.ts
new file mode 100644
index 0000000000..8d6800e790
--- /dev/null
+++ b/packages/database/data-migrations/20241002123456_migrate_survey_types/data-migration.ts
@@ -0,0 +1,75 @@
+/* eslint-disable no-console -- logging is allowed in migration scripts */
+import { PrismaClient } from "@prisma/client";
+
+const prisma = new PrismaClient();
+const TRANSACTION_TIMEOUT = 30 * 60 * 1000; // 30 minutes in milliseconds
+
+async function runMigration(): Promise {
+ const startTime = Date.now();
+ console.log("Starting data migration...");
+
+ await prisma.$transaction(
+ async (transactionPrisma) => {
+ const websiteSurveys = await transactionPrisma.survey.findMany({
+ where: { type: "website" },
+ });
+
+ const updationPromises = [];
+
+ for (const websiteSurvey of websiteSurveys) {
+ updationPromises.push(
+ transactionPrisma.survey.update({
+ where: { id: websiteSurvey.id },
+ data: {
+ type: "app",
+ segment: {
+ connectOrCreate: {
+ where: {
+ environmentId_title: {
+ environmentId: websiteSurvey.environmentId,
+ title: websiteSurvey.id,
+ },
+ },
+ create: {
+ title: websiteSurvey.id,
+ isPrivate: true,
+ environmentId: websiteSurvey.environmentId,
+ },
+ },
+ },
+ },
+ })
+ );
+ }
+
+ await Promise.all(updationPromises);
+ console.log(`Updated ${websiteSurveys.length.toString()} website surveys to app surveys`);
+ },
+ {
+ timeout: TRANSACTION_TIMEOUT,
+ }
+ );
+
+ const endTime = Date.now();
+ console.log(`Data migration completed. Total time: ${((endTime - startTime) / 1000).toFixed(2)}s`);
+}
+
+function handleError(error: unknown): void {
+ console.error("An error occurred during migration:", error);
+ process.exit(1);
+}
+
+function handleDisconnectError(): void {
+ console.error("Failed to disconnect Prisma client");
+ process.exit(1);
+}
+
+function main(): void {
+ runMigration()
+ .catch(handleError)
+ .finally(() => {
+ prisma.$disconnect().catch(handleDisconnectError);
+ });
+}
+
+main();
diff --git a/packages/database/migrations/20241004070040_removed_website_setup_completed/migration.sql b/packages/database/migrations/20241004070040_removed_website_setup_completed/migration.sql
new file mode 100644
index 0000000000..45812adf63
--- /dev/null
+++ b/packages/database/migrations/20241004070040_removed_website_setup_completed/migration.sql
@@ -0,0 +1,8 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `websiteSetupCompleted` on the `Environment` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "Environment" DROP COLUMN "websiteSetupCompleted";
diff --git a/packages/database/package.json b/packages/database/package.json
index 75e628477c..59a0105688 100644
--- a/packages/database/package.json
+++ b/packages/database/package.json
@@ -51,7 +51,8 @@
"data-migration:add-display-id-to-response": "ts-node ./data-migrations/20240905120500_refactor_display_response_relationship/data-migration.ts",
"data-migration:address-question": "ts-node ./data-migrations/20240924123456_migrate_address_question/data-migration.ts",
"data-migration:advanced-logic": "ts-node ./data-migrations/20240828122408_advanced_logic_editor/data-migration.ts",
- "data-migration:segments-actions-cleanup": "ts-node ./data-migrations/20240904091113_removed_actions_table/data-migration.ts"
+ "data-migration:segments-actions-cleanup": "ts-node ./data-migrations/20240904091113_removed_actions_table/data-migration.ts",
+ "data-migration:migrate-survey-types": "ts-node ./data-migrations/20241002123456_migrate_survey_types/data-migration.ts"
},
"dependencies": {
"@prisma/client": "^5.18.0",
diff --git a/packages/database/schema.prisma b/packages/database/schema.prisma
index d7ed320930..3342dca9ed 100644
--- a/packages/database/schema.prisma
+++ b/packages/database/schema.prisma
@@ -386,24 +386,23 @@ model Integration {
}
model Environment {
- id String @id @default(cuid())
- createdAt DateTime @default(now()) @map(name: "created_at")
- updatedAt DateTime @updatedAt @map(name: "updated_at")
- type EnvironmentType
- product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
- productId String
- widgetSetupCompleted Boolean @default(false)
- appSetupCompleted Boolean @default(false)
- websiteSetupCompleted Boolean @default(false)
- surveys Survey[]
- people Person[]
- actionClasses ActionClass[]
- attributeClasses AttributeClass[]
- apiKeys ApiKey[]
- webhooks Webhook[]
- tags Tag[]
- segments Segment[]
- integration Integration[]
+ id String @id @default(cuid())
+ createdAt DateTime @default(now()) @map(name: "created_at")
+ updatedAt DateTime @updatedAt @map(name: "updated_at")
+ type EnvironmentType
+ product Product @relation(fields: [productId], references: [id], onDelete: Cascade)
+ productId String
+ widgetSetupCompleted Boolean @default(false)
+ appSetupCompleted Boolean @default(false)
+ surveys Survey[]
+ people Person[]
+ actionClasses ActionClass[]
+ attributeClasses AttributeClass[]
+ apiKeys ApiKey[]
+ webhooks Webhook[]
+ tags Tag[]
+ segments Segment[]
+ integration Integration[]
@@index([productId])
}
diff --git a/packages/ee/advanced-targeting/components/advanced-targeting-card.tsx b/packages/ee/advanced-targeting/components/advanced-targeting-card.tsx
index 839b8d6fff..c09cf5a1d1 100644
--- a/packages/ee/advanced-targeting/components/advanced-targeting-card.tsx
+++ b/packages/ee/advanced-targeting/components/advanced-targeting-card.tsx
@@ -16,6 +16,7 @@ import type {
TSegmentUpdateInput,
} from "@formbricks/types/segment";
import type { TSurvey } from "@formbricks/types/surveys/types";
+import { Alert, AlertDescription } from "@formbricks/ui/components/Alert";
import { AlertDialog } from "@formbricks/ui/components/AlertDialog";
import { Button } from "@formbricks/ui/components/Button";
import { LoadSegmentModal } from "@formbricks/ui/components/LoadSegmentModal";
@@ -161,7 +162,7 @@ export function AdvancedTargetingCard({
return (