mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
Policies: let team leaders configure policies in the UI (#6634)
Frontend follow-up to #6632 (team-scoped policies, editing gated to team leaders on the backend). Brings the UI's edit gate in line. ## Problem The policy config UI gated editing to `config.isAdmin`. On SaaS, org users are never the single global admin, so **no one could open the policy editor** — the same lockout #6632 fixed on the backend. ## Fix `usePolicies` now allows a **team leader** to configure, falling back to a global admin self-hosted: ```ts canConfigure = config != null && (!config.enableLogin || isTeamLeader || config.isAdmin === true); ``` - SaaS → `isTeamLeader` (from `useSaaSTeam()`) — team leaders can configure; members get the read-only surface. - Self-hosted → `config.isAdmin` (the core `useSaaSTeam` stub returns `false`, so admins aren't locked out). - Login disabled (single-user) → always allowed. - The `config != null` guard keeps the gate closed until app-config resolves, so edit controls never flash for users who can't use them. The two locked-policy banners now read "Contact a team leader to change this policy" (updated in the `t()` defaults and the `en-GB` translations). ## Verification - Typecheck clean (proprietary + saas); eslint clean. - Tests pass: `usePolicies`, `PoliciesSidebar`.
This commit is contained in:
@@ -5869,7 +5869,7 @@ security = "Security"
|
|||||||
[policies.detail]
|
[policies.detail]
|
||||||
editSettings = "Edit Settings"
|
editSettings = "Edit Settings"
|
||||||
enforces = "Enforces"
|
enforces = "Enforces"
|
||||||
managedByOrg = "Managed by your organization. Contact an admin to change this policy."
|
managedByOrg = "Managed by your organization. Contact a team leader to change this policy."
|
||||||
noActivityDescription = "Documents will appear here once this policy runs."
|
noActivityDescription = "Documents will appear here once this policy runs."
|
||||||
noActivityTitle = "No activity yet"
|
noActivityTitle = "No activity yet"
|
||||||
onEveryUpload = "On every upload"
|
onEveryUpload = "On every upload"
|
||||||
@@ -5931,7 +5931,7 @@ filenamePrefix = "Prefix"
|
|||||||
filenameSuffix = "Suffix"
|
filenameSuffix = "Suffix"
|
||||||
filenameTextAria = "Filename text"
|
filenameTextAria = "Filename text"
|
||||||
filenameTextPlaceholder = "Text to add (optional)"
|
filenameTextPlaceholder = "Text to add (optional)"
|
||||||
lockedDescription = "Contact an admin to change this policy."
|
lockedDescription = "Contact a team leader to change this policy."
|
||||||
lockedTitle = "Managed by your organization"
|
lockedTitle = "Managed by your organization"
|
||||||
maxRetriesLabel = "Max retries"
|
maxRetriesLabel = "Max retries"
|
||||||
noToolsError = "Add at least one configured tool to the workflow first."
|
noToolsError = "Add at least one configured tool to the workflow first."
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ export function PolicyDetailPanel({
|
|||||||
icon={<LockIcon sx={{ fontSize: "1rem" }} />}
|
icon={<LockIcon sx={{ fontSize: "1rem" }} />}
|
||||||
description={t(
|
description={t(
|
||||||
"policies.detail.managedByOrg",
|
"policies.detail.managedByOrg",
|
||||||
"Managed by your organization. Contact an admin to change this policy.",
|
"Managed by your organization. Contact a team leader to change this policy.",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ export function PolicySetupWizard({
|
|||||||
)}
|
)}
|
||||||
description={t(
|
description={t(
|
||||||
"policies.wizard.lockedDescription",
|
"policies.wizard.lockedDescription",
|
||||||
"Contact an admin to change this policy.",
|
"Contact a team leader to change this policy.",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { useAppConfig } from "@app/contexts/AppConfigContext";
|
import { useAppConfig } from "@app/contexts/AppConfigContext";
|
||||||
|
import { useSaaSTeam } from "@app/contexts/SaaSTeamContext";
|
||||||
import {
|
import {
|
||||||
loadPolicies,
|
loadPolicies,
|
||||||
onPoliciesChange,
|
onPoliciesChange,
|
||||||
@@ -65,6 +66,7 @@ function toStoreRequest(
|
|||||||
export function usePolicies() {
|
export function usePolicies() {
|
||||||
const [policies, setPolicies] = useState<PoliciesByCategory>(loadPolicies);
|
const [policies, setPolicies] = useState<PoliciesByCategory>(loadPolicies);
|
||||||
const { config } = useAppConfig();
|
const { config } = useAppConfig();
|
||||||
|
const { isTeamLeader } = useSaaSTeam();
|
||||||
|
|
||||||
useEffect(() => onPoliciesChange(() => setPolicies(loadPolicies())), []);
|
useEffect(() => onPoliciesChange(() => setPolicies(loadPolicies())), []);
|
||||||
|
|
||||||
@@ -293,17 +295,13 @@ export function usePolicies() {
|
|||||||
return folder.id;
|
return folder.id;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Editing/creating policies is admin-only, mirroring the backend's enforcement
|
// Only a team leader (SaaS) or a global admin (self-hosted) may configure;
|
||||||
// on the save/delete endpoints. On single-user deployments (login disabled)
|
// everyone else gets the read-only surface. Login disabled (single-user)
|
||||||
// there's no admin concept, so the local operator can always configure. When
|
// always can. Stays closed until config loads, so edit controls never flash
|
||||||
// login is enabled, only admins can — non-admins get the read-only surface.
|
// for users who can't use them.
|
||||||
// The `config != null` guard keeps the gate CLOSED until app-config resolves:
|
|
||||||
// config is null while loading (and in bare test renders), and a failed/partial
|
|
||||||
// fetch can yield `{ enableLogin: true }` with isAdmin omitted. Without it a
|
|
||||||
// non-admin would briefly see edit controls during load, and an admin would be
|
|
||||||
// wrongly locked out on a transient config-fetch error.
|
|
||||||
const canConfigure =
|
const canConfigure =
|
||||||
config != null && (!config.enableLogin || config.isAdmin === true);
|
config != null &&
|
||||||
|
(!config.enableLogin || isTeamLeader || config.isAdmin === true);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
policies,
|
policies,
|
||||||
|
|||||||
Reference in New Issue
Block a user