Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions frontend/editor/public/locales/en-GB/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3065,6 +3065,10 @@ integration = "Integration Configuration"
security = "Security Configuration"
system = "System Configuration"

[config.payg]
label = "Billing & usage"
section = "Pay-as-you-go"

[connectionMode.status]
localOffline = "Offline mode running"
localOnline = "Offline mode running"
Expand Down Expand Up @@ -5484,6 +5488,72 @@ bullet3 = "Invalid expressions are ignored"
text = "Keep these guidelines in mind:"
title = "Tips"

[payg]
subtitle = "Pay-as-you-go — you only pay for what you process. Billing period {{start}} – {{end}}."

[payg.activity]
subtitle = "Only AI and automation draw from your budget. Everyday tools are free and aren't listed here."
title = "Recent billable activity"
units = "units"
viewAll = "View all"

[payg.cap]
amount = "Cap amount"
currency = "Currency"
degradeAt = "Limit spend at"
degradeAtDesc = "Set below 100% to leave yourself headroom."
perMonth = "/ month"
preview = "{{money}} ≈ {{units}} documents per month"
previewNote = "At current pricing. Final translation happens server-side on save."
previewShort = "≈ {{units}} documents per month at current pricing."
save = "Update cap"
subtitle = "Set your maximum spend. We convert this to documents using the current price tier."
title = "Monthly spending cap"
warnAt = "Warn me at"
warnAtDesc = "Notify when usage crosses this threshold."

[payg.gates]
ai = "AI tools (AI Create, suggestions, AI-OCR)"
automation = "Automations & pipelines"
client = "Browser-only tools (viewer, page editor, file management)"
offsite = "Server tools (compress, OCR, convert, watermark…)"
pauses = "pauses at cap"
subtitle = "Your everyday tools keep working. Only AI and automation pause until the cap resets or is raised."
title = "What happens when the cap is reached"

[payg.member]
askLeader = "Only your team owner can change the cap."
contactLeader = "Ask team owner to raise cap"
editCap = "Edit"
noCap = "· no sub-cap"
setCap = "Set cap"

[payg.role]
leader = "Team owner"
member = "Member"

[payg.state]
degraded = "Cap reached"
full = "Healthy"
warned = "Approaching cap"

[payg.stripe]
open = "Open billing portal"
subtitle = "Receipts, invoices, payment method, billing currency."
title = "Manage billing in Stripe"

[payg.subcaps]
subtitle = "Optional: cap individual teammates so one person can't drain the team budget."
title = "Per-member sub-caps"

[payg.usage]
credit = "{{amount}} account credit"
docs = "documents"
resetsIn = "Resets in {{days}} days"
resetsTomorrow = "Resets tomorrow"
spent = "{{spend}} of {{cap}} used"
thisPeriod = "This billing period"

[payment]
autoClose = "This window will close automatically..."
billingPeriod = "Billing Period"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const VALID_NAV_KEYS = [
"adminEndpoints",
"adminStorageSharing",
"help",
"payg",
] as const;

// Derive the type from the array
Expand Down
1 change: 1 addition & 0 deletions frontend/editor/src/core/types/appConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface AppConfig {
enableDesktopInstallSlide?: boolean;
premiumEnabled?: boolean;
premiumKey?: string;
paygEnabled?: boolean;
termsAndConditions?: string;
privacyPolicy?: string;
cookiePolicy?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ import AdminStorageSharingSection from "@app/components/shared/config/configSect
import ApiKeys from "@app/components/shared/config/configSections/ApiKeys";
import AccountSection from "@app/components/shared/config/configSections/AccountSection";
import GeneralSection from "@app/components/shared/config/configSections/GeneralSection";
// DEMO: Preview the SaaS Pay-as-you-go settings screen inside the proprietary
// build. PAYG is SaaS-only; this section should NOT ship in proprietary builds.
// Remove the import + the demo block below once the SaaS dev stack is set up.
// Toggle via `localStorage.setItem('paygDemo','1')` or the `?payg=1` URL param.
// Relative path (not @app/*) because the @app alias resolves to the proprietary
// flavor here, but Payg.tsx lives in the saas flavor. Demo-only, so the
// cross-flavor import lint rule is suppressed rather than satisfied.
// eslint-disable-next-line no-restricted-imports
import { PaygLeader } from "../../../../saas/components/shared/config/configSections/Payg";

/**
* Hook version of proprietary config nav sections with proper i18n support
Expand Down Expand Up @@ -263,6 +272,40 @@ export const useConfigNavSections = (
sections.splice(insertIndex, 0, developerSection);
}

// DEMO ONLY — preview the SaaS Pay-as-you-go screen in proprietary dev.
// Remove this block before merging. Real home is saasConfigNavSections.
if (typeof window !== "undefined") {
if (new URLSearchParams(window.location.search).get("payg") === "1") {
try {
localStorage.setItem("paygDemo", "1");
} catch {
/* ignore */
}
}
const demoEnabled = (() => {
try {
return localStorage.getItem("paygDemo") === "1";
} catch {
return false;
}
})();
if (demoEnabled) {
// DEMO: always force LEADER variant so the cap editor + sub-caps render.
// Real wiring should branch on team-owner role.
sections.push({
title: t("config.payg.section", "Pay-as-you-go"),
items: [
{
key: "payg",
label: t("config.payg.label", "Billing & usage"),
icon: "speed-rounded",
component: <PaygLeader />,
},
],
});
}
}

return sections;
};

Expand Down
21 changes: 20 additions & 1 deletion frontend/editor/src/saas/components/shared/AppConfigModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Modal, Button, Text, ActionIcon } from "@mantine/core";
import { useMediaQuery } from "@mantine/hooks";
import { useAuth } from "@app/auth/UseSession";
import { isUserAnonymous } from "@app/auth/supabase";
import { useAppConfig } from "@app/contexts/AppConfigContext";
import { useTranslation } from "react-i18next";
import LocalIcon from "@app/components/shared/LocalIcon";
import Overview from "@app/components/shared/config/configSections/Overview";
Expand Down Expand Up @@ -107,14 +108,32 @@ const AppConfigModal: React.FC<AppConfigModalProps> = ({ opened, onClose }) => {
const openLogoutConfirm = useCallback(() => setConfirmOpen(true), []);

// Left navigation structure and icons
const { config: appConfig } = useAppConfig();
// Until team-role lookup lands, proxy LEADER from the tenant-admin flag.
// DEMO: `?payg=1` query param (sticks in localStorage) force-enables the new
// section locally so the screen can be previewed before the backend
// ApplicationProperties.Payg wiring is in place. Remove once the real config
// flag flows from ConfigController.java.
let paygOverride = false;
if (typeof window !== "undefined") {
if (new URLSearchParams(window.location.search).get("payg") === "1") {
localStorage.setItem("paygDemo", "1");
}
paygOverride = localStorage.getItem("paygDemo") === "1";
}
const paygEnabled = Boolean(appConfig?.paygEnabled) || paygOverride;
const isLeader = Boolean(appConfig?.isAdmin) || paygOverride;

const configNavSections = useMemo(
() =>
createSaasConfigNavSections(Overview, openLogoutConfirm, {
isDev,
isAnonymous,
paygEnabled,
isLeader,
t,
}),
[openLogoutConfirm, isDev, isAnonymous, t],
[openLogoutConfirm, isDev, isAnonymous, paygEnabled, isLeader, t],
);

const activeLabel = useMemo(() => {
Expand Down
Loading
Loading