{/* Left navigation */}
diff --git a/frontend/editor/src/core/components/shared/Footer.tsx b/frontend/editor/src/core/components/shared/Footer.tsx
index e7037f6e4..5000ee0be 100644
--- a/frontend/editor/src/core/components/shared/Footer.tsx
+++ b/frontend/editor/src/core/components/shared/Footer.tsx
@@ -77,15 +77,6 @@ export default function Footer({
color: forceLightMode ? "#495057" : undefined,
}}
>
-
- {t("survey.nav", "Survey")}
-
,
+ },
+ ],
+ },
];
return sections;
diff --git a/frontend/editor/src/core/components/shared/config/configSections/LegalSection.tsx b/frontend/editor/src/core/components/shared/config/configSections/LegalSection.tsx
new file mode 100644
index 000000000..9106930e2
--- /dev/null
+++ b/frontend/editor/src/core/components/shared/config/configSections/LegalSection.tsx
@@ -0,0 +1,143 @@
+import React from "react";
+import { Anchor, Button, Group, Paper, Stack, Text } from "@mantine/core";
+import { useTranslation } from "react-i18next";
+import LocalIcon from "@app/components/shared/LocalIcon";
+import { useAppConfig } from "@app/contexts/AppConfigContext";
+import { useFooterInfo } from "@app/hooks/useFooterInfo";
+import { useCookieConsent } from "@app/hooks/useCookieConsent";
+
+interface LegalLink {
+ key: string;
+ label: string;
+ href: string;
+}
+
+const LegalSection: React.FC = () => {
+ const { t } = useTranslation();
+ const { config } = useAppConfig();
+ const { footerInfo } = useFooterInfo();
+
+ const analyticsEnabled =
+ config?.enableAnalytics ?? footerInfo?.analyticsEnabled ?? false;
+ const privacyPolicy = config?.privacyPolicy ?? footerInfo?.privacyPolicy;
+ const termsAndConditions =
+ config?.termsAndConditions ?? footerInfo?.termsAndConditions;
+ const accessibilityStatement =
+ config?.accessibilityStatement ?? footerInfo?.accessibilityStatement;
+ const cookiePolicy = config?.cookiePolicy ?? footerInfo?.cookiePolicy;
+ const impressum = config?.impressum ?? footerInfo?.impressum;
+
+ const { showCookiePreferences } = useCookieConsent({
+ analyticsEnabled: analyticsEnabled === true,
+ });
+
+ const isValidLink = (link?: string) => link && link.trim().length > 0;
+
+ const legalLinks: LegalLink[] = [
+ {
+ key: "privacy",
+ label: t("legal.privacy", "Privacy Policy"),
+ href: isValidLink(privacyPolicy)
+ ? privacyPolicy!
+ : "https://www.stirling.com/privacy",
+ },
+ {
+ key: "terms",
+ label: t("legal.terms", "Terms and Conditions"),
+ href: isValidLink(termsAndConditions)
+ ? termsAndConditions!
+ : "https://www.stirling.com/terms",
+ },
+ ...(isValidLink(accessibilityStatement)
+ ? [
+ {
+ key: "accessibility",
+ label: t("legal.accessibility", "Accessibility"),
+ href: accessibilityStatement!,
+ },
+ ]
+ : []),
+ ...(isValidLink(cookiePolicy)
+ ? [
+ {
+ key: "cookie",
+ label: t("legal.cookie", "Cookie Policy"),
+ href: cookiePolicy!,
+ },
+ ]
+ : []),
+ ...(isValidLink(impressum)
+ ? [
+ {
+ key: "impressum",
+ label: t("legal.impressum", "Impressum"),
+ href: impressum!,
+ },
+ ]
+ : []),
+ ];
+
+ const renderLink = (link: LegalLink) => (
+
+
+ {link.label}
+
+
+
+ );
+
+ return (
+
+
+
+
+
+ {t("settings.legal.documents.title", "Legal Documents")}
+
+
+ {t(
+ "settings.legal.documents.description",
+ "Policies and legal information for this service.",
+ )}
+
+
+ {legalLinks.map(renderLink)}
+
+
+
+ {analyticsEnabled === true && (
+
+
+
+
+ {t("legal.showCookieBanner", "Cookie Preferences")}
+
+
+ {t(
+ "settings.legal.cookiePreferences.description",
+ "Review or change your cookie consent choices.",
+ )}
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default LegalSection;
diff --git a/frontend/editor/src/core/components/shared/config/types.ts b/frontend/editor/src/core/components/shared/config/types.ts
index eaba23620..a71866d0c 100644
--- a/frontend/editor/src/core/components/shared/config/types.ts
+++ b/frontend/editor/src/core/components/shared/config/types.ts
@@ -31,6 +31,7 @@ export const VALID_NAV_KEYS = [
"adminStorageSharing",
"adminMcp",
"help",
+ "legal",
"payg",
] as const;
diff --git a/frontend/editor/src/core/hooks/useCookieConsent.ts b/frontend/editor/src/core/hooks/useCookieConsent.ts
index 0ab55f02e..8c8e38f48 100644
--- a/frontend/editor/src/core/hooks/useCookieConsent.ts
+++ b/frontend/editor/src/core/hooks/useCookieConsent.ts
@@ -2,6 +2,10 @@ import { useEffect, useState, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { BASE_PATH } from "@app/constants/app";
import { useAppConfig } from "@app/contexts/AppConfigContext";
+import {
+ Z_INDEX_COOKIE_CONSENT_BANNER,
+ Z_INDEX_COOKIE_PREFERENCES_MODAL,
+} from "@app/styles/zIndex";
import { TOUR_STATE_EVENT, type TourStatePayload } from "@app/constants/events";
import { getCookieConsentOverrides } from "@app/extensions/cookieConsentConfig";
@@ -11,6 +15,8 @@ declare global {
run: (config: any) => void;
show: (show?: boolean) => void;
hide: () => void;
+ showPreferences: () => void;
+ hidePreferences: () => void;
getCookie: (name?: string) => any;
acceptedCategory: (category: string) => boolean;
acceptedService: (serviceName: string, category: string) => boolean;
@@ -23,6 +29,16 @@ interface CookieConsentConfig {
forceLightMode?: boolean;
}
+// Shard so Mantine's scroll-lock doesn't swallow events on the consent dialog;
+// lazy because #cc-main only exists post-load.
+export const COOKIE_CONSENT_SCROLL_SHARD = {
+ get current(): HTMLElement | null {
+ return typeof document === "undefined"
+ ? null
+ : document.getElementById("cc-main");
+ },
+};
+
export const useCookieConsent = ({
analyticsEnabled = false,
forceLightMode = false,
@@ -34,6 +50,16 @@ export const useCookieConsent = ({
useEffect(() => {
if (!analyticsEnabled) return;
+ // Bridge the layering constants to the static cookie-consent stylesheets
+ document.documentElement.style.setProperty(
+ "--z-index-cookie-consent",
+ String(Z_INDEX_COOKIE_CONSENT_BANNER),
+ );
+ document.documentElement.style.setProperty(
+ "--z-index-cookie-preferences",
+ String(Z_INDEX_COOKIE_PREFERENCES_MODAL),
+ );
+
const mainCSS = document.createElement("link");
mainCSS.rel = "stylesheet";
mainCSS.href = `${BASE_PATH}/css/cookieconsent.css`;
@@ -391,7 +417,10 @@ export const useCookieConsent = ({
const showCookiePreferences = useCallback(() => {
if (isInitialized && window.CookieConsent) {
- window.CookieConsent?.show(true);
+ // Open the detailed preferences dialog directly (not the consent
+ // banner) — it gets the `.show--preferences` class, which the CSS
+ // raises above the settings modal.
+ window.CookieConsent?.showPreferences();
}
}, [isInitialized]);
diff --git a/frontend/editor/src/core/styles/zIndex.ts b/frontend/editor/src/core/styles/zIndex.ts
index 4a69ce8d5..3cec59abf 100644
--- a/frontend/editor/src/core/styles/zIndex.ts
+++ b/frontend/editor/src/core/styles/zIndex.ts
@@ -26,6 +26,11 @@ export const Z_INDEX_DRAG_BADGE = 1001;
// Modal that appears on top of config modal (e.g., restart confirmation, update modal)
export const Z_INDEX_OVER_CONFIG_MODAL = 2000;
+// Cookie-consent banner — above the chat FAB (1050), below all modals and onboarding; reaches CSS via --z-index-cookie-consent
+export const Z_INDEX_COOKIE_CONSENT_BANNER = 1060;
+// Cookie-consent preferences dialog — above the config modal it opens from; reaches CSS via --z-index-cookie-preferences
+export const Z_INDEX_COOKIE_PREFERENCES_MODAL = 1450;
+
// Sign-in modal — must appear above all app UI including config and analytics modals
export const Z_INDEX_SIGN_IN_MODAL = 9000;
diff --git a/frontend/editor/src/core/tests/stubbed/cookie-preferences.spec.ts b/frontend/editor/src/core/tests/stubbed/cookie-preferences.spec.ts
index 0e3e7edd2..975238eba 100644
--- a/frontend/editor/src/core/tests/stubbed/cookie-preferences.spec.ts
+++ b/frontend/editor/src/core/tests/stubbed/cookie-preferences.spec.ts
@@ -1,103 +1,107 @@
import { test, expect } from "@app/tests/helpers/stub-test-base";
+import { openSettings } from "@app/tests/helpers/ui-helpers";
test.describe("18. Cookie Preferences", () => {
test.describe("18.1 Cookie Banner", () => {
- test("should open and configure cookie preferences from footer", async ({
+ test("should open and configure cookie preferences from Settings → Legal", async ({
page,
}) => {
- // Step 1: Locate the "Cookie Preferences" button in the footer
- const cookieButton = page
- .locator(
- '#cookieBanner, button:has-text("Preferensi Cookie"), button:has-text("Cookie Preferences")',
- )
- .first();
+ // Analytics must be enabled for the Cookie Preferences button to render
+ await page.route("**/api/v1/ui-data/footer-info", (route) =>
+ route.fulfill({ json: { analyticsEnabled: true } }),
+ );
+ await page.goto("/");
- await page.waitForTimeout(1000);
- const isVisible = await cookieButton.isVisible().catch(() => false);
- if (!isVisible) {
- test.skip(
- true,
- "Cookie Preferences button not visible — analytics may be disabled",
- );
- return;
- }
+ // Step 1: The "Cookie Preferences" button lives in Settings → Legal
+ await openSettings(page);
+ const legalNav = page.locator('[data-tour="admin-legal-nav"]').first();
+ await expect(legalNav).toBeVisible({ timeout: 5000 });
+ await legalNav.click();
- await expect(cookieButton).toBeVisible();
+ const cookieButton = page.locator("#cookieBanner").first();
+ await expect(cookieButton).toBeVisible({ timeout: 10000 });
- // Step 2: Click the Cookie Preferences button
- await cookieButton.click({ force: true });
+ // The consent library lazy-loads when the Legal section mounts
+ await page.waitForFunction(
+ () => (window as unknown as { CookieConsent?: unknown }).CookieConsent,
+ { timeout: 10000 },
+ );
+ await page.waitForTimeout(500);
- // Step 3: Verify the cookie consent dialog opens
- // The CookieConsent library renders inside #cc-main
+ // Step 2: Click the button — it opens the detailed preferences dialog
+ // directly. No force: the click must land with the settings modal open,
+ // proving the dialog stacks above it.
+ await cookieButton.click();
+
+ // Step 3: Verify the preferences dialog opens inside #cc-main
const ccMain = page.locator("#cc-main");
- const consentDialog = ccMain.getByRole("dialog").first();
- await expect(consentDialog).toBeVisible({ timeout: 5000 });
+ const prefsDialog = ccMain.locator(".pm").first();
+ await expect(prefsDialog).toBeVisible({ timeout: 5000 });
- // Step 4: Verify options are available
- // The initial consent view shows: "Oke", "Tidak, terima kasih", "Kelola preferensi"
- const okeBtn = ccMain
- .locator('button:has-text("Oke"), button:has-text("OK")')
- .first();
- const noThanksBtn = ccMain
- .locator(
- 'button:has-text("Tidak, terima kasih"), button:has-text("No Thanks")',
- )
- .first();
- const manageBtn = ccMain
- .locator(
- 'button:has-text("Kelola preferensi"), button:has-text("Manage preferences")',
- )
+ // Step 4: Verify the dialog sits above the settings modal
+ // (Z_INDEX_CONFIG_MODAL = 1400)
+ const zIndex = await page.evaluate(() => {
+ const el = document.querySelector("#cc-main");
+ return el ? Number(getComputedStyle(el).zIndex) : -1;
+ });
+ expect(zIndex).toBeGreaterThan(1400);
+
+ // Step 5: Verify the preferences panel shows cookie categories
+ const necessaryCategory = ccMain
+ .locator("text=/Strictly Necessary|necessary/i")
.first();
+ const analyticsCategory = ccMain.locator("text=/Analytics/i").first();
+ await expect(necessaryCategory.or(analyticsCategory).first()).toBeVisible(
+ { timeout: 3000 },
+ );
- const hasOke = await okeBtn.isVisible().catch(() => false);
- const hasNoThanks = await noThanksBtn.isVisible().catch(() => false);
- const hasManage = await manageBtn.isVisible().catch(() => false);
-
- expect(hasOke || hasNoThanks || hasManage).toBe(true);
-
- // Step 5: Click "Kelola preferensi" to open the detailed preferences panel
- if (hasManage) {
- await manageBtn.click();
- await page.waitForTimeout(500);
-
- // Verify the preferences panel shows cookie categories
- const necessaryCategory = ccMain
- .locator(
- "text=/Cookie yang Sangat Diperlukan|Strictly Necessary|necessary/i",
- )
- .first();
- const analyticsCategory = ccMain
- .locator("text=/Analitik|Analytics/i")
- .first();
- await expect(
- necessaryCategory.or(analyticsCategory).first(),
- ).toBeVisible({ timeout: 3000 });
-
- // Click "Terima semua" (Accept all) or "Simpan preferensi" (Save preferences) to close
- const acceptAllBtn = ccMain
- .locator(
- 'button:has-text("Terima semua"), button:has-text("Accept all")',
- )
- .first();
- const savePrefBtn = ccMain
- .locator(
- 'button:has-text("Simpan preferensi"), button:has-text("Save preferences")',
- )
- .first();
-
- if (await acceptAllBtn.isVisible().catch(() => false)) {
- await acceptAllBtn.click();
- } else if (await savePrefBtn.isVisible().catch(() => false)) {
- await savePrefBtn.click();
- }
- } else if (hasOke) {
- await okeBtn.click();
- } else if (hasNoThanks) {
- await noThanksBtn.click();
+ // Step 6: Expand all sections and verify the dialog body wheel-scrolls
+ // while the settings modal is open (regression: the modal's
+ // react-remove-scroll lock swallowed wheel events on the dialog)
+ const expanders = ccMain.locator(
+ ".pm__section--expandable .pm__section-title",
+ );
+ const expanderCount = await expanders.count();
+ for (let i = 0; i < expanderCount; i++) {
+ await expanders.nth(i).click();
+ }
+ const dialogBody = ccMain.locator(".pm__body").first();
+ const canScroll = await dialogBody.evaluate(
+ (el) => el.scrollHeight > el.clientHeight,
+ );
+ if (canScroll) {
+ const box = await dialogBody.boundingBox();
+ await page.mouse.move(
+ box!.x + box!.width / 2,
+ box!.y + box!.height / 2,
+ );
+ await page.mouse.wheel(0, 300);
+ await expect
+ .poll(() => dialogBody.evaluate((el) => el.scrollTop), {
+ timeout: 3000,
+ })
+ .toBeGreaterThan(0);
+ await dialogBody.evaluate((el) => {
+ el.scrollTop = 0;
+ });
}
- // Step 6: Verify the dialog is dismissed
- await expect(consentDialog).toBeHidden({ timeout: 5000 });
+ // Step 7: Accept all (or save) to close — again without force
+ const acceptAllBtn = ccMain
+ .locator('button:has-text("Accept all")')
+ .first();
+ const savePrefBtn = ccMain
+ .locator('button:has-text("Save preferences")')
+ .first();
+
+ if (await acceptAllBtn.isVisible().catch(() => false)) {
+ await acceptAllBtn.click();
+ } else {
+ await savePrefBtn.click();
+ }
+
+ // Step 8: Verify the dialog is dismissed
+ await expect(prefsDialog).toBeHidden({ timeout: 5000 });
});
});
});
diff --git a/frontend/editor/src/core/tests/stubbed/main-dashboard.spec.ts b/frontend/editor/src/core/tests/stubbed/main-dashboard.spec.ts
index 11318c169..db715220d 100644
--- a/frontend/editor/src/core/tests/stubbed/main-dashboard.spec.ts
+++ b/frontend/editor/src/core/tests/stubbed/main-dashboard.spec.ts
@@ -1,5 +1,6 @@
import { test, expect } from "@app/tests/helpers/stub-test-base";
import { mockAppApis, seedCookieConsent } from "@app/tests/helpers/api-stubs";
+import { openSettings } from "@app/tests/helpers/ui-helpers";
test.describe("2. Main Dashboard / Home Page", () => {
test.beforeEach(async ({ page }) => {
@@ -87,33 +88,29 @@ test.describe("2. Main Dashboard / Home Page", () => {
});
});
- test.describe("2.4 Dashboard - Footer Links", () => {
- test("should display footer links with correct URLs", async ({ page }) => {
- await expect(page.getByText("Survey").first()).toBeVisible({
- timeout: 10000,
- });
+ test.describe("2.4 Dashboard - Legal Links", () => {
+ test("legal links live in Settings → Legal, not in a footer", async ({
+ page,
+ }) => {
+ await expect(
+ page.locator('[data-testid="config-button"]').first(),
+ ).toBeVisible({ timeout: 10000 });
+
+ // The dashboard footer (survey + legal links) was removed
+ await expect(page.locator(".footer-link")).toHaveCount(0);
+ await expect(page.getByText("Survey")).toHaveCount(0);
+
+ await openSettings(page);
+ const legalNav = page.locator('[data-tour="admin-legal-nav"]').first();
+ await expect(legalNav).toBeVisible({ timeout: 10000 });
+ await legalNav.click();
+
await expect(page.getByText("Privacy Policy").first()).toBeVisible({
timeout: 10000,
});
await expect(page.getByText(/Terms/i).first()).toBeVisible({
timeout: 10000,
});
- await expect(page.getByText("Discord").first()).toBeVisible({
- timeout: 10000,
- });
- await expect(page.getByText("GitHub").first()).toBeVisible({
- timeout: 10000,
- });
-
- const githubLink = page
- .locator('a[href*="github.com/Stirling-Tools/Stirling-PDF"]')
- .first();
- await expect(githubLink).toBeVisible();
-
- const discordLink = page
- .locator('a[href*="discord.gg/Cn8pWhQRxZ"]')
- .first();
- await expect(discordLink).toBeVisible();
});
});
diff --git a/frontend/editor/src/desktop/components/shared/config/configNavSections.tsx b/frontend/editor/src/desktop/components/shared/config/configNavSections.tsx
index e0c4da6e9..1004ffc7a 100644
--- a/frontend/editor/src/desktop/components/shared/config/configNavSections.tsx
+++ b/frontend/editor/src/desktop/components/shared/config/configNavSections.tsx
@@ -68,12 +68,16 @@ export const useConfigNavSections = (
],
};
- // In local mode only show Preferences + Connection Mode — everything else
- // requires a server and will 500 or show irrelevant admin UI.
+ // In local mode only show Preferences + Connection Mode + Legal — everything
+ // else requires a server and will 500 or show irrelevant admin UI.
if (isLocalMode) {
const result: ConfigNavSection[] = [];
if (sections.length > 0) result.push(sections[0]);
result.push(connectionModeSection);
+ const legalSection = sections.find((section) =>
+ section.items.some((item) => item.key === "legal"),
+ );
+ if (legalSection) result.push(legalSection);
return result;
}
diff --git a/frontend/editor/src/proprietary/components/chat/ChatFAB.css b/frontend/editor/src/proprietary/components/chat/ChatFAB.css
index e16456b26..efa55ea67 100644
--- a/frontend/editor/src/proprietary/components/chat/ChatFAB.css
+++ b/frontend/editor/src/proprietary/components/chat/ChatFAB.css
@@ -12,7 +12,7 @@
.chat-fab-trigger {
position: absolute;
right: 16px;
- bottom: calc(var(--footer-height, 2rem) + 16px);
+ bottom: 16px;
pointer-events: auto;
opacity: 1;
/* Include box-shadow so the ChatFABButton hover shadow still animates */
diff --git a/frontend/editor/src/proprietary/components/chat/ChatFAB.tsx b/frontend/editor/src/proprietary/components/chat/ChatFAB.tsx
index f03c903d4..382094cb6 100644
--- a/frontend/editor/src/proprietary/components/chat/ChatFAB.tsx
+++ b/frontend/editor/src/proprietary/components/chat/ChatFAB.tsx
@@ -15,8 +15,7 @@ const PANEL_HEIGHT_PX = 520;
const PANEL_MIN_WIDTH_PX = 300;
const PANEL_MIN_HEIGHT_PX = 380;
const FAB_GAP_PX = 16;
-// footer-height (2rem = 32px) + gap so the panel clears the footer
-const FAB_BOTTOM_OFFSET_PX = 32 + FAB_GAP_PX;
+const FAB_BOTTOM_OFFSET_PX = FAB_GAP_PX;
const RESET_MS = 380;
const RESET_EASING = "cubic-bezier(0.32, 0.72, 0, 1)";
diff --git a/frontend/editor/src/saas/components/shared/AppConfigModal.tsx b/frontend/editor/src/saas/components/shared/AppConfigModal.tsx
index dec671fbc..4f9a60a7c 100644
--- a/frontend/editor/src/saas/components/shared/AppConfigModal.tsx
+++ b/frontend/editor/src/saas/components/shared/AppConfigModal.tsx
@@ -9,6 +9,7 @@ import Overview from "@app/components/shared/config/configSections/Overview";
import { createSaasConfigNavSections } from "@app/components/shared/config/saasConfigNavSections";
import { NavKey } from "@app/components/shared/config/types";
import { withBasePath } from "@app/constants/app";
+import { COOKIE_CONSENT_SCROLL_SHARD } from "@app/hooks/useCookieConsent";
import "@app/components/shared/AppConfigModal.css";
import {
Z_INDEX_OVER_FULLSCREEN_SURFACE,
@@ -167,6 +168,7 @@ const AppConfigModal: React.FC
= ({ opened, onClose }) => {
overlayProps={{ opacity: 0.35, blur: 2 }}
padding={0}
fullScreen={isMobile}
+ removeScrollProps={{ shards: [COOKIE_CONSENT_SCROLL_SHARD] }}
// Hidden (not closed) while a child overlay like the PAYG UpgradeModal
// is up — see the appConfig:overlay listener above. The focus trap and
// escape/outside-close must release too: the trap would steal focus
diff --git a/frontend/editor/src/saas/components/shared/config/saasConfigNavSections.tsx b/frontend/editor/src/saas/components/shared/config/saasConfigNavSections.tsx
index c0a451a69..dbe40e7d8 100644
--- a/frontend/editor/src/saas/components/shared/config/saasConfigNavSections.tsx
+++ b/frontend/editor/src/saas/components/shared/config/saasConfigNavSections.tsx
@@ -10,6 +10,7 @@ import PasswordSecurity from "@app/components/shared/config/configSections/Passw
import ApiKeys from "@app/components/shared/config/configSections/ApiKeys";
import Plan from "@app/components/shared/config/configSections/Plan";
import McpSection from "@app/components/shared/config/configSections/McpSection";
+import LegalSection from "@app/components/shared/config/configSections/LegalSection";
import TeamSection from "@app/components/shared/config/configSections/TeamSection";
type OverviewComponent = React.ComponentType<{ onLogoutClick: () => void }>;
@@ -148,6 +149,36 @@ function appendMcpSection(
);
}
+// Legal links (privacy policy, terms, etc.). Shown to anonymous users too —
+// it's public information.
+function appendLegalSection(
+ sections: ConfigNavSection[],
+ t: TFunction<"translation", undefined>,
+): ConfigNavSection[] {
+ const hasLegal = sections.some((section) =>
+ section.items.some((item) => item.key === "legal"),
+ );
+
+ if (hasLegal) {
+ return sections;
+ }
+
+ return [
+ ...sections,
+ {
+ title: t("settings.legal.title", "Legal"),
+ items: [
+ {
+ key: "legal" as const,
+ label: t("settings.legal.label", "Legal"),
+ icon: "gavel-rounded",
+ component: ,
+ },
+ ],
+ },
+ ];
+}
+
export function createSaasConfigNavSections(
Overview: OverviewComponent,
onLogoutClick: () => void,
@@ -209,6 +240,8 @@ export function createSaasConfigNavSections(
sections = appendBillingSection(sections, t);
}
+ sections = appendLegalSection(sections, t);
+
if (isDev) {
console.debug("[AppConfigModal] SaaS navigation sections", sections);
}