diff --git a/apps/web/app/(app)/workspaces/[workspaceId]/components/MainNavigation.tsx b/apps/web/app/(app)/workspaces/[workspaceId]/components/MainNavigation.tsx
index 686359551c..3b44229ea6 100644
--- a/apps/web/app/(app)/workspaces/[workspaceId]/components/MainNavigation.tsx
+++ b/apps/web/app/(app)/workspaces/[workspaceId]/components/MainNavigation.tsx
@@ -72,6 +72,9 @@ interface NavigationProps {
organizationWorkspacesLimit: number;
isLicenseActive: boolean;
isAccessControlAllowed: boolean;
+ isUnifyFeedbackAllowed: boolean;
+ isFeedbackDirectoriesAllowed: boolean;
+ isDashboardsAllowed: boolean;
}
export const MainNavigation = ({
@@ -86,6 +89,9 @@ export const MainNavigation = ({
organizationWorkspacesLimit,
isLicenseActive,
isAccessControlAllowed,
+ isUnifyFeedbackAllowed,
+ isFeedbackDirectoriesAllowed,
+ isDashboardsAllowed,
}: NavigationProps) => {
const router = useRouter();
const pathname = usePathname();
@@ -158,7 +164,7 @@ export const MainNavigation = ({
href: `/workspaces/${workspace.id}/unify/feedback-records`,
icon: MessageSquareTextIcon,
isActive: pathname?.includes("/unify/feedback-records"),
- isHidden: false,
+ isHidden: !isUnifyFeedbackAllowed,
disabled: isMembershipPending || isBilling,
},
{
@@ -166,13 +172,13 @@ export const MainNavigation = ({
href: `/workspaces/${workspace.id}/dashboards`,
icon: BarChart3Icon,
isActive: pathname?.includes("/dashboards") || pathname?.includes("/charts"),
- isHidden: false,
+ isHidden: !isDashboardsAllowed,
disabled: isMembershipPending || isBilling,
},
],
},
],
- [t, workspace.id, pathname, isMembershipPending, isBilling]
+ [t, workspace.id, pathname, isMembershipPending, isBilling, isUnifyFeedbackAllowed, isDashboardsAllowed]
);
const settingsNavigationItem = useMemo(
@@ -464,6 +470,8 @@ export const MainNavigation = ({
isFormbricksCloud={isFormbricksCloud}
isCollapsed={false}
isTextVisible={false}
+ isUnifyFeedbackAllowed={isUnifyFeedbackAllowed}
+ isFeedbackDirectoriesAllowed={isFeedbackDirectoriesAllowed}
workspaces={workspaces}
isLoadingWorkspaces={isLoadingWorkspaces}
onWorkspaceChange={handleSettingsWorkspaceChange}
@@ -506,34 +514,36 @@ export const MainNavigation = ({
{/* Main Nav */}
- {mainNavigationSections.map((section) => (
- -
- {!isCollapsed && !isTextVisible && (
-
- {section.name}
-
- )}
-
-
- {section.items.map(
- (item) =>
- !item.isHidden && (
-
-
-
- )
+ {mainNavigationSections
+ .filter((section) => section.items.some((item) => !item.isHidden))
+ .map((section) => (
+ -
+ {!isCollapsed && !isTextVisible && (
+
+ {section.name}
+
)}
-
-
- ))}
+
+
+ {section.items.map(
+ (item) =>
+ !item.isHidden && (
+
+
+
+ )
+ )}
+
+
+ ))}
-
diff --git a/apps/web/app/(app)/workspaces/[workspaceId]/components/SettingsSidebarContent.tsx b/apps/web/app/(app)/workspaces/[workspaceId]/components/SettingsSidebarContent.tsx
index 3a8d2624a2..88d6a8d5f6 100644
--- a/apps/web/app/(app)/workspaces/[workspaceId]/components/SettingsSidebarContent.tsx
+++ b/apps/web/app/(app)/workspaces/[workspaceId]/components/SettingsSidebarContent.tsx
@@ -45,6 +45,8 @@ interface SettingsSidebarContentProps {
isFormbricksCloud: boolean;
isCollapsed: boolean;
isTextVisible: boolean;
+ isUnifyFeedbackAllowed: boolean;
+ isFeedbackDirectoriesAllowed: boolean;
// Workspace switcher
workspaces: { id: string; name: string }[];
isLoadingWorkspaces: boolean;
@@ -231,6 +233,8 @@ export const SettingsSidebarContent = ({
isFormbricksCloud,
isCollapsed,
isTextVisible,
+ isUnifyFeedbackAllowed,
+ isFeedbackDirectoriesAllowed,
workspaces,
isLoadingWorkspaces,
onWorkspaceChange,
@@ -283,6 +287,7 @@ export const SettingsSidebarContent = ({
href: `${basePath}/workspace/feedback-sources`,
icon: ,
disabled: isBilling,
+ hidden: !isUnifyFeedbackAllowed,
},
{
id: "integrations",
@@ -334,7 +339,7 @@ export const SettingsSidebarContent = ({
label: t("workspace.settings.feedback_directories.nav_label"),
href: `${basePath}/organization/feedback-directories`,
icon: ,
- hidden: isMember,
+ hidden: isMember || !isFeedbackDirectoriesAllowed,
},
{
id: "org-api-keys",
diff --git a/apps/web/app/(app)/workspaces/[workspaceId]/components/WorkspaceLayout.tsx b/apps/web/app/(app)/workspaces/[workspaceId]/components/WorkspaceLayout.tsx
index f478802821..f4dcd61d26 100644
--- a/apps/web/app/(app)/workspaces/[workspaceId]/components/WorkspaceLayout.tsx
+++ b/apps/web/app/(app)/workspaces/[workspaceId]/components/WorkspaceLayout.tsx
@@ -26,6 +26,9 @@ export const WorkspaceLayout = async ({ layoutData, children }: WorkspaceLayoutP
membership,
workspace, // Current workspace details
isAccessControlAllowed,
+ isUnifyFeedbackAllowed,
+ isFeedbackDirectoriesAllowed,
+ isDashboardsAllowed,
workspacePermission,
license,
responseCount,
@@ -71,6 +74,9 @@ export const WorkspaceLayout = async ({ layoutData, children }: WorkspaceLayoutP
organizationWorkspacesLimit={organizationWorkspacesLimit}
isLicenseActive={active}
isAccessControlAllowed={isAccessControlAllowed}
+ isUnifyFeedbackAllowed={isUnifyFeedbackAllowed}
+ isFeedbackDirectoriesAllowed={isFeedbackDirectoriesAllowed}
+ isDashboardsAllowed={isDashboardsAllowed}
/>
}>
-) {
- const t = await getTranslate();
- const params = await props.params;
-
- const { isOwner, isManager, hasReadAccess, hasReadWriteAccess, hasManageAccess, session } =
- await getWorkspaceAuth(params.workspaceId);
-
- if (!session) {
- throw new Error(t("common.session_not_found"));
- }
-
- const hasAccess = isOwner || isManager || hasReadAccess || hasReadWriteAccess || hasManageAccess;
- if (!hasAccess) {
- return notFound();
- }
-
- const directories = await getFeedbackDirectoriesByWorkspaceId(params.workspaceId);
- const directoryMap = Object.fromEntries(directories.map((directory) => [directory.id, directory.name]));
-
- return ;
-}
+export { UnifyTopicsSubtopicsPage as default } from "@/modules/ee/unify-feedback/topics-subtopics/page";
diff --git a/apps/web/modules/ee/analysis/charts/actions.ts b/apps/web/modules/ee/analysis/charts/actions.ts
index 2569e91fdb..3e49c14a59 100644
--- a/apps/web/modules/ee/analysis/charts/actions.ts
+++ b/apps/web/modules/ee/analysis/charts/actions.ts
@@ -5,6 +5,7 @@ import { Output, generateText } from "ai";
import { z } from "zod";
import { type TChartQuery, ZChartQuery } from "@formbricks/types/analysis";
import { ZId } from "@formbricks/types/common";
+import { OperationNotAllowedError } from "@formbricks/types/errors";
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context";
import { executeQuery } from "@/modules/ee/analysis/api/lib/cube-client";
@@ -21,6 +22,14 @@ import { checkWorkspaceAccess, verifyFeedbackDirectoryAccess } from "@/modules/e
import { generateSchemaContext } from "@/modules/ee/analysis/lib/ai-schema-context";
import { ZChartCreateInput, ZChartType, ZChartUpdateInput } from "@/modules/ee/analysis/types/analysis";
import { withAuditLogging } from "@/modules/ee/audit-logs/lib/handler";
+import { getIsDashboardsEnabled } from "@/modules/ee/license-check/lib/utils";
+
+const checkDashboardsEnabled = async (organizationId: string) => {
+ const isAllowed = await getIsDashboardsEnabled(organizationId);
+ if (!isAllowed) {
+ throw new OperationNotAllowedError("Dashboards are not enabled for this organization");
+ }
+};
/** Client-facing chart input (workspaceId and createdBy are resolved server-side) */
const ZChartCreateInputClient = ZChartCreateInput.omit({ workspaceId: true, createdBy: true });
@@ -46,6 +55,7 @@ export const createChartAction = authenticatedActionClient.inputSchema(ZCreateCh
parsedInput.workspaceId,
"readWrite"
);
+ await checkDashboardsEnabled(organizationId);
const chart = await createChart({
...parsedInput.chartInput,
@@ -84,6 +94,7 @@ export const updateChartAction = authenticatedActionClient.inputSchema(ZUpdateCh
parsedInput.workspaceId,
"readWrite"
);
+ await checkDashboardsEnabled(organizationId);
const { chart, updatedChart } = await updateChart(
parsedInput.chartId,
@@ -122,6 +133,7 @@ export const duplicateChartAction = authenticatedActionClient.inputSchema(ZDupli
parsedInput.workspaceId,
"readWrite"
);
+ await checkDashboardsEnabled(organizationId);
const duplicatedChart = await duplicateChart(parsedInput.chartId, workspaceId, ctx.user.id);
@@ -155,6 +167,7 @@ export const deleteChartAction = authenticatedActionClient.inputSchema(ZDeleteCh
parsedInput.workspaceId,
"readWrite"
);
+ await checkDashboardsEnabled(organizationId);
const chart = await deleteChart(parsedInput.chartId, workspaceId);
@@ -182,7 +195,12 @@ export const getChartAction = authenticatedActionClient
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer;
}) => {
- const { workspaceId } = await checkWorkspaceAccess(ctx.user.id, parsedInput.workspaceId, "read");
+ const { organizationId, workspaceId } = await checkWorkspaceAccess(
+ ctx.user.id,
+ parsedInput.workspaceId,
+ "read"
+ );
+ await checkDashboardsEnabled(organizationId);
return getChart(parsedInput.chartId, workspaceId);
}
@@ -202,7 +220,12 @@ export const getChartsAction = authenticatedActionClient
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer;
}) => {
- const { workspaceId } = await checkWorkspaceAccess(ctx.user.id, parsedInput.workspaceId, "read");
+ const { organizationId, workspaceId } = await checkWorkspaceAccess(
+ ctx.user.id,
+ parsedInput.workspaceId,
+ "read"
+ );
+ await checkDashboardsEnabled(organizationId);
const charts = await getCharts(workspaceId);
return charts;
}
@@ -226,7 +249,12 @@ export const executeQueryAction = authenticatedActionClient
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer;
}) => {
- const { workspaceId } = await checkWorkspaceAccess(ctx.user.id, parsedInput.workspaceId, "read");
+ const { organizationId, workspaceId } = await checkWorkspaceAccess(
+ ctx.user.id,
+ parsedInput.workspaceId,
+ "read"
+ );
+ await checkDashboardsEnabled(organizationId);
await verifyFeedbackDirectoryAccess(parsedInput.feedbackDirectoryId, workspaceId);
validateQueryMembers(parsedInput.query);
@@ -294,7 +322,12 @@ export const generateAIChartAction = authenticatedActionClient
ctx: AuthenticatedActionClientCtx;
parsedInput: z.infer;
}) => {
- const { workspaceId } = await checkWorkspaceAccess(ctx.user.id, parsedInput.workspaceId, "read");
+ const { organizationId, workspaceId } = await checkWorkspaceAccess(
+ ctx.user.id,
+ parsedInput.workspaceId,
+ "read"
+ );
+ await checkDashboardsEnabled(organizationId);
await verifyFeedbackDirectoryAccess(parsedInput.feedbackDirectoryId, workspaceId);
if (!process.env.OPENAI_API_KEY) {
diff --git a/apps/web/modules/ee/analysis/charts/components/charts-list-page.tsx b/apps/web/modules/ee/analysis/charts/components/charts-list-page.tsx
index ce22f39f83..b3c7ccaf54 100644
--- a/apps/web/modules/ee/analysis/charts/components/charts-list-page.tsx
+++ b/apps/web/modules/ee/analysis/charts/components/charts-list-page.tsx
@@ -1,5 +1,6 @@
import { use } from "react";
import { getConnectorsWithMappings } from "@/lib/connector/service";
+import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
import { getTranslate } from "@/lingodotdev/server";
import { ChartsList } from "@/modules/ee/analysis/charts/components/charts-list";
import { CreateChartButton } from "@/modules/ee/analysis/charts/components/create-chart-button";
@@ -9,6 +10,8 @@ import { NoFeedbackRecordsState } from "@/modules/ee/analysis/components/no-feed
import { hasFeedbackRecordsInDirectories } from "@/modules/ee/analysis/lib/feedback-records";
import type { TChartWithCreator } from "@/modules/ee/analysis/types/analysis";
import { getFeedbackDirectoriesByWorkspaceId } from "@/modules/ee/feedback-directory/lib/feedback-directory";
+import { getIsDashboardsEnabled } from "@/modules/ee/license-check/lib/utils";
+import { UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
import { getWorkspaceAuth } from "@/modules/workspaces/lib/utils";
interface ChartsListContentProps {
@@ -37,7 +40,37 @@ interface ChartsListPageProps {
export async function ChartsListPage({ workspaceId }: Readonly) {
const t = await getTranslate();
- const { isReadOnly } = await getWorkspaceAuth(workspaceId);
+ const { isReadOnly, organization } = await getWorkspaceAuth(workspaceId);
+
+ const isDashboardsAllowed = await getIsDashboardsEnabled(organization.id);
+ if (!isDashboardsAllowed) {
+ return (
+
+
+
+
+
+ );
+ }
+
const [directories, connectors] = await Promise.all([
getFeedbackDirectoriesByWorkspaceId(workspaceId),
getConnectorsWithMappings(workspaceId),
diff --git a/apps/web/modules/ee/analysis/dashboards/pages/dashboard-detail-page.tsx b/apps/web/modules/ee/analysis/dashboards/pages/dashboard-detail-page.tsx
index de921853b1..9afd9e475d 100644
--- a/apps/web/modules/ee/analysis/dashboards/pages/dashboard-detail-page.tsx
+++ b/apps/web/modules/ee/analysis/dashboards/pages/dashboard-detail-page.tsx
@@ -50,25 +50,27 @@ export async function DashboardDetailPage({
if (!isDashboardsAllowed) {
return (
-
+
+
+
);
}
diff --git a/apps/web/modules/ee/analysis/dashboards/pages/dashboards-list-page.tsx b/apps/web/modules/ee/analysis/dashboards/pages/dashboards-list-page.tsx
index 7f852cfa76..4d0de8d7af 100644
--- a/apps/web/modules/ee/analysis/dashboards/pages/dashboards-list-page.tsx
+++ b/apps/web/modules/ee/analysis/dashboards/pages/dashboards-list-page.tsx
@@ -41,25 +41,27 @@ export const DashboardsListPage = async ({ workspaceId }: Readonly
-
+
+
+
);
}
diff --git a/apps/web/modules/ee/feedback-directory/page.tsx b/apps/web/modules/ee/feedback-directory/page.tsx
index 90db034a3a..cc02b56b03 100644
--- a/apps/web/modules/ee/feedback-directory/page.tsx
+++ b/apps/web/modules/ee/feedback-directory/page.tsx
@@ -19,25 +19,27 @@ export const FeedbackDirectoriesPage = async (props: { params: Promise<{ workspa
if (!isFeedbackDirectoriesAllowed) {
return (
-
+
+
+
);
}
diff --git a/apps/web/modules/ee/unify-feedback/page.tsx b/apps/web/modules/ee/unify-feedback/page.tsx
index 3c6bb3e63c..8886ccc82d 100644
--- a/apps/web/modules/ee/unify-feedback/page.tsx
+++ b/apps/web/modules/ee/unify-feedback/page.tsx
@@ -6,9 +6,11 @@ import { getFeedbackDirectoriesByWorkspaceId } from "@/modules/ee/feedback-direc
import { getIsUnifyFeedbackEnabled } from "@/modules/ee/license-check/lib/utils";
import { listFeedbackRecords } from "@/modules/hub/service";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
+import { PageHeader } from "@/modules/ui/components/page-header";
import { UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
import { getWorkspaceAuth } from "@/modules/workspaces/lib/utils";
import { FeedbackRecordsPageClient } from "./components/feedback-records-page-client";
+import { UnifyConfigNavigation } from "./components/unify-config-navigation";
const INITIAL_PAGE_SIZE = 50;
@@ -35,25 +37,30 @@ export const UnifyFeedbackRecordsPage = async (
if (!isUnifyFeedbackAllowed) {
return (
-
+
+
+
+
+
+
);
}
diff --git a/apps/web/modules/ee/unify-feedback/sources/components/csv-connector-ui.tsx b/apps/web/modules/ee/unify-feedback/sources/components/csv-connector-ui.tsx
index ab62fc7750..178f611f66 100644
--- a/apps/web/modules/ee/unify-feedback/sources/components/csv-connector-ui.tsx
+++ b/apps/web/modules/ee/unify-feedback/sources/components/csv-connector-ui.tsx
@@ -142,7 +142,7 @@ export function CsvConnectorUI({
{csvPreview[0]?.map((header, i) => (
- |
+ |
{header}
|
))}
@@ -150,9 +150,11 @@ export function CsvConnectorUI({
{csvPreview.slice(1, 4).map((row, rowIndex) => (
-
+
{row.map((cell, cellIndex) => (
- |
+ |
{cell || —}
|
))}
diff --git a/apps/web/modules/ee/unify-feedback/sources/components/mapping-field.tsx b/apps/web/modules/ee/unify-feedback/sources/components/mapping-field.tsx
index f3dd3787b5..51f5ebffe7 100644
--- a/apps/web/modules/ee/unify-feedback/sources/components/mapping-field.tsx
+++ b/apps/web/modules/ee/unify-feedback/sources/components/mapping-field.tsx
@@ -20,6 +20,12 @@ interface DraggableSourceFieldProps {
isMapped: boolean;
}
+const getSourceFieldStateClass = (isDragging: boolean, isMapped: boolean): string => {
+ if (isDragging) return "border-brand-dark bg-slate-100 opacity-50";
+ if (isMapped) return "border-green-300 bg-green-50 text-green-800";
+ return "border-slate-200 bg-white hover:border-slate-300";
+};
+
export const DraggableSourceField = ({ field, isMapped }: DraggableSourceFieldProps) => {
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
id: field.id,
@@ -38,13 +44,7 @@ export const DraggableSourceField = ({ field, isMapped }: DraggableSourceFieldPr
style={style}
{...listeners}
{...attributes}
- className={`flex cursor-grab items-center gap-2 rounded-md border p-2 text-sm transition-colors ${
- isDragging
- ? "border-brand-dark bg-slate-100 opacity-50"
- : isMapped
- ? "border-green-300 bg-green-50 text-green-800"
- : "border-slate-200 bg-white hover:border-slate-300"
- }`}>
+ className={`flex cursor-grab items-center gap-2 rounded-md border p-2 text-sm transition-colors ${getSourceFieldStateClass(isDragging, isMapped)}`}>
{field.name}
diff --git a/apps/web/modules/ee/unify-feedback/sources/page.tsx b/apps/web/modules/ee/unify-feedback/sources/page.tsx
index 79e0c53c98..7bd9f21919 100644
--- a/apps/web/modules/ee/unify-feedback/sources/page.tsx
+++ b/apps/web/modules/ee/unify-feedback/sources/page.tsx
@@ -33,25 +33,27 @@ export const WorkspaceFeedbackSourcesPage = async (
if (!isUnifyFeedbackAllowed) {
return (
-
+
+
+
);
}
diff --git a/apps/web/app/(app)/workspaces/[workspaceId]/unify/topics-subtopics/actions.ts b/apps/web/modules/ee/unify-feedback/topics-subtopics/actions.ts
similarity index 91%
rename from apps/web/app/(app)/workspaces/[workspaceId]/unify/topics-subtopics/actions.ts
rename to apps/web/modules/ee/unify-feedback/topics-subtopics/actions.ts
index ed0b0c3dc7..8715035430 100644
--- a/apps/web/app/(app)/workspaces/[workspaceId]/unify/topics-subtopics/actions.ts
+++ b/apps/web/modules/ee/unify-feedback/topics-subtopics/actions.ts
@@ -2,11 +2,13 @@
import { z } from "zod";
import { ZId } from "@formbricks/types/common";
+import { OperationNotAllowedError } from "@formbricks/types/errors";
import { authenticatedActionClient } from "@/lib/utils/action-client";
import { checkAuthorizationUpdated } from "@/lib/utils/action-client/action-client-middleware";
import { AuthenticatedActionClientCtx } from "@/lib/utils/action-client/types/context";
import { getOrganizationIdFromWorkspaceId } from "@/lib/utils/helper";
import { getFeedbackDirectoriesByWorkspaceId } from "@/modules/ee/feedback-directory/lib/feedback-directory";
+import { getIsUnifyFeedbackEnabled } from "@/modules/ee/license-check/lib/utils";
import { semanticSearchFeedbackRecords } from "@/modules/hub/service";
import type { SemanticSearchResultItem } from "@/modules/hub/types";
@@ -33,6 +35,10 @@ export type TTopicsPreviewSearchActionResult = {
const ensureReadAccess = async (userId: string, workspaceId: string): Promise
=> {
const organizationId = await getOrganizationIdFromWorkspaceId(workspaceId);
+ const isUnifyFeedbackAllowed = await getIsUnifyFeedbackEnabled(organizationId);
+ if (!isUnifyFeedbackAllowed) {
+ throw new OperationNotAllowedError("Unify Feedback is not enabled for this organization");
+ }
await checkAuthorizationUpdated({
userId,
organizationId,
diff --git a/apps/web/app/(app)/workspaces/[workspaceId]/unify/topics-subtopics/components/topics-subtopics-preview.tsx b/apps/web/modules/ee/unify-feedback/topics-subtopics/components/topics-subtopics-preview.tsx
similarity index 98%
rename from apps/web/app/(app)/workspaces/[workspaceId]/unify/topics-subtopics/components/topics-subtopics-preview.tsx
rename to apps/web/modules/ee/unify-feedback/topics-subtopics/components/topics-subtopics-preview.tsx
index 007b48ae47..7dfef42107 100644
--- a/apps/web/app/(app)/workspaces/[workspaceId]/unify/topics-subtopics/components/topics-subtopics-preview.tsx
+++ b/apps/web/modules/ee/unify-feedback/topics-subtopics/components/topics-subtopics-preview.tsx
@@ -10,7 +10,7 @@ import { Button } from "@/modules/ui/components/button";
import { Input } from "@/modules/ui/components/input";
import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
import { PageHeader } from "@/modules/ui/components/page-header";
-import { UnifyConfigNavigation } from "../../components/UnifyConfigNavigation";
+import { UnifyConfigNavigation } from "../../components/unify-config-navigation";
import { semanticSearchFeedbackRecordsAction } from "../actions";
import type { TTopicsPreviewSearchResult } from "../actions";
diff --git a/apps/web/modules/ee/unify-feedback/topics-subtopics/page.tsx b/apps/web/modules/ee/unify-feedback/topics-subtopics/page.tsx
new file mode 100644
index 0000000000..5cbda9733f
--- /dev/null
+++ b/apps/web/modules/ee/unify-feedback/topics-subtopics/page.tsx
@@ -0,0 +1,67 @@
+import { notFound } from "next/navigation";
+import { IS_FORMBRICKS_CLOUD } from "@/lib/constants";
+import { getTranslate } from "@/lingodotdev/server";
+import { getFeedbackDirectoriesByWorkspaceId } from "@/modules/ee/feedback-directory/lib/feedback-directory";
+import { getIsUnifyFeedbackEnabled } from "@/modules/ee/license-check/lib/utils";
+import { UnifyConfigNavigation } from "@/modules/ee/unify-feedback/components/unify-config-navigation";
+import { PageContentWrapper } from "@/modules/ui/components/page-content-wrapper";
+import { PageHeader } from "@/modules/ui/components/page-header";
+import { UpgradePrompt } from "@/modules/ui/components/upgrade-prompt";
+import { getWorkspaceAuth } from "@/modules/workspaces/lib/utils";
+import { TopicsSubtopicsPreview } from "./components/topics-subtopics-preview";
+
+export const UnifyTopicsSubtopicsPage = async (
+ props: Readonly<{ params: Promise<{ workspaceId: string }> }>
+) => {
+ const t = await getTranslate();
+ const params = await props.params;
+
+ const { isOwner, isManager, hasReadAccess, hasReadWriteAccess, hasManageAccess, session, organization } =
+ await getWorkspaceAuth(params.workspaceId);
+
+ if (!session) {
+ throw new Error(t("common.session_not_found"));
+ }
+
+ const hasAccess = isOwner || isManager || hasReadAccess || hasReadWriteAccess || hasManageAccess;
+ if (!hasAccess) {
+ return notFound();
+ }
+
+ const isUnifyFeedbackAllowed = await getIsUnifyFeedbackEnabled(organization.id);
+ if (!isUnifyFeedbackAllowed) {
+ return (
+
+
+
+
+
+
+
+
+ );
+ }
+
+ const directories = await getFeedbackDirectoriesByWorkspaceId(params.workspaceId);
+ const directoryMap = Object.fromEntries(directories.map((directory) => [directory.id, directory.name]));
+
+ return ;
+};
diff --git a/apps/web/modules/workspaces/lib/utils.ts b/apps/web/modules/workspaces/lib/utils.ts
index 0f6a3ce0b3..fbcb0ee7a1 100644
--- a/apps/web/modules/workspaces/lib/utils.ts
+++ b/apps/web/modules/workspaces/lib/utils.ts
@@ -21,7 +21,12 @@ import { getWorkspace } from "@/lib/workspace/service";
import { getTranslate } from "@/lingodotdev/server";
import { authOptions } from "@/modules/auth/lib/authOptions";
import { getEnterpriseLicense } from "@/modules/ee/license-check/lib/license";
-import { getAccessControlPermission } from "@/modules/ee/license-check/lib/utils";
+import {
+ getAccessControlPermission,
+ getIsDashboardsEnabled,
+ getIsFeedbackDirectoriesEnabled,
+ getIsUnifyFeedbackEnabled,
+} from "@/modules/ee/license-check/lib/utils";
import { getWorkspacePermissionByUserId } from "@/modules/ee/teams/lib/roles";
import { getTeamPermissionFlags } from "@/modules/ee/teams/utils/teams";
import { TWorkspaceAuth, TWorkspaceLayoutData } from "@/modules/workspaces/types/workspace-auth";
@@ -278,8 +283,18 @@ export const getWorkspaceLayoutData = reactCache(
throw new AuthorizationError(t("common.membership_not_found"));
}
- const [isAccessControlAllowed, workspacePermission, license] = await Promise.all([
+ const [
+ isAccessControlAllowed,
+ isUnifyFeedbackAllowed,
+ isFeedbackDirectoriesAllowed,
+ isDashboardsAllowed,
+ workspacePermission,
+ license,
+ ] = await Promise.all([
getAccessControlPermission(organization.id),
+ getIsUnifyFeedbackEnabled(organization.id),
+ getIsFeedbackDirectoriesEnabled(organization.id),
+ getIsDashboardsEnabled(organization.id),
getWorkspacePermissionByUserId(userId, workspace.id),
getEnterpriseLicense(),
]);
@@ -296,6 +311,9 @@ export const getWorkspaceLayoutData = reactCache(
organization,
membership,
isAccessControlAllowed,
+ isUnifyFeedbackAllowed,
+ isFeedbackDirectoriesAllowed,
+ isDashboardsAllowed,
workspacePermission,
license,
responseCount,
diff --git a/apps/web/modules/workspaces/types/workspace-auth.ts b/apps/web/modules/workspaces/types/workspace-auth.ts
index d3882e4ecc..e56489d463 100644
--- a/apps/web/modules/workspaces/types/workspace-auth.ts
+++ b/apps/web/modules/workspaces/types/workspace-auth.ts
@@ -55,6 +55,9 @@ export type TWorkspaceLayoutData = {
organization: TOrganization;
membership: TMembership;
isAccessControlAllowed: boolean;
+ isUnifyFeedbackAllowed: boolean;
+ isFeedbackDirectoriesAllowed: boolean;
+ isDashboardsAllowed: boolean;
workspacePermission: TTeamPermission | null;
license: TEnterpriseLicense;
responseCount: number;