@burdenoff/microfe-admin 2026.610.1 → 2026.624.1
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.
- package/dist/admin/components/AccessDenied.js +2 -2
- package/dist/admin/components/AccessDenied.js.map +1 -1
- package/dist/admin/components/ActivityFeed.js +4 -4
- package/dist/admin/components/ActivityFeed.js.map +1 -1
- package/dist/admin/components/Charts.js +4 -4
- package/dist/admin/components/Charts.js.map +1 -1
- package/dist/admin/components/ConfirmDialog.js +6 -6
- package/dist/admin/components/ConfirmDialog.js.map +1 -1
- package/dist/admin/components/DataTable.js +7 -7
- package/dist/admin/components/DataTable.js.map +1 -1
- package/dist/admin/components/FormSheet.js +7 -7
- package/dist/admin/components/FormSheet.js.map +1 -1
- package/dist/admin/components/MetricCard.js +4 -4
- package/dist/admin/components/MetricCard.js.map +1 -1
- package/dist/admin/components/PageLayout.js +3 -3
- package/dist/admin/components/PageLayout.js.map +1 -1
- package/dist/admin/components/PermissionGuard.js +1 -1
- package/dist/admin/components/PermissionGuard.js.map +1 -1
- package/dist/admin/components/RowActions.js +2 -2
- package/dist/admin/components/RowActions.js.map +1 -1
- package/dist/admin/components/TenantMembersPanel.js +15 -15
- package/dist/admin/components/TenantMembersPanel.js.map +1 -1
- package/dist/admin/components/TenantSsoPanel.js +14 -14
- package/dist/admin/components/TenantSsoPanel.js.map +1 -1
- package/dist/admin/components/Toast.js +9 -9
- package/dist/admin/components/Toast.js.map +1 -1
- package/dist/admin/components/forms/SubscriptionForm.js +10 -10
- package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
- package/dist/admin/components/forms/TenantForm.js +8 -8
- package/dist/admin/components/forms/TenantForm.js.map +1 -1
- package/dist/admin/components/forms/TicketForm.js +2 -2
- package/dist/admin/components/forms/TicketForm.js.map +1 -1
- package/dist/admin/components/forms/UserForm.js +8 -8
- package/dist/admin/components/forms/UserForm.js.map +1 -1
- package/dist/admin/components/ui.js +16 -16
- package/dist/admin/components/ui.js.map +1 -1
- package/dist/admin/constants/index.js +8 -8
- package/dist/admin/constants/index.js.map +1 -1
- package/dist/admin/pages/AuditLogsPage.js +8 -8
- package/dist/admin/pages/AuditLogsPage.js.map +1 -1
- package/dist/admin/pages/CommunicationsPage.js +2 -2
- package/dist/admin/pages/CommunicationsPage.js.map +1 -1
- package/dist/admin/pages/FeatureFlagsPage.js +4 -4
- package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
- package/dist/admin/pages/OrganizationsListPage.js +6 -6
- package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
- package/dist/admin/pages/ProductsPage.js +7 -7
- package/dist/admin/pages/ProductsPage.js.map +1 -1
- package/dist/admin/pages/ReferralsPage.js +23 -23
- package/dist/admin/pages/ReferralsPage.js.map +1 -1
- package/dist/admin/pages/RolesPermissionsPage.js +8 -8
- package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
- package/dist/admin/pages/SREPage.js +8 -8
- package/dist/admin/pages/SREPage.js.map +1 -1
- package/dist/admin/pages/SandboxSettingsPage.js +20 -20
- package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage.js +20 -20
- package/dist/admin/pages/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SyncFailuresPage.js +11 -11
- package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
- package/dist/admin/pages/TenantDetailPage.js +22 -22
- package/dist/admin/pages/TenantDetailPage.js.map +1 -1
- package/dist/admin/pages/TenantFormRoutePage.js +2 -2
- package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
- package/dist/admin/pages/TenantsListPage.js +5 -5
- package/dist/admin/pages/TenantsListPage.js.map +1 -1
- package/dist/admin/pages/UsersListPage.js +6 -6
- package/dist/admin/pages/UsersListPage.js.map +1 -1
- package/dist/admin/pages/WaitlistPage.js +7 -7
- package/dist/admin/pages/WaitlistPage.js.map +1 -1
- package/dist/admin/pages/WorkflowsPage.js +3 -3
- package/dist/admin/pages/WorkflowsPage.js.map +1 -1
- package/dist/admin/pages/WorkspacesListPage.js +8 -8
- package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -38,7 +38,7 @@ function p({ actions: a, ariaLabel: o = "Row actions", stopPropagation: s = !0 }
|
|
|
38
38
|
children: /* @__PURE__ */ d(c, { className: "h-4 w-4" })
|
|
39
39
|
}), l && /* @__PURE__ */ d("div", {
|
|
40
40
|
ref: m,
|
|
41
|
-
className: e("absolute right-0 top-full z-50 mt-1 min-w-[160px] rounded-md border border-border bg-popover p-1 shadow-lg", "animate-in fade-in-0 zoom-in-95 duration-100"),
|
|
41
|
+
className: e("absolute right-0 top-full z-50 mt-1 min-w-[160px] rounded-md border border-border-subtle bg-popover p-1 shadow-lg", "animate-in fade-in-0 zoom-in-95 duration-100"),
|
|
42
42
|
role: "menu",
|
|
43
43
|
children: a.map((t, n) => /* @__PURE__ */ f("div", { children: [t.separator && n > 0 && /* @__PURE__ */ d("div", {
|
|
44
44
|
className: "my-1 h-px bg-border",
|
|
@@ -47,7 +47,7 @@ function p({ actions: a, ariaLabel: o = "Row actions", stopPropagation: s = !0 }
|
|
|
47
47
|
role: "menuitem",
|
|
48
48
|
onClick: (e) => g(t, e),
|
|
49
49
|
disabled: t.disabled,
|
|
50
|
-
className: e("flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors", "focus:bg-accent focus:outline-none", t.disabled ? "cursor-not-allowed opacity-50" : t.destructive ? "text-
|
|
50
|
+
className: e("flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors", "focus:bg-accent focus:outline-none", t.disabled ? "cursor-not-allowed opacity-50" : t.destructive ? "text-status-error-text hover:bg-status-error-bg/10 focus:bg-status-error-bg/10" : "text-text-primary hover:bg-accent"),
|
|
51
51
|
children: [t.icon && /* @__PURE__ */ d("span", {
|
|
52
52
|
className: "h-4 w-4",
|
|
53
53
|
children: t.icon
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RowActions.js","names":[],"sources":["../../../src/admin/components/RowActions.tsx"],"sourcesContent":["import { useState, useRef, useEffect, type ReactNode } from 'react';\nimport { MoreHorizontal, Pencil, Trash2, Eye, Copy, ExternalLink } from 'lucide-react';\nimport { Button } from './ui';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RowAction {\n /** Unique key for the action */\n key: string;\n /** Display label */\n label: string;\n /** Icon component */\n icon?: ReactNode;\n /** Action handler */\n onClick: () => void;\n /** Whether this is a destructive action */\n destructive?: boolean;\n /** Whether the action is disabled */\n disabled?: boolean;\n /** Separator before this item */\n separator?: boolean;\n}\n\nexport interface RowActionsProps {\n /** Actions to display */\n actions: RowAction[];\n /** Accessibility label */\n ariaLabel?: string;\n /** Stop event propagation on click */\n stopPropagation?: boolean;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * RowActions - Dropdown menu for table row actions\n */\nexport function RowActions({\n actions,\n ariaLabel = 'Row actions',\n stopPropagation = true,\n}: RowActionsProps) {\n const [open, setOpen] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n // Close on click outside\n useEffect(() => {\n if (!open) return;\n\n const handleClickOutside = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [open]);\n\n // Close on escape\n useEffect(() => {\n if (!open) return;\n\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n setOpen(false);\n }\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [open]);\n\n const handleTriggerClick = (e: React.MouseEvent) => {\n if (stopPropagation) {\n e.stopPropagation();\n }\n setOpen(!open);\n };\n\n const handleActionClick = (action: RowAction, e: React.MouseEvent) => {\n if (stopPropagation) {\n e.stopPropagation();\n }\n if (!action.disabled) {\n setOpen(false);\n action.onClick();\n }\n };\n\n return (\n <div ref={containerRef} className=\"relative\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleTriggerClick}\n aria-label={ariaLabel}\n aria-haspopup=\"true\"\n aria-expanded={open}\n className=\"h-8 w-8\"\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n\n {open && (\n <div\n ref={menuRef}\n className={cn(\n 'absolute right-0 top-full z-50 mt-1 min-w-[160px] rounded-md border border-border bg-popover p-1 shadow-lg',\n 'animate-in fade-in-0 zoom-in-95 duration-100'\n )}\n role=\"menu\"\n >\n {actions.map((action, index) => (\n <div key={action.key}>\n {action.separator && index > 0 && (\n <div className=\"my-1 h-px bg-border\" role=\"separator\" />\n )}\n <button\n role=\"menuitem\"\n onClick={(e) => handleActionClick(action, e)}\n disabled={action.disabled}\n className={cn(\n 'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors',\n 'focus:bg-accent focus:outline-none',\n action.disabled\n ? 'cursor-not-allowed opacity-50'\n : action.destructive\n ? 'text-
|
|
1
|
+
{"version":3,"file":"RowActions.js","names":[],"sources":["../../../src/admin/components/RowActions.tsx"],"sourcesContent":["import { useState, useRef, useEffect, type ReactNode } from 'react';\nimport { MoreHorizontal, Pencil, Trash2, Eye, Copy, ExternalLink } from 'lucide-react';\nimport { Button } from './ui';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RowAction {\n /** Unique key for the action */\n key: string;\n /** Display label */\n label: string;\n /** Icon component */\n icon?: ReactNode;\n /** Action handler */\n onClick: () => void;\n /** Whether this is a destructive action */\n destructive?: boolean;\n /** Whether the action is disabled */\n disabled?: boolean;\n /** Separator before this item */\n separator?: boolean;\n}\n\nexport interface RowActionsProps {\n /** Actions to display */\n actions: RowAction[];\n /** Accessibility label */\n ariaLabel?: string;\n /** Stop event propagation on click */\n stopPropagation?: boolean;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * RowActions - Dropdown menu for table row actions\n */\nexport function RowActions({\n actions,\n ariaLabel = 'Row actions',\n stopPropagation = true,\n}: RowActionsProps) {\n const [open, setOpen] = useState(false);\n const containerRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n // Close on click outside\n useEffect(() => {\n if (!open) return;\n\n const handleClickOutside = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [open]);\n\n // Close on escape\n useEffect(() => {\n if (!open) return;\n\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n setOpen(false);\n }\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [open]);\n\n const handleTriggerClick = (e: React.MouseEvent) => {\n if (stopPropagation) {\n e.stopPropagation();\n }\n setOpen(!open);\n };\n\n const handleActionClick = (action: RowAction, e: React.MouseEvent) => {\n if (stopPropagation) {\n e.stopPropagation();\n }\n if (!action.disabled) {\n setOpen(false);\n action.onClick();\n }\n };\n\n return (\n <div ref={containerRef} className=\"relative\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={handleTriggerClick}\n aria-label={ariaLabel}\n aria-haspopup=\"true\"\n aria-expanded={open}\n className=\"h-8 w-8\"\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n\n {open && (\n <div\n ref={menuRef}\n className={cn(\n 'absolute right-0 top-full z-50 mt-1 min-w-[160px] rounded-md border border-border-subtle bg-popover p-1 shadow-lg',\n 'animate-in fade-in-0 zoom-in-95 duration-100'\n )}\n role=\"menu\"\n >\n {actions.map((action, index) => (\n <div key={action.key}>\n {action.separator && index > 0 && (\n <div className=\"my-1 h-px bg-border\" role=\"separator\" />\n )}\n <button\n role=\"menuitem\"\n onClick={(e) => handleActionClick(action, e)}\n disabled={action.disabled}\n className={cn(\n 'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors',\n 'focus:bg-accent focus:outline-none',\n action.disabled\n ? 'cursor-not-allowed opacity-50'\n : action.destructive\n ? 'text-status-error-text hover:bg-status-error-bg/10 focus:bg-status-error-bg/10'\n : 'text-text-primary hover:bg-accent'\n )}\n >\n {action.icon && <span className=\"h-4 w-4\">{action.icon}</span>}\n {action.label}\n </button>\n </div>\n ))}\n </div>\n )}\n </div>\n );\n}\n\n// ============================================================================\n// Pre-built Action Helpers\n// ============================================================================\n\n/**\n * Create common row actions\n */\nexport function createRowActions(config: {\n onView?: () => void;\n onEdit?: () => void;\n onDuplicate?: () => void;\n onOpenExternal?: () => void;\n onDelete?: () => void;\n}): RowAction[] {\n const actions: RowAction[] = [];\n\n if (config.onView) {\n actions.push({\n key: 'view',\n label: 'View',\n icon: <Eye className=\"h-4 w-4\" />,\n onClick: config.onView,\n });\n }\n\n if (config.onEdit) {\n actions.push({\n key: 'edit',\n label: 'Edit',\n icon: <Pencil className=\"h-4 w-4\" />,\n onClick: config.onEdit,\n });\n }\n\n if (config.onDuplicate) {\n actions.push({\n key: 'duplicate',\n label: 'Duplicate',\n icon: <Copy className=\"h-4 w-4\" />,\n onClick: config.onDuplicate,\n });\n }\n\n if (config.onOpenExternal) {\n actions.push({\n key: 'open-external',\n label: 'Open in new tab',\n icon: <ExternalLink className=\"h-4 w-4\" />,\n onClick: config.onOpenExternal,\n });\n }\n\n if (config.onDelete) {\n actions.push({\n key: 'delete',\n label: 'Delete',\n icon: <Trash2 className=\"h-4 w-4\" />,\n onClick: config.onDelete,\n destructive: true,\n separator: actions.length > 0,\n });\n }\n\n return actions;\n}\n"],"mappings":";;;;;;AA0CA,SAAgB,EAAW,EACzB,YACA,eAAY,eACZ,qBAAkB,MACA;CAClB,IAAM,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,IAAe,EAAuB,KAAK,EAC3C,IAAU,EAAuB,KAAK;AAiB5C,CAdA,QAAgB;AACd,MAAI,CAAC,EAAM;EAEX,IAAM,KAAsB,MAAkB;AAC5C,GAAI,EAAa,WAAW,CAAC,EAAa,QAAQ,SAAS,EAAE,OAAe,IAC1E,EAAQ,GAAM;;AAKlB,SADA,SAAS,iBAAiB,aAAa,EAAmB,QAC7C,SAAS,oBAAoB,aAAa,EAAmB;IACzE,CAAC,EAAK,CAAC,EAGV,QAAgB;AACd,MAAI,CAAC,EAAM;EAEX,IAAM,KAAgB,MAAqB;AACzC,GAAI,EAAE,QAAQ,YACZ,EAAQ,GAAM;;AAKlB,SADA,SAAS,iBAAiB,WAAW,EAAa,QACrC,SAAS,oBAAoB,WAAW,EAAa;IACjE,CAAC,EAAK,CAAC;CAEV,IAAM,KAAsB,MAAwB;AAIlD,EAHI,KACF,EAAE,iBAAiB,EAErB,EAAQ,CAAC,EAAK;IAGV,KAAqB,GAAmB,MAAwB;AAIpE,EAHI,KACF,EAAE,iBAAiB,EAEhB,EAAO,aACV,EAAQ,GAAM,EACd,EAAO,SAAS;;AAIpB,QACE,kBAAC,OAAD;EAAK,KAAK;EAAc,WAAU;YAAlC,CACE,kBAAC,GAAD;GACE,SAAQ;GACR,MAAK;GACL,SAAS;GACT,cAAY;GACZ,iBAAc;GACd,iBAAe;GACf,WAAU;aAEV,kBAAC,GAAD,EAAgB,WAAU,WAAY,CAAA;GAC/B,CAAA,EAER,KACC,kBAAC,OAAD;GACE,KAAK;GACL,WAAW,EACT,qHACA,+CACD;GACD,MAAK;aAEJ,EAAQ,KAAK,GAAQ,MACpB,kBAAC,OAAD,EAAA,UAAA,CACG,EAAO,aAAa,IAAQ,KAC3B,kBAAC,OAAD;IAAK,WAAU;IAAsB,MAAK;IAAc,CAAA,EAE1D,kBAAC,UAAD;IACE,MAAK;IACL,UAAU,MAAM,EAAkB,GAAQ,EAAE;IAC5C,UAAU,EAAO;IACjB,WAAW,EACT,mFACA,sCACA,EAAO,WACH,kCACA,EAAO,cACL,mFACA,oCACP;cAZH,CAcG,EAAO,QAAQ,kBAAC,QAAD;KAAM,WAAU;eAAW,EAAO;KAAY,CAAA,EAC7D,EAAO,MACD;MACL,EAAA,EArBI,EAAO,IAqBX,CACN;GACE,CAAA,CAEJ;;;AAWV,SAAgB,EAAiB,GAMjB;CACd,IAAM,IAAuB,EAAE;AAiD/B,QA/CI,EAAO,UACT,EAAQ,KAAK;EACX,KAAK;EACL,OAAO;EACP,MAAM,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;EACjC,SAAS,EAAO;EACjB,CAAC,EAGA,EAAO,UACT,EAAQ,KAAK;EACX,KAAK;EACL,OAAO;EACP,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;EACpC,SAAS,EAAO;EACjB,CAAC,EAGA,EAAO,eACT,EAAQ,KAAK;EACX,KAAK;EACL,OAAO;EACP,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;EAClC,SAAS,EAAO;EACjB,CAAC,EAGA,EAAO,kBACT,EAAQ,KAAK;EACX,KAAK;EACL,OAAO;EACP,MAAM,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;EAC1C,SAAS,EAAO;EACjB,CAAC,EAGA,EAAO,YACT,EAAQ,KAAK;EACX,KAAK;EACL,OAAO;EACP,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;EACpC,SAAS,EAAO;EAChB,aAAa;EACb,WAAW,EAAQ,SAAS;EAC7B,CAAC,EAGG"}
|
|
@@ -28,7 +28,7 @@ function E({ tenantId: E, tenantType: D, canManage: O }) {
|
|
|
28
28
|
className: "flex items-center gap-2 text-lg",
|
|
29
29
|
children: [/* @__PURE__ */ x(y, { className: "h-4 w-4" }), A("admin.tenantMembers.title", "Tenant Members")]
|
|
30
30
|
}) }), /* @__PURE__ */ x(r, { children: /* @__PURE__ */ x("p", {
|
|
31
|
-
className: "text-sm text-
|
|
31
|
+
className: "text-sm text-text-secondary",
|
|
32
32
|
children: A("admin.tenantMembers.sharedImplicit", "This is the shared tenant. Every authenticated user has implicit access — no explicit membership rows are stored.")
|
|
33
33
|
}) })] });
|
|
34
34
|
let J = async (e) => {
|
|
@@ -71,7 +71,7 @@ function E({ tenantId: E, tenantType: D, canManage: O }) {
|
|
|
71
71
|
/* @__PURE__ */ x(y, { className: "h-4 w-4" }),
|
|
72
72
|
A("admin.tenantMembers.title", "Tenant Members"),
|
|
73
73
|
Q > 0 && /* @__PURE__ */ S("span", {
|
|
74
|
-
className: "text-sm text-
|
|
74
|
+
className: "text-sm text-text-secondary",
|
|
75
75
|
children: [
|
|
76
76
|
"(",
|
|
77
77
|
Q,
|
|
@@ -83,7 +83,7 @@ function E({ tenantId: E, tenantType: D, canManage: O }) {
|
|
|
83
83
|
className: "space-y-4",
|
|
84
84
|
children: [
|
|
85
85
|
/* @__PURE__ */ x("p", {
|
|
86
|
-
className: "text-sm text-
|
|
86
|
+
className: "text-sm text-text-secondary",
|
|
87
87
|
children: A("admin.tenantMembers.description", "Grant a user explicit access to this dedicated tenant. They will be able to create organizations under it and see it in their tenant switcher.")
|
|
88
88
|
}),
|
|
89
89
|
O && /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ S("form", {
|
|
@@ -93,7 +93,7 @@ function E({ tenantId: E, tenantType: D, canManage: O }) {
|
|
|
93
93
|
/* @__PURE__ */ S("div", {
|
|
94
94
|
className: "flex-1 min-w-[240px]",
|
|
95
95
|
children: [/* @__PURE__ */ x("label", {
|
|
96
|
-
className: "mb-1 block text-xs font-medium text-
|
|
96
|
+
className: "mb-1 block text-xs font-medium text-text-secondary",
|
|
97
97
|
children: A("admin.tenantMembers.form.userIdLabel", "User ID")
|
|
98
98
|
}), /* @__PURE__ */ x(o, {
|
|
99
99
|
value: M.userId,
|
|
@@ -109,7 +109,7 @@ function E({ tenantId: E, tenantType: D, canManage: O }) {
|
|
|
109
109
|
/* @__PURE__ */ S("div", {
|
|
110
110
|
className: "min-w-[140px]",
|
|
111
111
|
children: [/* @__PURE__ */ x("label", {
|
|
112
|
-
className: "mb-1 block text-xs font-medium text-
|
|
112
|
+
className: "mb-1 block text-xs font-medium text-text-secondary",
|
|
113
113
|
children: A("admin.tenantMembers.form.roleLabel", "Role")
|
|
114
114
|
}), /* @__PURE__ */ S("select", {
|
|
115
115
|
value: M.role,
|
|
@@ -118,7 +118,7 @@ function E({ tenantId: E, tenantType: D, canManage: O }) {
|
|
|
118
118
|
role: e.target.value
|
|
119
119
|
}),
|
|
120
120
|
disabled: W,
|
|
121
|
-
className: "h-9 w-full rounded-md border border-
|
|
121
|
+
className: "h-9 w-full rounded-md border border-border-default bg-bg-surface px-3 text-sm",
|
|
122
122
|
children: [/* @__PURE__ */ x("option", {
|
|
123
123
|
value: h.Member,
|
|
124
124
|
children: A("admin.tenantMembers.role.member", "Member")
|
|
@@ -136,25 +136,25 @@ function E({ tenantId: E, tenantType: D, canManage: O }) {
|
|
|
136
136
|
]
|
|
137
137
|
}), /* @__PURE__ */ x(s, {})] }),
|
|
138
138
|
B && Z.length === 0 ? /* @__PURE__ */ S("div", {
|
|
139
|
-
className: "flex h-24 items-center justify-center text-
|
|
139
|
+
className: "flex h-24 items-center justify-center text-text-secondary",
|
|
140
140
|
children: [/* @__PURE__ */ x(_, { className: "mr-2 h-4 w-4 animate-spin" }), A("admin.tenantMembers.loading", "Loading members...")]
|
|
141
141
|
}) : V ? /* @__PURE__ */ x("div", {
|
|
142
|
-
className: "rounded-md border border-
|
|
142
|
+
className: "rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-3 text-sm text-status-error-text",
|
|
143
143
|
children: V.message
|
|
144
144
|
}) : Z.length === 0 ? /* @__PURE__ */ x("p", {
|
|
145
|
-
className: "text-sm text-
|
|
145
|
+
className: "text-sm text-text-secondary",
|
|
146
146
|
children: A("admin.tenantMembers.empty", "No explicit members yet. Grant access above to add one.")
|
|
147
147
|
}) : /* @__PURE__ */ S("div", {
|
|
148
148
|
className: "space-y-2",
|
|
149
149
|
children: [Z.map((e) => /* @__PURE__ */ S("div", {
|
|
150
|
-
className: "flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border p-3",
|
|
150
|
+
className: "flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border-subtle p-3",
|
|
151
151
|
children: [/* @__PURE__ */ S("div", {
|
|
152
152
|
className: "min-w-0 flex-1",
|
|
153
153
|
children: [/* @__PURE__ */ x("p", {
|
|
154
|
-
className: "truncate font-mono text-xs text-
|
|
154
|
+
className: "truncate font-mono text-xs text-text-primary",
|
|
155
155
|
children: e.userId
|
|
156
156
|
}), /* @__PURE__ */ x("p", {
|
|
157
|
-
className: "text-xs text-
|
|
157
|
+
className: "text-xs text-text-secondary",
|
|
158
158
|
children: A("admin.tenantMembers.addedAt", "Added {{when}} by {{by}}", {
|
|
159
159
|
when: c(e.createdAt),
|
|
160
160
|
by: e.addedBy
|
|
@@ -166,7 +166,7 @@ function E({ tenantId: E, tenantType: D, canManage: O }) {
|
|
|
166
166
|
value: e.role,
|
|
167
167
|
onChange: (t) => X(e.userId, t.target.value),
|
|
168
168
|
disabled: !O,
|
|
169
|
-
className: "h-8 rounded-md border border-
|
|
169
|
+
className: "h-8 rounded-md border border-border-default bg-bg-surface px-2 text-xs",
|
|
170
170
|
children: [/* @__PURE__ */ x("option", {
|
|
171
171
|
value: h.Member,
|
|
172
172
|
children: A("admin.tenantMembers.role.member", "Member")
|
|
@@ -180,13 +180,13 @@ function E({ tenantId: E, tenantType: D, canManage: O }) {
|
|
|
180
180
|
onClick: () => F({ userId: e.userId }),
|
|
181
181
|
disabled: K,
|
|
182
182
|
"aria-label": A("admin.tenantMembers.remove", "Remove"),
|
|
183
|
-
children: /* @__PURE__ */ x(v, { className: "h-4 w-4 text-
|
|
183
|
+
children: /* @__PURE__ */ x(v, { className: "h-4 w-4 text-status-error-text" })
|
|
184
184
|
})]
|
|
185
185
|
})]
|
|
186
186
|
}, e.id)), te && /* @__PURE__ */ S("div", {
|
|
187
187
|
className: "flex items-center justify-between gap-2 pt-2",
|
|
188
188
|
children: [/* @__PURE__ */ x("span", {
|
|
189
|
-
className: "text-xs text-
|
|
189
|
+
className: "text-xs text-text-secondary",
|
|
190
190
|
children: A("admin.tenantMembers.shownOfTotal", "Showing {{shown}} of {{total}}", {
|
|
191
191
|
shown: Z.length,
|
|
192
192
|
total: Q
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TenantMembersPanel.js","names":[],"sources":["../../../src/admin/components/TenantMembersPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Loader2, Trash2, UserPlus, Users } from 'lucide-react';\nimport {\n useListTenantMembersQuery,\n useAddTenantMemberMutation,\n useRemoveTenantMemberMutation,\n useUpdateTenantMemberRoleMutation,\n type TenantMemberFieldsFragment,\n} from '../../generated/global-operations';\nimport { TenantMemberRole } from '../../generated/global-types';\nimport { Card, CardHeader, CardTitle, CardContent, Button, Input, Separator } from './ui';\nimport { useToast } from './Toast';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { formatDate } from '../utils/formatters';\n\ninterface TenantMembersPanelProps {\n tenantId: string;\n tenantType: string;\n canManage: boolean;\n}\n\nconst EMPTY_ADD_FORM = { userId: '', role: TenantMemberRole.Member };\nconst PAGE_SIZE = 100;\n\n/**\n * Tenant Members panel (BOFF-2652) — super_admin UI to grant non-super-admin\n * users explicit access to a DEDICATED tenant. SHARED-tenant access is\n * implicit, so the panel is hidden for SHARED tenants.\n */\nexport function TenantMembersPanel({ tenantId, tenantType, canManage }: TenantMembersPanelProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const toast = useToast();\n\n const [addForm, setAddForm] = useState(EMPTY_ADD_FORM);\n const [removeTarget, setRemoveTarget] = useState<{ userId: string } | null>(null);\n // Pagination: each \"Load more\" click widens the window by PAGE_SIZE rows;\n // Apollo re-fetches the bigger page rather than appending in JS so we\n // don't need a custom merge type policy. (BOFF-2652 review)\n const [pageSize, setPageSize] = useState(PAGE_SIZE);\n\n const isShared = tenantType?.toUpperCase() === 'SHARED';\n\n const { data, loading, error, refetch } = useListTenantMembersQuery({\n variables: { tenantId, limit: pageSize, offset: 0 },\n skip: !tenantId || isShared,\n fetchPolicy: 'cache-and-network',\n });\n\n const [addMember, { loading: adding }] = useAddTenantMemberMutation();\n const [removeMember, { loading: removing }] = useRemoveTenantMemberMutation();\n const [updateRole] = useUpdateTenantMemberRoleMutation();\n\n if (isShared) {\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <Users className=\"h-4 w-4\" />\n {tr('admin.tenantMembers.title', 'Tenant Members')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'admin.tenantMembers.sharedImplicit',\n 'This is the shared tenant. Every authenticated user has implicit access — no explicit membership rows are stored.'\n )}\n </p>\n </CardContent>\n </Card>\n );\n }\n\n const handleAdd = async (e: React.FormEvent) => {\n e.preventDefault();\n const userId = addForm.userId.trim();\n if (!userId) return;\n try {\n await addMember({ variables: { input: { tenantId, userId, role: addForm.role } } });\n toast.success(\n tr('admin.tenantMembers.toast.addedTitle', 'Member added'),\n tr(\n 'admin.tenantMembers.toast.addedDescription',\n 'User {{userId}} can now access this tenant.',\n {\n userId,\n }\n )\n );\n setAddForm(EMPTY_ADD_FORM);\n await refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantMembers.toast.errorTitle', 'Error'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantMembers.toast.addFailed', 'Failed to add tenant member.')\n );\n }\n };\n\n const handleRemove = async () => {\n if (!removeTarget) return;\n const { userId } = removeTarget;\n try {\n await removeMember({ variables: { tenantId, userId } });\n toast.success(\n tr('admin.tenantMembers.toast.removedTitle', 'Member removed'),\n tr(\n 'admin.tenantMembers.toast.removedDescription',\n 'User {{userId}} no longer has access.',\n {\n userId,\n }\n )\n );\n setRemoveTarget(null);\n await refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantMembers.toast.errorTitle', 'Error'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantMembers.toast.removeFailed', 'Failed to remove tenant member.')\n );\n }\n };\n\n const handleRoleChange = async (userId: string, role: TenantMemberRole) => {\n try {\n await updateRole({ variables: { tenantId, userId, role } });\n await refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantMembers.toast.errorTitle', 'Error'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantMembers.toast.updateRoleFailed', 'Failed to update role.')\n );\n }\n };\n\n const members = data?.tenantMembers?.items ?? [];\n const total = data?.tenantMembers?.total ?? 0;\n const hasMore = data?.tenantMembers?.hasMore ?? false;\n const loadingMore = loading && members.length > 0;\n\n return (\n <>\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <Users className=\"h-4 w-4\" />\n {tr('admin.tenantMembers.title', 'Tenant Members')}\n {total > 0 && <span className=\"text-sm text-muted-foreground\">({total})</span>}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'admin.tenantMembers.description',\n 'Grant a user explicit access to this dedicated tenant. They will be able to create organizations under it and see it in their tenant switcher.'\n )}\n </p>\n\n {canManage && (\n <>\n <form onSubmit={handleAdd} className=\"flex flex-wrap items-end gap-2\">\n <div className=\"flex-1 min-w-[240px]\">\n <label className=\"mb-1 block text-xs font-medium text-muted-foreground\">\n {tr('admin.tenantMembers.form.userIdLabel', 'User ID')}\n </label>\n <Input\n value={addForm.userId}\n onChange={(e) => setAddForm({ ...addForm, userId: e.target.value })}\n placeholder={tr(\n 'admin.tenantMembers.form.userIdPlaceholder',\n 'UUID of the user to grant access'\n )}\n required\n disabled={adding}\n />\n </div>\n <div className=\"min-w-[140px]\">\n <label className=\"mb-1 block text-xs font-medium text-muted-foreground\">\n {tr('admin.tenantMembers.form.roleLabel', 'Role')}\n </label>\n <select\n value={addForm.role}\n onChange={(e) =>\n setAddForm({ ...addForm, role: e.target.value as TenantMemberRole })\n }\n disabled={adding}\n className=\"h-9 w-full rounded-md border border-input bg-background px-3 text-sm\"\n >\n <option value={TenantMemberRole.Member}>\n {tr('admin.tenantMembers.role.member', 'Member')}\n </option>\n <option value={TenantMemberRole.Admin}>\n {tr('admin.tenantMembers.role.admin', 'Admin')}\n </option>\n </select>\n </div>\n <Button type=\"submit\" disabled={adding || !addForm.userId.trim()}>\n {adding ? (\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n ) : (\n <UserPlus className=\"mr-2 h-4 w-4\" />\n )}\n {tr('admin.tenantMembers.form.submit', 'Grant access')}\n </Button>\n </form>\n <Separator />\n </>\n )}\n\n {loading && members.length === 0 ? (\n <div className=\"flex h-24 items-center justify-center text-muted-foreground\">\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {tr('admin.tenantMembers.loading', 'Loading members...')}\n </div>\n ) : error ? (\n <div className=\"rounded-md border border-destructive/50 bg-destructive/10 p-3 text-sm text-destructive\">\n {error.message}\n </div>\n ) : members.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'admin.tenantMembers.empty',\n 'No explicit members yet. Grant access above to add one.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {members.map((m: TenantMemberFieldsFragment) => (\n <div\n key={m.id}\n className=\"flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border p-3\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-mono text-xs text-foreground\">{m.userId}</p>\n <p className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantMembers.addedAt', 'Added {{when}} by {{by}}', {\n when: formatDate(m.createdAt),\n by: m.addedBy,\n })}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={m.role}\n onChange={(e) =>\n handleRoleChange(m.userId, e.target.value as TenantMemberRole)\n }\n disabled={!canManage}\n className=\"h-8 rounded-md border border-input bg-background px-2 text-xs\"\n >\n <option value={TenantMemberRole.Member}>\n {tr('admin.tenantMembers.role.member', 'Member')}\n </option>\n <option value={TenantMemberRole.Admin}>\n {tr('admin.tenantMembers.role.admin', 'Admin')}\n </option>\n </select>\n {canManage && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setRemoveTarget({ userId: m.userId })}\n disabled={removing}\n aria-label={tr('admin.tenantMembers.remove', 'Remove')}\n >\n <Trash2 className=\"h-4 w-4 text-destructive\" />\n </Button>\n )}\n </div>\n </div>\n ))}\n {hasMore && (\n <div className=\"flex items-center justify-between gap-2 pt-2\">\n <span className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantMembers.shownOfTotal', 'Showing {{shown}} of {{total}}', {\n shown: members.length,\n total,\n })}\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setPageSize((prev) => prev + PAGE_SIZE)}\n disabled={loadingMore}\n >\n {loadingMore ? <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" /> : null}\n {tr('admin.tenantMembers.loadMore', 'Load more')}\n </Button>\n </div>\n )}\n </div>\n )}\n </CardContent>\n </Card>\n\n <ConfirmDialog\n open={Boolean(removeTarget)}\n title={tr('admin.tenantMembers.confirmRemove.title', 'Revoke tenant access?')}\n description={tr(\n 'admin.tenantMembers.confirmRemove.description',\n 'User {{userId}} will no longer be able to create organizations in this tenant. Existing organization and workspace memberships are not affected.',\n { userId: removeTarget?.userId ?? '' }\n )}\n confirmLabel={tr('admin.tenantMembers.confirmRemove.confirm', 'Revoke')}\n variant=\"destructive\"\n loading={removing}\n onConfirm={handleRemove}\n onClose={() => setRemoveTarget(null)}\n />\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TenantMembersPanel.js","names":[],"sources":["../../../src/admin/components/TenantMembersPanel.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Loader2, Trash2, UserPlus, Users } from 'lucide-react';\nimport {\n useListTenantMembersQuery,\n useAddTenantMemberMutation,\n useRemoveTenantMemberMutation,\n useUpdateTenantMemberRoleMutation,\n type TenantMemberFieldsFragment,\n} from '../../generated/global-operations';\nimport { TenantMemberRole } from '../../generated/global-types';\nimport { Card, CardHeader, CardTitle, CardContent, Button, Input, Separator } from './ui';\nimport { useToast } from './Toast';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { formatDate } from '../utils/formatters';\n\ninterface TenantMembersPanelProps {\n tenantId: string;\n tenantType: string;\n canManage: boolean;\n}\n\nconst EMPTY_ADD_FORM = { userId: '', role: TenantMemberRole.Member };\nconst PAGE_SIZE = 100;\n\n/**\n * Tenant Members panel (BOFF-2652) — super_admin UI to grant non-super-admin\n * users explicit access to a DEDICATED tenant. SHARED-tenant access is\n * implicit, so the panel is hidden for SHARED tenants.\n */\nexport function TenantMembersPanel({ tenantId, tenantType, canManage }: TenantMembersPanelProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const toast = useToast();\n\n const [addForm, setAddForm] = useState(EMPTY_ADD_FORM);\n const [removeTarget, setRemoveTarget] = useState<{ userId: string } | null>(null);\n // Pagination: each \"Load more\" click widens the window by PAGE_SIZE rows;\n // Apollo re-fetches the bigger page rather than appending in JS so we\n // don't need a custom merge type policy. (BOFF-2652 review)\n const [pageSize, setPageSize] = useState(PAGE_SIZE);\n\n const isShared = tenantType?.toUpperCase() === 'SHARED';\n\n const { data, loading, error, refetch } = useListTenantMembersQuery({\n variables: { tenantId, limit: pageSize, offset: 0 },\n skip: !tenantId || isShared,\n fetchPolicy: 'cache-and-network',\n });\n\n const [addMember, { loading: adding }] = useAddTenantMemberMutation();\n const [removeMember, { loading: removing }] = useRemoveTenantMemberMutation();\n const [updateRole] = useUpdateTenantMemberRoleMutation();\n\n if (isShared) {\n return (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <Users className=\"h-4 w-4\" />\n {tr('admin.tenantMembers.title', 'Tenant Members')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'admin.tenantMembers.sharedImplicit',\n 'This is the shared tenant. Every authenticated user has implicit access — no explicit membership rows are stored.'\n )}\n </p>\n </CardContent>\n </Card>\n );\n }\n\n const handleAdd = async (e: React.FormEvent) => {\n e.preventDefault();\n const userId = addForm.userId.trim();\n if (!userId) return;\n try {\n await addMember({ variables: { input: { tenantId, userId, role: addForm.role } } });\n toast.success(\n tr('admin.tenantMembers.toast.addedTitle', 'Member added'),\n tr(\n 'admin.tenantMembers.toast.addedDescription',\n 'User {{userId}} can now access this tenant.',\n {\n userId,\n }\n )\n );\n setAddForm(EMPTY_ADD_FORM);\n await refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantMembers.toast.errorTitle', 'Error'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantMembers.toast.addFailed', 'Failed to add tenant member.')\n );\n }\n };\n\n const handleRemove = async () => {\n if (!removeTarget) return;\n const { userId } = removeTarget;\n try {\n await removeMember({ variables: { tenantId, userId } });\n toast.success(\n tr('admin.tenantMembers.toast.removedTitle', 'Member removed'),\n tr(\n 'admin.tenantMembers.toast.removedDescription',\n 'User {{userId}} no longer has access.',\n {\n userId,\n }\n )\n );\n setRemoveTarget(null);\n await refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantMembers.toast.errorTitle', 'Error'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantMembers.toast.removeFailed', 'Failed to remove tenant member.')\n );\n }\n };\n\n const handleRoleChange = async (userId: string, role: TenantMemberRole) => {\n try {\n await updateRole({ variables: { tenantId, userId, role } });\n await refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantMembers.toast.errorTitle', 'Error'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantMembers.toast.updateRoleFailed', 'Failed to update role.')\n );\n }\n };\n\n const members = data?.tenantMembers?.items ?? [];\n const total = data?.tenantMembers?.total ?? 0;\n const hasMore = data?.tenantMembers?.hasMore ?? false;\n const loadingMore = loading && members.length > 0;\n\n return (\n <>\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <Users className=\"h-4 w-4\" />\n {tr('admin.tenantMembers.title', 'Tenant Members')}\n {total > 0 && <span className=\"text-sm text-text-secondary\">({total})</span>}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'admin.tenantMembers.description',\n 'Grant a user explicit access to this dedicated tenant. They will be able to create organizations under it and see it in their tenant switcher.'\n )}\n </p>\n\n {canManage && (\n <>\n <form onSubmit={handleAdd} className=\"flex flex-wrap items-end gap-2\">\n <div className=\"flex-1 min-w-[240px]\">\n <label className=\"mb-1 block text-xs font-medium text-text-secondary\">\n {tr('admin.tenantMembers.form.userIdLabel', 'User ID')}\n </label>\n <Input\n value={addForm.userId}\n onChange={(e) => setAddForm({ ...addForm, userId: e.target.value })}\n placeholder={tr(\n 'admin.tenantMembers.form.userIdPlaceholder',\n 'UUID of the user to grant access'\n )}\n required\n disabled={adding}\n />\n </div>\n <div className=\"min-w-[140px]\">\n <label className=\"mb-1 block text-xs font-medium text-text-secondary\">\n {tr('admin.tenantMembers.form.roleLabel', 'Role')}\n </label>\n <select\n value={addForm.role}\n onChange={(e) =>\n setAddForm({ ...addForm, role: e.target.value as TenantMemberRole })\n }\n disabled={adding}\n className=\"h-9 w-full rounded-md border border-border-default bg-bg-surface px-3 text-sm\"\n >\n <option value={TenantMemberRole.Member}>\n {tr('admin.tenantMembers.role.member', 'Member')}\n </option>\n <option value={TenantMemberRole.Admin}>\n {tr('admin.tenantMembers.role.admin', 'Admin')}\n </option>\n </select>\n </div>\n <Button type=\"submit\" disabled={adding || !addForm.userId.trim()}>\n {adding ? (\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n ) : (\n <UserPlus className=\"mr-2 h-4 w-4\" />\n )}\n {tr('admin.tenantMembers.form.submit', 'Grant access')}\n </Button>\n </form>\n <Separator />\n </>\n )}\n\n {loading && members.length === 0 ? (\n <div className=\"flex h-24 items-center justify-center text-text-secondary\">\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {tr('admin.tenantMembers.loading', 'Loading members...')}\n </div>\n ) : error ? (\n <div className=\"rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n {error.message}\n </div>\n ) : members.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'admin.tenantMembers.empty',\n 'No explicit members yet. Grant access above to add one.'\n )}\n </p>\n ) : (\n <div className=\"space-y-2\">\n {members.map((m: TenantMemberFieldsFragment) => (\n <div\n key={m.id}\n className=\"flex flex-wrap items-center justify-between gap-2 rounded-lg border border-border-subtle p-3\"\n >\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-mono text-xs text-text-primary\">{m.userId}</p>\n <p className=\"text-xs text-text-secondary\">\n {tr('admin.tenantMembers.addedAt', 'Added {{when}} by {{by}}', {\n when: formatDate(m.createdAt),\n by: m.addedBy,\n })}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={m.role}\n onChange={(e) =>\n handleRoleChange(m.userId, e.target.value as TenantMemberRole)\n }\n disabled={!canManage}\n className=\"h-8 rounded-md border border-border-default bg-bg-surface px-2 text-xs\"\n >\n <option value={TenantMemberRole.Member}>\n {tr('admin.tenantMembers.role.member', 'Member')}\n </option>\n <option value={TenantMemberRole.Admin}>\n {tr('admin.tenantMembers.role.admin', 'Admin')}\n </option>\n </select>\n {canManage && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setRemoveTarget({ userId: m.userId })}\n disabled={removing}\n aria-label={tr('admin.tenantMembers.remove', 'Remove')}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" />\n </Button>\n )}\n </div>\n </div>\n ))}\n {hasMore && (\n <div className=\"flex items-center justify-between gap-2 pt-2\">\n <span className=\"text-xs text-text-secondary\">\n {tr('admin.tenantMembers.shownOfTotal', 'Showing {{shown}} of {{total}}', {\n shown: members.length,\n total,\n })}\n </span>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setPageSize((prev) => prev + PAGE_SIZE)}\n disabled={loadingMore}\n >\n {loadingMore ? <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" /> : null}\n {tr('admin.tenantMembers.loadMore', 'Load more')}\n </Button>\n </div>\n )}\n </div>\n )}\n </CardContent>\n </Card>\n\n <ConfirmDialog\n open={Boolean(removeTarget)}\n title={tr('admin.tenantMembers.confirmRemove.title', 'Revoke tenant access?')}\n description={tr(\n 'admin.tenantMembers.confirmRemove.description',\n 'User {{userId}} will no longer be able to create organizations in this tenant. Existing organization and workspace memberships are not affected.',\n { userId: removeTarget?.userId ?? '' }\n )}\n confirmLabel={tr('admin.tenantMembers.confirmRemove.confirm', 'Revoke')}\n variant=\"destructive\"\n loading={removing}\n onConfirm={handleRemove}\n onClose={() => setRemoveTarget(null)}\n />\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAuBA,IAAM,IAAiB;CAAE,QAAQ;CAAI,MAAM,EAAiB;CAAQ,EAC9D,IAAY;AAOlB,SAAgB,EAAmB,EAAE,aAAU,eAAY,gBAAsC;CAC/F,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,GAAkB,MACzC,EAAc,GAAG,GAAK,GAAU,EAAO,EACnC,IAAQ,GAAU,EAElB,CAAC,GAAS,KAAc,EAAS,EAAe,EAChD,CAAC,GAAc,KAAmB,EAAoC,KAAK,EAI3E,CAAC,GAAU,KAAe,EAAS,EAAU,EAE7C,IAAW,GAAY,aAAa,KAAK,UAEzC,EAAE,SAAM,YAAS,UAAO,eAAY,EAA0B;EAClE,WAAW;GAAE;GAAU,OAAO;GAAU,QAAQ;GAAG;EACnD,MAAM,CAAC,KAAY;EACnB,aAAa;EACd,CAAC,EAEI,CAAC,GAAW,EAAE,SAAS,OAAY,GAA4B,EAC/D,CAAC,GAAc,EAAE,SAAS,OAAc,GAA+B,EACvE,CAAC,KAAc,GAAmC;AAExD,KAAI,EACF,QACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAW,WAAU;YAArB,CACE,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA,EAC5B,EAAG,6BAA6B,iBAAiB,CACxC;KACD,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,KAAD;EAAG,WAAU;YACV,EACC,sCACA,oHACD;EACC,CAAA,EACQ,CAAA,CACT,EAAA,CAAA;CAIX,IAAM,IAAY,OAAO,MAAuB;AAC9C,IAAE,gBAAgB;EAClB,IAAM,IAAS,EAAQ,OAAO,MAAM;AAC/B,QACL,KAAI;AAaF,GAZA,MAAM,EAAU,EAAE,WAAW,EAAE,OAAO;IAAE;IAAU;IAAQ,MAAM,EAAQ;IAAM,EAAE,EAAE,CAAC,EACnF,EAAM,QACJ,EAAG,wCAAwC,eAAe,EAC1D,EACE,8CACA,+CACA,EACE,WACD,CACF,CACF,EACD,EAAW,EAAe,EAC1B,MAAM,GAAS;WACR,GAAK;AACZ,KAAM,MACJ,EAAG,wCAAwC,QAAQ,EACnD,aAAe,QACX,EAAI,UACJ,EAAG,uCAAuC,+BAA+B,CAC9E;;IAIC,IAAe,YAAY;AAC/B,MAAI,CAAC,EAAc;EACnB,IAAM,EAAE,cAAW;AACnB,MAAI;AAaF,GAZA,MAAM,EAAa,EAAE,WAAW;IAAE;IAAU;IAAQ,EAAE,CAAC,EACvD,EAAM,QACJ,EAAG,0CAA0C,iBAAiB,EAC9D,EACE,gDACA,yCACA,EACE,WACD,CACF,CACF,EACD,EAAgB,KAAK,EACrB,MAAM,GAAS;WACR,GAAK;AACZ,KAAM,MACJ,EAAG,wCAAwC,QAAQ,EACnD,aAAe,QACX,EAAI,UACJ,EAAG,0CAA0C,kCAAkC,CACpF;;IAIC,IAAmB,OAAO,GAAgB,MAA2B;AACzE,MAAI;AAEF,GADA,MAAM,EAAW,EAAE,WAAW;IAAE;IAAU;IAAQ;IAAM,EAAE,CAAC,EAC3D,MAAM,GAAS;WACR,GAAK;AACZ,KAAM,MACJ,EAAG,wCAAwC,QAAQ,EACnD,aAAe,QACX,EAAI,UACJ,EAAG,8CAA8C,yBAAyB,CAC/E;;IAIC,IAAU,GAAM,eAAe,SAAS,EAAE,EAC1C,IAAQ,GAAM,eAAe,SAAS,GACtC,KAAU,GAAM,eAAe,WAAW,IAC1C,IAAc,KAAW,EAAQ,SAAS;AAEhD,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAW,WAAU;YAArB;GACE,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;GAC5B,EAAG,6BAA6B,iBAAiB;GACjD,IAAQ,KAAK,kBAAC,QAAD;IAAM,WAAU;cAAhB;KAA8C;KAAE;KAAM;KAAQ;;GAClE;KACD,CAAA,EACb,kBAAC,GAAD;EAAa,WAAU;YAAvB;GACE,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,mCACA,iJACD;IACC,CAAA;GAEH,KACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;IAAM,UAAU;IAAW,WAAU;cAArC;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,SAAD;OAAO,WAAU;iBACd,EAAG,wCAAwC,UAAU;OAChD,CAAA,EACR,kBAAC,GAAD;OACE,OAAO,EAAQ;OACf,WAAW,MAAM,EAAW;QAAE,GAAG;QAAS,QAAQ,EAAE,OAAO;QAAO,CAAC;OACnE,aAAa,EACX,8CACA,mCACD;OACD,UAAA;OACA,UAAU;OACV,CAAA,CACE;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,SAAD;OAAO,WAAU;iBACd,EAAG,sCAAsC,OAAO;OAC3C,CAAA,EACR,kBAAC,UAAD;OACE,OAAO,EAAQ;OACf,WAAW,MACT,EAAW;QAAE,GAAG;QAAS,MAAM,EAAE,OAAO;QAA2B,CAAC;OAEtE,UAAU;OACV,WAAU;iBANZ,CAQE,kBAAC,UAAD;QAAQ,OAAO,EAAiB;kBAC7B,EAAG,mCAAmC,SAAS;QACzC,CAAA,EACT,kBAAC,UAAD;QAAQ,OAAO,EAAiB;kBAC7B,EAAG,kCAAkC,QAAQ;QACvC,CAAA,CACF;SACL;;KACN,kBAAC,GAAD;MAAQ,MAAK;MAAS,UAAU,KAAU,CAAC,EAAQ,OAAO,MAAM;gBAAhE,CACG,IACC,kBAAC,GAAD,EAAS,WAAU,6BAA8B,CAAA,GAEjD,kBAAC,IAAD,EAAU,WAAU,gBAAiB,CAAA,EAEtC,EAAG,mCAAmC,eAAe,CAC/C;;KACJ;OACP,kBAAC,GAAD,EAAa,CAAA,CACZ,EAAA,CAAA;GAGJ,KAAW,EAAQ,WAAW,IAC7B,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAS,WAAU,6BAA8B,CAAA,EAChD,EAAG,+BAA+B,qBAAqB,CACpD;QACJ,IACF,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAM;IACH,CAAA,GACJ,EAAQ,WAAW,IACrB,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,6BACA,0DACD;IACC,CAAA,GAEJ,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAQ,KAAK,MACZ,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAgD,EAAE;OAAW,CAAA,EAC1E,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAG,+BAA+B,4BAA4B;QAC7D,MAAM,EAAW,EAAE,UAAU;QAC7B,IAAI,EAAE;QACP,CAAC;OACA,CAAA,CACA;SACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,OAAO,EAAE;OACT,WAAW,MACT,EAAiB,EAAE,QAAQ,EAAE,OAAO,MAA0B;OAEhE,UAAU,CAAC;OACX,WAAU;iBANZ,CAQE,kBAAC,UAAD;QAAQ,OAAO,EAAiB;kBAC7B,EAAG,mCAAmC,SAAS;QACzC,CAAA,EACT,kBAAC,UAAD;QAAQ,OAAO,EAAiB;kBAC7B,EAAG,kCAAkC,QAAQ;QACvC,CAAA,CACF;UACR,KACC,kBAAC,GAAD;OACE,SAAQ;OACR,MAAK;OACL,eAAe,EAAgB,EAAE,QAAQ,EAAE,QAAQ,CAAC;OACpD,UAAU;OACV,cAAY,EAAG,8BAA8B,SAAS;iBAEtD,kBAAC,GAAD,EAAQ,WAAU,kCAAmC,CAAA;OAC9C,CAAA,CAEP;QACF;OAxCC,EAAE,GAwCH,CACN,EACD,MACC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAG,oCAAoC,kCAAkC;OACxE,OAAO,EAAQ;OACf;OACD,CAAC;MACG,CAAA,EACP,kBAAC,GAAD;MACE,SAAQ;MACR,MAAK;MACL,eAAe,GAAa,MAAS,IAAO,EAAU;MACtD,UAAU;gBAJZ,CAMG,IAAc,kBAAC,GAAD,EAAS,WAAU,6BAA8B,CAAA,GAAG,MAClE,EAAG,gCAAgC,YAAY,CACzC;QACL;OAEJ;;GAEI;IACT,EAAA,CAAA,EAEP,kBAAC,GAAD;EACE,MAAM,EAAQ;EACd,OAAO,EAAG,2CAA2C,wBAAwB;EAC7E,aAAa,EACX,iDACA,oJACA,EAAE,QAAQ,GAAc,UAAU,IAAI,CACvC;EACD,cAAc,EAAG,6CAA6C,SAAS;EACvE,SAAQ;EACR,SAAS;EACT,WAAW;EACX,eAAe,EAAgB,KAAK;EACpC,CAAA,CACD,EAAA,CAAA"}
|
|
@@ -36,7 +36,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
36
36
|
}), /* @__PURE__ */ S("button", {
|
|
37
37
|
type: "button",
|
|
38
38
|
onClick: () => M((e) => !e),
|
|
39
|
-
className: "flex items-center gap-1 text-sm text-
|
|
39
|
+
className: "flex items-center gap-1 text-sm text-text-secondary hover:text-text-primary transition-colors",
|
|
40
40
|
"aria-expanded": j,
|
|
41
41
|
"aria-label": j ? k("admin.tenantDetail.sso.collapse", "Collapse SSO configuration") : k("admin.tenantDetail.sso.expand", "Expand SSO configuration"),
|
|
42
42
|
children: S(j ? g : h, { className: "h-4 w-4" })
|
|
@@ -45,9 +45,9 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
45
45
|
className: "space-y-4",
|
|
46
46
|
children: B ? /* @__PURE__ */ S("div", {
|
|
47
47
|
className: "flex items-center justify-center py-6",
|
|
48
|
-
children: /* @__PURE__ */ S(_, { className: "h-6 w-6 animate-spin text-
|
|
48
|
+
children: /* @__PURE__ */ S(_, { className: "h-6 w-6 animate-spin text-text-secondary" })
|
|
49
49
|
}) : V ? /* @__PURE__ */ S("p", {
|
|
50
|
-
className: "text-sm text-
|
|
50
|
+
className: "text-sm text-status-error-text",
|
|
51
51
|
children: V.message
|
|
52
52
|
}) : q && !N ? /* @__PURE__ */ C("div", {
|
|
53
53
|
className: "space-y-3",
|
|
@@ -65,7 +65,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
65
65
|
/* @__PURE__ */ C("div", {
|
|
66
66
|
className: "flex flex-col gap-0.5",
|
|
67
67
|
children: [/* @__PURE__ */ S("dt", {
|
|
68
|
-
className: "text-xs text-
|
|
68
|
+
className: "text-xs text-text-secondary",
|
|
69
69
|
children: k("admin.tenantDetail.sso.entityId", "Entity ID")
|
|
70
70
|
}), /* @__PURE__ */ S("dd", {
|
|
71
71
|
className: "font-mono text-xs break-all",
|
|
@@ -75,7 +75,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
75
75
|
/* @__PURE__ */ C("div", {
|
|
76
76
|
className: "flex flex-col gap-0.5",
|
|
77
77
|
children: [/* @__PURE__ */ S("dt", {
|
|
78
|
-
className: "text-xs text-
|
|
78
|
+
className: "text-xs text-text-secondary",
|
|
79
79
|
children: k("admin.tenantDetail.sso.ssoUrl", "SSO URL")
|
|
80
80
|
}), /* @__PURE__ */ S("dd", {
|
|
81
81
|
className: "font-mono text-xs break-all",
|
|
@@ -85,7 +85,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
85
85
|
q.sloUrl && /* @__PURE__ */ C("div", {
|
|
86
86
|
className: "flex flex-col gap-0.5",
|
|
87
87
|
children: [/* @__PURE__ */ S("dt", {
|
|
88
|
-
className: "text-xs text-
|
|
88
|
+
className: "text-xs text-text-secondary",
|
|
89
89
|
children: k("admin.tenantDetail.sso.sloUrl", "SLO URL")
|
|
90
90
|
}), /* @__PURE__ */ S("dd", {
|
|
91
91
|
className: "font-mono text-xs break-all",
|
|
@@ -104,7 +104,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
104
104
|
}), /* @__PURE__ */ C(t, {
|
|
105
105
|
variant: "outline",
|
|
106
106
|
size: "sm",
|
|
107
|
-
className: "text-
|
|
107
|
+
className: "text-status-error-text hover:text-status-error-text",
|
|
108
108
|
onClick: () => R(!0),
|
|
109
109
|
children: [/* @__PURE__ */ S(b, { className: "mr-1.5 h-3.5 w-3.5" }), k("admin.tenantDetail.sso.deleteButton", "Remove")]
|
|
110
110
|
})]
|
|
@@ -134,7 +134,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
134
134
|
className: "space-y-4",
|
|
135
135
|
children: [
|
|
136
136
|
!q && /* @__PURE__ */ S("p", {
|
|
137
|
-
className: "text-sm text-
|
|
137
|
+
className: "text-sm text-text-secondary",
|
|
138
138
|
children: k("admin.tenantDetail.sso.notConfigured", "No SSO configuration. Fill in the SAML details below to enable enterprise SSO.")
|
|
139
139
|
}),
|
|
140
140
|
/* @__PURE__ */ S(s, {}),
|
|
@@ -147,7 +147,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
147
147
|
className: "text-sm font-medium",
|
|
148
148
|
htmlFor: `sso-entityId-${E}`,
|
|
149
149
|
children: [k("admin.tenantDetail.sso.entityId", "Entity ID"), /* @__PURE__ */ S("span", {
|
|
150
|
-
className: "ml-1 text-
|
|
150
|
+
className: "ml-1 text-status-error-text",
|
|
151
151
|
children: "*"
|
|
152
152
|
})]
|
|
153
153
|
}), /* @__PURE__ */ S(o, {
|
|
@@ -167,7 +167,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
167
167
|
className: "text-sm font-medium",
|
|
168
168
|
htmlFor: `sso-ssoUrl-${E}`,
|
|
169
169
|
children: [k("admin.tenantDetail.sso.ssoUrl", "SSO URL"), /* @__PURE__ */ S("span", {
|
|
170
|
-
className: "ml-1 text-
|
|
170
|
+
className: "ml-1 text-status-error-text",
|
|
171
171
|
children: "*"
|
|
172
172
|
})]
|
|
173
173
|
}), /* @__PURE__ */ S(o, {
|
|
@@ -187,7 +187,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
187
187
|
className: "text-sm font-medium",
|
|
188
188
|
htmlFor: `sso-sloUrl-${E}`,
|
|
189
189
|
children: [k("admin.tenantDetail.sso.sloUrl", "SLO URL"), /* @__PURE__ */ S("span", {
|
|
190
|
-
className: "ml-0.5 text-xs text-
|
|
190
|
+
className: "ml-0.5 text-xs text-text-secondary",
|
|
191
191
|
children: k("admin.tenantDetail.sso.optional", "(optional)")
|
|
192
192
|
})]
|
|
193
193
|
}), /* @__PURE__ */ S(o, {
|
|
@@ -206,7 +206,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
206
206
|
className: "text-sm font-medium",
|
|
207
207
|
htmlFor: `sso-cert-${E}`,
|
|
208
208
|
children: [k("admin.tenantDetail.sso.certificate", "X.509 Certificate"), /* @__PURE__ */ S("span", {
|
|
209
|
-
className: "ml-1 text-
|
|
209
|
+
className: "ml-1 text-status-error-text",
|
|
210
210
|
children: "*"
|
|
211
211
|
})]
|
|
212
212
|
}), /* @__PURE__ */ S("textarea", {
|
|
@@ -219,7 +219,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
219
219
|
placeholder: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
|
|
220
220
|
required: !0,
|
|
221
221
|
rows: 5,
|
|
222
|
-
className: "w-full rounded-md border border-
|
|
222
|
+
className: "w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 font-mono text-xs text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none"
|
|
223
223
|
})]
|
|
224
224
|
}),
|
|
225
225
|
/* @__PURE__ */ C("div", {
|
|
@@ -233,7 +233,7 @@ function E({ tenantId: E, canManage: D }) {
|
|
|
233
233
|
...e,
|
|
234
234
|
enabled: !e.enabled
|
|
235
235
|
})),
|
|
236
|
-
className: `relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${F.enabled ? "bg-primary" : "bg-
|
|
236
|
+
className: `relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${F.enabled ? "bg-action-primary-bg" : "bg-bg-sunken"}`,
|
|
237
237
|
children: /* @__PURE__ */ S("span", { className: `pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg transition-transform ${F.enabled ? "translate-x-4" : "translate-x-0"}` })
|
|
238
238
|
}), /* @__PURE__ */ S("span", {
|
|
239
239
|
className: "text-sm",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TenantSsoPanel.js","names":[],"sources":["../../../src/admin/components/TenantSsoPanel.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { ShieldCheck, Loader2, Trash2, Save, ChevronDown, ChevronUp } from 'lucide-react';\nimport {\n useAdminGetSsoConfigQuery,\n useAdminSaveSsoConfigMutation,\n useAdminDeleteSsoConfigMutation,\n} from '../../generated/global-operations';\nimport { Card, CardHeader, CardTitle, CardContent, Button, Input, Separator } from './ui';\nimport { useToast } from './Toast';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\ninterface TenantSsoPanelProps {\n tenantId: string;\n canManage: boolean;\n}\n\ninterface SsoFormData {\n entityId: string;\n ssoUrl: string;\n sloUrl: string;\n certificate: string;\n enabled: boolean;\n}\n\nconst EMPTY_FORM: SsoFormData = {\n entityId: '',\n ssoUrl: '',\n sloUrl: '',\n certificate: '',\n enabled: true,\n};\n\nexport function TenantSsoPanel({ tenantId, canManage }: TenantSsoPanelProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const toast = useToast();\n\n const [expanded, setExpanded] = useState(false);\n const [editing, setEditing] = useState(false);\n const [form, setForm] = useState<SsoFormData>(EMPTY_FORM);\n const [confirmDelete, setConfirmDelete] = useState(false);\n\n const { data, loading, error, refetch } = useAdminGetSsoConfigQuery({\n variables: { tenantId },\n fetchPolicy: 'cache-and-network',\n });\n\n const [saveSso, { loading: saving }] = useAdminSaveSsoConfigMutation();\n const [deleteSso, { loading: deleting }] = useAdminDeleteSsoConfigMutation();\n\n const config = data?.getSSOConfig;\n\n useEffect(() => {\n if (config) {\n setForm({\n entityId: config.entityId,\n ssoUrl: config.ssoUrl,\n sloUrl: config.sloUrl ?? '',\n certificate: config.certificate,\n enabled: config.enabled,\n });\n } else {\n setForm(EMPTY_FORM);\n }\n }, [config]);\n\n const handleSave = async (e: React.FormEvent) => {\n e.preventDefault();\n try {\n await saveSso({\n variables: {\n tenantId,\n input: {\n entityId: form.entityId,\n ssoUrl: form.ssoUrl,\n sloUrl: form.sloUrl || undefined,\n certificate: form.certificate,\n enabled: form.enabled,\n },\n },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.savedTitle', 'SSO configuration saved'),\n tr(\n 'admin.tenantDetail.sso.savedDescription',\n 'SAML SSO has been configured for this tenant.'\n )\n );\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to save SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n const handleDelete = async () => {\n try {\n await deleteSso({\n variables: { tenantId },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.deletedTitle', 'SSO configuration removed'),\n tr('admin.tenantDetail.sso.deletedDescription', 'SSO has been disabled for this tenant.')\n );\n setConfirmDelete(false);\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to delete SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n return (\n <>\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <ShieldCheck className=\"h-5 w-5\" />\n {tr('admin.tenantDetail.sso.title', 'SSO Configuration')}\n </CardTitle>\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors\"\n aria-expanded={expanded}\n aria-label={\n expanded\n ? tr('admin.tenantDetail.sso.collapse', 'Collapse SSO configuration')\n : tr('admin.tenantDetail.sso.expand', 'Expand SSO configuration')\n }\n >\n {expanded ? <ChevronUp className=\"h-4 w-4\" /> : <ChevronDown className=\"h-4 w-4\" />}\n </button>\n </div>\n </CardHeader>\n\n {expanded && (\n <CardContent className=\"space-y-4\">\n {loading ? (\n <div className=\"flex items-center justify-center py-6\">\n <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n </div>\n ) : error ? (\n <p className=\"text-sm text-destructive\">{error.message}</p>\n ) : config && !editing ? (\n /* ── Configured state — read-only view ── */\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${\n config.enabled\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {config.enabled\n ? tr('admin.tenantDetail.sso.enabled', 'Enabled')\n : tr('admin.tenantDetail.sso.disabled', 'Disabled')}\n </span>\n </div>\n\n <dl className=\"grid gap-2 text-sm\">\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.entityId}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.ssoUrl}</dd>\n </div>\n {config.sloUrl && (\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.sloUrl}</dd>\n </div>\n )}\n </dl>\n\n {canManage && (\n <div className=\"flex gap-2 pt-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditing(true)}>\n {tr('admin.tenantDetail.sso.editButton', 'Edit')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"text-destructive hover:text-destructive\"\n onClick={() => setConfirmDelete(true)}\n >\n <Trash2 className=\"mr-1.5 h-3.5 w-3.5\" />\n {tr('admin.tenantDetail.sso.deleteButton', 'Remove')}\n </Button>\n </div>\n )}\n </div>\n ) : (\n /* ── Not configured or editing — show form ── */\n <form onSubmit={handleSave} className=\"space-y-4\">\n {!config && (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'admin.tenantDetail.sso.notConfigured',\n 'No SSO configuration. Fill in the SAML details below to enable enterprise SSO.'\n )}\n </p>\n )}\n\n <Separator />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-entityId-${tenantId}`}>\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n id={`sso-entityId-${tenantId}`}\n value={form.entityId}\n onChange={(e) => setForm((f) => ({ ...f, entityId: e.target.value }))}\n placeholder=\"https://idp.example.com/entity\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-ssoUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n id={`sso-ssoUrl-${tenantId}`}\n value={form.ssoUrl}\n onChange={(e) => setForm((f) => ({ ...f, ssoUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/sso\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-sloUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n <span className=\"ml-0.5 text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.optional', '(optional)')}\n </span>\n </label>\n <Input\n id={`sso-sloUrl-${tenantId}`}\n value={form.sloUrl}\n onChange={(e) => setForm((f) => ({ ...f, sloUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/slo\"\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-cert-${tenantId}`}>\n {tr('admin.tenantDetail.sso.certificate', 'X.509 Certificate')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <textarea\n id={`sso-cert-${tenantId}`}\n value={form.certificate}\n onChange={(e) => setForm((f) => ({ ...f, certificate: e.target.value }))}\n placeholder=\"-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----\"\n required\n rows={5}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none\"\n />\n </div>\n\n <div className=\"sm:col-span-2 flex items-center gap-3\">\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={form.enabled}\n aria-label={tr(\n 'admin.tenantDetail.sso.enabledLabel',\n 'Enable SSO for this tenant'\n )}\n onClick={() => setForm((f) => ({ ...f, enabled: !f.enabled }))}\n className={`relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${\n form.enabled ? 'bg-primary' : 'bg-muted'\n }`}\n >\n <span\n className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg transition-transform ${\n form.enabled ? 'translate-x-4' : 'translate-x-0'\n }`}\n />\n </button>\n <span className=\"text-sm\">\n {tr('admin.tenantDetail.sso.enabledLabel', 'Enable SSO for this tenant')}\n </span>\n </div>\n </div>\n\n <Separator />\n\n <div className=\"flex gap-2\">\n <Button type=\"submit\" size=\"sm\" disabled={saving}>\n {saving ? (\n <Loader2 className=\"mr-1.5 h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Save className=\"mr-1.5 h-3.5 w-3.5\" />\n )}\n {tr('admin.tenantDetail.sso.saveButton', 'Save SSO Config')}\n </Button>\n {editing && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setEditing(false)}\n >\n {tr('admin.tenantDetail.sso.cancelButton', 'Cancel')}\n </Button>\n )}\n </div>\n </form>\n )}\n </CardContent>\n )}\n </Card>\n\n <ConfirmDialog\n open={confirmDelete}\n onClose={() => setConfirmDelete(false)}\n title={tr('admin.tenantDetail.sso.deleteTitle', 'Remove SSO configuration?')}\n description={tr(\n 'admin.tenantDetail.sso.deleteDescription',\n 'This will remove the SAML SSO configuration. Users will no longer be able to sign in via SSO.'\n )}\n confirmLabel={tr('admin.tenantDetail.sso.deleteConfirm', 'Remove SSO')}\n variant=\"destructive\"\n onConfirm={handleDelete}\n loading={deleting}\n />\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TenantSsoPanel.js","names":[],"sources":["../../../src/admin/components/TenantSsoPanel.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { ShieldCheck, Loader2, Trash2, Save, ChevronDown, ChevronUp } from 'lucide-react';\nimport {\n useAdminGetSsoConfigQuery,\n useAdminSaveSsoConfigMutation,\n useAdminDeleteSsoConfigMutation,\n} from '../../generated/global-operations';\nimport { Card, CardHeader, CardTitle, CardContent, Button, Input, Separator } from './ui';\nimport { useToast } from './Toast';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\ninterface TenantSsoPanelProps {\n tenantId: string;\n canManage: boolean;\n}\n\ninterface SsoFormData {\n entityId: string;\n ssoUrl: string;\n sloUrl: string;\n certificate: string;\n enabled: boolean;\n}\n\nconst EMPTY_FORM: SsoFormData = {\n entityId: '',\n ssoUrl: '',\n sloUrl: '',\n certificate: '',\n enabled: true,\n};\n\nexport function TenantSsoPanel({ tenantId, canManage }: TenantSsoPanelProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const toast = useToast();\n\n const [expanded, setExpanded] = useState(false);\n const [editing, setEditing] = useState(false);\n const [form, setForm] = useState<SsoFormData>(EMPTY_FORM);\n const [confirmDelete, setConfirmDelete] = useState(false);\n\n const { data, loading, error, refetch } = useAdminGetSsoConfigQuery({\n variables: { tenantId },\n fetchPolicy: 'cache-and-network',\n });\n\n const [saveSso, { loading: saving }] = useAdminSaveSsoConfigMutation();\n const [deleteSso, { loading: deleting }] = useAdminDeleteSsoConfigMutation();\n\n const config = data?.getSSOConfig;\n\n useEffect(() => {\n if (config) {\n setForm({\n entityId: config.entityId,\n ssoUrl: config.ssoUrl,\n sloUrl: config.sloUrl ?? '',\n certificate: config.certificate,\n enabled: config.enabled,\n });\n } else {\n setForm(EMPTY_FORM);\n }\n }, [config]);\n\n const handleSave = async (e: React.FormEvent) => {\n e.preventDefault();\n try {\n await saveSso({\n variables: {\n tenantId,\n input: {\n entityId: form.entityId,\n ssoUrl: form.ssoUrl,\n sloUrl: form.sloUrl || undefined,\n certificate: form.certificate,\n enabled: form.enabled,\n },\n },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.savedTitle', 'SSO configuration saved'),\n tr(\n 'admin.tenantDetail.sso.savedDescription',\n 'SAML SSO has been configured for this tenant.'\n )\n );\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to save SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n const handleDelete = async () => {\n try {\n await deleteSso({\n variables: { tenantId },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.deletedTitle', 'SSO configuration removed'),\n tr('admin.tenantDetail.sso.deletedDescription', 'SSO has been disabled for this tenant.')\n );\n setConfirmDelete(false);\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to delete SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n return (\n <>\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <ShieldCheck className=\"h-5 w-5\" />\n {tr('admin.tenantDetail.sso.title', 'SSO Configuration')}\n </CardTitle>\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"flex items-center gap-1 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={expanded}\n aria-label={\n expanded\n ? tr('admin.tenantDetail.sso.collapse', 'Collapse SSO configuration')\n : tr('admin.tenantDetail.sso.expand', 'Expand SSO configuration')\n }\n >\n {expanded ? <ChevronUp className=\"h-4 w-4\" /> : <ChevronDown className=\"h-4 w-4\" />}\n </button>\n </div>\n </CardHeader>\n\n {expanded && (\n <CardContent className=\"space-y-4\">\n {loading ? (\n <div className=\"flex items-center justify-center py-6\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n ) : config && !editing ? (\n /* ── Configured state — read-only view ── */\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${\n config.enabled\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {config.enabled\n ? tr('admin.tenantDetail.sso.enabled', 'Enabled')\n : tr('admin.tenantDetail.sso.disabled', 'Disabled')}\n </span>\n </div>\n\n <dl className=\"grid gap-2 text-sm\">\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-text-secondary\">\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.entityId}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-text-secondary\">\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.ssoUrl}</dd>\n </div>\n {config.sloUrl && (\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-text-secondary\">\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.sloUrl}</dd>\n </div>\n )}\n </dl>\n\n {canManage && (\n <div className=\"flex gap-2 pt-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditing(true)}>\n {tr('admin.tenantDetail.sso.editButton', 'Edit')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"text-status-error-text hover:text-status-error-text\"\n onClick={() => setConfirmDelete(true)}\n >\n <Trash2 className=\"mr-1.5 h-3.5 w-3.5\" />\n {tr('admin.tenantDetail.sso.deleteButton', 'Remove')}\n </Button>\n </div>\n )}\n </div>\n ) : (\n /* ── Not configured or editing — show form ── */\n <form onSubmit={handleSave} className=\"space-y-4\">\n {!config && (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'admin.tenantDetail.sso.notConfigured',\n 'No SSO configuration. Fill in the SAML details below to enable enterprise SSO.'\n )}\n </p>\n )}\n\n <Separator />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-entityId-${tenantId}`}>\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n <span className=\"ml-1 text-status-error-text\">*</span>\n </label>\n <Input\n id={`sso-entityId-${tenantId}`}\n value={form.entityId}\n onChange={(e) => setForm((f) => ({ ...f, entityId: e.target.value }))}\n placeholder=\"https://idp.example.com/entity\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-ssoUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n <span className=\"ml-1 text-status-error-text\">*</span>\n </label>\n <Input\n id={`sso-ssoUrl-${tenantId}`}\n value={form.ssoUrl}\n onChange={(e) => setForm((f) => ({ ...f, ssoUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/sso\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-sloUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n <span className=\"ml-0.5 text-xs text-text-secondary\">\n {tr('admin.tenantDetail.sso.optional', '(optional)')}\n </span>\n </label>\n <Input\n id={`sso-sloUrl-${tenantId}`}\n value={form.sloUrl}\n onChange={(e) => setForm((f) => ({ ...f, sloUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/slo\"\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-cert-${tenantId}`}>\n {tr('admin.tenantDetail.sso.certificate', 'X.509 Certificate')}\n <span className=\"ml-1 text-status-error-text\">*</span>\n </label>\n <textarea\n id={`sso-cert-${tenantId}`}\n value={form.certificate}\n onChange={(e) => setForm((f) => ({ ...f, certificate: e.target.value }))}\n placeholder=\"-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----\"\n required\n rows={5}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 font-mono text-xs text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none\"\n />\n </div>\n\n <div className=\"sm:col-span-2 flex items-center gap-3\">\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={form.enabled}\n aria-label={tr(\n 'admin.tenantDetail.sso.enabledLabel',\n 'Enable SSO for this tenant'\n )}\n onClick={() => setForm((f) => ({ ...f, enabled: !f.enabled }))}\n className={`relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${\n form.enabled ? 'bg-action-primary-bg' : 'bg-bg-sunken'\n }`}\n >\n <span\n className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg transition-transform ${\n form.enabled ? 'translate-x-4' : 'translate-x-0'\n }`}\n />\n </button>\n <span className=\"text-sm\">\n {tr('admin.tenantDetail.sso.enabledLabel', 'Enable SSO for this tenant')}\n </span>\n </div>\n </div>\n\n <Separator />\n\n <div className=\"flex gap-2\">\n <Button type=\"submit\" size=\"sm\" disabled={saving}>\n {saving ? (\n <Loader2 className=\"mr-1.5 h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Save className=\"mr-1.5 h-3.5 w-3.5\" />\n )}\n {tr('admin.tenantDetail.sso.saveButton', 'Save SSO Config')}\n </Button>\n {editing && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setEditing(false)}\n >\n {tr('admin.tenantDetail.sso.cancelButton', 'Cancel')}\n </Button>\n )}\n </div>\n </form>\n )}\n </CardContent>\n )}\n </Card>\n\n <ConfirmDialog\n open={confirmDelete}\n onClose={() => setConfirmDelete(false)}\n title={tr('admin.tenantDetail.sso.deleteTitle', 'Remove SSO configuration?')}\n description={tr(\n 'admin.tenantDetail.sso.deleteDescription',\n 'This will remove the SAML SSO configuration. Users will no longer be able to sign in via SSO.'\n )}\n confirmLabel={tr('admin.tenantDetail.sso.deleteConfirm', 'Remove SSO')}\n variant=\"destructive\"\n onConfirm={handleDelete}\n loading={deleting}\n />\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -81,9 +81,9 @@ var _ = {
|
|
|
81
81
|
},
|
|
82
82
|
error: {
|
|
83
83
|
icon: o,
|
|
84
|
-
bg: "bg-
|
|
85
|
-
border: "border-
|
|
86
|
-
iconColor: "text-
|
|
84
|
+
bg: "bg-status-error-bg/10",
|
|
85
|
+
border: "border-status-error-border/30",
|
|
86
|
+
iconColor: "text-status-error-text"
|
|
87
87
|
},
|
|
88
88
|
warning: {
|
|
89
89
|
icon: s,
|
|
@@ -93,7 +93,7 @@ var _ = {
|
|
|
93
93
|
},
|
|
94
94
|
info: {
|
|
95
95
|
icon: l,
|
|
96
|
-
bg: "bg-primary/10",
|
|
96
|
+
bg: "bg-action-primary-bg/10",
|
|
97
97
|
border: "border-primary/30",
|
|
98
98
|
iconColor: "text-primary"
|
|
99
99
|
}
|
|
@@ -104,25 +104,25 @@ function v({ toast: t, onDismiss: n }) {
|
|
|
104
104
|
let e = t.duration ?? 4e3, r = setTimeout(n, e);
|
|
105
105
|
return () => clearTimeout(r);
|
|
106
106
|
}, [t.duration, n]), /* @__PURE__ */ f("div", {
|
|
107
|
-
className: e("flex items-start gap-3 rounded-lg border p-4 shadow-lg backdrop-blur-sm", "animate-in slide-in-from-right-full duration-300", "bg-
|
|
107
|
+
className: e("flex items-start gap-3 rounded-lg border p-4 shadow-lg backdrop-blur-sm", "animate-in slide-in-from-right-full duration-300", "bg-bg-surface/95", a, o),
|
|
108
108
|
role: "alert",
|
|
109
109
|
children: [
|
|
110
110
|
/* @__PURE__ */ d(r, { className: e("h-5 w-5 shrink-0 mt-0.5", s) }),
|
|
111
111
|
/* @__PURE__ */ f("div", {
|
|
112
112
|
className: "flex-1 min-w-0",
|
|
113
113
|
children: [/* @__PURE__ */ d("p", {
|
|
114
|
-
className: "text-sm font-medium text-
|
|
114
|
+
className: "text-sm font-medium text-text-primary",
|
|
115
115
|
children: t.title
|
|
116
116
|
}), t.description && /* @__PURE__ */ d("p", {
|
|
117
|
-
className: "mt-1 text-xs text-
|
|
117
|
+
className: "mt-1 text-xs text-text-secondary",
|
|
118
118
|
children: t.description
|
|
119
119
|
})]
|
|
120
120
|
}),
|
|
121
121
|
/* @__PURE__ */ d("button", {
|
|
122
122
|
onClick: n,
|
|
123
|
-
className: "shrink-0 rounded-md p-1 hover:bg-
|
|
123
|
+
className: "shrink-0 rounded-md p-1 hover:bg-bg-sunken transition-colors",
|
|
124
124
|
"aria-label": "Dismiss",
|
|
125
|
-
children: /* @__PURE__ */ d(u, { className: "h-4 w-4 text-
|
|
125
|
+
children: /* @__PURE__ */ d(u, { className: "h-4 w-4 text-text-secondary" })
|
|
126
126
|
})
|
|
127
127
|
]
|
|
128
128
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Toast.js","names":[],"sources":["../../../src/admin/components/Toast.tsx"],"sourcesContent":["import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react';\nimport { X, CheckCircle2, AlertCircle, AlertTriangle, Info } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ToastType = 'success' | 'error' | 'warning' | 'info';\n\nexport interface Toast {\n id: string;\n type: ToastType;\n title: string;\n description?: string;\n duration?: number;\n}\n\ninterface ToastContextValue {\n toasts: Toast[];\n addToast: (toast: Omit<Toast, 'id'>) => void;\n removeToast: (id: string) => void;\n success: (title: string, description?: string) => void;\n error: (title: string, description?: string) => void;\n warning: (title: string, description?: string) => void;\n info: (title: string, description?: string) => void;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nexport function useToast() {\n const context = useContext(ToastContext);\n if (!context) {\n throw new Error('useToast must be used within a ToastProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider\n// ============================================================================\n\ninterface ToastProviderProps {\n children: ReactNode;\n}\n\nexport function ToastProvider({ children }: ToastProviderProps) {\n const [toasts, setToasts] = useState<Toast[]>([]);\n\n const addToast = useCallback((toast: Omit<Toast, 'id'>) => {\n const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n setToasts((prev) => [...prev, { ...toast, id }]);\n }, []);\n\n const removeToast = useCallback((id: string) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const success = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'success', title, description, duration: 4000 });\n },\n [addToast]\n );\n\n const error = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'error', title, description, duration: 6000 });\n },\n [addToast]\n );\n\n const warning = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'warning', title, description, duration: 5000 });\n },\n [addToast]\n );\n\n const info = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'info', title, description, duration: 4000 });\n },\n [addToast]\n );\n\n return (\n <ToastContext.Provider value={{ toasts, addToast, removeToast, success, error, warning, info }}>\n {children}\n <ToastContainer toasts={toasts} removeToast={removeToast} />\n </ToastContext.Provider>\n );\n}\n\n// ============================================================================\n// Toast Container\n// ============================================================================\n\ninterface ToastContainerProps {\n toasts: Toast[];\n removeToast: (id: string) => void;\n}\n\nfunction ToastContainer({ toasts, removeToast }: ToastContainerProps) {\n if (toasts.length === 0) return null;\n\n return (\n <div className=\"fixed bottom-4 right-4 z-[100] flex flex-col gap-2 max-w-sm\">\n {toasts.map((toast) => (\n <ToastItem key={toast.id} toast={toast} onDismiss={() => removeToast(toast.id)} />\n ))}\n </div>\n );\n}\n\n// ============================================================================\n// Toast Item\n// ============================================================================\n\ninterface ToastItemProps {\n toast: Toast;\n onDismiss: () => void;\n}\n\nconst toastStyles: Record<\n ToastType,\n { icon: typeof CheckCircle2; bg: string; border: string; iconColor: string }\n> = {\n success: {\n icon: CheckCircle2,\n bg: 'bg-success/10',\n border: 'border-success/30',\n iconColor: 'text-success',\n },\n error: {\n icon: AlertCircle,\n bg: 'bg-
|
|
1
|
+
{"version":3,"file":"Toast.js","names":[],"sources":["../../../src/admin/components/Toast.tsx"],"sourcesContent":["import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react';\nimport { X, CheckCircle2, AlertCircle, AlertTriangle, Info } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ToastType = 'success' | 'error' | 'warning' | 'info';\n\nexport interface Toast {\n id: string;\n type: ToastType;\n title: string;\n description?: string;\n duration?: number;\n}\n\ninterface ToastContextValue {\n toasts: Toast[];\n addToast: (toast: Omit<Toast, 'id'>) => void;\n removeToast: (id: string) => void;\n success: (title: string, description?: string) => void;\n error: (title: string, description?: string) => void;\n warning: (title: string, description?: string) => void;\n info: (title: string, description?: string) => void;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nexport function useToast() {\n const context = useContext(ToastContext);\n if (!context) {\n throw new Error('useToast must be used within a ToastProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider\n// ============================================================================\n\ninterface ToastProviderProps {\n children: ReactNode;\n}\n\nexport function ToastProvider({ children }: ToastProviderProps) {\n const [toasts, setToasts] = useState<Toast[]>([]);\n\n const addToast = useCallback((toast: Omit<Toast, 'id'>) => {\n const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n setToasts((prev) => [...prev, { ...toast, id }]);\n }, []);\n\n const removeToast = useCallback((id: string) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const success = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'success', title, description, duration: 4000 });\n },\n [addToast]\n );\n\n const error = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'error', title, description, duration: 6000 });\n },\n [addToast]\n );\n\n const warning = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'warning', title, description, duration: 5000 });\n },\n [addToast]\n );\n\n const info = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'info', title, description, duration: 4000 });\n },\n [addToast]\n );\n\n return (\n <ToastContext.Provider value={{ toasts, addToast, removeToast, success, error, warning, info }}>\n {children}\n <ToastContainer toasts={toasts} removeToast={removeToast} />\n </ToastContext.Provider>\n );\n}\n\n// ============================================================================\n// Toast Container\n// ============================================================================\n\ninterface ToastContainerProps {\n toasts: Toast[];\n removeToast: (id: string) => void;\n}\n\nfunction ToastContainer({ toasts, removeToast }: ToastContainerProps) {\n if (toasts.length === 0) return null;\n\n return (\n <div className=\"fixed bottom-4 right-4 z-[100] flex flex-col gap-2 max-w-sm\">\n {toasts.map((toast) => (\n <ToastItem key={toast.id} toast={toast} onDismiss={() => removeToast(toast.id)} />\n ))}\n </div>\n );\n}\n\n// ============================================================================\n// Toast Item\n// ============================================================================\n\ninterface ToastItemProps {\n toast: Toast;\n onDismiss: () => void;\n}\n\nconst toastStyles: Record<\n ToastType,\n { icon: typeof CheckCircle2; bg: string; border: string; iconColor: string }\n> = {\n success: {\n icon: CheckCircle2,\n bg: 'bg-success/10',\n border: 'border-success/30',\n iconColor: 'text-success',\n },\n error: {\n icon: AlertCircle,\n bg: 'bg-status-error-bg/10',\n border: 'border-status-error-border/30',\n iconColor: 'text-status-error-text',\n },\n warning: {\n icon: AlertTriangle,\n bg: 'bg-warning/10',\n border: 'border-warning/30',\n iconColor: 'text-warning',\n },\n info: {\n icon: Info,\n bg: 'bg-action-primary-bg/10',\n border: 'border-primary/30',\n iconColor: 'text-primary',\n },\n};\n\nfunction ToastItem({ toast, onDismiss }: ToastItemProps) {\n const { icon: Icon, bg, border, iconColor } = toastStyles[toast.type];\n\n // Auto-dismiss\n useEffect(() => {\n const duration = toast.duration ?? 4000;\n const timer = setTimeout(onDismiss, duration);\n return () => clearTimeout(timer);\n }, [toast.duration, onDismiss]);\n\n return (\n <div\n className={cn(\n 'flex items-start gap-3 rounded-lg border p-4 shadow-lg backdrop-blur-sm',\n 'animate-in slide-in-from-right-full duration-300',\n 'bg-bg-surface/95',\n bg,\n border\n )}\n role=\"alert\"\n >\n <Icon className={cn('h-5 w-5 shrink-0 mt-0.5', iconColor)} />\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">{toast.title}</p>\n {toast.description && (\n <p className=\"mt-1 text-xs text-text-secondary\">{toast.description}</p>\n )}\n </div>\n <button\n onClick={onDismiss}\n className=\"shrink-0 rounded-md p-1 hover:bg-bg-sunken transition-colors\"\n aria-label=\"Dismiss\"\n >\n <X className=\"h-4 w-4 text-text-secondary\" />\n </button>\n </div>\n );\n}\n"],"mappings":";;;;;AAgCA,IAAM,IAAe,EAAwC,KAAK;AAElE,SAAgB,IAAW;CACzB,IAAM,IAAU,EAAW,EAAa;AACxC,KAAI,CAAC,EACH,OAAU,MAAM,+CAA+C;AAEjE,QAAO;;AAWT,SAAgB,EAAc,EAAE,eAAgC;CAC9D,IAAM,CAAC,GAAQ,KAAa,EAAkB,EAAE,CAAC,EAE3C,IAAW,GAAa,MAA6B;EACzD,IAAM,IAAK,SAAS,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;AACxE,KAAW,MAAS,CAAC,GAAG,GAAM;GAAE,GAAG;GAAO;GAAI,CAAC,CAAC;IAC/C,EAAE,CAAC,EAEA,IAAc,GAAa,MAAe;AAC9C,KAAW,MAAS,EAAK,QAAQ,MAAM,EAAE,OAAO,EAAG,CAAC;IACnD,EAAE,CAAC,EAEA,IAAU,GACb,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAW;GAAO;GAAa,UAAU;GAAM,CAAC;IAEnE,CAAC,EAAS,CACX,EAEK,IAAQ,GACX,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAS;GAAO;GAAa,UAAU;GAAM,CAAC;IAEjE,CAAC,EAAS,CACX,EAEK,IAAU,GACb,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAW;GAAO;GAAa,UAAU;GAAM,CAAC;IAEnE,CAAC,EAAS,CACX,EAEK,IAAO,GACV,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAQ;GAAO;GAAa,UAAU;GAAM,CAAC;IAEhE,CAAC,EAAS,CACX;AAED,QACE,kBAAC,EAAa,UAAd;EAAuB,OAAO;GAAE;GAAQ;GAAU;GAAa;GAAS;GAAO;GAAS;GAAM;YAA9F,CACG,GACD,kBAAC,GAAD;GAAwB;GAAqB;GAAe,CAAA,CACtC;;;AAa5B,SAAS,EAAe,EAAE,WAAQ,kBAAoC;AAGpE,QAFI,EAAO,WAAW,IAAU,OAG9B,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAO,KAAK,MACX,kBAAC,GAAD;GAAiC;GAAO,iBAAiB,EAAY,EAAM,GAAG;GAAI,EAAlE,EAAM,GAA4D,CAClF;EACE,CAAA;;AAaV,IAAM,IAGF;CACF,SAAS;EACP,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACD,OAAO;EACL,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACD,SAAS;EACP,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACD,MAAM;EACJ,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACF;AAED,SAAS,EAAU,EAAE,UAAO,gBAA6B;CACvD,IAAM,EAAE,MAAM,GAAM,OAAI,WAAQ,iBAAc,EAAY,EAAM;AAShE,QANA,QAAgB;EACd,IAAM,IAAW,EAAM,YAAY,KAC7B,IAAQ,WAAW,GAAW,EAAS;AAC7C,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAM,UAAU,EAAU,CAAC,EAG7B,kBAAC,OAAD;EACE,WAAW,EACT,2EACA,oDACA,oBACA,GACA,EACD;EACD,MAAK;YARP;GAUE,kBAAC,GAAD,EAAM,WAAW,EAAG,2BAA2B,EAAU,EAAI,CAAA;GAC7D,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAyC,EAAM;KAAU,CAAA,EACrE,EAAM,eACL,kBAAC,KAAD;KAAG,WAAU;eAAoC,EAAM;KAAgB,CAAA,CAErE;;GACN,kBAAC,UAAD;IACE,SAAS;IACT,WAAU;IACV,cAAW;cAEX,kBAAC,GAAD,EAAG,WAAU,+BAAgC,CAAA;IACtC,CAAA;GACL"}
|