mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-22 06:00:51 -06:00
Compare commits
28 Commits
fix-ios-is
...
action-tes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2254c24cad | ||
|
|
0f28cea7c8 | ||
|
|
868ee71e34 | ||
|
|
1dbbaca8fd | ||
|
|
8b895b4b43 | ||
|
|
f81eae3691 | ||
|
|
317013eee7 | ||
|
|
d19470f71a | ||
|
|
3bf5693af1 | ||
|
|
70cf58b55c | ||
|
|
68fc25587c | ||
|
|
4c54c0d934 | ||
|
|
93b49969f9 | ||
|
|
2839e49ccb | ||
|
|
e1dae1bd98 | ||
|
|
606305e54b | ||
|
|
0d1ded1139 | ||
|
|
6b1b2895f8 | ||
|
|
a3cb37b128 | ||
|
|
b27314cec6 | ||
|
|
1891f286e7 | ||
|
|
48409ced60 | ||
|
|
9a7e5bfa8d | ||
|
|
adcba0139a | ||
|
|
0d3f7103af | ||
|
|
a51d68d23f | ||
|
|
0d33c27295 | ||
|
|
4fa4528771 |
16
.github/actions/cache-build-web/action.yml
vendored
16
.github/actions/cache-build-web/action.yml
vendored
@@ -1,5 +1,13 @@
|
|||||||
name: Build & Cache Web App
|
name: Build & Cache Web App
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
e2e_testing_mode:
|
||||||
|
description: "Set E2E Testing Mode"
|
||||||
|
required: false
|
||||||
|
default: "0"
|
||||||
|
|
||||||
runs:
|
runs:
|
||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
@@ -8,6 +16,9 @@ runs:
|
|||||||
|
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
|
|
||||||
|
- run: echo "E2E Testing Mode is ${{ inputs.e2e_testing_mode }}"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Cache Build
|
- name: Cache Build
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
id: cache-build
|
id: cache-build
|
||||||
@@ -41,6 +52,11 @@ runs:
|
|||||||
run: cp .env.example .env
|
run: cp .env.example .env
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
- name: Add E2E Testing Mode
|
||||||
|
run: |
|
||||||
|
echo "E2E_TESTING=${{ inputs.e2e_testing_mode }}" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
|
||||||
- name: Generate Random ENCRYPTION_KEY
|
- name: Generate Random ENCRYPTION_KEY
|
||||||
run: |
|
run: |
|
||||||
SECRET=$(openssl rand -hex 32)
|
SECRET=$(openssl rand -hex 32)
|
||||||
|
|||||||
5
.github/workflows/e2e.yml
vendored
5
.github/workflows/e2e.yml
vendored
@@ -2,6 +2,9 @@ name: E2E Tests
|
|||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- action-test
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Run E2E Tests
|
name: Run E2E Tests
|
||||||
@@ -13,6 +16,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Build & Cache Web Binaries
|
- name: Build & Cache Web Binaries
|
||||||
uses: ./.github/actions/cache-build-web
|
uses: ./.github/actions/cache-build-web
|
||||||
|
with:
|
||||||
|
e2e_testing_mode: "1"
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v2
|
uses: pnpm/action-setup@v2
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default async function PeopleLayout({ params, children }) {
|
|||||||
throw new Error("Team not found");
|
throw new Error("Team not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isUserTargetingAllowed = getAdvancedTargetingPermission(team);
|
const isUserTargetingAllowed = await getAdvancedTargetingPermission(team);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default async function SegmentsPage({ params }) {
|
|||||||
throw new Error("Team not found");
|
throw new Error("Team not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAdvancedTargetingAllowed = getAdvancedTargetingPermission(team);
|
const isAdvancedTargetingAllowed = await getAdvancedTargetingPermission(team);
|
||||||
|
|
||||||
if (!segments) {
|
if (!segments) {
|
||||||
throw new Error("Failed to fetch segments");
|
throw new Error("Failed to fetch segments");
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default async function EnvironmentsNavbar({ environmentId, session }: Env
|
|||||||
return <ErrorComponent />;
|
return <ErrorComponent />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMultiLanguageAllowed = getMultiLanguagePermission(team);
|
const isMultiLanguageAllowed = await getMultiLanguagePermission(team);
|
||||||
|
|
||||||
const [products, environments] = await Promise.all([
|
const [products, environments] = await Promise.all([
|
||||||
getProducts(team.id),
|
getProducts(team.id),
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export default async function EnterpriseLicensePage({ params }) {
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEnterpriseEdition = getIsEnterpriseEdition();
|
const isEnterpriseEdition = await getIsEnterpriseEdition();
|
||||||
|
|
||||||
const paidFeatures = [
|
const paidFeatures = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export default async function LanguageSettingsPage({ params }: { params: { envir
|
|||||||
throw new Error("Team not found");
|
throw new Error("Team not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMultiLanguageAllowed = getMultiLanguagePermission(team);
|
const isMultiLanguageAllowed = await getMultiLanguagePermission(team);
|
||||||
|
|
||||||
if (!isMultiLanguageAllowed) {
|
if (!isMultiLanguageAllowed) {
|
||||||
notFound();
|
notFound();
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default async function SettingsLayout({ children, params }) {
|
|||||||
throw new Error("Unauthenticated");
|
throw new Error("Unauthenticated");
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMultiLanguageAllowed = getMultiLanguagePermission(team);
|
const isMultiLanguageAllowed = await getMultiLanguagePermission(team);
|
||||||
|
|
||||||
const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id);
|
const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id);
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export async function EditMemberships({
|
|||||||
|
|
||||||
const currentUserRole = membership?.role;
|
const currentUserRole = membership?.role;
|
||||||
const isUserAdminOrOwner = membership?.role === "admin" || membership?.role === "owner";
|
const isUserAdminOrOwner = membership?.role === "admin" || membership?.role === "owner";
|
||||||
const canDoRoleManagement = getRoleManagementPermission(team);
|
const canDoRoleManagement = await getRoleManagementPermission(team);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export default async function MembersSettingsPage({ params }: { params: { enviro
|
|||||||
if (!team) {
|
if (!team) {
|
||||||
throw new Error("Team not found");
|
throw new Error("Team not found");
|
||||||
}
|
}
|
||||||
const canDoRoleManagement = getRoleManagementPermission(team);
|
const canDoRoleManagement = await getRoleManagementPermission(team);
|
||||||
|
|
||||||
const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id);
|
const currentUserMembership = await getMembershipByUserIdTeamId(session?.user.id, team.id);
|
||||||
const { isOwner, isAdmin } = getAccessFlags(currentUserMembership?.role);
|
const { isOwner, isAdmin } = getAccessFlags(currentUserMembership?.role);
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ export default async function SurveysEditPage({ params }) {
|
|||||||
const { isViewer } = getAccessFlags(currentUserMembership?.role);
|
const { isViewer } = getAccessFlags(currentUserMembership?.role);
|
||||||
const isSurveyCreationDeletionDisabled = isViewer;
|
const isSurveyCreationDeletionDisabled = isViewer;
|
||||||
|
|
||||||
const isUserTargetingAllowed = getAdvancedTargetingPermission(team);
|
const isUserTargetingAllowed = await getAdvancedTargetingPermission(team);
|
||||||
const isMultiLanguageAllowed = getMultiLanguagePermission(team);
|
const isMultiLanguageAllowed = await getMultiLanguagePermission(team);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!survey ||
|
!survey ||
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export default async function LinkSurveyPage({ params, searchParams }: LinkSurve
|
|||||||
if (!team) {
|
if (!team) {
|
||||||
throw new Error("Team not found");
|
throw new Error("Team not found");
|
||||||
}
|
}
|
||||||
const isMultiLanguageAllowed = getMultiLanguagePermission(team);
|
const isMultiLanguageAllowed = await getMultiLanguagePermission(team);
|
||||||
|
|
||||||
if (survey && survey.status !== "inProgress") {
|
if (survey && survey.status !== "inProgress") {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,13 +1,134 @@
|
|||||||
import "server-only";
|
import "server-only";
|
||||||
|
|
||||||
import { ENTERPRISE_LICENSE_KEY, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
import { cache, revalidateTag } from "@formbricks/lib/cache";
|
||||||
|
import { E2E_TESTING, ENTERPRISE_LICENSE_KEY, IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||||
|
import { hashString } from "@formbricks/lib/hashString";
|
||||||
import { TTeam } from "@formbricks/types/teams";
|
import { TTeam } from "@formbricks/types/teams";
|
||||||
|
|
||||||
export const getIsEnterpriseEdition = (): boolean => {
|
import { prisma } from "../../database/src";
|
||||||
if (ENTERPRISE_LICENSE_KEY) {
|
|
||||||
return ENTERPRISE_LICENSE_KEY.length > 0;
|
const PREVIOUS_RESULTS_CACHE_TAG = "getPreviousResult";
|
||||||
|
|
||||||
|
// This function is used to get the previous result of the license check from the cache
|
||||||
|
// This might seem confusing at first since we only return the default value from this function,
|
||||||
|
// but since we are using a cache and the cache key is the same, the cache will return the previous result - so this functions as a cache getter
|
||||||
|
const getPreviousResult = (): Promise<{ active: boolean | null; lastChecked: Date }> =>
|
||||||
|
cache(
|
||||||
|
async () => ({
|
||||||
|
active: null,
|
||||||
|
lastChecked: new Date(0),
|
||||||
|
}),
|
||||||
|
[PREVIOUS_RESULTS_CACHE_TAG],
|
||||||
|
{
|
||||||
|
tags: [PREVIOUS_RESULTS_CACHE_TAG],
|
||||||
}
|
}
|
||||||
|
)();
|
||||||
|
|
||||||
|
// This function is used to set the previous result of the license check to the cache so that we can use it in the next call
|
||||||
|
// Uses the same cache key as the getPreviousResult function
|
||||||
|
const setPreviousResult = async (previousResult: { active: boolean | null; lastChecked: Date }) => {
|
||||||
|
revalidateTag(PREVIOUS_RESULTS_CACHE_TAG);
|
||||||
|
const { lastChecked, active } = previousResult;
|
||||||
|
|
||||||
|
await cache(
|
||||||
|
async () => ({
|
||||||
|
active,
|
||||||
|
lastChecked,
|
||||||
|
}),
|
||||||
|
[PREVIOUS_RESULTS_CACHE_TAG],
|
||||||
|
{
|
||||||
|
tags: [PREVIOUS_RESULTS_CACHE_TAG],
|
||||||
|
}
|
||||||
|
)();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getIsEnterpriseEdition = async (): Promise<boolean> => {
|
||||||
|
if (!ENTERPRISE_LICENSE_KEY || ENTERPRISE_LICENSE_KEY.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedKey = hashString(ENTERPRISE_LICENSE_KEY);
|
||||||
|
|
||||||
|
if (E2E_TESTING) {
|
||||||
|
const previousResult = await getPreviousResult();
|
||||||
|
if (previousResult.lastChecked.getTime() === new Date(0).getTime()) {
|
||||||
|
// first call
|
||||||
|
await setPreviousResult({ active: true, lastChecked: new Date() });
|
||||||
|
return true;
|
||||||
|
} else if (new Date().getTime() - previousResult.lastChecked.getTime() > 24 * 60 * 60 * 1000) {
|
||||||
|
// Fail after 24 hours
|
||||||
|
console.log("E2E_TESTING is enabled. Enterprise license was revoked after 24 hours.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return previousResult.active !== null ? previousResult.active : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the server responds with a boolean, we return it
|
||||||
|
// if the server errors, we return null
|
||||||
|
// null signifies an error
|
||||||
|
const isValid: boolean | null = await cache(
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
const now = new Date();
|
||||||
|
const startOfYear = new Date(now.getFullYear(), 0, 1); // January 1st of the current year
|
||||||
|
const endOfYear = new Date(now.getFullYear() + 1, 0, 0); // December 31st of the current year
|
||||||
|
|
||||||
|
const responseCount = await prisma.response.count({
|
||||||
|
where: {
|
||||||
|
createdAt: {
|
||||||
|
gte: startOfYear,
|
||||||
|
lt: endOfYear,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await fetch("https://ee.formbricks.com/api/licenses/check", {
|
||||||
|
body: JSON.stringify({
|
||||||
|
licenseKey: ENTERPRISE_LICENSE_KEY,
|
||||||
|
usage: { responseCount: responseCount },
|
||||||
|
}),
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const responseJson = await res.json();
|
||||||
|
return responseJson.data.status === "active";
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error while checking license: ", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[`getIsEnterpriseEdition-${hashedKey}`],
|
||||||
|
{ revalidate: 60 * 60 * 24 }
|
||||||
|
)();
|
||||||
|
|
||||||
|
const previousResult = await getPreviousResult();
|
||||||
|
|
||||||
|
if (previousResult.active === null) {
|
||||||
|
if (isValid === null) {
|
||||||
|
await setPreviousResult({ active: false, lastChecked: new Date() });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValid !== null) {
|
||||||
|
await setPreviousResult({ active: isValid, lastChecked: new Date() });
|
||||||
|
return isValid;
|
||||||
|
} else {
|
||||||
|
// if result is undefined -> error
|
||||||
|
// if the last check was less than 24 hours, return the previous value:
|
||||||
|
if (new Date().getTime() - previousResult.lastChecked.getTime() <= 24 * 60 * 60 * 1000) {
|
||||||
|
return previousResult.active !== null ? previousResult.active : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the last check was more than 24 hours, throw an error
|
||||||
|
throw new Error("Error while checking license");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRemoveInAppBrandingPermission = (team: TTeam): boolean => {
|
export const getRemoveInAppBrandingPermission = (team: TTeam): boolean => {
|
||||||
@@ -22,20 +143,20 @@ export const getRemoveLinkBrandingPermission = (team: TTeam): boolean => {
|
|||||||
else return false;
|
else return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRoleManagementPermission = (team: TTeam): boolean => {
|
export const getRoleManagementPermission = async (team: TTeam): Promise<boolean> => {
|
||||||
if (IS_FORMBRICKS_CLOUD) return team.billing.features.inAppSurvey.status !== "inactive";
|
if (IS_FORMBRICKS_CLOUD) return team.billing.features.inAppSurvey.status !== "inactive";
|
||||||
else if (!IS_FORMBRICKS_CLOUD) return getIsEnterpriseEdition();
|
else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition();
|
||||||
else return false;
|
else return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAdvancedTargetingPermission = (team: TTeam): boolean => {
|
export const getAdvancedTargetingPermission = async (team: TTeam): Promise<boolean> => {
|
||||||
if (IS_FORMBRICKS_CLOUD) return team.billing.features.userTargeting.status !== "inactive";
|
if (IS_FORMBRICKS_CLOUD) return team.billing.features.userTargeting.status !== "inactive";
|
||||||
else if (!IS_FORMBRICKS_CLOUD) return getIsEnterpriseEdition();
|
else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition();
|
||||||
else return false;
|
else return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMultiLanguagePermission = (team: TTeam): boolean => {
|
export const getMultiLanguagePermission = async (team: TTeam): Promise<boolean> => {
|
||||||
if (IS_FORMBRICKS_CLOUD) return team.billing.features.inAppSurvey.status !== "inactive";
|
if (IS_FORMBRICKS_CLOUD) return team.billing.features.inAppSurvey.status !== "inactive";
|
||||||
else if (!IS_FORMBRICKS_CLOUD) return getIsEnterpriseEdition();
|
else if (!IS_FORMBRICKS_CLOUD) return await getIsEnterpriseEdition();
|
||||||
else return false;
|
else return false;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
import { unstable_cache } from "next/cache";
|
import { unstable_cache } from "next/cache";
|
||||||
import { parse, stringify } from "superjson";
|
import { parse, stringify } from "superjson";
|
||||||
|
|
||||||
|
export { revalidateTag } from "next/cache";
|
||||||
|
|
||||||
export const cache = <T, P extends unknown[]>(
|
export const cache = <T, P extends unknown[]>(
|
||||||
fn: (...params: P) => Promise<T>,
|
fn: (...params: P) => Promise<T>,
|
||||||
keys: Parameters<typeof unstable_cache>[1],
|
keys: Parameters<typeof unstable_cache>[1],
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ export const SYNC_USER_IDENTIFICATION_RATE_LIMIT = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DEBUG = env.DEBUG === "1";
|
export const DEBUG = env.DEBUG === "1";
|
||||||
|
export const E2E_TESTING = env.E2E_TESTING === "1";
|
||||||
|
|
||||||
// Enterprise License constant
|
// Enterprise License constant
|
||||||
export const ENTERPRISE_LICENSE_KEY = env.ENTERPRISE_LICENSE_KEY;
|
export const ENTERPRISE_LICENSE_KEY = env.ENTERPRISE_LICENSE_KEY;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export const env = createEnv({
|
|||||||
DEBUG: z.enum(["1", "0"]).optional(),
|
DEBUG: z.enum(["1", "0"]).optional(),
|
||||||
DEFAULT_TEAM_ID: z.string().optional(),
|
DEFAULT_TEAM_ID: z.string().optional(),
|
||||||
DEFAULT_TEAM_ROLE: z.enum(["owner", "admin", "editor", "developer", "viewer"]).optional(),
|
DEFAULT_TEAM_ROLE: z.enum(["owner", "admin", "editor", "developer", "viewer"]).optional(),
|
||||||
|
E2E_TESTING: z.enum(["1", "0"]).optional(),
|
||||||
EMAIL_AUTH_DISABLED: z.enum(["1", "0"]).optional(),
|
EMAIL_AUTH_DISABLED: z.enum(["1", "0"]).optional(),
|
||||||
EMAIL_VERIFICATION_DISABLED: z.enum(["1", "0"]).optional(),
|
EMAIL_VERIFICATION_DISABLED: z.enum(["1", "0"]).optional(),
|
||||||
ENCRYPTION_KEY: z.string().length(64).or(z.string().length(32)),
|
ENCRYPTION_KEY: z.string().length(64).or(z.string().length(32)),
|
||||||
@@ -121,6 +122,7 @@ export const env = createEnv({
|
|||||||
DEBUG: process.env.DEBUG,
|
DEBUG: process.env.DEBUG,
|
||||||
DEFAULT_TEAM_ID: process.env.DEFAULT_TEAM_ID,
|
DEFAULT_TEAM_ID: process.env.DEFAULT_TEAM_ID,
|
||||||
DEFAULT_TEAM_ROLE: process.env.DEFAULT_TEAM_ROLE,
|
DEFAULT_TEAM_ROLE: process.env.DEFAULT_TEAM_ROLE,
|
||||||
|
E2E_TESTING: process.env.E2E_TESTING,
|
||||||
EMAIL_AUTH_DISABLED: process.env.EMAIL_AUTH_DISABLED,
|
EMAIL_AUTH_DISABLED: process.env.EMAIL_AUTH_DISABLED,
|
||||||
EMAIL_VERIFICATION_DISABLED: process.env.EMAIL_VERIFICATION_DISABLED,
|
EMAIL_VERIFICATION_DISABLED: process.env.EMAIL_VERIFICATION_DISABLED,
|
||||||
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
|
ENCRYPTION_KEY: process.env.ENCRYPTION_KEY,
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import { ENTERPRISE_LICENSE_KEY, IS_FORMBRICKS_CLOUD } from "../constants";
|
|
||||||
import { getTeam } from "../team/service";
|
|
||||||
|
|
||||||
export const getIsEnterpriseEdition = (): boolean => {
|
|
||||||
if (ENTERPRISE_LICENSE_KEY) {
|
|
||||||
return ENTERPRISE_LICENSE_KEY.length > 0;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMultiLanguagePermission = async (teamId: string): Promise<boolean> => {
|
|
||||||
const team = await getTeam(teamId);
|
|
||||||
if (!team) return false;
|
|
||||||
if (IS_FORMBRICKS_CLOUD) return team.billing.features.inAppSurvey.status !== "inactive";
|
|
||||||
else if (!IS_FORMBRICKS_CLOUD) return getIsEnterpriseEdition();
|
|
||||||
else return false;
|
|
||||||
};
|
|
||||||
@@ -67,6 +67,7 @@
|
|||||||
"CUSTOMER_IO_API_KEY",
|
"CUSTOMER_IO_API_KEY",
|
||||||
"CUSTOMER_IO_SITE_ID",
|
"CUSTOMER_IO_SITE_ID",
|
||||||
"DEBUG",
|
"DEBUG",
|
||||||
|
"E2E_TESTING",
|
||||||
"EMAIL_AUTH_DISABLED",
|
"EMAIL_AUTH_DISABLED",
|
||||||
"EMAIL_VERIFICATION_DISABLED",
|
"EMAIL_VERIFICATION_DISABLED",
|
||||||
"ENCRYPTION_KEY",
|
"ENCRYPTION_KEY",
|
||||||
|
|||||||
Reference in New Issue
Block a user