fix: update project config navbar + wording (#4918)

This commit is contained in:
Johannes
2025-03-12 05:40:01 -07:00
committed by GitHub
parent a25e5dcfcd
commit 4870dc8d45
35 changed files with 282 additions and 274 deletions
@@ -27,7 +27,7 @@ export const EditAlerts = ({
return (
<>
{memberships.map((membership) => (
<>
<div key={membership.organization.id}>
<div className="mb-5 grid grid-cols-6 items-center space-x-3">
<div className="col-span-3 flex items-center space-x-3">
<UsersIcon className="h-6 w-7 text-slate-600" />
@@ -110,7 +110,7 @@ export const EditAlerts = ({
</Link>
</p>
</div>
</>
</div>
))}
</>
);
@@ -18,7 +18,7 @@ export const EditWeeklySummary = ({ memberships, user, environmentId }: EditAler
return (
<>
{memberships.map((membership) => (
<>
<div key={membership.organization.id}>
<div className="mb-5 flex items-center space-x-3 text-sm font-medium">
<UsersIcon className="h-6 w-7 text-slate-600" />
@@ -52,7 +52,7 @@ export const EditWeeklySummary = ({ memberships, user, environmentId }: EditAler
</Link>
</p>
</div>
</>
</div>
))}
</>
);
@@ -9,8 +9,10 @@ import { UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
import { getTranslate } from "@/tolgee/server";
import { getServerSession } from "next-auth";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getOrganizationsWhereUserIsSingleOwner } from "@formbricks/lib/organization/service";
import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/service";
import {
getOrganizationByEnvironmentId,
getOrganizationsWhereUserIsSingleOwner,
} from "@formbricks/lib/organization/service";
import { getUser } from "@formbricks/lib/user/service";
import { SettingsCard } from "../../components/SettingsCard";
import { DeleteAccount } from "./components/DeleteAccount";
@@ -71,7 +73,9 @@ const Page = async (props: { params: Promise<{ environmentId: string }> }) => {
description={t("environments.settings.profile.two_factor_authentication_description")}
buttons={[
{
text: t("common.start_free_trial"),
text: IS_FORMBRICKS_CLOUD
? t("common.start_free_trial")
: t("common.request_trial_license"),
href: IS_FORMBRICKS_CLOUD
? `/environments/${params.environmentId}/settings/billing`
: "https://formbricks.com/upgrade-self-hosting-license",
+1 -1
View File
@@ -95,7 +95,7 @@ export const ContactsPage = async ({
description={t("environments.contacts.unlock_contacts_description")}
buttons={[
{
text: t("common.start_free_trial"),
text: IS_FORMBRICKS_CLOUD ? t("common.start_free_trial") : t("common.request_trial_license"),
href: IS_FORMBRICKS_CLOUD
? `/environments/${params.environmentId}/settings/billing`
: "https://formbricks.com/upgrade-self-hosting-license",
@@ -100,7 +100,7 @@ export const SegmentsPage = async ({
description={t("environments.segments.unlock_segments_description")}
buttons={[
{
text: t("common.start_free_trial"),
text: IS_FORMBRICKS_CLOUD ? t("common.start_free_trial") : t("common.request_trial_license"),
href: IS_FORMBRICKS_CLOUD
? `/environments/${params.environmentId}/settings/billing`
: "https://formbricks.com/upgrade-self-hosting-license",
+1 -1
View File
@@ -11,7 +11,7 @@ export const LanguagesLoading = () => {
const { t } = useTranslate();
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation activeId="languages" loading />
</PageHeader>
<SettingsCard
+12 -18
View File
@@ -1,9 +1,6 @@
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
getRoleManagementPermission,
} from "@/modules/ee/license-check/lib/utils";
import { getMultiLanguagePermission } from "@/modules/ee/license-check/lib/utils";
import { EditLanguage } from "@/modules/ee/multi-language-surveys/components/edit-language";
import { getProjectPermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
@@ -12,7 +9,7 @@ import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper
import { PageHeader } from "@/modules/ui/components/page-header";
import { getTranslate } from "@/tolgee/server";
import { getServerSession } from "next-auth";
import { notFound } from "next/navigation";
import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants";
import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service";
import { getAccessFlags } from "@formbricks/lib/membership/utils";
import { getOrganization } from "@formbricks/lib/organization/service";
@@ -35,11 +32,6 @@ export const LanguagesPage = async (props: { params: Promise<{ environmentId: st
}
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
if (!isMultiLanguageAllowed) {
notFound();
}
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
const session = await getServerSession(authOptions);
@@ -63,18 +55,20 @@ export const LanguagesPage = async (props: { params: Promise<{ environmentId: st
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<ProjectConfigNavigation
environmentId={params.environmentId}
activeId="languages"
isMultiLanguageAllowed={isMultiLanguageAllowed}
canDoRoleManagement={canDoRoleManagement}
/>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation environmentId={params.environmentId} activeId="languages" />
</PageHeader>
<SettingsCard
title={t("environments.project.languages.multi_language_surveys")}
description={t("environments.project.languages.multi_language_surveys_description")}>
<EditLanguage project={project} locale={user.locale} isReadOnly={isReadOnly} />
<EditLanguage
project={project}
locale={user.locale}
isReadOnly={isReadOnly}
isMultiLanguageAllowed={isMultiLanguageAllowed}
environmentId={params.environmentId}
isFormbricksCloud={IS_FORMBRICKS_CLOUD}
/>
</SettingsCard>
</PageContentWrapper>
);
@@ -4,9 +4,9 @@ import { getFormattedErrorMessage } from "@/lib/utils/helper";
import { Alert, AlertDescription } from "@/modules/ui/components/alert";
import { Button } from "@/modules/ui/components/button";
import { ConfirmationModal } from "@/modules/ui/components/confirmation-modal";
import { ModalButton, UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
import { Language } from "@prisma/client";
import { useTranslate } from "@tolgee/react";
import { TFnType } from "@tolgee/react";
import { TFnType, useTranslate } from "@tolgee/react";
import { PlusIcon } from "lucide-react";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
@@ -26,6 +26,9 @@ interface EditLanguageProps {
project: TProject;
locale: TUserLocale;
isReadOnly: boolean;
isMultiLanguageAllowed: boolean;
environmentId: string;
isFormbricksCloud: boolean;
}
const checkIfDuplicateExists = (arr: string[]) => {
@@ -57,7 +60,7 @@ const validateLanguages = (languages: Language[], t: TFnType) => {
return false;
}
// Check if the chosen alias matches an ISO identifier of a language that hasnt been added
// Check if the chosen alias matches an ISO identifier of a language that hasn't been added
for (const alias of languageAliases) {
if (iso639Languages.some((language) => language.alpha2 === alias && !languageCodes.includes(alias))) {
toast.error(t("environments.project.languages.conflict_between_selected_alias_and_another_language"), {
@@ -70,7 +73,14 @@ const validateLanguages = (languages: Language[], t: TFnType) => {
return true;
};
export function EditLanguage({ project, locale, isReadOnly }: EditLanguageProps) {
export function EditLanguage({
project,
locale,
isReadOnly,
isMultiLanguageAllowed,
environmentId,
isFormbricksCloud,
}: EditLanguageProps) {
const { t } = useTranslate();
const [languages, setLanguages] = useState<Language[]>(project.languages);
const [isEditing, setIsEditing] = useState(false);
@@ -150,6 +160,21 @@ export function EditLanguage({ project, locale, isReadOnly }: EditLanguageProps)
setIsEditing(false);
};
const buttons: [ModalButton, ModalButton] = [
{
text: isFormbricksCloud ? t("common.start_free_trial") : t("common.request_trial_license"),
href: isFormbricksCloud
? `/environments/${environmentId}/settings/billing`
: "https://formbricks.com/upgrade-self-hosting-license",
},
{
text: t("common.learn_more"),
href: isFormbricksCloud
? `/environments/${environmentId}/settings/billing`
: "https://formbricks.com/learn-more-self-hosting-license",
},
];
const handleSaveChanges = async () => {
if (!validateLanguages(languages, t)) return;
await Promise.all(
@@ -179,63 +204,75 @@ export function EditLanguage({ project, locale, isReadOnly }: EditLanguageProps)
) : null;
return (
<div className="flex flex-col space-y-4">
<div className="space-y-4">
{languages.length > 0 ? (
<>
<LanguageLabels />
{languages.map((language, index) => (
<LanguageRow
index={index}
isEditing={isEditing}
key={language.id}
language={language}
locale={locale}
onDelete={() => handleDeleteLanguage(language.id)}
onLanguageChange={(newLanguage: Language) => {
const updatedLanguages = [...languages];
updatedLanguages[index] = newLanguage;
setLanguages(updatedLanguages);
}}
/>
))}
</>
) : (
<p className="text-sm italic text-slate-500">
{t("environments.project.languages.no_language_found")}
</p>
)}
<AddLanguageButton onClick={handleAddLanguage} />
</div>
<EditSaveButtons
isEditing={isEditing}
onCancel={handleCancelChanges}
disabled={isReadOnly}
onEdit={() => {
setIsEditing(true);
}}
onSave={handleSaveChanges}
t={t}
/>
{isReadOnly && (
<Alert variant="warning" className="mt-4">
<AlertDescription>
{t("common.only_owners_managers_and_manage_access_members_can_perform_this_action")}
</AlertDescription>
</Alert>
<>
{isMultiLanguageAllowed ? (
<div className="flex flex-col space-y-4">
<div className="space-y-4">
{languages.length > 0 ? (
<>
<LanguageLabels />
{languages.map((language, index) => (
<LanguageRow
index={index}
isEditing={isEditing}
key={language.id}
language={language}
locale={locale}
onDelete={() => handleDeleteLanguage(language.id)}
onLanguageChange={(newLanguage: Language) => {
const updatedLanguages = [...languages];
updatedLanguages[index] = newLanguage;
setLanguages(updatedLanguages);
}}
/>
))}
</>
) : (
<p className="text-sm italic text-slate-500">
{t("environments.project.languages.no_language_found")}
</p>
)}
<AddLanguageButton onClick={handleAddLanguage} />
</div>
<EditSaveButtons
isEditing={isEditing}
onCancel={handleCancelChanges}
disabled={isReadOnly}
onEdit={() => {
setIsEditing(true);
}}
onSave={handleSaveChanges}
t={t}
/>
{isReadOnly && (
<Alert variant="warning" className="mt-4">
<AlertDescription>
{t("common.only_owners_managers_and_manage_access_members_can_perform_this_action")}
</AlertDescription>
</Alert>
)}
<ConfirmationModal
buttonText={t("environments.project.languages.remove_language")}
isButtonDisabled={confirmationModal.isButtonDisabled}
onConfirm={() => performLanguageDeletion(confirmationModal.languageId)}
open={confirmationModal.isOpen}
setOpen={() => {
setConfirmationModal((prev) => ({ ...prev, isOpen: !prev.isOpen }));
}}
text={confirmationModal.text}
title={t("environments.project.languages.remove_language")}
/>
</div>
) : (
<UpgradePrompt
title={t("environments.settings.general.use_multi_language_surveys_with_a_higher_plan")}
description={t(
"environments.settings.general.use_multi_language_surveys_with_a_higher_plan_description"
)}
buttons={buttons}
/>
)}
<ConfirmationModal
buttonText={t("environments.project.languages.remove_language")}
isButtonDisabled={confirmationModal.isButtonDisabled}
onConfirm={() => performLanguageDeletion(confirmationModal.languageId)}
open={confirmationModal.isOpen}
setOpen={() => {
setConfirmationModal((prev) => ({ ...prev, isOpen: !prev.isOpen }));
}}
text={confirmationModal.text}
title={t("environments.project.languages.remove_language")}
/>
</div>
</>
);
}
@@ -230,7 +230,9 @@ export const MultiLanguageCard: FC<MultiLanguageCardProps> = ({
description={t("environments.surveys.edit.upgrade_notice_description")}
buttons={[
{
text: t("common.start_free_trial"),
text: isFormbricksCloud
? t("common.start_free_trial")
: t("common.request_trial_license"),
href: isFormbricksCloud
? `/environments/${environmentId}/settings/billing`
: "https://formbricks.com/docs/self-hosting/license#30-day-trial-license-request",
@@ -0,0 +1,36 @@
"use client";
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
import { useTranslate } from "@tolgee/react";
export const TeamsLoading = () => {
const { t } = useTranslate();
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation activeId="teams" loading />
</PageHeader>
<div className="p-4">
<div className="mb-4">
<div className="h-6 w-1/3 animate-pulse rounded bg-slate-200" />
</div>
<div className="space-y-4">
{[...Array(3)].map((_, idx) => (
<div
key={idx}
className="flex animate-pulse items-center space-x-4 rounded border border-slate-200 p-4">
<div className="h-10 w-10 rounded-full bg-slate-300" />
<div className="flex-1 space-y-2">
<div className="h-4 w-3/4 rounded bg-slate-200" />
<div className="h-4 w-1/2 rounded bg-slate-200" />
</div>
</div>
))}
</div>
</div>
</PageContentWrapper>
);
};
@@ -1,8 +1,4 @@
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
getRoleManagementPermission,
} from "@/modules/ee/license-check/lib/utils";
import { AccessView } from "@/modules/ee/teams/project-teams/components/access-view";
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
@@ -37,9 +33,6 @@ export const ProjectTeams = async (props: { params: Promise<{ environmentId: str
const currentUserMembership = await getMembershipByUserIdOrganizationId(session?.user.id, organization.id);
const { isOwner, isManager } = getAccessFlags(currentUserMembership?.role);
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
const teams = await getTeamsByProjectId(project.id);
if (!teams) {
@@ -50,13 +43,8 @@ export const ProjectTeams = async (props: { params: Promise<{ environmentId: str
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<ProjectConfigNavigation
environmentId={params.environmentId}
activeId="teams"
isMultiLanguageAllowed={isMultiLanguageAllowed}
canDoRoleManagement={canDoRoleManagement}
/>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation environmentId={params.environmentId} activeId="teams" />
</PageHeader>
<AccessView environmentId={params.environmentId} teams={teams} isOwnerOrManager={isOwnerOrManager} />
</PageContentWrapper>
@@ -37,7 +37,7 @@ export const TeamsView = async ({
const buttons: [ModalButton, ModalButton] = [
{
text: t("common.start_free_trial"),
text: IS_FORMBRICKS_CLOUD ? t("common.start_free_trial") : t("common.request_trial_license"),
href: IS_FORMBRICKS_CLOUD
? `/environments/${environmentId}/settings/billing`
: "https://formbricks.com/docs/self-hosting/license#30-day-trial-license-request",
@@ -162,7 +162,7 @@ export const EmailCustomizationSettings = ({
const buttons: [ModalButton, ModalButton] = [
{
text: t("common.start_free_trial"),
text: isFormbricksCloud ? t("common.start_free_trial") : t("common.request_trial_license"),
href: isFormbricksCloud
? `/environments/${environmentId}/settings/billing`
: "https://formbricks.com/upgrade-self-hosting-license",
@@ -23,7 +23,7 @@ export const BrandingSettingsCard = async ({
const buttons: [ModalButton, ModalButton] = [
{
text: t("common.start_free_trial"),
text: IS_FORMBRICKS_CLOUD ? t("common.start_free_trial") : t("common.request_trial_license"),
href: IS_FORMBRICKS_CLOUD
? `/environments/${environmentId}/settings/billing`
: "https://formbricks.com/upgrade-self-hosting-license",
@@ -35,7 +35,7 @@ export const AppConnectionLoading = () => {
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation activeId="app-connection" loading />
</PageHeader>
<div className="mt-4 flex max-w-4xl animate-pulse items-center space-y-4 rounded-lg border bg-blue-50 p-6 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base"></div>
@@ -1,9 +1,5 @@
import { WidgetStatusIndicator } from "@/app/(app)/environments/[environmentId]/components/WidgetStatusIndicator";
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import {
getMultiLanguagePermission,
getRoleManagementPermission,
} from "@/modules/ee/license-check/lib/utils";
import { EnvironmentIdField } from "@/modules/projects/settings/(setup)/components/environment-id-field";
import { SetupInstructions } from "@/modules/projects/settings/(setup)/components/setup-instructions";
import { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
@@ -31,18 +27,10 @@ export const AppConnectionPage = async (props) => {
throw new Error(t("common.organization_not_found"));
}
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<ProjectConfigNavigation
environmentId={params.environmentId}
activeId="app-connection"
isMultiLanguageAllowed={isMultiLanguageAllowed}
canDoRoleManagement={canDoRoleManagement}
/>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation environmentId={params.environmentId} activeId="app-connection" />
</PageHeader>
<div className="space-y-4">
<EnvironmentNotice environmentId={params.environmentId} subPageUrl="/project/app-connection" />
@@ -42,7 +42,7 @@ export const APIKeysLoading = () => {
const { t } = useTranslate();
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation activeId="api-keys" loading />
</PageHeader>
<div className="mt-4 flex max-w-4xl animate-pulse items-center space-y-4 rounded-lg border bg-blue-50 p-6 text-sm text-blue-900 shadow-sm md:space-y-0 md:text-base"></div>
@@ -1,9 +1,5 @@
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
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 { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
@@ -53,18 +49,10 @@ export const APIKeysPage = async (props) => {
const isReadOnly = isMember && !hasManageAccess;
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<ProjectConfigNavigation
environmentId={params.environmentId}
activeId="api-keys"
isMultiLanguageAllowed={isMultiLanguageAllowed}
canDoRoleManagement={canDoRoleManagement}
/>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation environmentId={params.environmentId} activeId="api-keys" />
</PageHeader>
<EnvironmentNotice environmentId={environment.id} subPageUrl="/project/api-keys" />
{environment.type === "development" ? (
@@ -8,17 +8,13 @@ import { usePathname } from "next/navigation";
interface ProjectConfigNavigationProps {
activeId: string;
environmentId?: string;
isMultiLanguageAllowed?: boolean;
loading?: boolean;
canDoRoleManagement?: boolean;
}
export const ProjectConfigNavigation = ({
activeId,
environmentId,
isMultiLanguageAllowed,
loading,
canDoRoleManagement,
}: ProjectConfigNavigationProps) => {
const { t } = useTranslate();
const pathname = usePathname();
@@ -43,7 +39,6 @@ export const ProjectConfigNavigation = ({
label: t("common.survey_languages"),
icon: <LanguagesIcon className="h-5 w-5" />,
href: `/environments/${environmentId}/project/languages`,
hidden: !isMultiLanguageAllowed,
current: pathname?.includes("/languages"),
},
{
@@ -70,8 +65,8 @@ export const ProjectConfigNavigation = ({
{
id: "teams",
label: t("common.team_access"),
icon: <UsersIcon className="h-5 w-5" />,
href: `/environments/${environmentId}/project/teams`,
hidden: !canDoRoleManagement,
current: pathname?.includes("/teams"),
},
];
@@ -28,7 +28,7 @@ export const GeneralSettingsLoading = () => {
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation activeId="general" loading />
</PageHeader>
{cards.map((card, index) => (
@@ -1,9 +1,5 @@
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
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 { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
@@ -51,21 +47,12 @@ export const GeneralSettingsPage = async (props: { params: Promise<{ environment
const isReadOnly = isMember && !hasManageAccess;
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
const isOwnerOrManager = isOwner || isManager;
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.project_configuration")}>
{/* </PageHeader><PageHeader pageTitle={t("common.configuration")}> */}
<ProjectConfigNavigation
environmentId={params.environmentId}
activeId="general"
isMultiLanguageAllowed={isMultiLanguageAllowed}
canDoRoleManagement={canDoRoleManagement}
/>
<ProjectConfigNavigation environmentId={params.environmentId} activeId="general" />
</PageHeader>
<SettingsCard
title={t("common.project_name")}
@@ -9,7 +9,7 @@ import { getOrganizationByEnvironmentId } from "@formbricks/lib/organization/ser
import { getProjectByEnvironmentId } from "@formbricks/lib/project/service";
export const metadata: Metadata = {
title: "Config",
title: "Configuration",
};
export const ProjectSettingsLayout = async (props) => {
@@ -24,7 +24,7 @@ export const ProjectLookSettingsLoading = () => {
const { t } = useTranslate();
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation activeId="look" loading />
</PageHeader>
<SettingsCard
@@ -1,10 +1,6 @@
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
getRoleManagementPermission,
getWhiteLabelPermission,
} from "@/modules/ee/license-check/lib/utils";
import { getWhiteLabelPermission } 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";
@@ -51,18 +47,10 @@ export const ProjectLookSettingsPage = async (props: { params: Promise<{ environ
const isReadOnly = isMember && !hasManageAccess;
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<ProjectConfigNavigation
environmentId={params.environmentId}
activeId="look"
isMultiLanguageAllowed={isMultiLanguageAllowed}
canDoRoleManagement={canDoRoleManagement}
/>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation environmentId={params.environmentId} activeId="look" />
</PageHeader>
<SettingsCard
title={t("environments.project.look.theme")}
@@ -10,7 +10,7 @@ export const TagsLoading = () => {
const { t } = useTranslate();
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation activeId="tags" />
</PageHeader>
<SettingsCard
@@ -1,9 +1,5 @@
import { SettingsCard } from "@/app/(app)/environments/[environmentId]/settings/components/SettingsCard";
import { authOptions } from "@/modules/auth/lib/authOptions";
import {
getMultiLanguagePermission,
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 { ProjectConfigNavigation } from "@/modules/projects/settings/components/project-config-navigation";
@@ -59,18 +55,10 @@ export const TagsPage = async (props) => {
const isReadOnly = isMember && !hasManageAccess;
const isMultiLanguageAllowed = await getMultiLanguagePermission(organization.billing.plan);
const canDoRoleManagement = await getRoleManagementPermission(organization.billing.plan);
return (
<PageContentWrapper>
<PageHeader pageTitle={t("common.configuration")}>
<ProjectConfigNavigation
environmentId={params.environmentId}
activeId="tags"
isMultiLanguageAllowed={isMultiLanguageAllowed}
canDoRoleManagement={canDoRoleManagement}
/>
<PageHeader pageTitle={t("common.project_configuration")}>
<ProjectConfigNavigation environmentId={params.environmentId} activeId="tags" />
</PageHeader>
<SettingsCard
title={t("environments.project.tags.manage_tags")}
@@ -43,7 +43,7 @@ export const TargetingLockedCard = ({ isFormbricksCloud, environmentId }: Target
description={t("environments.surveys.edit.unlock_targeting_description")}
buttons={[
{
text: t("common.start_free_trial"),
text: isFormbricksCloud ? t("common.start_free_trial") : t("common.request_trial_license"),
href: isFormbricksCloud
? `/environments/${environmentId}/settings/billing`
: "https://formbricks.com/upgrade-self-hosting-license",
@@ -8,8 +8,8 @@ export const SlackIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
fill="none"
stroke="currentColor"
strokeWidth="2"
stroke-linecap="round"
stroke-linejoin="round"
strokeLinecap="round"
strokeLinejoin="round"
{...props}>
<rect width="3" height="8" x="13" y="2" rx="1.5" />
<path d="M19 8.5V10h1.5A1.5 1.5 0 1 0 19 8.5" />
@@ -16,77 +16,72 @@ interface SecondaryNavbarProps {
export const SecondaryNavigation = ({ navigation, activeId, loading, ...props }: SecondaryNavbarProps) => {
return (
<div {...props}>
<div className="grid h-10 w-full grid-cols-[auto,1fr]">
<nav className="flex h-full min-w-full items-center space-x-4" aria-label="Tabs">
{loading
? navigation.map((navElem) => (
<div className="group flex h-full flex-col" key={navElem.id}>
<div
aria-disabled="true"
className={cn(
navElem.id === activeId ? "font-semibold text-slate-900" : "text-slate-500",
"flex h-full items-center px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === activeId ? "page" : undefined}>
{navElem.label}
</div>
<div
className={cn(
"bottom-0 mt-auto h-[2px] w-full rounded-t-lg transition-all duration-150 ease-in-out",
navElem.id === activeId ? "bg-slate-300" : "bg-transparent group-hover:bg-slate-300",
navElem.hidden && "hidden"
)}
/>
<nav className="flex h-10 w-full items-center space-x-4" aria-label="Tabs">
{loading
? navigation.map((navElem) => (
<div className="group flex h-full flex-col truncate" key={navElem.id}>
<div
aria-disabled="true"
className={cn(
navElem.id === activeId ? "font-semibold text-slate-900" : "text-slate-500",
"flex h-full items-center truncate px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === activeId ? "page" : undefined}>
{navElem.label}
</div>
))
: navigation.map(
(navElem) =>
!navElem.hidden && (
<div className="group flex h-full flex-col" key={navElem.id}>
{navElem.href ? (
<Link
href={navElem.href}
{...(navElem.onClick ? { onClick: navElem.onClick } : {})}
className={cn(
navElem.id === activeId
? "font-semibold text-slate-900"
: "text-slate-500 hover:text-slate-700",
"flex h-full items-center px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === activeId ? "page" : undefined}>
{navElem.label}
</Link>
) : (
<button
{...(navElem.onClick ? { onClick: navElem.onClick } : {})}
className={cn(
navElem.id === activeId
? "font-semibold text-slate-900"
: "text-slate-500 hover:text-slate-700",
"grow items-center px-3 text-sm font-medium transition-all duration-150 ease-in-out",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === activeId ? "page" : undefined}>
{navElem.label}
</button>
)}
<div
<div
className={cn(
"bottom-0 mt-auto h-[2px] w-full rounded-t-lg transition-all duration-150 ease-in-out",
navElem.id === activeId ? "bg-slate-300" : "bg-transparent group-hover:bg-slate-300",
navElem.hidden && "hidden"
)}
/>
</div>
))
: navigation.map(
(navElem) =>
!navElem.hidden && (
<div className="group flex h-full flex-col truncate" key={navElem.id}>
{navElem.href ? (
<Link
href={navElem.href}
{...(navElem.onClick ? { onClick: navElem.onClick } : {})}
className={cn(
"bottom-0 mt-auto h-[2px] w-full rounded-t-lg transition-all duration-150 ease-in-out",
navElem.id === activeId
? "bg-brand-dark"
: "bg-transparent group-hover:bg-slate-300",
? "font-semibold text-slate-900"
: "text-slate-500 hover:text-slate-700",
"flex h-full items-center px-3 text-sm font-medium",
navElem.hidden && "hidden"
)}
/>
</div>
)
)}
</nav>
<div className="justify-self-end"></div>
</div>
aria-current={navElem.id === activeId ? "page" : undefined}>
{navElem.label}
</Link>
) : (
<button
{...(navElem.onClick ? { onClick: navElem.onClick } : {})}
className={cn(
navElem.id === activeId
? "font-semibold text-slate-900"
: "text-slate-500 hover:text-slate-700",
"grow items-center px-3 text-sm font-medium transition-all duration-150 ease-in-out",
navElem.hidden && "hidden"
)}
aria-current={navElem.id === activeId ? "page" : undefined}>
{navElem.label}
</button>
)}
<div
className={cn(
"bottom-0 mt-auto h-[2px] w-full rounded-t-lg transition-all duration-150 ease-in-out",
navElem.id === activeId ? "bg-brand-dark" : "bg-transparent group-hover:bg-slate-300",
navElem.hidden && "hidden"
)}
/>
</div>
)
)}
</nav>
</div>
);
};
+5 -2
View File
@@ -310,6 +310,7 @@
"remove": "Entfernen",
"reorder_and_hide_columns": "Spalten neu anordnen und ausblenden",
"report_survey": "Umfrage melden",
"request_trial_license": "Test-Lizenz anfordern",
"reset_to_default": "Auf Standard zurücksetzen",
"response": "Antwort",
"responses": "Antworten",
@@ -1133,7 +1134,9 @@
"resend_invitation_email": "Einladungsemail erneut senden",
"share_invite_link": "Einladungslink teilen",
"share_this_link_to_let_your_organization_member_join_your_organization": "Teile diesen Link, damit dein Organisationsmitglied deiner Organisation beitreten kann:",
"test_email_sent_successfully": "Test-E-Mail erfolgreich gesendet"
"test_email_sent_successfully": "Test-E-Mail erfolgreich gesendet",
"use_multi_language_surveys_with_a_higher_plan": "Nutze mehrsprachige Umfragen mit einem höheren Plan",
"use_multi_language_surveys_with_a_higher_plan_description": "Befrage deine Nutzer in verschiedenen Sprachen."
},
"notifications": {
"auto_subscribe_to_new_surveys": "Neue Umfragenbenachrichtigungen abonnieren",
@@ -1177,7 +1180,7 @@
"remove_image": "Bild entfernen",
"save_the_following_backup_codes_in_a_safe_place": "Speichere die folgenden Backup-Codes an einem sicheren Ort.",
"scan_the_qr_code_below_with_your_authenticator_app": "Scanne den QR-Code unten mit deiner Authentifizierungs-App.",
"security_description": "Verwalte dein Passwort und andere Sicherheitseinstellungen.",
"security_description": "Verwalte dein Passwort und andere Sicherheitseinstellungen wie Zwei-Faktor-Authentifizierung (2FA).",
"two_factor_authentication": "Zwei-Faktor-Authentifizierung",
"two_factor_authentication_description": "Füge eine zusätzliche Sicherheitsebene zu deinem Konto hinzu, falls dein Passwort gestohlen wird.",
"two_factor_authentication_enabled_please_enter_the_six_digit_code_from_your_authenticator_app": "Zwei-Faktor-Authentifizierung aktiviert. Bitte gib den sechsstelligen Code aus deiner Authentifizierungs-App ein.",
+7 -4
View File
@@ -296,7 +296,7 @@
"product_not_found": "Product not found",
"profile": "Profile",
"project": "Project",
"project_configuration": "Project's Configuration",
"project_configuration": "Project Configuration",
"project_id": "Project ID",
"project_name": "Project Name",
"project_not_found": "Project not found",
@@ -310,6 +310,7 @@
"remove": "Remove",
"reorder_and_hide_columns": "Reorder and hide columns",
"report_survey": "Report Survey",
"request_trial_license": "Request trial license",
"reset_to_default": "Reset to default",
"response": "Response",
"responses": "Responses",
@@ -346,7 +347,7 @@
"some_files_failed_to_upload": "Some files failed to upload",
"something_went_wrong_please_try_again": "Something went wrong. Please try again.",
"sort_by": "Sort by",
"start_free_trial": "Start Free Trial",
"start_free_trial": "Start free trial",
"status": "Status",
"step_by_step_manual": "Step by step manual",
"styling": "Styling",
@@ -1133,7 +1134,9 @@
"resend_invitation_email": "Resend Invitation Email",
"share_invite_link": "Share Invite Link",
"share_this_link_to_let_your_organization_member_join_your_organization": "Share this link to let your organization member join your organization:",
"test_email_sent_successfully": "Test email sent successfully"
"test_email_sent_successfully": "Test email sent successfully",
"use_multi_language_surveys_with_a_higher_plan": "Use multi-language surveys with a higher plan",
"use_multi_language_surveys_with_a_higher_plan_description": "Survey your users in different languages."
},
"notifications": {
"auto_subscribe_to_new_surveys": "Auto-subscribe to new surveys",
@@ -1177,7 +1180,7 @@
"remove_image": "Remove image",
"save_the_following_backup_codes_in_a_safe_place": "Save the following backup codes in a safe place.",
"scan_the_qr_code_below_with_your_authenticator_app": "Scan the QR code below with your authenticator app.",
"security_description": "Manage your password and other security settings.",
"security_description": "Manage your password and other security settings like two-factor authentication (2FA).",
"two_factor_authentication": "Two factor authentication",
"two_factor_authentication_description": "Add an extra layer of security to your account in case your password is stolen.",
"two_factor_authentication_enabled_please_enter_the_six_digit_code_from_your_authenticator_app": "Two-factor authentication enabled. Please enter the six-digit code from your authenticator app.",
+5 -2
View File
@@ -310,6 +310,7 @@
"remove": "Retirer",
"reorder_and_hide_columns": "Réorganiser et masquer des colonnes",
"report_survey": "Rapport d'enquête",
"request_trial_license": "Demander licence d'essai",
"reset_to_default": "Réinitialiser par défaut",
"response": "Réponse",
"responses": "Réponses",
@@ -1133,7 +1134,9 @@
"resend_invitation_email": "Renvoyer l'e-mail d'invitation",
"share_invite_link": "Partager le lien d'invitation",
"share_this_link_to_let_your_organization_member_join_your_organization": "Partagez ce lien pour permettre à un membre de votre organisation de rejoindre votre organisation :",
"test_email_sent_successfully": "E-mail de test envoyé avec succès"
"test_email_sent_successfully": "E-mail de test envoyé avec succès",
"use_multi_language_surveys_with_a_higher_plan": "Utiliser des sondages multilingues avec un plan supérieur",
"use_multi_language_surveys_with_a_higher_plan_description": "Sondage vos utilisateurs dans différentes langues."
},
"notifications": {
"auto_subscribe_to_new_surveys": "S'abonner automatiquement aux nouveaux sondages",
@@ -1177,7 +1180,7 @@
"remove_image": "Supprimer l'image",
"save_the_following_backup_codes_in_a_safe_place": "Enregistrez les codes de sauvegarde suivants dans un endroit sûr.",
"scan_the_qr_code_below_with_your_authenticator_app": "Scannez le code QR ci-dessous avec votre application d'authentification.",
"security_description": "Gérez votre mot de passe et d'autres paramètres de sécurité.",
"security_description": "Gérez votre mot de passe et d'autres paramètres de sécurité comme l'authentification à deux facteurs (2FA).",
"two_factor_authentication": "Authentification à deux facteurs",
"two_factor_authentication_description": "Ajoutez une couche de sécurité supplémentaire à votre compte au cas où votre mot de passe serait volé.",
"two_factor_authentication_enabled_please_enter_the_six_digit_code_from_your_authenticator_app": "Authentification à deux facteurs activée. Veuillez entrer le code à six chiffres de votre application d'authentification.",
+6 -3
View File
@@ -310,6 +310,7 @@
"remove": "remover",
"reorder_and_hide_columns": "Reordenar e ocultar colunas",
"report_survey": "Relatório de Pesquisa",
"request_trial_license": "Solicitar licença de teste",
"reset_to_default": "Restaurar para o padrão",
"response": "Resposta",
"responses": "Respostas",
@@ -346,7 +347,7 @@
"some_files_failed_to_upload": "Alguns arquivos falharam ao enviar",
"something_went_wrong_please_try_again": "Algo deu errado. Tente novamente.",
"sort_by": "Ordenar por",
"start_free_trial": "Iniciar Teste Grátis",
"start_free_trial": "Iniciar teste grátis",
"status": "status",
"step_by_step_manual": "Manual passo a passo",
"styling": "estilização",
@@ -1133,7 +1134,9 @@
"resend_invitation_email": "Reenviar E-mail de Convite",
"share_invite_link": "Compartilhar Link de Convite",
"share_this_link_to_let_your_organization_member_join_your_organization": "Compartilhe esse link para que o membro da sua organização possa entrar na sua organização:",
"test_email_sent_successfully": "E-mail de teste enviado com sucesso"
"test_email_sent_successfully": "E-mail de teste enviado com sucesso",
"use_multi_language_surveys_with_a_higher_plan": "Use pesquisas multilingues com um plano superior",
"use_multi_language_surveys_with_a_higher_plan_description": "Pesquise seus usuários em diferentes idiomas."
},
"notifications": {
"auto_subscribe_to_new_surveys": "Inscrever-se automaticamente em novas pesquisas",
@@ -1177,7 +1180,7 @@
"remove_image": "Remover imagem",
"save_the_following_backup_codes_in_a_safe_place": "Guarde os seguintes códigos de backup em um lugar seguro.",
"scan_the_qr_code_below_with_your_authenticator_app": "Escaneie o código QR abaixo com seu app autenticador.",
"security_description": "Gerencie sua senha e outras configurações de segurança.",
"security_description": "Gerencie sua senha e outras configurações de segurança como a autenticação de dois fatores (2FA).",
"two_factor_authentication": "Autenticação de dois fatores",
"two_factor_authentication_description": "Adicione uma camada extra de segurança à sua conta caso sua senha seja roubada.",
"two_factor_authentication_enabled_please_enter_the_six_digit_code_from_your_authenticator_app": "Autenticação de dois fatores ativada. Por favor, insira o código de seis dígitos do seu app autenticador.",
+6 -3
View File
@@ -310,6 +310,7 @@
"remove": "Remover",
"reorder_and_hide_columns": "Reordenar e ocultar colunas",
"report_survey": "Relatório de Inquérito",
"request_trial_license": "Solicitar licença de teste",
"reset_to_default": "Repor para o padrão",
"response": "Resposta",
"responses": "Respostas",
@@ -346,7 +347,7 @@
"some_files_failed_to_upload": "Alguns ficheiros falharam ao carregar",
"something_went_wrong_please_try_again": "Algo correu mal. Por favor, tente novamente.",
"sort_by": "Ordenar por",
"start_free_trial": "Iniciar Teste Grátis",
"start_free_trial": "Iniciar teste grátis",
"status": "Estado",
"step_by_step_manual": "Manual passo a passo",
"styling": "Estilo",
@@ -1133,7 +1134,9 @@
"resend_invitation_email": "Reenviar Email de Convite",
"share_invite_link": "Partilhar Link de Convite",
"share_this_link_to_let_your_organization_member_join_your_organization": "Partilhe este link para permitir que o membro da sua organização se junte à sua organização:",
"test_email_sent_successfully": "Email de teste enviado com sucesso"
"test_email_sent_successfully": "Email de teste enviado com sucesso",
"use_multi_language_surveys_with_a_higher_plan": "Use inquéritos multilingues com um plano superior",
"use_multi_language_surveys_with_a_higher_plan_description": "Inquira os seus utilizadores em diferentes idiomas."
},
"notifications": {
"auto_subscribe_to_new_surveys": "Subscrever automaticamente a novos inquéritos",
@@ -1177,7 +1180,7 @@
"remove_image": "Remover imagem",
"save_the_following_backup_codes_in_a_safe_place": "Guarde os seguintes códigos de backup num local seguro.",
"scan_the_qr_code_below_with_your_authenticator_app": "Digitalize o código QR abaixo com a sua aplicação de autenticação.",
"security_description": "Gerir a sua palavra-passe e outras definições de segurança.",
"security_description": "Gerir a sua palavra-passe e outras definições de segurança como a autenticação de dois fatores (2FA).",
"two_factor_authentication": "Autenticação de dois fatores",
"two_factor_authentication_description": "Adicione uma camada extra de segurança à sua conta caso a sua palavra-passe seja roubada.",
"two_factor_authentication_enabled_please_enter_the_six_digit_code_from_your_authenticator_app": "Autenticação de dois fatores ativada. Introduza o código de seis dígitos da sua aplicação de autenticação.",
+5 -2
View File
@@ -310,6 +310,7 @@
"remove": "移除",
"reorder_and_hide_columns": "重新排序和隱藏欄位",
"report_survey": "報告問卷",
"request_trial_license": "申請試用授權",
"reset_to_default": "重設為預設值",
"response": "回應",
"responses": "回應",
@@ -1133,7 +1134,9 @@
"resend_invitation_email": "重新發送邀請電子郵件",
"share_invite_link": "分享邀請連結",
"share_this_link_to_let_your_organization_member_join_your_organization": "分享此連結以讓您的組織成員加入您的組織:",
"test_email_sent_successfully": "測試電子郵件已成功發送"
"test_email_sent_successfully": "測試電子郵件已成功發送",
"use_multi_language_surveys_with_a_higher_plan": "使用多語言問卷與更高方案",
"use_multi_language_surveys_with_a_higher_plan_description": "用不同語言調查您的用戶。"
},
"notifications": {
"auto_subscribe_to_new_surveys": "自動訂閱新問卷",
@@ -1177,7 +1180,7 @@
"remove_image": "移除圖片",
"save_the_following_backup_codes_in_a_safe_place": "將下列備份碼儲存在安全的地方。",
"scan_the_qr_code_below_with_your_authenticator_app": "使用您的驗證器應用程式掃描下方的 QR 碼。",
"security_description": "管理您的密碼和其他安全性設定。",
"security_description": "管理您的密碼和其他安全性設定,例如雙重驗證 (2FA)。",
"two_factor_authentication": "雙重驗證",
"two_factor_authentication_description": "在您的密碼被盜時,為您的帳戶新增額外的安全層。",
"two_factor_authentication_enabled_please_enter_the_six_digit_code_from_your_authenticator_app": "已啟用雙重驗證。請輸入您驗證器應用程式中的六位數程式碼。",