mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-28 12:42:44 -05:00
feat: whitelabel-1(moving branding to EE) (#4393)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
This commit is contained in:
@@ -81,7 +81,15 @@ const fetchLicenseForE2ETesting = async (): Promise<{
|
||||
// first call
|
||||
const newResult = {
|
||||
active: true,
|
||||
features: { isMultiOrgEnabled: true, twoFactorAuth: true, sso: true, contacts: true, projects: 3 },
|
||||
features: {
|
||||
isMultiOrgEnabled: true,
|
||||
twoFactorAuth: true,
|
||||
sso: true,
|
||||
contacts: true,
|
||||
projects: 3,
|
||||
whitelabel: true,
|
||||
removeBranding: true,
|
||||
},
|
||||
lastChecked: currentTime,
|
||||
};
|
||||
await setPreviousResult(newResult);
|
||||
@@ -140,10 +148,12 @@ export const getEnterpriseLicense = async (): Promise<{
|
||||
active: false,
|
||||
features: {
|
||||
isMultiOrgEnabled: false,
|
||||
projects: 3,
|
||||
twoFactorAuth: false,
|
||||
sso: false,
|
||||
whitelabel: false,
|
||||
removeBranding: false,
|
||||
contacts: false,
|
||||
projects: 3,
|
||||
},
|
||||
lastChecked: new Date(),
|
||||
};
|
||||
@@ -249,24 +259,28 @@ export const fetchLicense = reactCache(
|
||||
)()
|
||||
);
|
||||
|
||||
export const getRemoveInAppBrandingPermission = (organization: TOrganization): boolean => {
|
||||
if (IS_FORMBRICKS_CLOUD) return organization.billing.plan !== PROJECT_FEATURE_KEYS.FREE;
|
||||
else if (!IS_FORMBRICKS_CLOUD) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getRemoveLinkBrandingPermission = (organization: TOrganization): boolean => {
|
||||
if (IS_FORMBRICKS_CLOUD) return organization.billing.plan !== PROJECT_FEATURE_KEYS.FREE;
|
||||
else if (!IS_FORMBRICKS_CLOUD) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getSurveyFollowUpsPermission = async (organization: TOrganization): Promise<boolean> => {
|
||||
if (IS_FORMBRICKS_CLOUD) return organization.billing.plan !== PROJECT_FEATURE_KEYS.FREE;
|
||||
else if (!IS_FORMBRICKS_CLOUD) return (await getEnterpriseLicense()).active;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const getRemoveBrandingPermission = async (organization: TOrganization): Promise<boolean> => {
|
||||
if (E2E_TESTING) {
|
||||
const previousResult = await fetchLicenseForE2ETesting();
|
||||
return previousResult?.features?.removeBranding ?? false;
|
||||
}
|
||||
|
||||
if (IS_FORMBRICKS_CLOUD && (await getEnterpriseLicense()).active) {
|
||||
return organization.billing.plan !== PROJECT_FEATURE_KEYS.FREE;
|
||||
} else {
|
||||
const licenseFeatures = await getLicenseFeatures();
|
||||
if (!licenseFeatures) return false;
|
||||
|
||||
return licenseFeatures.removeBranding;
|
||||
}
|
||||
};
|
||||
|
||||
export const getRoleManagementPermission = async (organization: TOrganization): Promise<boolean> => {
|
||||
if (E2E_TESTING) {
|
||||
const previousResult = await fetchLicenseForE2ETesting();
|
||||
|
||||
@@ -8,6 +8,8 @@ const ZEnterpriseLicenseFeatures = z.object({
|
||||
isMultiOrgEnabled: z.boolean(),
|
||||
contacts: z.boolean(),
|
||||
projects: z.number().nullable(),
|
||||
whitelabel: z.boolean(),
|
||||
removeBranding: z.boolean(),
|
||||
twoFactorAuth: z.boolean(),
|
||||
sso: z.boolean(),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
"use server";
|
||||
|
||||
import { authenticatedActionClient } from "@/lib/utils/action-client";
|
||||
import { checkAuthorizationUpdated } from "@/lib/utils/action-client-middleware";
|
||||
import { getOrganizationIdFromProjectId } from "@/lib/utils/helper";
|
||||
import { getRemoveBrandingPermission } from "@/modules/ee/license-check/lib/utils";
|
||||
import { updateProjectBranding } from "@/modules/ee/whitelabel/remove-branding/lib/project";
|
||||
import { ZProjectUpdateBrandingInput } from "@/modules/ee/whitelabel/remove-branding/types/project";
|
||||
import { z } from "zod";
|
||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { OperationNotAllowedError } from "@formbricks/types/errors";
|
||||
|
||||
const ZUpdateProjectAction = z.object({
|
||||
projectId: ZId,
|
||||
data: ZProjectUpdateBrandingInput,
|
||||
});
|
||||
|
||||
export const updateProjectBrandingAction = authenticatedActionClient
|
||||
.schema(ZUpdateProjectAction)
|
||||
.action(async ({ ctx, parsedInput }) => {
|
||||
const organizationId = await getOrganizationIdFromProjectId(parsedInput.projectId);
|
||||
|
||||
await checkAuthorizationUpdated({
|
||||
userId: ctx.user.id,
|
||||
organizationId,
|
||||
access: [
|
||||
{
|
||||
type: "organization",
|
||||
roles: ["owner", "manager"],
|
||||
},
|
||||
{
|
||||
type: "projectTeam",
|
||||
projectId: parsedInput.projectId,
|
||||
minPermission: "manage",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (
|
||||
parsedInput.data.inAppSurveyBranding !== undefined ||
|
||||
parsedInput.data.linkSurveyBranding !== undefined
|
||||
) {
|
||||
const organization = await getOrganization(organizationId);
|
||||
|
||||
if (!organization) {
|
||||
throw new Error("Organization not found");
|
||||
}
|
||||
const canRemoveBranding = await getRemoveBrandingPermission(organization);
|
||||
|
||||
if (parsedInput.data.inAppSurveyBranding !== undefined) {
|
||||
if (!canRemoveBranding) {
|
||||
throw new OperationNotAllowedError("You are not allowed to remove in-app branding");
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedInput.data.linkSurveyBranding !== undefined) {
|
||||
if (!canRemoveBranding) {
|
||||
throw new OperationNotAllowedError("You are not allowed to remove link survey branding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await updateProjectBranding(parsedInput.projectId, parsedInput.data);
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
|
||||
import { EditBranding } from "@/modules/ee/whitelabel/remove-branding/components/edit-branding";
|
||||
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
|
||||
import { EmptyContent, ModalButton } from "@/modules/ui/components/empty-content";
|
||||
import { KeyIcon } from "lucide-react";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
|
||||
import { TProject } from "@formbricks/types/project";
|
||||
|
||||
interface BrandingSettingsCardProps {
|
||||
canRemoveBranding: boolean;
|
||||
project: TProject;
|
||||
environmentId: string;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export const BrandingSettingsCard = async ({
|
||||
canRemoveBranding,
|
||||
project,
|
||||
environmentId,
|
||||
isReadOnly,
|
||||
}: BrandingSettingsCardProps) => {
|
||||
const t = await getTranslations();
|
||||
|
||||
const buttons: [ModalButton, ModalButton] = [
|
||||
{
|
||||
text: t("common.start_free_trial"),
|
||||
href: IS_FORMBRICKS_CLOUD
|
||||
? `/environments/${environmentId}/settings/billing`
|
||||
: "https://formbricks.com/docs/self-hosting/license#30-day-trial-license-request",
|
||||
},
|
||||
{
|
||||
text: t("common.learn_more"),
|
||||
href: "https://formbricks.com/docs/self-hosting/license",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<SettingsCard
|
||||
title={t("environments.project.look.formbricks_branding")}
|
||||
description={t("environments.project.look.formbricks_branding_settings_description")}>
|
||||
{canRemoveBranding ? (
|
||||
<div className="space-y-4">
|
||||
<EditBranding
|
||||
type="linkSurvey"
|
||||
isEnabled={project.linkSurveyBranding}
|
||||
projectId={project.id}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
<EditBranding
|
||||
type="appSurvey"
|
||||
isEnabled={project.inAppSurveyBranding}
|
||||
projectId={project.id}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<EmptyContent
|
||||
icon={<KeyIcon className="h-6 w-6 text-slate-900" />}
|
||||
title={t("environments.project.look.remove_branding_with_a_higher_plan")}
|
||||
description={t("environments.project.look.eliminate_branding_with_whitelabel")}
|
||||
buttons={buttons}
|
||||
/>
|
||||
)}
|
||||
{isReadOnly && (
|
||||
<Alert variant="warning" className="mt-4">
|
||||
<AlertDescription>
|
||||
{t("common.only_owners_managers_and_manage_access_members_can_perform_this_action")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</SettingsCard>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
"use client";
|
||||
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { updateProjectBrandingAction } from "@/modules/ee/whitelabel/remove-branding/actions";
|
||||
import { TProjectUpdateBrandingInput } from "@/modules/ee/whitelabel/remove-branding/types/project";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface EditBrandingProps {
|
||||
type: "linkSurvey" | "appSurvey";
|
||||
isEnabled: boolean;
|
||||
projectId: string;
|
||||
isReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export const EditBranding = ({ type, isEnabled, projectId, isReadOnly }: EditBrandingProps) => {
|
||||
const t = useTranslations();
|
||||
const [isBrandingEnabled, setIsBrandingEnabled] = useState(isEnabled);
|
||||
const [updatingBranding, setUpdatingBranding] = useState(false);
|
||||
|
||||
const toggleBranding = async () => {
|
||||
setUpdatingBranding(true);
|
||||
const newBrandingState = !isBrandingEnabled;
|
||||
setIsBrandingEnabled(newBrandingState);
|
||||
|
||||
let inputProject: TProjectUpdateBrandingInput = {
|
||||
[type === "linkSurvey" ? "linkSurveyBranding" : "inAppSurveyBranding"]: newBrandingState,
|
||||
};
|
||||
const updateBrandingResponse = await updateProjectBrandingAction({ projectId, data: inputProject });
|
||||
|
||||
if (updateBrandingResponse?.data) {
|
||||
toast.success(
|
||||
newBrandingState
|
||||
? t("environments.project.look.formbricks_branding_shown")
|
||||
: t("environments.project.look.formbricks_branding_hidden")
|
||||
);
|
||||
} else {
|
||||
const errorMessage = getFormattedErrorMessage(updateBrandingResponse);
|
||||
toast.error(errorMessage);
|
||||
}
|
||||
setUpdatingBranding(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id={`branding-${type}`}
|
||||
checked={isBrandingEnabled}
|
||||
onCheckedChange={toggleBranding}
|
||||
disabled={updatingBranding || isReadOnly}
|
||||
/>
|
||||
<Label htmlFor={`branding-${type}`}>
|
||||
{t("environments.project.look.show_formbricks_branding_in", {
|
||||
type: type === "linkSurvey" ? t("common.link") : t("common.app"),
|
||||
})}
|
||||
</Label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import "server-only";
|
||||
import {
|
||||
TProjectUpdateBrandingInput,
|
||||
ZProjectUpdateBrandingInput,
|
||||
} from "@/modules/ee/whitelabel/remove-branding/types/project";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { projectCache } from "@formbricks/lib/project/cache";
|
||||
import { validateInputs } from "@formbricks/lib/utils/validate";
|
||||
import { ZId } from "@formbricks/types/common";
|
||||
import { ValidationError } from "@formbricks/types/errors";
|
||||
|
||||
export const updateProjectBranding = async (
|
||||
projectId: string,
|
||||
inputProject: TProjectUpdateBrandingInput
|
||||
): Promise<boolean> => {
|
||||
validateInputs([projectId, ZId], [inputProject, ZProjectUpdateBrandingInput]);
|
||||
try {
|
||||
const updatedProject = await prisma.project.update({
|
||||
where: {
|
||||
id: projectId,
|
||||
},
|
||||
data: {
|
||||
...inputProject,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
organizationId: true,
|
||||
environments: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
projectCache.revalidate({
|
||||
id: updatedProject.id,
|
||||
organizationId: updatedProject.organizationId,
|
||||
});
|
||||
|
||||
updatedProject.environments.forEach((environment) => {
|
||||
// revalidate environment cache
|
||||
projectCache.revalidate({
|
||||
environmentId: environment.id,
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
console.error(JSON.stringify(error.errors, null, 2));
|
||||
}
|
||||
throw new ValidationError("Data validation of project failed");
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ZProjectUpdateBrandingInput = z.object({
|
||||
linkSurveyBranding: z.boolean().optional(),
|
||||
inAppSurveyBranding: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type TProjectUpdateBrandingInput = z.infer<typeof ZProjectUpdateBrandingInput>;
|
||||
@@ -3,10 +3,7 @@
|
||||
import { authenticatedActionClient } from "@/lib/utils/action-client";
|
||||
import { checkAuthorizationUpdated } from "@/lib/utils/action-client-middleware";
|
||||
import { getOrganizationIdFromProjectId } from "@/lib/utils/helper";
|
||||
import {
|
||||
getRemoveInAppBrandingPermission,
|
||||
getRemoveLinkBrandingPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getRemoveBrandingPermission } from "@/modules/ee/license-check/lib/utils";
|
||||
import { updateProject } from "@/modules/projects/settings/lib/project";
|
||||
import { z } from "zod";
|
||||
import { getOrganization } from "@formbricks/lib/organization/service";
|
||||
@@ -52,16 +49,16 @@ export const updateProjectAction = authenticatedActionClient
|
||||
throw new Error("Organization not found");
|
||||
}
|
||||
|
||||
const canRemoveBranding = await getRemoveBrandingPermission(organization);
|
||||
|
||||
if (parsedInput.data.inAppSurveyBranding !== undefined) {
|
||||
const canRemoveInAppBranding = getRemoveInAppBrandingPermission(organization);
|
||||
if (!canRemoveInAppBranding) {
|
||||
if (!canRemoveBranding) {
|
||||
throw new OperationNotAllowedError("You are not allowed to remove in-app branding");
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedInput.data.linkSurveyBranding !== undefined) {
|
||||
const canRemoveLinkSurveyBranding = getRemoveLinkBrandingPermission(organization);
|
||||
if (!canRemoveLinkSurveyBranding) {
|
||||
if (!canRemoveBranding) {
|
||||
throw new OperationNotAllowedError("You are not allowed to remove link survey branding");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { updateProjectAction } from "@/modules/projects/settings/actions";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
import { Switch } from "@/modules/ui/components/switch";
|
||||
import { UpgradePlanNotice } from "@/modules/ui/components/upgrade-plan-notice";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { TProject, TProjectUpdateInput } from "@formbricks/types/project";
|
||||
|
||||
interface EditBrandingProps {
|
||||
type: "linkSurvey" | "appSurvey";
|
||||
project: TProject;
|
||||
canRemoveBranding: boolean;
|
||||
environmentId: string;
|
||||
isReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export const EditBranding = ({
|
||||
type,
|
||||
project,
|
||||
canRemoveBranding,
|
||||
environmentId,
|
||||
isReadOnly,
|
||||
}: EditBrandingProps) => {
|
||||
const t = useTranslations();
|
||||
const [isBrandingEnabled, setIsBrandingEnabled] = useState(
|
||||
type === "linkSurvey" ? project.linkSurveyBranding : project.inAppSurveyBranding
|
||||
);
|
||||
const [updatingBranding, setUpdatingBranding] = useState(false);
|
||||
|
||||
const toggleBranding = async () => {
|
||||
try {
|
||||
setUpdatingBranding(true);
|
||||
const newBrandingState = !isBrandingEnabled;
|
||||
setIsBrandingEnabled(newBrandingState);
|
||||
let inputProject: Partial<TProjectUpdateInput> = {
|
||||
[type === "linkSurvey" ? "linkSurveyBranding" : "inAppSurveyBranding"]: newBrandingState,
|
||||
};
|
||||
await updateProjectAction({ projectId: project.id, data: inputProject });
|
||||
toast.success(
|
||||
newBrandingState
|
||||
? t("environments.project.look.formbricks_branding_shown")
|
||||
: t("environments.project.look.formbricks_branding_hidden")
|
||||
);
|
||||
} catch (error) {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
} finally {
|
||||
setUpdatingBranding(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full items-center space-y-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch
|
||||
id={`branding-${type}`}
|
||||
checked={isBrandingEnabled}
|
||||
onCheckedChange={toggleBranding}
|
||||
disabled={!canRemoveBranding || updatingBranding || isReadOnly}
|
||||
/>
|
||||
<Label htmlFor={`branding-${type}`}>
|
||||
{t("environments.project.look.show_formbricks_branding_in", {
|
||||
type: type === "linkSurvey" ? t("common.link") : t("common.app"),
|
||||
})}
|
||||
</Label>
|
||||
</div>
|
||||
{!canRemoveBranding && (
|
||||
<div>
|
||||
{type === "linkSurvey" && (
|
||||
<div className="mb-8">
|
||||
<UpgradePlanNotice
|
||||
message={t("environments.project.look.formbricks_branding_upgrade_message")}
|
||||
textForUrl={t("environments.project.look.formbricks_branding_upgrade_text")}
|
||||
url={`/environments/${environmentId}/settings/billing`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{type !== "linkSurvey" && (
|
||||
<UpgradePlanNotice
|
||||
message={t("environments.project.look.formbricks_branding_upgrade_message_in_app")}
|
||||
textForUrl={t("environments.project.look.formbricks_branding_upgrade_text")}
|
||||
url={`/environments/${environmentId}/settings/billing`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -2,15 +2,14 @@ import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/
|
||||
import { authOptions } from "@/modules/auth/lib/authOptions";
|
||||
import {
|
||||
getMultiLanguagePermission,
|
||||
getRemoveInAppBrandingPermission,
|
||||
getRemoveLinkBrandingPermission,
|
||||
getRemoveBrandingPermission,
|
||||
getRoleManagementPermission,
|
||||
} from "@/modules/ee/license-check/lib/utils";
|
||||
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
|
||||
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
|
||||
import { BrandingSettingsCard } from "@/modules/ee/whitelabel/remove-branding/components/branding-settings-card";
|
||||
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
|
||||
import { EditLogo } from "@/modules/projects/settings/look/components/edit-logo";
|
||||
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
|
||||
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
|
||||
import { PageHeader } from "@/modules/ui/components/page-header";
|
||||
import { getServerSession } from "next-auth";
|
||||
@@ -22,7 +21,6 @@ import { getAccessFlags } from "@formbricks/lib/membership/utils";
|
||||
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
|
||||
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
|
||||
import { getUserLocale } from "@formbricks/lib/user/service";
|
||||
import { EditBranding } from "./components/edit-branding";
|
||||
import { EditPlacementForm } from "./components/edit-placement-form";
|
||||
import { ThemeStyling } from "./components/theme-styling";
|
||||
|
||||
@@ -45,8 +43,7 @@ export const ProjectLookSettingsPage = async (props: { params: Promise<{ environ
|
||||
throw new Error(t("common.organization_not_found"));
|
||||
}
|
||||
const locale = session?.user.id ? await getUserLocale(session.user.id) : undefined;
|
||||
const canRemoveInAppBranding = getRemoveInAppBrandingPermission(organization);
|
||||
const canRemoveLinkBranding = getRemoveLinkBrandingPermission(organization);
|
||||
const canRemoveBranding = await getRemoveBrandingPermission(organization);
|
||||
|
||||
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
|
||||
const { isMember } = getAccessFlags(currentUserMembership?.role);
|
||||
@@ -92,34 +89,12 @@ export const ProjectLookSettingsPage = async (props: { params: Promise<{ environ
|
||||
description={t("environments.project.look.app_survey_placement_settings_description")}>
|
||||
<EditPlacementForm project={project} environmentId={params.environmentId} isReadOnly={isReadOnly} />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
title={t("environments.project.look.formbricks_branding")}
|
||||
description={t("environments.project.look.formbricks_branding_settings_description")}>
|
||||
<div className="space-y-4">
|
||||
<EditBranding
|
||||
type="linkSurvey"
|
||||
project={project}
|
||||
canRemoveBranding={canRemoveLinkBranding}
|
||||
environmentId={params.environmentId}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
<EditBranding
|
||||
type="appSurvey"
|
||||
project={project}
|
||||
canRemoveBranding={canRemoveInAppBranding}
|
||||
environmentId={params.environmentId}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isReadOnly && (
|
||||
<Alert variant="warning" className="mt-4">
|
||||
<AlertDescription>
|
||||
{t("common.only_owners_managers_and_manage_access_members_can_perform_this_action")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</SettingsCard>
|
||||
<BrandingSettingsCard
|
||||
canRemoveBranding={canRemoveBranding}
|
||||
project={project}
|
||||
environmentId={params.environmentId}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</PageContentWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user