@burdenoff/microfe-devportal 2026.623.3 → 2026.623.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,7 +33,10 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
33
33
  }), K = W?.myWorkspaces?.items?.map((e) => ({
34
34
  id: e.id,
35
35
  name: e.name
36
- })) ?? [], [q, { loading: J }] = t(), { t: Y } = ee(), X = (e, t) => {
36
+ })) ?? [], [q, { loading: J }] = t({
37
+ refetchQueries: ["CurrentUserDelegations"],
38
+ awaitRefetchQueries: !0
39
+ }), { t: Y } = ee(), X = (e, t) => {
37
40
  let n = Y(e);
38
41
  return n === e ? t : n;
39
42
  };
@@ -94,13 +97,23 @@ var _ = ({ open: _, onOpenChange: v, appId: y, appName: b, appScopes: x, preSele
94
97
  e = t.toISOString();
95
98
  }
96
99
  try {
97
- let t = (await q({ variables: { input: {
100
+ let t = await q({ variables: { input: {
98
101
  appId: y,
99
102
  scopes: k,
100
103
  workspaceIds: !N && j.length > 0 ? j : void 0,
101
104
  expiresAt: e
102
- } } })).data?.grantDelegation, n = N ? "all workspaces" : j.length > 0 ? `${j.length} workspace${j.length === 1 ? "" : "s"}` : "all workspaces";
103
- H(`Delegation granted to "${b}" with ${k.length} scope${k.length === 1 ? "" : "s"} for ${n}.`), w?.(t);
105
+ } } });
106
+ if (t.error) {
107
+ B(t.error.message ?? X("devportal.failedToGrantDelegation", "Failed to grant delegation."));
108
+ return;
109
+ }
110
+ let n = t.data?.grantDelegation;
111
+ if (!n) {
112
+ B(X("devportal.failedToGrantDelegation", "Failed to grant delegation."));
113
+ return;
114
+ }
115
+ let r = N ? "all workspaces" : j.length > 0 ? `${j.length} workspace${j.length === 1 ? "" : "s"}` : "all workspaces";
116
+ H(`Delegation granted to "${b}" with ${k.length} scope${k.length === 1 ? "" : "s"} for ${r}.`), w?.(n);
104
117
  } catch (e) {
105
118
  B(e instanceof Error ? e.message : X("devportal.failedToGrantDelegation", "Failed to grant delegation."));
106
119
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GrantDelegationModal.js","names":[],"sources":["../../../src/devportal/components/GrantDelegationModal.tsx"],"sourcesContent":["import {\n useState,\n useCallback,\n useEffect,\n useId,\n useRef,\n type FC,\n} from \"react\";\nimport {\n Shield,\n X,\n Loader2,\n CheckCircle,\n AlertCircle,\n Search,\n Check,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { cn } from \"../utils/cn\";\nimport { useGrantDelegationMutation } from \"../../generated/global-operations\";\nimport { useMyWorkspacesQuery } from \"../../generated/wspace-operations\";\n\ninterface WorkspaceOption {\n id: string;\n name: string;\n}\n\n// ============================================================================\n// Scope grouping helper\n// ============================================================================\n\nfunction groupScopes(scopes: string[]): Map<string, string[]> {\n const groups = new Map<string, string[]>();\n for (const scope of scopes) {\n const [resource] = scope.split(\":\");\n const group = resource || \"other\";\n if (!groups.has(group)) groups.set(group, []);\n groups.get(group)!.push(scope);\n }\n return groups;\n}\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface GrantDelegationModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n appId: string;\n appName: string;\n appScopes: string[];\n /** Pre-selected workspace IDs (e.g., from installation flow) */\n preSelectedWorkspaceIds?: string[];\n /** Pre-selected scopes (e.g., required scopes from app) */\n preSelectedScopes?: string[];\n onSuccess?: (delegation: unknown) => void;\n}\n\n/**\n * Reusable modal for granting delegation access to an app.\n *\n * Allows users to select scopes, specify workspace IDs,\n * and optionally set an expiration date before granting\n * an app permission to act on their behalf.\n */\nexport const GrantDelegationModal: FC<GrantDelegationModalProps> = ({\n open,\n onOpenChange,\n appId,\n appName,\n appScopes,\n preSelectedWorkspaceIds,\n preSelectedScopes,\n onSuccess,\n}) => {\n const titleId = useId();\n const descriptionId = useId();\n const dialogRef = useRef<HTMLDivElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const [selectedScopes, setSelectedScopes] = useState<string[]>(\n preSelectedScopes ?? [],\n );\n const [selectedWorkspaceIds, setSelectedWorkspaceIds] = useState<string[]>(\n preSelectedWorkspaceIds ?? [],\n );\n const [allWorkspaces, setAllWorkspaces] = useState(false);\n const [workspaceSearch, setWorkspaceSearch] = useState(\"\");\n const [expiresAt, setExpiresAt] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // useGatewayClient throws when no MultiGatewayProvider is mounted (e.g. in\n // standalone tests). We intentionally swallow that to render in degraded\n // mode without the workspaces dropdown.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const workspaceClient = (() => {\n try {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useGatewayClient(\"workspace\");\n } catch {\n return null;\n }\n })();\n const { data: workspacesData, loading: loadingWorkspaces } =\n useMyWorkspacesQuery({\n client: workspaceClient ?? undefined,\n skip: !open || !workspaceClient,\n variables: {\n pagination: {\n limit: 100,\n offset: 0,\n },\n },\n fetchPolicy: \"cache-and-network\",\n });\n const workspaces: WorkspaceOption[] =\n workspacesData?.myWorkspaces?.items?.map(\n (workspace: { id: string; name: string }) => ({\n id: workspace.id,\n name: workspace.name,\n }),\n ) ?? [];\n const [grantDelegationMutation, { loading }] = useGrantDelegationMutation();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Apply pre-selections when they change\n useEffect(() => {\n if (preSelectedWorkspaceIds?.length)\n setSelectedWorkspaceIds(preSelectedWorkspaceIds);\n }, [preSelectedWorkspaceIds]);\n useEffect(() => {\n if (preSelectedScopes?.length) setSelectedScopes(preSelectedScopes);\n }, [preSelectedScopes]);\n\n const resetState = useCallback(() => {\n setSelectedScopes(preSelectedScopes ?? []);\n setSelectedWorkspaceIds(preSelectedWorkspaceIds ?? []);\n setAllWorkspaces(false);\n setWorkspaceSearch(\"\");\n setExpiresAt(\"\");\n setError(null);\n setSuccessMessage(null);\n }, [preSelectedScopes, preSelectedWorkspaceIds]);\n\n const handleClose = useCallback(() => {\n if (loading) return;\n resetState();\n onOpenChange(false);\n }, [loading, onOpenChange, resetState]);\n\n useEffect(() => {\n if (!open) return undefined;\n\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n window.requestAnimationFrame(() => {\n dialogRef.current?.focus();\n });\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && !loading) {\n event.preventDefault();\n handleClose();\n }\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n document.body.style.overflow = originalOverflow;\n document.removeEventListener(\"keydown\", onKeyDown);\n previousFocusRef.current?.focus();\n };\n }, [handleClose, loading, open]);\n\n const toggleScope = useCallback((scope: string) => {\n setSelectedScopes((prev) =>\n prev.includes(scope) ? prev.filter((s) => s !== scope) : [...prev, scope],\n );\n }, []);\n\n const toggleWorkspace = useCallback((wsId: string) => {\n setSelectedWorkspaceIds((prev) =>\n prev.includes(wsId) ? prev.filter((id) => id !== wsId) : [...prev, wsId],\n );\n setAllWorkspaces(false);\n }, []);\n\n const handleAllWorkspacesToggle = useCallback(() => {\n setAllWorkspaces((prev) => {\n if (!prev) setSelectedWorkspaceIds([]);\n return !prev;\n });\n }, []);\n\n const filteredWorkspaces = workspaces.filter((ws) =>\n ws.name.toLowerCase().includes(workspaceSearch.toLowerCase()),\n );\n\n const scopeGroups = groupScopes(appScopes);\n\n const handleGrant = useCallback(async () => {\n setError(null);\n\n if (selectedScopes.length === 0) {\n setError(\n tr(\n \"devportal.selectAtLeastOneScope\",\n \"Please select at least one scope to grant.\",\n ),\n );\n return;\n }\n\n let parsedExpiresAt: string | undefined;\n if (expiresAt) {\n const date = new Date(expiresAt);\n if (Number.isNaN(date.getTime())) {\n setError(\n tr(\n \"devportal.invalidExpirationDate\",\n \"Invalid expiration date. Please use a valid date format.\",\n ),\n );\n return;\n }\n if (date <= new Date()) {\n setError(\n tr(\n \"devportal.expirationDateFuture\",\n \"Expiration date must be in the future.\",\n ),\n );\n return;\n }\n parsedExpiresAt = date.toISOString();\n }\n\n try {\n const result = await grantDelegationMutation({\n variables: {\n input: {\n appId,\n scopes: selectedScopes,\n workspaceIds:\n !allWorkspaces && selectedWorkspaceIds.length > 0\n ? selectedWorkspaceIds\n : undefined,\n expiresAt: parsedExpiresAt,\n },\n },\n });\n\n const delegation = result.data?.grantDelegation;\n const wsCount = allWorkspaces\n ? \"all workspaces\"\n : selectedWorkspaceIds.length > 0\n ? `${selectedWorkspaceIds.length} workspace${selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"}`\n : \"all workspaces\";\n setSuccessMessage(\n `Delegation granted to \"${appName}\" with ${selectedScopes.length} scope${selectedScopes.length !== 1 ? \"s\" : \"\"} for ${wsCount}.`,\n );\n onSuccess?.(delegation);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n }\n }, [\n selectedScopes,\n selectedWorkspaceIds,\n allWorkspaces,\n expiresAt,\n appId,\n appName,\n grantDelegationMutation,\n onSuccess,\n ]);\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <button\n type=\"button\"\n aria-label=\"Close delegation dialog backdrop\"\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={handleClose}\n />\n\n {/* Dialog */}\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={descriptionId}\n tabIndex={-1}\n className=\"relative z-10 w-full max-w-lg rounded-lg bg-background shadow-xl\"\n >\n {/* Header */}\n <div className=\"flex items-start gap-4 border-b p-6\">\n <div className=\"rounded-full bg-status-success-bg-subtle p-2\">\n <Shield className=\"h-6 w-6 text-status-success-text\" />\n </div>\n <div className=\"flex-1\">\n <h3 id={titleId} className=\"text-lg font-semibold\">\n Grant Delegation\n </h3>\n <p\n id={descriptionId}\n className=\"mt-1 text-sm text-muted-foreground\"\n >\n Allow{\" \"}\n <span className=\"font-medium text-foreground\">{appName}</span> to\n act on your behalf\n </p>\n </div>\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md p-1 text-muted-foreground hover:bg-muted transition-colors disabled:opacity-50\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"space-y-5 p-6\">\n {/* Success state */}\n {successMessage && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-success-bg-subtle p-4\">\n <CheckCircle className=\"h-5 w-5 shrink-0 text-status-success-text\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-success-text\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"mt-2 text-sm font-medium text-status-success-text hover:underline\"\n >\n {tr(\"devportal.close\", \"Close\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Form fields (hidden after success) */}\n {!successMessage && (\n <>\n {/* Scope selection (grouped by resource) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.permissionsScopes\", \"Permissions (Scopes)\")}\n <span className=\"ml-1.5 text-xs font-normal text-muted-foreground\">\n {selectedScopes.length} {tr(\"devportal.of\", \"of\")}{\" \"}\n {appScopes.length} {tr(\"devportal.selected\", \"selected\")}\n </span>\n </label>\n {appScopes.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n This app has no scopes available.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {/* Quick actions */}\n <div className=\"flex gap-2 text-xs\">\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([...appScopes])}\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.selectAll\", \"Select all\")}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n setSelectedScopes(\n appScopes.filter(\n (s) => s.includes(\":read\") || s.includes(\":list\"),\n ),\n )\n }\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.readOnly\", \"Read-only\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([])}\n className=\"text-muted-foreground hover:underline\"\n >\n {tr(\"devportal.clear\", \"Clear\")}\n </button>\n </div>\n {/* Grouped scopes */}\n {[...scopeGroups.entries()].map(([group, scopes]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-semibold uppercase text-muted-foreground\">\n {group}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {scopes.map((scope) => {\n const isSelected = selectedScopes.includes(scope);\n const action = scope.split(\":\")[1] || scope;\n const isWrite =\n action.includes(\"write\") ||\n action.includes(\"create\") ||\n action.includes(\"delete\") ||\n action.includes(\"admin\");\n return (\n <button\n key={scope}\n type=\"button\"\n onClick={() => toggleScope(scope)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n isSelected\n ? isWrite\n ? \"bg-status-warning-bg text-white\"\n : \"bg-status-success-bg text-white\"\n : \"border bg-muted text-muted-foreground hover:bg-muted/80\",\n )}\n >\n {action}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Workspace Selection (multi-select picker) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.workspaceAccess\", \"Workspace Access\")}\n </label>\n\n {/* All workspaces toggle */}\n <button\n type=\"button\"\n onClick={handleAllWorkspacesToggle}\n disabled={loading}\n className={cn(\n \"mb-2 flex w-full items-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors\",\n allWorkspaces\n ? \"border-status-success-bg bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-muted\",\n )}\n >\n {allWorkspaces && (\n <Check className=\"h-4 w-4 text-status-success-text\" />\n )}\n <span>\n {tr(\n \"devportal.allWorkspacesUnrestricted\",\n \"All workspaces (unrestricted)\",\n )}\n </span>\n </button>\n\n {!allWorkspaces && (\n <>\n {/* Search */}\n <div className=\"relative mb-2\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground\" />\n <input\n type=\"text\"\n value={workspaceSearch}\n onChange={(e) => setWorkspaceSearch(e.target.value)}\n placeholder={tr(\n \"devportal.searchWorkspaces\",\n \"Search workspaces...\",\n )}\n disabled={loading}\n className=\"block w-full rounded-md border py-2 pl-9 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n </div>\n\n {/* Workspace list */}\n <div className=\"max-h-40 overflow-y-auto rounded-md border\">\n {loadingWorkspaces ? (\n <div className=\"flex items-center justify-center p-4\">\n <Loader2 className=\"h-4 w-4 animate-spin text-muted-foreground\" />\n <span className=\"ml-2 text-sm text-muted-foreground\">\n {tr(\n \"devportal.loadingWorkspaces\",\n \"Loading workspaces...\",\n )}\n </span>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <p className=\"p-4 text-center text-sm text-muted-foreground\">\n {workspaces.length === 0\n ? tr(\n \"devportal.noWorkspacesFound\",\n \"No workspaces found\",\n )\n : tr(\n \"devportal.noMatchingWorkspaces\",\n \"No matching workspaces\",\n )}\n </p>\n ) : (\n filteredWorkspaces.map((ws) => {\n const isSelected = selectedWorkspaceIds.includes(\n ws.id,\n );\n return (\n <button\n key={ws.id}\n type=\"button\"\n onClick={() => toggleWorkspace(ws.id)}\n disabled={loading}\n className={cn(\n \"flex w-full items-center gap-2 border-b px-3 py-2 text-left text-sm transition-colors last:border-b-0\",\n isSelected\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-muted\",\n )}\n >\n <div\n className={cn(\n \"flex h-4 w-4 shrink-0 items-center justify-center rounded border\",\n isSelected\n ? \"border-status-success-bg bg-status-success-bg\"\n : \"border-muted-foreground/30\",\n )}\n >\n {isSelected && (\n <Check className=\"h-3 w-3 text-white\" />\n )}\n </div>\n <span className=\"font-medium\">{ws.name}</span>\n </button>\n );\n })\n )}\n </div>\n\n {/* Selected count */}\n {selectedWorkspaceIds.length > 0 && (\n <p className=\"mt-1.5 text-xs text-muted-foreground\">\n {selectedWorkspaceIds.length} workspace\n {selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"} selected\n </p>\n )}\n </>\n )}\n </div>\n\n {/* Expiration */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.expiration\", \"Expiration\")}\n <span className=\"ml-1.5 text-xs font-normal text-muted-foreground\">\n {tr(\"devportal.optional\", \"(optional)\")}\n </span>\n </label>\n <input\n type=\"datetime-local\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n disabled={loading}\n min={new Date().toISOString().slice(0, 16)}\n className=\"block w-full rounded-md border px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n <p className=\"mt-1.5 text-xs text-muted-foreground\">\n {tr(\n \"devportal.leaveEmptyForNonExpiring\",\n \"Leave empty for a delegation that does not expire.\",\n )}\n </p>\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-error-bg-subtle p-4\">\n <AlertCircle className=\"h-5 w-5 shrink-0 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n </>\n )}\n </div>\n\n {/* Footer */}\n {!successMessage && (\n <div className=\"flex justify-end gap-3 border-t p-6\">\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors disabled:opacity-50\"\n >\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleGrant}\n disabled={loading || selectedScopes.length === 0}\n className={cn(\n \"flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed\",\n \"bg-status-success-bg hover:bg-status-success-bg/90\",\n )}\n >\n {loading ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr(\"devportal.granting\", \"Granting...\")}\n </>\n ) : (\n tr(\"devportal.grant\", \"Grant\")\n )}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;AAgCA,SAAS,EAAY,GAAyC;CAC5D,IAAM,oBAAS,IAAI,KAAuB;AAC1C,MAAK,IAAM,KAAS,GAAQ;EAC1B,IAAM,CAAC,KAAY,EAAM,MAAM,IAAI,EAC7B,IAAQ,KAAY;AAE1B,EADK,EAAO,IAAI,EAAM,IAAE,EAAO,IAAI,GAAO,EAAE,CAAC,EAC7C,EAAO,IAAI,EAAM,CAAE,KAAK,EAAM;;AAEhC,QAAO;;AA2BT,IAAa,KAAuD,EAClE,SACA,iBACA,UACA,YACA,cACA,4BACA,sBACA,mBACI;CACJ,IAAM,IAAU,GAAO,EACjB,IAAgB,GAAO,EACvB,IAAY,EAA8B,KAAK,EAC/C,IAAmB,EAA2B,KAAK,EACnD,CAAC,GAAgB,KAAqB,EAC1C,KAAqB,EAAE,CACxB,EACK,CAAC,GAAsB,KAA2B,EACtD,KAA2B,EAAE,CAC9B,EACK,CAAC,GAAe,KAAoB,EAAS,GAAM,EACnD,CAAC,GAAiB,KAAsB,EAAS,GAAG,EACpD,CAAC,GAAW,KAAgB,EAAS,GAAG,EACxC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAgB,KAAqB,EAAwB,KAAK,EAMnE,WAAyB;AAC7B,MAAI;AAEF,UAAO,EAAiB,YAAY;UAC9B;AACN,UAAO;;KAEP,EACE,EAAE,MAAM,GAAgB,SAAS,MACrC,EAAqB;EACnB,QAAQ,KAAmB,KAAA;EAC3B,MAAM,CAAC,KAAQ,CAAC;EAChB,WAAW,EACT,YAAY;GACV,OAAO;GACP,QAAQ;GACT,EACF;EACD,aAAa;EACd,CAAC,EACE,IACJ,GAAgB,cAAc,OAAO,KAClC,OAA6C;EAC5C,IAAI,EAAU;EACd,MAAM,EAAU;EACjB,EACF,IAAI,EAAE,EACH,CAAC,GAAyB,EAAE,gBAAa,GAA4B,EACrE,EAAE,SAAM,IAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;;AAQpC,CAJA,QAAgB;AACd,EAAI,GAAyB,UAC3B,EAAwB,EAAwB;IACjD,CAAC,EAAwB,CAAC,EAC7B,QAAgB;AACd,EAAI,GAAmB,UAAQ,EAAkB,EAAkB;IAClE,CAAC,EAAkB,CAAC;CAEvB,IAAM,IAAa,QAAkB;AAOnC,EANA,EAAkB,KAAqB,EAAE,CAAC,EAC1C,EAAwB,KAA2B,EAAE,CAAC,EACtD,EAAiB,GAAM,EACvB,EAAmB,GAAG,EACtB,EAAa,GAAG,EAChB,EAAS,KAAK,EACd,EAAkB,KAAK;IACtB,CAAC,GAAmB,EAAwB,CAAC,EAE1C,IAAc,QAAkB;AAChC,QACJ,GAAY,EACZ,EAAa,GAAM;IAClB;EAAC;EAAS;EAAc;EAAW,CAAC;AAEvC,SAAgB;AACd,MAAI,CAAC,EAAM;AAEX,IAAiB,UAAU,SAAS;EACpC,IAAM,IAAmB,SAAS,KAAK,MAAM;AAG7C,EAFA,SAAS,KAAK,MAAM,WAAW,UAE/B,OAAO,4BAA4B;AACjC,KAAU,SAAS,OAAO;IAC1B;EAEF,IAAM,KAAa,MAAyB;AAC1C,GAAI,EAAM,QAAQ,YAAY,CAAC,MAC7B,EAAM,gBAAgB,EACtB,GAAa;;AAMjB,SAFA,SAAS,iBAAiB,WAAW,EAAU,QAElC;AAGX,GAFA,SAAS,KAAK,MAAM,WAAW,GAC/B,SAAS,oBAAoB,WAAW,EAAU,EAClD,EAAiB,SAAS,OAAO;;IAElC;EAAC;EAAa;EAAS;EAAK,CAAC;CAEhC,IAAM,KAAc,GAAa,MAAkB;AACjD,KAAmB,MACjB,EAAK,SAAS,EAAM,GAAG,EAAK,QAAQ,MAAM,MAAM,EAAM,GAAG,CAAC,GAAG,GAAM,EAAM,CAC1E;IACA,EAAE,CAAC,EAEA,KAAkB,GAAa,MAAiB;AAIpD,EAHA,GAAyB,MACvB,EAAK,SAAS,EAAK,GAAG,EAAK,QAAQ,MAAO,MAAO,EAAK,GAAG,CAAC,GAAG,GAAM,EAAK,CACzE,EACD,EAAiB,GAAM;IACtB,EAAE,CAAC,EAEA,KAA4B,QAAkB;AAClD,KAAkB,OACX,KAAM,EAAwB,EAAE,CAAC,EAC/B,CAAC,GACR;IACD,EAAE,CAAC,EAEA,IAAqB,EAAW,QAAQ,MAC5C,EAAG,KAAK,aAAa,CAAC,SAAS,EAAgB,aAAa,CAAC,CAC9D,EAEK,KAAc,EAAY,EAAU,EAEpC,KAAc,EAAY,YAAY;AAG1C,MAFA,EAAS,KAAK,EAEV,EAAe,WAAW,GAAG;AAC/B,KACE,EACE,mCACA,6CACD,CACF;AACD;;EAGF,IAAI;AACJ,MAAI,GAAW;GACb,IAAM,IAAO,IAAI,KAAK,EAAU;AAChC,OAAI,OAAO,MAAM,EAAK,SAAS,CAAC,EAAE;AAChC,MACE,EACE,mCACA,2DACD,CACF;AACD;;AAEF,OAAI,qBAAQ,IAAI,MAAM,EAAE;AACtB,MACE,EACE,kCACA,yCACD,CACF;AACD;;AAEF,OAAkB,EAAK,aAAa;;AAGtC,MAAI;GAeF,IAAM,KAdS,MAAM,EAAwB,EAC3C,WAAW,EACT,OAAO;IACL;IACA,QAAQ;IACR,cACE,CAAC,KAAiB,EAAqB,SAAS,IAC5C,IACA,KAAA;IACN,WAAW;IACZ,EACF,EACF,CAAC,EAEwB,MAAM,iBAC1B,IAAU,IACZ,mBACA,EAAqB,SAAS,IAC5B,GAAG,EAAqB,OAAO,YAAY,EAAqB,WAAW,IAAU,KAAN,QAC/E;AAIN,GAHA,EACE,0BAA0B,EAAQ,SAAS,EAAe,OAAO,QAAQ,EAAe,WAAW,IAAU,KAAN,IAAS,OAAO,EAAQ,GAChI,EACD,IAAY,EAAW;WAChB,GAAK;AACZ,KACE,aAAe,QACX,EAAI,UACJ,EACE,qCACA,8BACD,CACN;;IAEF;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AAIF,QAFK,IAGH,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,UAAD;GACE,MAAK;GACL,cAAW;GACX,WAAU;GACV,SAAS;GACT,CAAA,EAGF,kBAAC,OAAD;GACE,KAAK;GACL,MAAK;GACL,cAAW;GACX,mBAAiB;GACjB,oBAAkB;GAClB,UAAU;GACV,WAAU;aAPZ;IAUE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAQ,WAAU,oCAAqC,CAAA;OACnD,CAAA;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,MAAD;QAAI,IAAI;QAAS,WAAU;kBAAwB;QAE9C,CAAA,EACL,kBAAC,KAAD;QACE,IAAI;QACJ,WAAU;kBAFZ;SAGC;SACO;SACN,kBAAC,QAAD;UAAM,WAAU;oBAA+B;UAAe,CAAA;;SAE5D;UACA;;MACN,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,UAAU;OACV,WAAU;iBAEV,kBAAC,IAAD,EAAG,WAAU,WAAY,CAAA;OAClB,CAAA;MACL;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CAEG,KACC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,IAAD,EAAa,WAAU,6CAA8C,CAAA,EACrE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV;QACC,CAAA,EACJ,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,WAAU;kBAET,EAAG,mBAAmB,QAAQ;QACxB,CAAA,CACL;SACF;SAIP,CAAC,KACA,kBAAA,GAAA,EAAA,UAAA;MAEE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;iBAAjB,CACG,EAAG,+BAA+B,uBAAuB,EAC1D,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG,EAAe;SAAO;SAAE,EAAG,gBAAgB,KAAK;SAAE;SAClD,EAAU;SAAO;SAAE,EAAG,sBAAsB,WAAW;SACnD;UACD;UACP,EAAU,WAAW,IACpB,kBAAC,KAAD;OAAG,WAAU;iBAAgC;OAEzC,CAAA,GAEJ,kBAAC,OAAD;OAAK,WAAU;iBAAf,CAEE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAkB,CAAC,GAAG,EAAU,CAAC;UAChD,WAAU;oBAET,EAAG,uBAAuB,aAAa;UACjC,CAAA;SACT,kBAAC,UAAD;UACE,MAAK;UACL,eACE,EACE,EAAU,QACP,MAAM,EAAE,SAAS,QAAQ,IAAI,EAAE,SAAS,QAAQ,CAClD,CACF;UAEH,WAAU;oBAET,EAAG,sBAAsB,YAAY;UAC/B,CAAA;SACT,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAkB,EAAE,CAAC;UACpC,WAAU;oBAET,EAAG,mBAAmB,QAAQ;UACxB,CAAA;SACL;WAEL,CAAC,GAAG,GAAY,SAAS,CAAC,CAAC,KAAK,CAAC,GAAO,OACvC,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV;QACC,CAAA,EACJ,kBAAC,OAAD;QAAK,WAAU;kBACZ,EAAO,KAAK,MAAU;SACrB,IAAM,IAAa,EAAe,SAAS,EAAM,EAC3C,IAAS,EAAM,MAAM,IAAI,CAAC,MAAM,GAChC,IACJ,EAAO,SAAS,QAAQ,IACxB,EAAO,SAAS,SAAS,IACzB,EAAO,SAAS,SAAS,IACzB,EAAO,SAAS,QAAQ;AAC1B,gBACE,kBAAC,UAAD;UAEE,MAAK;UACL,eAAe,GAAY,EAAM;UACjC,WAAW,EACT,gEACA,IACI,IACE,oCACA,oCACF,0DACL;oBAEA;UACM,EAbF,EAaE;UAEX;QACE,CAAA,CACF,EAAA,EAhCI,EAgCJ,CACN,CACE;SAEJ,EAAA,CAAA;MAGN,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,6BAA6B,mBAAmB;QAC9C,CAAA;OAGR,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,UAAU;QACV,WAAW,EACT,6FACA,IACI,kFACA,iBACL;kBATH,CAWG,KACC,kBAAC,GAAD,EAAO,WAAU,oCAAqC,CAAA,EAExD,kBAAC,QAAD,EAAA,UACG,EACC,uCACA,gCACD,EACI,CAAA,CACA;;OAER,CAAC,KACA,kBAAA,GAAA,EAAA,UAAA;QAEE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,0EAA2E,CAAA,EAC7F,kBAAC,SAAD;UACE,MAAK;UACL,OAAO;UACP,WAAW,MAAM,EAAmB,EAAE,OAAO,MAAM;UACnD,aAAa,EACX,8BACA,uBACD;UACD,UAAU;UACV,WAAU;UACV,CAAA,CACE;;QAGN,kBAAC,OAAD;SAAK,WAAU;mBACZ,IACC,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAS,WAAU,8CAA+C,CAAA,EAClE,kBAAC,QAAD;WAAM,WAAU;qBACb,EACC,+BACA,wBACD;WACI,CAAA,CACH;cACJ,EAAmB,WAAW,IAChC,kBAAC,KAAD;UAAG,WAAU;oBACV,EAAW,WAAW,IACnB,EACE,+BACA,sBACD,GACD,EACE,kCACA,yBACD;UACH,CAAA,GAEJ,EAAmB,KAAK,MAAO;UAC7B,IAAM,IAAa,EAAqB,SACtC,EAAG,GACJ;AACD,iBACE,kBAAC,UAAD;WAEE,MAAK;WACL,eAAe,GAAgB,EAAG,GAAG;WACrC,UAAU;WACV,WAAW,EACT,yGACA,IACI,yDACA,iBACL;qBAVH,CAYE,kBAAC,OAAD;YACE,WAAW,EACT,oEACA,IACI,kDACA,6BACL;sBAEA,KACC,kBAAC,GAAD,EAAO,WAAU,sBAAuB,CAAA;YAEtC,CAAA,EACN,kBAAC,QAAD;YAAM,WAAU;sBAAe,EAAG;YAAY,CAAA,CACvC;aAxBF,EAAG,GAwBD;WAEX;SAEA,CAAA;QAGL,EAAqB,SAAS,KAC7B,kBAAC,KAAD;SAAG,WAAU;mBAAb;UACG,EAAqB;UAAO;UAC5B,EAAqB,WAAW,IAAU,KAAN;UAAS;UAC5C;;QAEL,EAAA,CAAA;OAED,EAAA,CAAA;MAGN,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CACG,EAAG,wBAAwB,aAAa,EACzC,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAG,sBAAsB,aAAa;SAClC,CAAA,CACD;;OACR,kBAAC,SAAD;QACE,MAAK;QACL,OAAO;QACP,WAAW,MAAM,EAAa,EAAE,OAAO,MAAM;QAC7C,UAAU;QACV,sBAAK,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG;QAC1C,WAAU;QACV,CAAA;OACF,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,sCACA,qDACD;QACC,CAAA;OACA,EAAA,CAAA;MAGL,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,IAAD,EAAa,WAAU,2CAA4C,CAAA,EACnE,kBAAC,KAAD;QAAG,WAAU;kBAAkC;QAAU,CAAA,CACrD;;MAEP,EAAA,CAAA,CAED;;IAGL,CAAC,KACA,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,UAAU;MACV,WAAU;gBAET,EAAG,oBAAoB,SAAS;MAC1B,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,UAAU,KAAW,EAAe,WAAW;MAC/C,WAAW,EACT,iJACA,qDACD;gBAEA,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAC3C,EAAG,sBAAsB,cAAc,CACvC,EAAA,CAAA,GAEH,EAAG,mBAAmB,QAAQ;MAEzB,CAAA,CACL;;IAEJ;KACF;MAzVU"}
1
+ {"version":3,"file":"GrantDelegationModal.js","names":[],"sources":["../../../src/devportal/components/GrantDelegationModal.tsx"],"sourcesContent":["import {\n useState,\n useCallback,\n useEffect,\n useId,\n useRef,\n type FC,\n} from \"react\";\nimport {\n Shield,\n X,\n Loader2,\n CheckCircle,\n AlertCircle,\n Search,\n Check,\n} from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { useGatewayClient } from \"@burdenoff/fe-libs/shared/graphql\";\nimport { cn } from \"../utils/cn\";\nimport { useGrantDelegationMutation } from \"../../generated/global-operations\";\nimport { useMyWorkspacesQuery } from \"../../generated/wspace-operations\";\n\ninterface WorkspaceOption {\n id: string;\n name: string;\n}\n\n// ============================================================================\n// Scope grouping helper\n// ============================================================================\n\nfunction groupScopes(scopes: string[]): Map<string, string[]> {\n const groups = new Map<string, string[]>();\n for (const scope of scopes) {\n const [resource] = scope.split(\":\");\n const group = resource || \"other\";\n if (!groups.has(group)) groups.set(group, []);\n groups.get(group)!.push(scope);\n }\n return groups;\n}\n\n// ============================================================================\n// Props\n// ============================================================================\n\nexport interface GrantDelegationModalProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n appId: string;\n appName: string;\n appScopes: string[];\n /** Pre-selected workspace IDs (e.g., from installation flow) */\n preSelectedWorkspaceIds?: string[];\n /** Pre-selected scopes (e.g., required scopes from app) */\n preSelectedScopes?: string[];\n onSuccess?: (delegation: unknown) => void;\n}\n\n/**\n * Reusable modal for granting delegation access to an app.\n *\n * Allows users to select scopes, specify workspace IDs,\n * and optionally set an expiration date before granting\n * an app permission to act on their behalf.\n */\nexport const GrantDelegationModal: FC<GrantDelegationModalProps> = ({\n open,\n onOpenChange,\n appId,\n appName,\n appScopes,\n preSelectedWorkspaceIds,\n preSelectedScopes,\n onSuccess,\n}) => {\n const titleId = useId();\n const descriptionId = useId();\n const dialogRef = useRef<HTMLDivElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n const [selectedScopes, setSelectedScopes] = useState<string[]>(\n preSelectedScopes ?? [],\n );\n const [selectedWorkspaceIds, setSelectedWorkspaceIds] = useState<string[]>(\n preSelectedWorkspaceIds ?? [],\n );\n const [allWorkspaces, setAllWorkspaces] = useState(false);\n const [workspaceSearch, setWorkspaceSearch] = useState(\"\");\n const [expiresAt, setExpiresAt] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n\n // useGatewayClient throws when no MultiGatewayProvider is mounted (e.g. in\n // standalone tests). We intentionally swallow that to render in degraded\n // mode without the workspaces dropdown.\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const workspaceClient = (() => {\n try {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n return useGatewayClient(\"workspace\");\n } catch {\n return null;\n }\n })();\n const { data: workspacesData, loading: loadingWorkspaces } =\n useMyWorkspacesQuery({\n client: workspaceClient ?? undefined,\n skip: !open || !workspaceClient,\n variables: {\n pagination: {\n limit: 100,\n offset: 0,\n },\n },\n fetchPolicy: \"cache-and-network\",\n });\n const workspaces: WorkspaceOption[] =\n workspacesData?.myWorkspaces?.items?.map(\n (workspace: { id: string; name: string }) => ({\n id: workspace.id,\n name: workspace.name,\n }),\n ) ?? [];\n const [grantDelegationMutation, { loading }] = useGrantDelegationMutation({\n refetchQueries: [\"CurrentUserDelegations\"],\n awaitRefetchQueries: true,\n });\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Apply pre-selections when they change\n useEffect(() => {\n if (preSelectedWorkspaceIds?.length)\n setSelectedWorkspaceIds(preSelectedWorkspaceIds);\n }, [preSelectedWorkspaceIds]);\n useEffect(() => {\n if (preSelectedScopes?.length) setSelectedScopes(preSelectedScopes);\n }, [preSelectedScopes]);\n\n const resetState = useCallback(() => {\n setSelectedScopes(preSelectedScopes ?? []);\n setSelectedWorkspaceIds(preSelectedWorkspaceIds ?? []);\n setAllWorkspaces(false);\n setWorkspaceSearch(\"\");\n setExpiresAt(\"\");\n setError(null);\n setSuccessMessage(null);\n }, [preSelectedScopes, preSelectedWorkspaceIds]);\n\n const handleClose = useCallback(() => {\n if (loading) return;\n resetState();\n onOpenChange(false);\n }, [loading, onOpenChange, resetState]);\n\n useEffect(() => {\n if (!open) return undefined;\n\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n\n window.requestAnimationFrame(() => {\n dialogRef.current?.focus();\n });\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\" && !loading) {\n event.preventDefault();\n handleClose();\n }\n };\n\n document.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n document.body.style.overflow = originalOverflow;\n document.removeEventListener(\"keydown\", onKeyDown);\n previousFocusRef.current?.focus();\n };\n }, [handleClose, loading, open]);\n\n const toggleScope = useCallback((scope: string) => {\n setSelectedScopes((prev) =>\n prev.includes(scope) ? prev.filter((s) => s !== scope) : [...prev, scope],\n );\n }, []);\n\n const toggleWorkspace = useCallback((wsId: string) => {\n setSelectedWorkspaceIds((prev) =>\n prev.includes(wsId) ? prev.filter((id) => id !== wsId) : [...prev, wsId],\n );\n setAllWorkspaces(false);\n }, []);\n\n const handleAllWorkspacesToggle = useCallback(() => {\n setAllWorkspaces((prev) => {\n if (!prev) setSelectedWorkspaceIds([]);\n return !prev;\n });\n }, []);\n\n const filteredWorkspaces = workspaces.filter((ws) =>\n ws.name.toLowerCase().includes(workspaceSearch.toLowerCase()),\n );\n\n const scopeGroups = groupScopes(appScopes);\n\n const handleGrant = useCallback(async () => {\n setError(null);\n\n if (selectedScopes.length === 0) {\n setError(\n tr(\n \"devportal.selectAtLeastOneScope\",\n \"Please select at least one scope to grant.\",\n ),\n );\n return;\n }\n\n let parsedExpiresAt: string | undefined;\n if (expiresAt) {\n const date = new Date(expiresAt);\n if (Number.isNaN(date.getTime())) {\n setError(\n tr(\n \"devportal.invalidExpirationDate\",\n \"Invalid expiration date. Please use a valid date format.\",\n ),\n );\n return;\n }\n if (date <= new Date()) {\n setError(\n tr(\n \"devportal.expirationDateFuture\",\n \"Expiration date must be in the future.\",\n ),\n );\n return;\n }\n parsedExpiresAt = date.toISOString();\n }\n\n try {\n const result = await grantDelegationMutation({\n variables: {\n input: {\n appId,\n scopes: selectedScopes,\n workspaceIds:\n !allWorkspaces && selectedWorkspaceIds.length > 0\n ? selectedWorkspaceIds\n : undefined,\n expiresAt: parsedExpiresAt,\n },\n },\n });\n\n if (result.error) {\n setError(\n result.error.message ??\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const delegation = result.data?.grantDelegation;\n if (!delegation) {\n setError(\n tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n return;\n }\n\n const wsCount = allWorkspaces\n ? \"all workspaces\"\n : selectedWorkspaceIds.length > 0\n ? `${selectedWorkspaceIds.length} workspace${selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"}`\n : \"all workspaces\";\n setSuccessMessage(\n `Delegation granted to \"${appName}\" with ${selectedScopes.length} scope${selectedScopes.length !== 1 ? \"s\" : \"\"} for ${wsCount}.`,\n );\n onSuccess?.(delegation);\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToGrantDelegation\",\n \"Failed to grant delegation.\",\n ),\n );\n }\n }, [\n selectedScopes,\n selectedWorkspaceIds,\n allWorkspaces,\n expiresAt,\n appId,\n appName,\n grantDelegationMutation,\n onSuccess,\n ]);\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <button\n type=\"button\"\n aria-label=\"Close delegation dialog backdrop\"\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={handleClose}\n />\n\n {/* Dialog */}\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={descriptionId}\n tabIndex={-1}\n className=\"relative z-10 w-full max-w-lg rounded-lg bg-background shadow-xl\"\n >\n {/* Header */}\n <div className=\"flex items-start gap-4 border-b p-6\">\n <div className=\"rounded-full bg-status-success-bg-subtle p-2\">\n <Shield className=\"h-6 w-6 text-status-success-text\" />\n </div>\n <div className=\"flex-1\">\n <h3 id={titleId} className=\"text-lg font-semibold\">\n Grant Delegation\n </h3>\n <p\n id={descriptionId}\n className=\"mt-1 text-sm text-muted-foreground\"\n >\n Allow{\" \"}\n <span className=\"font-medium text-foreground\">{appName}</span> to\n act on your behalf\n </p>\n </div>\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md p-1 text-muted-foreground hover:bg-muted transition-colors disabled:opacity-50\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Body */}\n <div className=\"space-y-5 p-6\">\n {/* Success state */}\n {successMessage && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-success-bg-subtle p-4\">\n <CheckCircle className=\"h-5 w-5 shrink-0 text-status-success-text\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-success-text\">\n {successMessage}\n </p>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"mt-2 text-sm font-medium text-status-success-text hover:underline\"\n >\n {tr(\"devportal.close\", \"Close\")}\n </button>\n </div>\n </div>\n )}\n\n {/* Form fields (hidden after success) */}\n {!successMessage && (\n <>\n {/* Scope selection (grouped by resource) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.permissionsScopes\", \"Permissions (Scopes)\")}\n <span className=\"ml-1.5 text-xs font-normal text-muted-foreground\">\n {selectedScopes.length} {tr(\"devportal.of\", \"of\")}{\" \"}\n {appScopes.length} {tr(\"devportal.selected\", \"selected\")}\n </span>\n </label>\n {appScopes.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n This app has no scopes available.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {/* Quick actions */}\n <div className=\"flex gap-2 text-xs\">\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([...appScopes])}\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.selectAll\", \"Select all\")}\n </button>\n <button\n type=\"button\"\n onClick={() =>\n setSelectedScopes(\n appScopes.filter(\n (s) => s.includes(\":read\") || s.includes(\":list\"),\n ),\n )\n }\n className=\"text-status-success-text hover:underline\"\n >\n {tr(\"devportal.readOnly\", \"Read-only\")}\n </button>\n <button\n type=\"button\"\n onClick={() => setSelectedScopes([])}\n className=\"text-muted-foreground hover:underline\"\n >\n {tr(\"devportal.clear\", \"Clear\")}\n </button>\n </div>\n {/* Grouped scopes */}\n {[...scopeGroups.entries()].map(([group, scopes]) => (\n <div key={group}>\n <p className=\"mb-1 text-xs font-semibold uppercase text-muted-foreground\">\n {group}\n </p>\n <div className=\"flex flex-wrap gap-1.5\">\n {scopes.map((scope) => {\n const isSelected = selectedScopes.includes(scope);\n const action = scope.split(\":\")[1] || scope;\n const isWrite =\n action.includes(\"write\") ||\n action.includes(\"create\") ||\n action.includes(\"delete\") ||\n action.includes(\"admin\");\n return (\n <button\n key={scope}\n type=\"button\"\n onClick={() => toggleScope(scope)}\n className={cn(\n \"rounded-md px-2.5 py-1 text-xs font-medium transition-colors\",\n isSelected\n ? isWrite\n ? \"bg-status-warning-bg text-white\"\n : \"bg-status-success-bg text-white\"\n : \"border bg-muted text-muted-foreground hover:bg-muted/80\",\n )}\n >\n {action}\n </button>\n );\n })}\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Workspace Selection (multi-select picker) */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.workspaceAccess\", \"Workspace Access\")}\n </label>\n\n {/* All workspaces toggle */}\n <button\n type=\"button\"\n onClick={handleAllWorkspacesToggle}\n disabled={loading}\n className={cn(\n \"mb-2 flex w-full items-center gap-2 rounded-md border px-3 py-2 text-sm transition-colors\",\n allWorkspaces\n ? \"border-status-success-bg bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-muted\",\n )}\n >\n {allWorkspaces && (\n <Check className=\"h-4 w-4 text-status-success-text\" />\n )}\n <span>\n {tr(\n \"devportal.allWorkspacesUnrestricted\",\n \"All workspaces (unrestricted)\",\n )}\n </span>\n </button>\n\n {!allWorkspaces && (\n <>\n {/* Search */}\n <div className=\"relative mb-2\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground\" />\n <input\n type=\"text\"\n value={workspaceSearch}\n onChange={(e) => setWorkspaceSearch(e.target.value)}\n placeholder={tr(\n \"devportal.searchWorkspaces\",\n \"Search workspaces...\",\n )}\n disabled={loading}\n className=\"block w-full rounded-md border py-2 pl-9 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n </div>\n\n {/* Workspace list */}\n <div className=\"max-h-40 overflow-y-auto rounded-md border\">\n {loadingWorkspaces ? (\n <div className=\"flex items-center justify-center p-4\">\n <Loader2 className=\"h-4 w-4 animate-spin text-muted-foreground\" />\n <span className=\"ml-2 text-sm text-muted-foreground\">\n {tr(\n \"devportal.loadingWorkspaces\",\n \"Loading workspaces...\",\n )}\n </span>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <p className=\"p-4 text-center text-sm text-muted-foreground\">\n {workspaces.length === 0\n ? tr(\n \"devportal.noWorkspacesFound\",\n \"No workspaces found\",\n )\n : tr(\n \"devportal.noMatchingWorkspaces\",\n \"No matching workspaces\",\n )}\n </p>\n ) : (\n filteredWorkspaces.map((ws) => {\n const isSelected = selectedWorkspaceIds.includes(\n ws.id,\n );\n return (\n <button\n key={ws.id}\n type=\"button\"\n onClick={() => toggleWorkspace(ws.id)}\n disabled={loading}\n className={cn(\n \"flex w-full items-center gap-2 border-b px-3 py-2 text-left text-sm transition-colors last:border-b-0\",\n isSelected\n ? \"bg-status-success-bg-subtle text-status-success-text\"\n : \"hover:bg-muted\",\n )}\n >\n <div\n className={cn(\n \"flex h-4 w-4 shrink-0 items-center justify-center rounded border\",\n isSelected\n ? \"border-status-success-bg bg-status-success-bg\"\n : \"border-muted-foreground/30\",\n )}\n >\n {isSelected && (\n <Check className=\"h-3 w-3 text-white\" />\n )}\n </div>\n <span className=\"font-medium\">{ws.name}</span>\n </button>\n );\n })\n )}\n </div>\n\n {/* Selected count */}\n {selectedWorkspaceIds.length > 0 && (\n <p className=\"mt-1.5 text-xs text-muted-foreground\">\n {selectedWorkspaceIds.length} workspace\n {selectedWorkspaceIds.length !== 1 ? \"s\" : \"\"} selected\n </p>\n )}\n </>\n )}\n </div>\n\n {/* Expiration */}\n <div>\n <label className=\"mb-2 block text-sm font-medium\">\n {tr(\"devportal.expiration\", \"Expiration\")}\n <span className=\"ml-1.5 text-xs font-normal text-muted-foreground\">\n {tr(\"devportal.optional\", \"(optional)\")}\n </span>\n </label>\n <input\n type=\"datetime-local\"\n value={expiresAt}\n onChange={(e) => setExpiresAt(e.target.value)}\n disabled={loading}\n min={new Date().toISOString().slice(0, 16)}\n className=\"block w-full rounded-md border px-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-status-success-bg/20 focus:border-status-success-bg transition-colors disabled:opacity-50\"\n />\n <p className=\"mt-1.5 text-xs text-muted-foreground\">\n {tr(\n \"devportal.leaveEmptyForNonExpiring\",\n \"Leave empty for a delegation that does not expire.\",\n )}\n </p>\n </div>\n\n {/* Error */}\n {error && (\n <div className=\"flex items-start gap-3 rounded-md border border-border-subtle bg-status-error-bg-subtle p-4\">\n <AlertCircle className=\"h-5 w-5 shrink-0 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n </>\n )}\n </div>\n\n {/* Footer */}\n {!successMessage && (\n <div className=\"flex justify-end gap-3 border-t p-6\">\n <button\n type=\"button\"\n onClick={handleClose}\n disabled={loading}\n className=\"rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted transition-colors disabled:opacity-50\"\n >\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"button\"\n onClick={handleGrant}\n disabled={loading || selectedScopes.length === 0}\n className={cn(\n \"flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed\",\n \"bg-status-success-bg hover:bg-status-success-bg/90\",\n )}\n >\n {loading ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr(\"devportal.granting\", \"Granting...\")}\n </>\n ) : (\n tr(\"devportal.grant\", \"Grant\")\n )}\n </button>\n </div>\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}