Merge remote-tracking branch 'origin/main' into SaaS

# Conflicts:
#	frontend/editor/src/core/components/shared/AppConfigModal.tsx
This commit is contained in:
Anthony Stirling
2026-06-10 10:51:06 +01:00
143 changed files with 28540 additions and 984 deletions
@@ -1,19 +1,18 @@
import { Text, Button } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { useAuth } from "@app/auth/UseSession";
import { useNavigate } from "react-router-dom";
export function OverviewHeader() {
const { t } = useTranslation();
const { signOut, user } = useAuth();
const navigate = useNavigate();
const handleLogout = async () => {
try {
await signOut();
navigate("/login");
} catch (error) {
console.error("Logout error:", error);
} finally {
window.location.assign("/login");
}
};
@@ -71,10 +71,6 @@ export const useConfigNavSections = (
"settings.tooltips.enableLoginFirst",
"Enable login mode first",
);
const requiresEnterpriseTooltip = t(
"settings.tooltips.requiresEnterprise",
"Requires Enterprise license",
);
// Workspace
sections.push({
@@ -199,10 +195,10 @@ export const useConfigNavSections = (
label: t("settings.licensingAnalytics.audit", "Audit"),
icon: "fact-check-rounded",
component: <AdminAuditSection />,
disabled: !runningEE || requiresLogin,
disabledTooltip: requiresLogin
? enableLoginTooltip
: requiresEnterpriseTooltip,
// Non-Enterprise users can still click in: AdminAuditSection
// renders a demo preview when `!hasEnterpriseLicense`.
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined,
},
{
key: "adminUsage",
@@ -212,10 +208,9 @@ export const useConfigNavSections = (
),
icon: "analytics-rounded",
component: <AdminUsageSection />,
disabled: !runningEE || requiresLogin,
disabledTooltip: requiresLogin
? enableLoginTooltip
: requiresEnterpriseTooltip,
// Same demo-preview story as adminAudit above.
disabled: requiresLogin,
disabledTooltip: requiresLogin ? enableLoginTooltip : undefined,
},
],
});
@@ -441,20 +436,22 @@ export const createConfigNavSections = (
label: "Audit",
icon: "fact-check-rounded",
component: <AdminAuditSection />,
disabled: !runningEE || requiresLogin,
// Non-Enterprise users can click in to see the demo preview.
disabled: requiresLogin,
disabledTooltip: requiresLogin
? "Enable login mode first"
: "Requires Enterprise license",
: undefined,
},
{
key: "adminUsage",
label: "Usage Analytics",
icon: "analytics-rounded",
component: <AdminUsageSection />,
disabled: !runningEE || requiresLogin,
// Non-Enterprise users can click in to see the demo preview.
disabled: requiresLogin,
disabledTooltip: requiresLogin
? "Enable login mode first"
: "Requires Enterprise license",
: undefined,
},
],
});
@@ -503,7 +503,7 @@ export default function AdminAdvancedSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.advanced.enableAlphaFunctionality.label",
@@ -543,7 +543,7 @@ export default function AdminAdvancedSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.advanced.enableUrlToPDF.label",
@@ -581,7 +581,7 @@ export default function AdminAdvancedSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.advanced.disableSanitize.label",
@@ -959,7 +959,7 @@ export default function AdminAdvancedSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.advanced.tempFileManagement.startupCleanup.label",
@@ -1002,7 +1002,7 @@ export default function AdminAdvancedSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.advanced.tempFileManagement.cleanupSystemTemp.label",
@@ -603,7 +603,7 @@ export default function AdminConnectionsSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.connections.ssoAutoLogin.enable",
@@ -674,7 +674,7 @@ export default function AdminConnectionsSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.connections.mobileScanner.enable",
@@ -496,7 +496,7 @@ export default function AdminDatabaseSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.database.enableCustom.label",
@@ -200,7 +200,7 @@ export default function AdminFeaturesSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.features.serverCertificate.enabled.label",
@@ -316,7 +316,7 @@ export default function AdminFeaturesSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.features.serverCertificate.regenerateOnStartup.label",
@@ -703,7 +703,7 @@ export default function AdminGeneralSection() {
marginBottom: "1rem",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.general.hideDisabledTools.googleDrive.label",
@@ -747,7 +747,7 @@ export default function AdminGeneralSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.general.hideDisabledTools.mobileScanner.label",
@@ -793,7 +793,7 @@ export default function AdminGeneralSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.general.showUpdate.label",
@@ -832,7 +832,7 @@ export default function AdminGeneralSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.general.showUpdateOnlyAdmin.label",
@@ -873,7 +873,7 @@ export default function AdminGeneralSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.general.customHTMLFiles.label",
@@ -938,7 +938,7 @@ export default function AdminGeneralSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.general.customMetadata.autoUpdate.label",
@@ -197,7 +197,7 @@ export default function AdminPremiumSection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.premium.enabled.label",
@@ -165,7 +165,7 @@ export default function AdminPrivacySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.privacy.enableAnalytics.label",
@@ -203,7 +203,7 @@ export default function AdminPrivacySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.privacy.metricsEnabled.label",
@@ -253,7 +253,7 @@ export default function AdminPrivacySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.privacy.googleVisibility.label",
@@ -292,7 +292,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.enableLogin.label",
@@ -519,7 +519,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.jwt.persistence.label",
@@ -556,7 +556,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.jwt.enableKeyRotation.label",
@@ -596,7 +596,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.jwt.enableKeyCleanup.label",
@@ -780,7 +780,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.jwt.secureCookie.label",
@@ -840,7 +840,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.audit.enabled.label",
@@ -955,7 +955,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.audit.captureFileHash.label",
@@ -995,7 +995,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.audit.capturePdfAuthor.label",
@@ -1035,7 +1035,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.audit.captureOperationResults.label",
@@ -1097,7 +1097,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.htmlUrlSecurity.enabled.label",
@@ -1374,7 +1374,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.htmlUrlSecurity.blockPrivateNetworks.label",
@@ -1424,7 +1424,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.htmlUrlSecurity.blockLocalhost.label",
@@ -1473,7 +1473,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.htmlUrlSecurity.blockLinkLocal.label",
@@ -1522,7 +1522,7 @@ export default function AdminSecuritySection() {
justifyContent: "space-between",
}}
>
<div>
<div style={{ flex: 1, minWidth: 0 }}>
<Text fw={500} size="sm">
{t(
"admin.settings.security.htmlUrlSecurity.blockCloudMetadata.label",
@@ -585,9 +585,12 @@ export default function PeopleSection() {
{t("workspace.people.user")}
</Table.Th>
<Table.Th
style={{ fontWeight: 600, color: "var(--mantine-color-gray-7)" }}
style={{
fontWeight: 600,
color: "var(--mantine-color-gray-7)",
whiteSpace: "nowrap",
}}
fz="sm"
w={100}
>
{t("workspace.people.role")}
</Table.Th>
@@ -690,7 +693,7 @@ export default function PeopleSection() {
</Box>
</Group>
</Table.Td>
<Table.Td w={100}>
<Table.Td style={{ whiteSpace: "nowrap" }}>
<Badge
size="sm"
variant="light"
@@ -699,6 +702,10 @@ export default function PeopleSection() {
? "blue"
: "cyan"
}
styles={{
root: { maxWidth: "none" },
label: { overflow: "visible" },
}}
>
{(user.rolesAsString || "").includes("ROLE_ADMIN")
? t("workspace.people.admin", "Admin")
@@ -1,4 +1,4 @@
import React from "react";
import React, { useMemo } from "react";
import { Stack, Text, Loader } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { loadStripe } from "@stripe/stripe-js";
@@ -8,9 +8,7 @@ import {
} from "@stripe/react-stripe-js";
import { PlanTier } from "@app/services/licenseService";
// Load Stripe once
const STRIPE_KEY = import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY;
const stripePromise = STRIPE_KEY ? loadStripe(STRIPE_KEY) : null;
interface PaymentStageProps {
clientSecret: string | null;
@@ -24,6 +22,14 @@ export const PaymentStage: React.FC<PaymentStageProps> = ({
onPaymentComplete,
}) => {
const { t } = useTranslation();
// Load Stripe.js lazily, only when PaymentStage mounts. Loading at module
// scope pulled the Stripe script into every page (CheckoutContext is in
// AppProviders), which triggered the dev "HTTPS required" warning on every
// non-payment route.
const stripePromise = useMemo(
() => (STRIPE_KEY ? loadStripe(STRIPE_KEY) : null),
[],
);
// Show loading while creating checkout session
if (!clientSecret || !selectedPlan) {