hazo_auth 4.4.1 → 4.5.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.
Files changed (94) hide show
  1. package/README.md +207 -5
  2. package/SETUP_CHECKLIST.md +1 -1
  3. package/cli-src/lib/auth/auth_types.ts +22 -0
  4. package/cli-src/lib/auth/hazo_get_auth.server.ts +25 -1
  5. package/cli-src/lib/auth/session_token_validator.edge.ts +1 -0
  6. package/cli-src/lib/config/default_config.ts +36 -0
  7. package/cli-src/lib/navbar_config.server.ts +129 -0
  8. package/cli-src/lib/scope_hierarchy_config.server.ts +3 -14
  9. package/cli-src/lib/services/registration_service.ts +12 -0
  10. package/cli-src/lib/services/scope_labels_service.ts +21 -21
  11. package/cli-src/lib/services/scope_service.ts +15 -11
  12. package/cli-src/lib/services/session_token_service.ts +1 -0
  13. package/cli-src/lib/ui_shell_config.server.ts +15 -0
  14. package/cli-src/lib/user_types_config.server.ts +178 -0
  15. package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
  16. package/dist/app/api/hazo_auth/me/route.js +17 -0
  17. package/dist/app/api/hazo_auth/org_management/orgs/route.d.ts +26 -0
  18. package/dist/app/api/hazo_auth/org_management/orgs/route.d.ts.map +1 -0
  19. package/dist/app/api/hazo_auth/org_management/orgs/route.js +315 -0
  20. package/dist/app/api/hazo_auth/user_management/users/route.d.ts +11 -1
  21. package/dist/app/api/hazo_auth/user_management/users/route.d.ts.map +1 -1
  22. package/dist/app/api/hazo_auth/user_management/users/route.js +121 -16
  23. package/dist/components/layouts/my_settings/components/profile_picture_library_tab.d.ts.map +1 -1
  24. package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +8 -14
  25. package/dist/components/layouts/rbac_test/index.d.ts +1 -3
  26. package/dist/components/layouts/rbac_test/index.d.ts.map +1 -1
  27. package/dist/components/layouts/rbac_test/index.js +2 -2
  28. package/dist/components/layouts/shared/components/auth_navbar.d.ts +26 -0
  29. package/dist/components/layouts/shared/components/auth_navbar.d.ts.map +1 -0
  30. package/dist/components/layouts/shared/components/auth_navbar.js +14 -0
  31. package/dist/components/layouts/shared/components/auth_page_shell.d.ts +3 -1
  32. package/dist/components/layouts/shared/components/auth_page_shell.d.ts.map +1 -1
  33. package/dist/components/layouts/shared/components/auth_page_shell.js +17 -2
  34. package/dist/components/layouts/shared/components/standalone_layout_wrapper.d.ts +6 -1
  35. package/dist/components/layouts/shared/components/standalone_layout_wrapper.d.ts.map +1 -1
  36. package/dist/components/layouts/shared/components/standalone_layout_wrapper.js +7 -2
  37. package/dist/components/layouts/shared/index.d.ts +2 -0
  38. package/dist/components/layouts/shared/index.d.ts.map +1 -1
  39. package/dist/components/layouts/shared/index.js +1 -0
  40. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +3 -2
  41. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -1
  42. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +45 -18
  43. package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +3 -2
  44. package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -1
  45. package/dist/components/layouts/user_management/components/scope_labels_tab.js +48 -20
  46. package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -1
  47. package/dist/components/layouts/user_management/components/user_scopes_tab.js +1 -1
  48. package/dist/components/layouts/user_management/index.d.ts +11 -3
  49. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  50. package/dist/components/layouts/user_management/index.js +52 -5
  51. package/dist/components/ui/user-type-badge.d.ts +23 -0
  52. package/dist/components/ui/user-type-badge.d.ts.map +1 -0
  53. package/dist/components/ui/user-type-badge.js +42 -0
  54. package/dist/lib/auth/auth_types.d.ts +17 -0
  55. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  56. package/dist/lib/auth/auth_types.js +11 -0
  57. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  58. package/dist/lib/auth/hazo_get_auth.server.js +21 -1
  59. package/dist/lib/config/default_config.d.ts +60 -0
  60. package/dist/lib/config/default_config.d.ts.map +1 -1
  61. package/dist/lib/config/default_config.js +34 -0
  62. package/dist/lib/navbar_config.server.d.ts +36 -0
  63. package/dist/lib/navbar_config.server.d.ts.map +1 -0
  64. package/dist/lib/navbar_config.server.js +45 -0
  65. package/dist/lib/scope_hierarchy_config.server.d.ts +3 -7
  66. package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -1
  67. package/dist/lib/scope_hierarchy_config.server.js +1 -10
  68. package/dist/lib/services/registration_service.d.ts.map +1 -1
  69. package/dist/lib/services/registration_service.js +8 -0
  70. package/dist/lib/services/scope_labels_service.d.ts +7 -7
  71. package/dist/lib/services/scope_labels_service.d.ts.map +1 -1
  72. package/dist/lib/services/scope_labels_service.js +20 -20
  73. package/dist/lib/services/scope_service.d.ts +8 -5
  74. package/dist/lib/services/scope_service.d.ts.map +1 -1
  75. package/dist/lib/services/scope_service.js +9 -8
  76. package/dist/lib/ui_shell_config.server.d.ts +5 -0
  77. package/dist/lib/ui_shell_config.server.d.ts.map +1 -1
  78. package/dist/lib/ui_shell_config.server.js +5 -0
  79. package/dist/lib/user_types_config.server.d.ts +56 -0
  80. package/dist/lib/user_types_config.server.d.ts.map +1 -0
  81. package/dist/lib/user_types_config.server.js +100 -0
  82. package/dist/server/routes/index.d.ts +1 -0
  83. package/dist/server/routes/index.d.ts.map +1 -1
  84. package/dist/server/routes/index.js +2 -0
  85. package/dist/server/routes/org_management_orgs.d.ts +2 -0
  86. package/dist/server/routes/org_management_orgs.d.ts.map +1 -0
  87. package/dist/server/routes/org_management_orgs.js +2 -0
  88. package/hazo_auth_config.example.ini +9 -0
  89. package/package.json +1 -1
  90. package/cli-src/server/logging/logger_service.ts +0 -56
  91. /package/public/profile_pictures/library/Cars/{050 - citroe/314/210n_c3.jpeg" → 050 - citro/303/253n_c3.jpeg"} +0 -0
  92. /package/public/profile_pictures/library/Cars/{064 - lamborghini_huraca/314/201n.jpeg" → 064 - lamborghini_hurac/303/241n.jpeg"} +0 -0
  93. /package/public/profile_pictures/library/Cars/{099 - citroe/314/210n_2cv_(classic).jpeg" → 099 - citro/303/253n_2cv_(classic).jpeg"} +0 -0
  94. /package/public/profile_pictures/library/Cars/{131 - lamborghini_huraca/314/201n_sto.jpeg" → 131 - lamborghini_hurac/303/241n_sto.jpeg"} +0 -0
@@ -60,7 +60,7 @@ function getUserInitials(user) {
60
60
  * @param props - Component props
61
61
  * @returns RBAC test layout component
62
62
  */
63
- export function RbacTestLayout({ className, hrbacEnabled = false, defaultOrg = "", }) {
63
+ export function RbacTestLayout({ className, hrbacEnabled = false, }) {
64
64
  var _a;
65
65
  const { apiBasePath } = useHazoAuthConfig();
66
66
  const authResult = use_hazo_auth();
@@ -361,7 +361,7 @@ export function RbacTestLayout({ className, hrbacEnabled = false, defaultOrg = "
361
361
  }
362
362
  return (_jsxs("div", { className: `cls_rbac_test_layout flex flex-col gap-6 p-4 w-full max-w-5xl mx-auto ${className || ""}`, children: [_jsxs("div", { className: "cls_rbac_test_header", children: [_jsx("h1", { className: "text-2xl font-bold", children: "RBAC & HRBAC Test" }), _jsx("p", { className: "text-muted-foreground", children: "Test Role-Based Access Control (RBAC) and Hierarchical RBAC (HRBAC) for any user." })] }), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-lg flex items-center gap-2", children: [_jsx(User, { className: "h-5 w-5" }), "Select User to Test"] }), _jsx(CardDescription, { children: "Choose a user to test their permissions and scope access" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "user_select", children: "User" }), usersLoading ? (_jsxs("div", { className: "flex items-center gap-2 p-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), _jsx("span", { className: "text-sm text-muted-foreground", children: "Loading users..." })] })) : (_jsxs(Select, { value: selectedUserId, onValueChange: setSelectedUserId, children: [_jsx(SelectTrigger, { id: "user_select", className: "w-full", children: _jsx(SelectValue, { placeholder: "Select a user" }) }), _jsx(SelectContent, { children: users.map((user) => (_jsx(SelectItem, { value: user.id, children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Avatar, { className: "h-6 w-6", children: [_jsx(AvatarImage, { src: user.profile_picture_url || undefined }), _jsx(AvatarFallback, { className: "bg-slate-200 text-slate-600 text-xs", children: getUserInitials(user) })] }), _jsx("span", { children: user.name || user.email_address }), user.name && (_jsxs("span", { className: "text-muted-foreground text-xs", children: ["(", user.email_address, ")"] }))] }) }, user.id))) })] }))] }), selectedUser && (_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 p-4 border rounded-lg bg-muted/30", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsxs(Avatar, { className: "h-12 w-12", children: [_jsx(AvatarImage, { src: selectedUser.profile_picture_url || undefined }), _jsx(AvatarFallback, { className: "bg-slate-200 text-slate-600", children: getUserInitials(selectedUser) })] }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: selectedUser.name || selectedUser.email_address }), selectedUser.name && (_jsx("p", { className: "text-sm text-muted-foreground", children: selectedUser.email_address })), _jsxs("p", { className: "text-xs text-muted-foreground font-mono", children: [selectedUser.id.substring(0, 8), "..."] })] })] }), userDataLoading ? (_jsx("div", { className: "flex items-center justify-center", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permissions" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: userPermissions.length > 0 ? (userPermissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-blue-100 text-blue-700 rounded text-xs", children: p }, p)))) : (_jsx("span", { className: "text-muted-foreground text-sm", children: "None" })) })] }), hrbacEnabled && (_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Assigned Scopes" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: userScopes.length > 0 ? (userScopes.map((s) => (_jsx("span", { className: "px-2 py-0.5 bg-green-100 text-green-700 rounded text-xs", title: `${SCOPE_LEVEL_LABELS[s.scope_type]}: ${s.scope_seq}`, children: s.scope_seq }, `${s.scope_type}-${s.scope_id}`)))) : (_jsx("span", { className: "text-muted-foreground text-sm", children: "None" })) })] }))] }))] }))] })] }), _jsxs(Tabs, { defaultValue: "rbac", className: "w-full", children: [_jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [_jsxs(TabsTrigger, { value: "rbac", className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-4 w-4" }), "RBAC Test"] }), _jsxs(TabsTrigger, { value: "hrbac", className: "flex items-center gap-2", disabled: !hrbacEnabled, children: [_jsx(FolderTree, { className: "h-4 w-4" }), "HRBAC Test ", !hrbacEnabled && "(Disabled)"] })] }), _jsxs(TabsContent, { value: "rbac", className: "flex flex-col gap-4", children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { className: "text-lg", children: "Permission Test" }), _jsx(CardDescription, { children: "Select permissions to test if the selected user has them" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [permissionsLoading ? (_jsx("div", { className: "flex items-center justify-center p-4", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 max-h-[300px] overflow-auto p-2 border rounded", children: availablePermissions.map((perm) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: `rbac_${perm.permission_name}`, checked: selectedPermissions.includes(perm.permission_name), onCheckedChange: (checked) => handlePermissionToggle(perm.permission_name, checked) }), _jsxs("label", { htmlFor: `rbac_${perm.permission_name}`, className: "text-sm cursor-pointer flex-1", children: [perm.permission_name, userPermissions.includes(perm.permission_name) && (_jsx(CheckCircle, { className: "inline h-3 w-3 text-green-500 ml-1" }))] })] }, perm.permission_name))) })), _jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsxs("span", { children: ["Selected: ", selectedPermissions.length, " permission(s)"] }), selectedPermissions.length > 0 && (_jsxs("span", { className: "text-xs", children: ["(", selectedPermissions.join(", "), ")"] }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: handleRunRbacTest, disabled: rbacTesting || !selectedUserId, children: rbacTesting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Testing..."] })) : (_jsxs(_Fragment, { children: [_jsx(Play, { className: "h-4 w-4 mr-2" }), "Test Permissions"] })) }), _jsx(Button, { onClick: handleClearRbac, variant: "outline", children: "Clear" })] })] })] }), rbacResult && (_jsxs(Card, { className: rbacResult.permission_ok
363
363
  ? "border-green-200 bg-green-50/50"
364
- : "border-red-200 bg-red-50/50", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg flex items-center gap-2", children: rbacResult.permission_ok ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-green-700", children: "Permission Check Passed" })] })) : (_jsxs(_Fragment, { children: [_jsx(XCircle, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-red-700", children: "Permission Check Failed" })] })) }) }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [rbacResult.error && (_jsx("div", { className: "bg-red-100 border border-red-200 rounded p-3", children: _jsx("p", { className: "text-red-700 text-sm", children: rbacResult.error }) })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Authenticated" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.authenticated ? "text-green-600" : "text-red-600"}`, children: rbacResult.authenticated ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permission OK" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.permission_ok ? "text-green-600" : "text-red-600"}`, children: rbacResult.permission_ok ? "Yes" : "No" })] })] }), rbacResult.missing_permissions && rbacResult.missing_permissions.length > 0 && (_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Missing Permissions" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: rbacResult.missing_permissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-red-100 text-red-700 rounded text-xs", children: p }, p))) })] }))] })] }))] }), _jsx(TabsContent, { value: "hrbac", className: "flex flex-col gap-4", children: !hrbacEnabled ? (_jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-center justify-center p-8 gap-4", children: [_jsx(AlertCircle, { className: "h-12 w-12 text-amber-500" }), _jsx("h2", { className: "text-lg font-semibold", children: "HRBAC Not Enabled" }), _jsxs("p", { className: "text-muted-foreground text-center max-w-md", children: ["Enable HRBAC by setting", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "enable_hrbac = true" }), " in the", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "[hazo_auth__scope_hierarchy]" }), " ", "section."] })] }) })) : (_jsxs(_Fragment, { children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-lg flex items-center justify-between", children: [_jsx("span", { children: "Scope Access Test" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadScopeTree(), disabled: treeLoading, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${treeLoading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx(CardDescription, { children: "Select a scope from the tree and test if the selected user has access" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Select Scope" }), treeLoading ? (_jsx("div", { className: "flex items-center justify-center p-8 border rounded-lg", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : scopeTree.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center p-6 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-8 w-8 text-muted-foreground mb-2" }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "No scopes available. Create scopes in User Management first." })] })) : (_jsx("div", { className: "border rounded-lg max-h-[250px] overflow-auto", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleTreeSelectChange, initialSelectedItemId: selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.id, className: "w-full" }) }))] }), (selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData) && (_jsxs("div", { className: "p-3 border rounded-lg bg-muted/50", children: [_jsxs("p", { className: "text-sm", children: [_jsx("span", { className: "font-medium", children: "Selected:" }), " ", selectedTreeItem.scopeData.name] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [SCOPE_LEVEL_LABELS[selectedTreeItem.scopeData.level], " -", " ", selectedTreeItem.scopeData.seq, " (", selectedTreeItem.scopeData.org, ")"] })] })), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Additional Permissions (Optional)" }), permissionsLoading ? (_jsx("div", { className: "flex items-center justify-center p-4", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 max-h-[150px] overflow-auto p-2 border rounded", children: availablePermissions.map((perm) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: `hrbac_${perm.permission_name}`, checked: hrbacPermissions.includes(perm.permission_name), onCheckedChange: (checked) => handleHrbacPermissionToggle(perm.permission_name, checked) }), _jsx("label", { htmlFor: `hrbac_${perm.permission_name}`, className: "text-sm cursor-pointer flex-1", children: perm.permission_name })] }, perm.permission_name))) }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: handleRunHrbacTest, disabled: hrbacTesting || !selectedUserId || !(selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData), children: hrbacTesting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Testing..."] })) : (_jsxs(_Fragment, { children: [_jsx(Play, { className: "h-4 w-4 mr-2" }), "Test Scope Access"] })) }), _jsx(Button, { onClick: handleClearHrbac, variant: "outline", children: "Clear" })] })] })] }), hrbacResult && (_jsxs(Card, { className: hrbacResult.scope_ok
364
+ : "border-red-200 bg-red-50/50", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg flex items-center gap-2", children: rbacResult.permission_ok ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-green-700", children: "Permission Check Passed" })] })) : (_jsxs(_Fragment, { children: [_jsx(XCircle, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-red-700", children: "Permission Check Failed" })] })) }) }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [rbacResult.error && (_jsx("div", { className: "bg-red-100 border border-red-200 rounded p-3", children: _jsx("p", { className: "text-red-700 text-sm", children: rbacResult.error }) })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Authenticated" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.authenticated ? "text-green-600" : "text-red-600"}`, children: rbacResult.authenticated ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permission OK" }), _jsx("p", { className: `text-sm font-medium ${rbacResult.permission_ok ? "text-green-600" : "text-red-600"}`, children: rbacResult.permission_ok ? "Yes" : "No" })] })] }), rbacResult.missing_permissions && rbacResult.missing_permissions.length > 0 && (_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Missing Permissions" }), _jsx("div", { className: "flex flex-wrap gap-1 mt-1", children: rbacResult.missing_permissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-red-100 text-red-700 rounded text-xs", children: p }, p))) })] }))] })] }))] }), _jsx(TabsContent, { value: "hrbac", className: "flex flex-col gap-4", children: !hrbacEnabled ? (_jsx(Card, { children: _jsxs(CardContent, { className: "flex flex-col items-center justify-center p-8 gap-4", children: [_jsx(AlertCircle, { className: "h-12 w-12 text-amber-500" }), _jsx("h2", { className: "text-lg font-semibold", children: "HRBAC Not Enabled" }), _jsxs("p", { className: "text-muted-foreground text-center max-w-md", children: ["Enable HRBAC by setting", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "enable_hrbac = true" }), " in the", " ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "[hazo_auth__scope_hierarchy]" }), " ", "section."] })] }) })) : (_jsxs(_Fragment, { children: [_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs(CardTitle, { className: "text-lg flex items-center justify-between", children: [_jsx("span", { children: "Scope Access Test" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadScopeTree(), disabled: treeLoading, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${treeLoading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx(CardDescription, { children: "Select a scope from the tree and test if the selected user has access" })] }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Select Scope" }), treeLoading ? (_jsx("div", { className: "flex items-center justify-center p-8 border rounded-lg", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : scopeTree.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center p-6 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-8 w-8 text-muted-foreground mb-2" }), _jsx("p", { className: "text-sm text-muted-foreground text-center", children: "No scopes available. Create scopes in User Management first." })] })) : (_jsx("div", { className: "border rounded-lg max-h-[250px] overflow-auto", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleTreeSelectChange, initialSelectedItemId: selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.id, className: "w-full" }) }))] }), (selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData) && (_jsxs("div", { className: "p-3 border rounded-lg bg-muted/50", children: [_jsxs("p", { className: "text-sm", children: [_jsx("span", { className: "font-medium", children: "Selected:" }), " ", selectedTreeItem.scopeData.name] }), _jsxs("p", { className: "text-xs text-muted-foreground", children: [SCOPE_LEVEL_LABELS[selectedTreeItem.scopeData.level], " -", " ", selectedTreeItem.scopeData.seq] })] })), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: "Additional Permissions (Optional)" }), permissionsLoading ? (_jsx("div", { className: "flex items-center justify-center p-4", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 max-h-[150px] overflow-auto p-2 border rounded", children: availablePermissions.map((perm) => (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: `hrbac_${perm.permission_name}`, checked: hrbacPermissions.includes(perm.permission_name), onCheckedChange: (checked) => handleHrbacPermissionToggle(perm.permission_name, checked) }), _jsx("label", { htmlFor: `hrbac_${perm.permission_name}`, className: "text-sm cursor-pointer flex-1", children: perm.permission_name })] }, perm.permission_name))) }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { onClick: handleRunHrbacTest, disabled: hrbacTesting || !selectedUserId || !(selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData), children: hrbacTesting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Testing..."] })) : (_jsxs(_Fragment, { children: [_jsx(Play, { className: "h-4 w-4 mr-2" }), "Test Scope Access"] })) }), _jsx(Button, { onClick: handleClearHrbac, variant: "outline", children: "Clear" })] })] })] }), hrbacResult && (_jsxs(Card, { className: hrbacResult.scope_ok
365
365
  ? "border-green-200 bg-green-50/50"
366
366
  : "border-red-200 bg-red-50/50", children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-lg flex items-center gap-2", children: hrbacResult.scope_ok ? (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "h-5 w-5 text-green-500" }), _jsx("span", { className: "text-green-700", children: "Scope Access Granted" })] })) : (_jsxs(_Fragment, { children: [_jsx(XCircle, { className: "h-5 w-5 text-red-500" }), _jsx("span", { className: "text-red-700", children: "Scope Access Denied" })] })) }) }), _jsxs(CardContent, { className: "flex flex-col gap-3", children: [hrbacResult.error && (_jsx("div", { className: "bg-red-100 border border-red-200 rounded p-3", children: _jsx("p", { className: "text-red-700 text-sm", children: hrbacResult.error }) })), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Authenticated" }), _jsx("p", { className: `text-sm font-medium ${hrbacResult.authenticated ? "text-green-600" : "text-red-600"}`, children: hrbacResult.authenticated ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Permission OK" }), _jsx("p", { className: `text-sm font-medium ${hrbacResult.permission_ok ? "text-green-600" : "text-red-600"}`, children: hrbacResult.permission_ok ? "Yes" : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Scope OK" }), _jsx("p", { className: `text-sm font-medium ${hrbacResult.scope_ok === undefined
367
367
  ? "text-muted-foreground"
@@ -0,0 +1,26 @@
1
+ export type AuthNavbarProps = {
2
+ /** Logo image path */
3
+ logo_path?: string;
4
+ /** Logo width in pixels */
5
+ logo_width?: number;
6
+ /** Logo height in pixels */
7
+ logo_height?: number;
8
+ /** Company/application name displayed next to logo */
9
+ company_name?: string;
10
+ /** Home link path */
11
+ home_path?: string;
12
+ /** Home link label */
13
+ home_label?: string;
14
+ /** Show home link */
15
+ show_home_link?: boolean;
16
+ /** Navbar background color */
17
+ background_color?: string;
18
+ /** Navbar text color */
19
+ text_color?: string;
20
+ /** Navbar height in pixels */
21
+ height?: number;
22
+ /** Additional CSS class */
23
+ className?: string;
24
+ };
25
+ export declare function AuthNavbar({ logo_path, logo_width, logo_height, company_name, home_path, home_label, show_home_link, background_color, text_color, height, className, }: AuthNavbarProps): import("react/jsx-runtime").JSX.Element;
26
+ //# sourceMappingURL=auth_navbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth_navbar.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/auth_navbar.tsx"],"names":[],"mappings":"AAWA,MAAM,MAAM,eAAe,GAAG;IAC5B,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qBAAqB;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8BAA8B;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAGF,wBAAgB,UAAU,CAAC,EACzB,SAAuB,EACvB,UAAe,EACf,WAAgB,EAChB,YAAiB,EACjB,SAAe,EACf,UAAmB,EACnB,cAAqB,EACrB,gBAAgB,EAChB,UAAU,EACV,MAAW,EACX,SAAS,GACV,EAAE,eAAe,2CAiDjB"}
@@ -0,0 +1,14 @@
1
+ // file_description: configurable navbar component for auth pages with logo, company name, and home link
2
+ // section: client_directive
3
+ "use client";
4
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
+ // section: imports
6
+ import Link from "next/link";
7
+ import Image from "next/image";
8
+ import { Home } from "lucide-react";
9
+ import { cn } from "../../../../lib/utils";
10
+ // section: component
11
+ export function AuthNavbar({ logo_path = "/logo.png", logo_width = 32, logo_height = 32, company_name = "", home_path = "/", home_label = "Home", show_home_link = true, background_color, text_color, height = 64, className, }) {
12
+ const navStyle = Object.assign(Object.assign({ height: `${height}px` }, (background_color && { backgroundColor: background_color })), (text_color && { color: text_color }));
13
+ return (_jsxs("nav", { className: cn("cls_auth_navbar flex w-full items-center justify-between border-b border-border/40 bg-background/95 px-4 backdrop-blur supports-[backdrop-filter]:bg-background/60", className), style: navStyle, "aria-label": "Authentication page navigation", children: [_jsx("div", { className: "cls_auth_navbar_brand flex items-center gap-3", children: _jsxs(Link, { href: home_path, className: "cls_auth_navbar_logo_link flex items-center gap-3", children: [_jsx(Image, { src: logo_path, alt: company_name ? `${company_name} logo` : "Logo", width: logo_width, height: logo_height, className: "cls_auth_navbar_logo object-contain" }), company_name && (_jsx("span", { className: "cls_auth_navbar_company_name text-lg font-semibold text-foreground", children: company_name }))] }) }), show_home_link && (_jsx("div", { className: "cls_auth_navbar_links flex items-center gap-4", children: _jsxs(Link, { href: home_path, className: "cls_auth_navbar_home_link flex items-center gap-2 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground", "aria-label": `Navigate to ${home_label}`, children: [_jsx(Home, { className: "h-4 w-4", "aria-hidden": "true" }), _jsx("span", { children: home_label })] }) }))] }));
14
+ }
@@ -1,7 +1,9 @@
1
1
  import type { ReactNode } from "react";
2
2
  type AuthPageShellProps = {
3
3
  children: ReactNode;
4
+ /** Force disable navbar for this page (e.g., dev_lock) */
5
+ disableNavbar?: boolean;
4
6
  };
5
- export declare function AuthPageShell({ children }: AuthPageShellProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function AuthPageShell({ children, disableNavbar }: AuthPageShellProps): import("react/jsx-runtime").JSX.Element;
6
8
  export {};
7
9
  //# sourceMappingURL=auth_page_shell.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth_page_shell.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/auth_page_shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAGF,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE,kBAAkB,2CAmB7D"}
1
+ {"version":3,"file":"auth_page_shell.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/auth_page_shell.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,SAAS,CAAC;IACpB,0DAA0D;IAC1D,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAGF,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,aAAqB,EAAE,EAAE,kBAAkB,2CAsCpF"}
@@ -3,10 +3,25 @@ import { SidebarLayoutWrapper } from "./sidebar_layout_wrapper";
3
3
  import { StandaloneLayoutWrapper } from "./standalone_layout_wrapper";
4
4
  import { get_ui_shell_config } from "../../../../lib/ui_shell_config.server";
5
5
  // section: component
6
- export function AuthPageShell({ children }) {
6
+ export function AuthPageShell({ children, disableNavbar = false }) {
7
7
  const uiShellConfig = get_ui_shell_config();
8
8
  if (uiShellConfig.layout_mode === "standalone") {
9
- return (_jsx(StandaloneLayoutWrapper, { heading: uiShellConfig.standalone_heading, description: uiShellConfig.standalone_description, wrapperClassName: uiShellConfig.standalone_wrapper_class, contentClassName: uiShellConfig.standalone_content_class, showHeading: uiShellConfig.standalone_show_heading, showDescription: uiShellConfig.standalone_show_description, children: children }));
9
+ // Build navbar props (or null if disabled)
10
+ const navbarProps = !disableNavbar && uiShellConfig.navbar.enable_navbar
11
+ ? {
12
+ logo_path: uiShellConfig.navbar.logo_path,
13
+ logo_width: uiShellConfig.navbar.logo_width,
14
+ logo_height: uiShellConfig.navbar.logo_height,
15
+ company_name: uiShellConfig.navbar.company_name,
16
+ home_path: uiShellConfig.navbar.home_path,
17
+ home_label: uiShellConfig.navbar.home_label,
18
+ show_home_link: uiShellConfig.navbar.show_home_link,
19
+ background_color: uiShellConfig.navbar.background_color,
20
+ text_color: uiShellConfig.navbar.text_color,
21
+ height: uiShellConfig.navbar.height,
22
+ }
23
+ : null;
24
+ return (_jsx(StandaloneLayoutWrapper, { heading: uiShellConfig.standalone_heading, description: uiShellConfig.standalone_description, wrapperClassName: uiShellConfig.standalone_wrapper_class, contentClassName: uiShellConfig.standalone_content_class, showHeading: uiShellConfig.standalone_show_heading, showDescription: uiShellConfig.standalone_show_description, navbar: navbarProps, verticalCenter: uiShellConfig.vertical_center, children: children }));
10
25
  }
11
26
  return _jsx(SidebarLayoutWrapper, { children: children });
12
27
  }
@@ -1,3 +1,4 @@
1
+ import { type AuthNavbarProps } from "./auth_navbar";
1
2
  export type StandaloneLayoutWrapperProps = {
2
3
  children: React.ReactNode;
3
4
  heading?: string;
@@ -6,6 +7,10 @@ export type StandaloneLayoutWrapperProps = {
6
7
  contentClassName?: string;
7
8
  showHeading?: boolean;
8
9
  showDescription?: boolean;
10
+ /** Navbar configuration (pass null to disable navbar) */
11
+ navbar?: AuthNavbarProps | null;
12
+ /** Enable vertical centering of content (default: true) */
13
+ verticalCenter?: boolean;
9
14
  };
10
- export declare function StandaloneLayoutWrapper({ children, heading, description, wrapperClassName, contentClassName, showHeading, showDescription, }: StandaloneLayoutWrapperProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function StandaloneLayoutWrapper({ children, heading, description, wrapperClassName, contentClassName, showHeading, showDescription, navbar, verticalCenter, }: StandaloneLayoutWrapperProps): import("react/jsx-runtime").JSX.Element;
11
16
  //# sourceMappingURL=standalone_layout_wrapper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"standalone_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/standalone_layout_wrapper.tsx"],"names":[],"mappings":"AAQA,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAGF,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,OAAqB,EACrB,WAA8E,EAC9E,gBAAgB,EAChB,gBAAgB,EAChB,WAAkB,EAClB,eAAsB,GACvB,EAAE,4BAA4B,2CAsB9B"}
1
+ {"version":3,"file":"standalone_layout_wrapper.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/shared/components/standalone_layout_wrapper.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAGjE,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,yDAAyD;IACzD,MAAM,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,2DAA2D;IAC3D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B,CAAC;AAGF,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,OAAqB,EACrB,WAA8E,EAC9E,gBAAgB,EAChB,gBAAgB,EAChB,WAAkB,EAClB,eAAsB,EACtB,MAAM,EACN,cAAqB,GACtB,EAAE,4BAA4B,2CAmD9B"}
@@ -4,7 +4,12 @@
4
4
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  // section: imports
6
6
  import { cn } from "../../../../lib/utils";
7
+ import { AuthNavbar } from "./auth_navbar";
7
8
  // section: component
8
- export function StandaloneLayoutWrapper({ children, heading = "hazo auth", description = "Drop-in authentication flows that inherit your existing theme.", wrapperClassName, contentClassName, showHeading = true, showDescription = true, }) {
9
- return (_jsx("div", { className: cn("cls_standalone_layout_wrapper min-h-screen w-full bg-background", wrapperClassName), children: _jsxs("div", { className: cn("cls_standalone_layout_content mx-auto flex w-full max-w-5xl flex-col gap-8 p-6", contentClassName), children: [(showHeading || showDescription) && (_jsxs("div", { className: "cls_standalone_layout_header text-center", children: [showHeading && (_jsx("h1", { className: "cls_standalone_layout_title text-2xl font-semibold tracking-tight text-foreground", children: heading })), showDescription && (_jsx("p", { className: "cls_standalone_layout_description mt-2 text-sm text-muted-foreground", children: description }))] })), _jsx("div", { className: "cls_standalone_layout_body", children: children })] }) }));
9
+ export function StandaloneLayoutWrapper({ children, heading = "hazo auth", description = "Drop-in authentication flows that inherit your existing theme.", wrapperClassName, contentClassName, showHeading = true, showDescription = true, navbar, verticalCenter = true, }) {
10
+ var _a;
11
+ const hasNavbar = navbar !== null && navbar !== undefined;
12
+ // Calculate navbar height for vertical centering offset
13
+ const navbarHeight = hasNavbar ? ((_a = navbar === null || navbar === void 0 ? void 0 : navbar.height) !== null && _a !== void 0 ? _a : 64) : 0;
14
+ return (_jsxs("div", { className: cn("cls_standalone_layout_wrapper flex min-h-screen w-full flex-col bg-background", wrapperClassName), children: [hasNavbar && _jsx(AuthNavbar, Object.assign({}, navbar)), _jsx("div", { className: cn("cls_standalone_layout_content_area flex-1", verticalCenter && "flex items-center justify-center"), style: verticalCenter ? { minHeight: `calc(100vh - ${navbarHeight}px)` } : undefined, children: _jsxs("div", { className: cn("cls_standalone_layout_content mx-auto flex w-full max-w-5xl flex-col gap-8 p-6", contentClassName), children: [(showHeading || showDescription) && (_jsxs("div", { className: "cls_standalone_layout_header text-center", children: [showHeading && (_jsx("h1", { className: "cls_standalone_layout_title text-2xl font-semibold tracking-tight text-foreground", children: heading })), showDescription && (_jsx("p", { className: "cls_standalone_layout_description mt-2 text-sm text-muted-foreground", children: description }))] })), _jsx("div", { className: "cls_standalone_layout_body", children: children })] }) })] }));
10
15
  }
@@ -1,4 +1,6 @@
1
1
  export { AlreadyLoggedInGuard } from "./components/already_logged_in_guard";
2
+ export { AuthNavbar } from "./components/auth_navbar";
3
+ export type { AuthNavbarProps } from "./components/auth_navbar";
2
4
  export { FieldErrorMessage } from "./components/field_error_message";
3
5
  export { FormActionButtons } from "./components/form_action_buttons";
4
6
  export { FormFieldWrapper } from "./components/form_field_wrapper";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/shared/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAE5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAE7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACjF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGnF,cAAc,+BAA+B,CAAC;AAG9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/shared/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAE7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,YAAY,EAAE,uBAAuB,EAAE,MAAM,oCAAoC,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,YAAY,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AACjF,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGnF,cAAc,+BAA+B,CAAC;AAG9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACnE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,cAAc,oBAAoB,CAAC"}
@@ -1,6 +1,7 @@
1
1
  // file_description: barrel export for shared layout components, hooks, and utilities
2
2
  // section: component_exports
3
3
  export { AlreadyLoggedInGuard } from "./components/already_logged_in_guard";
4
+ export { AuthNavbar } from "./components/auth_navbar";
4
5
  // AuthPageShell - NOT exported (test workspace component only)
5
6
  export { FieldErrorMessage } from "./components/field_error_message";
6
7
  export { FormActionButtons } from "./components/form_action_buttons";
@@ -1,12 +1,13 @@
1
1
  export type ScopeHierarchyTabProps = {
2
2
  className?: string;
3
- defaultOrg?: string;
4
3
  };
5
4
  /**
6
5
  * Scope Hierarchy tab component for managing HRBAC scopes
7
6
  * Displays scopes in a tree view for intuitive hierarchy configuration
7
+ * Non-global admins see only their org's scopes (auto-filtered by API)
8
+ * Global admins can view/manage any org's scopes by providing org_id
8
9
  * @param props - Component props
9
10
  * @returns Scope Hierarchy tab component
10
11
  */
11
- export declare function ScopeHierarchyTab({ className, defaultOrg, }: ScopeHierarchyTabProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function ScopeHierarchyTab({ className, }: ScopeHierarchyTabProps): import("react/jsx-runtime").JSX.Element;
12
13
  //# sourceMappingURL=scope_hierarchy_tab.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope_hierarchy_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_hierarchy_tab.tsx"],"names":[],"mappings":"AA2CA,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA8HF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,UAAe,GAChB,EAAE,sBAAsB,2CA2fxB"}
1
+ {"version":3,"file":"scope_hierarchy_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_hierarchy_tab.tsx"],"names":[],"mappings":"AA4CA,MAAM,MAAM,sBAAsB,GAAG;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA+HF;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,GACV,EAAE,sBAAsB,2CA2gBxB"}
@@ -13,6 +13,7 @@ import { Label } from "../../../ui/label";
13
13
  import { Loader2, Plus, Edit, Trash2, CircleCheck, CircleX, Building2, FolderTree, RefreshCw, } from "lucide-react";
14
14
  import { toast } from "sonner";
15
15
  import { useHazoAuthConfig } from "../../../../contexts/hazo_auth_provider";
16
+ import { use_hazo_auth } from "../../shared/hooks/use_hazo_auth";
16
17
  const SCOPE_LEVEL_LABELS = {
17
18
  hazo_scopes_l1: "Level 1",
18
19
  hazo_scopes_l2: "Level 2",
@@ -64,16 +65,26 @@ function convertToTreeData(nodes, onEdit, onDelete, onAddChild) {
64
65
  /**
65
66
  * Scope Hierarchy tab component for managing HRBAC scopes
66
67
  * Displays scopes in a tree view for intuitive hierarchy configuration
68
+ * Non-global admins see only their org's scopes (auto-filtered by API)
69
+ * Global admins can view/manage any org's scopes by providing org_id
67
70
  * @param props - Component props
68
71
  * @returns Scope Hierarchy tab component
69
72
  */
70
- export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
73
+ export function ScopeHierarchyTab({ className, }) {
74
+ var _a;
71
75
  const { apiBasePath } = useHazoAuthConfig();
76
+ const authResult = use_hazo_auth();
77
+ // Determine if user is global admin (can manage any org)
78
+ const isGlobalAdmin = authResult.authenticated && authResult.permissions.includes("hazo_org_global_admin");
79
+ // Get user's org_id (for non-global admins, this is the only org they can manage)
80
+ const userOrgId = ((_a = authResult.user) === null || _a === void 0 ? void 0 : _a.org_id) || "";
72
81
  // State
73
82
  const [tree, setTree] = useState([]);
74
83
  const [loading, setLoading] = useState(true);
75
84
  const [actionLoading, setActionLoading] = useState(false);
76
- const [org, setOrg] = useState(defaultOrg);
85
+ // For non-global admins, org_id is fixed to their own org
86
+ // For global admins, they can enter any org_id to view that org's scopes
87
+ const [orgId, setOrgId] = useState("");
77
88
  const [selectedItem, setSelectedItem] = useState();
78
89
  // Dialog state
79
90
  const [addDialogOpen, setAddDialogOpen] = useState(false);
@@ -83,18 +94,29 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
83
94
  const [addParentScope, setAddParentScope] = useState(null);
84
95
  // Form state
85
96
  const [newName, setNewName] = useState("");
86
- const [newOrg, setNewOrg] = useState(defaultOrg);
87
97
  const [editName, setEditName] = useState("");
98
+ // Initialize orgId from user's org when auth loads
99
+ useEffect(() => {
100
+ if (!authResult.loading && userOrgId && !orgId) {
101
+ setOrgId(userOrgId);
102
+ }
103
+ }, [authResult.loading, userOrgId, orgId]);
88
104
  // Load tree data
89
105
  const loadTree = useCallback(async () => {
90
- if (!org) {
106
+ // For non-global admins, API will auto-filter to their org
107
+ // For global admins, org_id is required to specify which org to view
108
+ if (isGlobalAdmin && !orgId) {
91
109
  setTree([]);
92
110
  setLoading(false);
93
111
  return;
94
112
  }
95
113
  setLoading(true);
96
114
  try {
97
- const params = new URLSearchParams({ action: "tree", org });
115
+ const params = new URLSearchParams({ action: "tree" });
116
+ // Only pass org_id for global admins (non-global admins are auto-filtered by API)
117
+ if (isGlobalAdmin && orgId) {
118
+ params.set("org_id", orgId);
119
+ }
98
120
  const response = await fetch(`${apiBasePath}/scope_management/scopes?${params}`);
99
121
  const data = await response.json();
100
122
  if (data.success) {
@@ -112,22 +134,22 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
112
134
  finally {
113
135
  setLoading(false);
114
136
  }
115
- }, [apiBasePath, org]);
116
- // Load data when org changes
137
+ }, [apiBasePath, orgId, isGlobalAdmin]);
138
+ // Load data when orgId changes or auth finishes loading
117
139
  useEffect(() => {
118
- void loadTree();
119
- }, [loadTree]);
140
+ if (!authResult.loading) {
141
+ void loadTree();
142
+ }
143
+ }, [loadTree, authResult.loading]);
120
144
  // Handle add scope (root level)
121
145
  const handleAddRootScope = () => {
122
146
  setAddParentScope(null);
123
- setNewOrg(org || defaultOrg);
124
147
  setNewName("");
125
148
  setAddDialogOpen(true);
126
149
  };
127
150
  // Handle add child scope
128
151
  const handleAddChildScope = (parent) => {
129
152
  setAddParentScope(parent);
130
- setNewOrg(parent.org);
131
153
  setNewName("");
132
154
  setAddDialogOpen(true);
133
155
  };
@@ -148,10 +170,6 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
148
170
  toast.error("Name is required");
149
171
  return;
150
172
  }
151
- if (!newOrg.trim()) {
152
- toast.error("Organization is required");
153
- return;
154
- }
155
173
  setActionLoading(true);
156
174
  try {
157
175
  const level = addParentScope
@@ -161,13 +179,22 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
161
179
  toast.error("Cannot add children to Level 7 scopes");
162
180
  return;
163
181
  }
182
+ // Build request body - API will use user's org_id for non-global admins
183
+ // or the provided org_id for global admins
164
184
  const body = {
165
185
  level,
166
- org: newOrg.trim(),
167
186
  name: newName.trim(),
168
187
  };
188
+ // For global admins, pass the selected org_id
189
+ if (isGlobalAdmin && orgId) {
190
+ body.org_id = orgId;
191
+ body.root_org_id = orgId; // Default root_org_id to same as org_id
192
+ }
169
193
  if (addParentScope) {
170
194
  body.parent_scope_id = addParentScope.id;
195
+ // Inherit org_id from parent scope
196
+ body.org_id = addParentScope.org_id;
197
+ body.root_org_id = addParentScope.root_org_id;
171
198
  }
172
199
  const response = await fetch(`${apiBasePath}/scope_management/scopes`, {
173
200
  method: "POST",
@@ -278,10 +305,10 @@ export function ScopeHierarchyTab({ className, defaultOrg = "", }) {
278
305
  const childLevel = getChildLevel(addParentScope.level);
279
306
  return childLevel ? SCOPE_LEVEL_LABELS[childLevel] : "Unknown";
280
307
  };
281
- return (_jsxs("div", { className: `cls_scope_hierarchy_tab flex flex-col gap-4 w-full ${className || ""}`, children: [_jsxs("div", { className: "cls_scope_hierarchy_header flex items-center justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "cls_scope_hierarchy_header_left flex items-center gap-4", children: [_jsxs("div", { className: "cls_scope_hierarchy_org_filter flex items-center gap-2", children: [_jsx(Label, { htmlFor: "scope_org", className: "text-sm font-medium", children: "Organization:" }), _jsx(Input, { id: "scope_org", value: org, onChange: (e) => setOrg(e.target.value), placeholder: "Enter organization", className: "w-[200px]" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadTree(), disabled: loading || !org, children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx("div", { className: "cls_scope_hierarchy_header_right", children: _jsxs(Button, { onClick: handleAddRootScope, variant: "default", size: "sm", disabled: !org, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Add Root Scope"] }) })] }), loading ? (_jsx("div", { className: "cls_scope_hierarchy_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : !org ? (_jsxs("div", { className: "cls_scope_hierarchy_empty flex flex-col items-center justify-center p-8 border rounded-lg border-dashed", children: [_jsx(Building2, { className: "h-12 w-12 text-muted-foreground mb-4" }), _jsx("p", { className: "text-muted-foreground text-center", children: "Enter an organization name to view scope hierarchy" })] })) : tree.length === 0 ? (_jsxs("div", { className: "cls_scope_hierarchy_empty flex flex-col items-center justify-center p-8 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-12 w-12 text-muted-foreground mb-4" }), _jsxs("p", { className: "text-muted-foreground text-center mb-4", children: ["No scopes found for organization \"", org, "\""] }), _jsxs(Button, { onClick: handleAddRootScope, variant: "outline", size: "sm", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Create First Scope"] })] })) : (_jsx("div", { className: "cls_scope_hierarchy_tree_container border rounded-lg overflow-auto w-full min-h-[300px]", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleSelectChange, className: "w-full" }) })), (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.scopeData) && (_jsxs("div", { className: "cls_scope_hierarchy_selected_info p-4 border rounded-lg bg-muted/50", children: [_jsx("h4", { className: "font-medium mb-2", children: "Selected Scope" }), _jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Name:" }), " ", selectedItem.scopeData.name] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Seq:" }), " ", selectedItem.scopeData.seq] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Level:" }), " ", SCOPE_LEVEL_LABELS[selectedItem.scopeData.level]] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Org:" }), " ", selectedItem.scopeData.org] })] })] })), _jsx(Dialog, { open: addDialogOpen, onOpenChange: setAddDialogOpen, children: _jsxs(DialogContent, { className: "cls_scope_hierarchy_add_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: addParentScope
308
+ return (_jsxs("div", { className: `cls_scope_hierarchy_tab flex flex-col gap-4 w-full ${className || ""}`, children: [_jsxs("div", { className: "cls_scope_hierarchy_header flex items-center justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "cls_scope_hierarchy_header_left flex items-center gap-4", children: [isGlobalAdmin && (_jsxs("div", { className: "cls_scope_hierarchy_org_filter flex items-center gap-2", children: [_jsx(Label, { htmlFor: "scope_org_id", className: "text-sm font-medium", children: "Organization ID:" }), _jsx(Input, { id: "scope_org_id", value: orgId, onChange: (e) => setOrgId(e.target.value), placeholder: "Enter org UUID", className: "w-[280px] font-mono text-sm" })] })), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void loadTree(), disabled: loading || (isGlobalAdmin && !orgId), children: [_jsx(RefreshCw, { className: `h-4 w-4 mr-2 ${loading ? "animate-spin" : ""}` }), "Refresh"] })] }), _jsx("div", { className: "cls_scope_hierarchy_header_right", children: _jsxs(Button, { onClick: handleAddRootScope, variant: "default", size: "sm", disabled: isGlobalAdmin && !orgId, children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Add Root Scope"] }) })] }), loading || authResult.loading ? (_jsx("div", { className: "cls_scope_hierarchy_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : isGlobalAdmin && !orgId ? (_jsxs("div", { className: "cls_scope_hierarchy_empty flex flex-col items-center justify-center p-8 border rounded-lg border-dashed", children: [_jsx(Building2, { className: "h-12 w-12 text-muted-foreground mb-4" }), _jsx("p", { className: "text-muted-foreground text-center", children: "Enter an organization ID to view scope hierarchy" })] })) : tree.length === 0 ? (_jsxs("div", { className: "cls_scope_hierarchy_empty flex flex-col items-center justify-center p-8 border rounded-lg border-dashed", children: [_jsx(FolderTree, { className: "h-12 w-12 text-muted-foreground mb-4" }), _jsx("p", { className: "text-muted-foreground text-center mb-4", children: "No scopes found for this organization" }), _jsxs(Button, { onClick: handleAddRootScope, variant: "outline", size: "sm", children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Create First Scope"] })] })) : (_jsx("div", { className: "cls_scope_hierarchy_tree_container border rounded-lg overflow-auto w-full min-h-[300px]", children: _jsx(TreeView, { data: treeData, expandAll: true, defaultNodeIcon: Building2, defaultLeafIcon: Building2, onSelectChange: handleSelectChange, className: "w-full" }) })), (selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.scopeData) && (_jsxs("div", { className: "cls_scope_hierarchy_selected_info p-4 border rounded-lg bg-muted/50", children: [_jsx("h4", { className: "font-medium mb-2", children: "Selected Scope" }), _jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Name:" }), " ", selectedItem.scopeData.name] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Seq:" }), " ", selectedItem.scopeData.seq] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Level:" }), " ", SCOPE_LEVEL_LABELS[selectedItem.scopeData.level]] }), _jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: "Org ID:" }), " ", _jsx("span", { className: "font-mono text-xs", children: selectedItem.scopeData.org_id })] })] })] })), _jsx(Dialog, { open: addDialogOpen, onOpenChange: setAddDialogOpen, children: _jsxs(DialogContent, { className: "cls_scope_hierarchy_add_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: addParentScope
282
309
  ? `Add Child Scope to "${addParentScope.name}"`
283
310
  : "Add Root Scope" }), _jsxs(DialogDescription, { children: ["Create a new scope at ", getAddDialogLevelLabel(), ".", addParentScope &&
284
- ` This will be a child of "${addParentScope.name}".`] })] }), _jsxs("div", { className: "flex flex-col gap-4 py-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "new_scope_name", children: "Name *" }), _jsx(Input, { id: "new_scope_name", value: newName, onChange: (e) => setNewName(e.target.value), placeholder: "Enter scope name" })] }), !addParentScope && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "new_scope_org", children: "Organization *" }), _jsx(Input, { id: "new_scope_org", value: newOrg, onChange: (e) => setNewOrg(e.target.value), placeholder: "Enter organization" })] }))] }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleCreateScope, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Creating..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Create"] })) }), _jsxs(Button, { onClick: () => setAddDialogOpen(false), variant: "outline", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) }), _jsx(Dialog, { open: editDialogOpen, onOpenChange: setEditDialogOpen, children: _jsxs(DialogContent, { className: "cls_scope_hierarchy_edit_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Edit Scope" }), _jsxs(DialogDescription, { children: ["Update scope: ", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.name, " (", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.seq, ")"] })] }), _jsx("div", { className: "flex flex-col gap-4 py-4", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "edit_scope_name", children: "Name *" }), _jsx(Input, { id: "edit_scope_name", value: editName, onChange: (e) => setEditName(e.target.value), placeholder: "Enter scope name" })] }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleUpdateScope, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Save"] })) }), _jsxs(Button, { onClick: () => {
311
+ ` This will be a child of "${addParentScope.name}".`] })] }), _jsx("div", { className: "flex flex-col gap-4 py-4", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "new_scope_name", children: "Name *" }), _jsx(Input, { id: "new_scope_name", value: newName, onChange: (e) => setNewName(e.target.value), placeholder: "Enter scope name" })] }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleCreateScope, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Creating..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Create"] })) }), _jsxs(Button, { onClick: () => setAddDialogOpen(false), variant: "outline", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) }), _jsx(Dialog, { open: editDialogOpen, onOpenChange: setEditDialogOpen, children: _jsxs(DialogContent, { className: "cls_scope_hierarchy_edit_dialog", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Edit Scope" }), _jsxs(DialogDescription, { children: ["Update scope: ", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.name, " (", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.seq, ")"] })] }), _jsx("div", { className: "flex flex-col gap-4 py-4", children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "edit_scope_name", children: "Name *" }), _jsx(Input, { id: "edit_scope_name", value: editName, onChange: (e) => setEditName(e.target.value), placeholder: "Enter scope name" })] }) }), _jsxs(DialogFooter, { children: [_jsx(Button, { onClick: handleUpdateScope, disabled: actionLoading, variant: "default", children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(CircleCheck, { className: "h-4 w-4 mr-2" }), "Save"] })) }), _jsxs(Button, { onClick: () => {
285
312
  setEditDialogOpen(false);
286
313
  setSelectedScope(null);
287
314
  }, variant: "outline", children: [_jsx(CircleX, { className: "h-4 w-4 mr-2" }), "Cancel"] })] })] }) }), _jsx(AlertDialog, { open: deleteDialogOpen, onOpenChange: setDeleteDialogOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Delete Scope" }), _jsxs(AlertDialogDescription, { children: ["Are you sure you want to delete \"", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.name, "\" (", selectedScope === null || selectedScope === void 0 ? void 0 : selectedScope.seq, ")? This action cannot be undone and will also delete all child scopes."] })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogAction, { onClick: handleDeleteScope, disabled: actionLoading, children: actionLoading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Deleting..."] })) : ("Delete") }), _jsx(AlertDialogCancel, { onClick: () => {
@@ -1,13 +1,14 @@
1
1
  export type ScopeLabelsTabProps = {
2
2
  className?: string;
3
- defaultOrg?: string;
4
3
  };
5
4
  /**
6
5
  * Scope Labels tab component for configuring friendly names for scope levels
7
6
  * Shows all 7 scope levels with their current labels from database
8
7
  * Empty inputs for levels without labels - no placeholders
8
+ * Non-global admins see only their org's labels (auto-filtered by API)
9
+ * Global admins can view/manage any org's labels by providing org_id
9
10
  * @param props - Component props
10
11
  * @returns Scope Labels tab component
11
12
  */
12
- export declare function ScopeLabelsTab({ className, defaultOrg }: ScopeLabelsTabProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function ScopeLabelsTab({ className }: ScopeLabelsTabProps): import("react/jsx-runtime").JSX.Element;
13
14
  //# sourceMappingURL=scope_labels_tab.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope_labels_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_labels_tab.tsx"],"names":[],"mappings":"AAsBA,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAeF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,UAAe,EAAE,EAAE,mBAAmB,2CA4NjF"}
1
+ {"version":3,"file":"scope_labels_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/scope_labels_tab.tsx"],"names":[],"mappings":"AAuBA,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAeF;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE,mBAAmB,2CAwPhE"}
@@ -11,6 +11,7 @@ import { Label } from "../../../ui/label";
11
11
  import { Loader2, Save } from "lucide-react";
12
12
  import { toast } from "sonner";
13
13
  import { useHazoAuthConfig } from "../../../../contexts/hazo_auth_provider";
14
+ import { use_hazo_auth } from "../../shared/hooks/use_hazo_auth";
14
15
  const SCOPE_LEVELS = [
15
16
  "hazo_scopes_l1",
16
17
  "hazo_scopes_l2",
@@ -25,11 +26,19 @@ const SCOPE_LEVELS = [
25
26
  * Scope Labels tab component for configuring friendly names for scope levels
26
27
  * Shows all 7 scope levels with their current labels from database
27
28
  * Empty inputs for levels without labels - no placeholders
29
+ * Non-global admins see only their org's labels (auto-filtered by API)
30
+ * Global admins can view/manage any org's labels by providing org_id
28
31
  * @param props - Component props
29
32
  * @returns Scope Labels tab component
30
33
  */
31
- export function ScopeLabelsTab({ className, defaultOrg = "" }) {
34
+ export function ScopeLabelsTab({ className }) {
35
+ var _a;
32
36
  const { apiBasePath } = useHazoAuthConfig();
37
+ const authResult = use_hazo_auth();
38
+ // Determine if user is global admin (can manage any org)
39
+ const isGlobalAdmin = authResult.authenticated && authResult.permissions.includes("hazo_org_global_admin");
40
+ // Get user's org_id (for non-global admins, this is the only org they can manage)
41
+ const userOrgId = ((_a = authResult.user) === null || _a === void 0 ? void 0 : _a.org_id) || "";
33
42
  // State - simple record of scope_type to label string (empty string if not set)
34
43
  const [labels, setLabels] = useState(() => {
35
44
  const initial = {};
@@ -41,11 +50,21 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
41
50
  const [originalLabels, setOriginalLabels] = useState(null);
42
51
  const [loading, setLoading] = useState(true);
43
52
  const [saving, setSaving] = useState(false);
44
- const [org, setOrg] = useState(defaultOrg);
53
+ // For non-global admins, org_id is fixed to their own org
54
+ // For global admins, they can enter any org_id to view that org's labels
55
+ const [orgId, setOrgId] = useState("");
56
+ // Initialize orgId from user's org when auth loads
57
+ useEffect(() => {
58
+ if (!authResult.loading && userOrgId && !orgId) {
59
+ setOrgId(userOrgId);
60
+ }
61
+ }, [authResult.loading, userOrgId, orgId]);
45
62
  // Load labels from database (only real DB records, not synthetic defaults)
46
63
  const loadLabels = useCallback(async () => {
47
- if (!org.trim()) {
48
- // Reset to empty if no org
64
+ // For non-global admins, API will auto-filter to their org
65
+ // For global admins, org_id is required to specify which org to view
66
+ if (isGlobalAdmin && !orgId.trim()) {
67
+ // Reset to empty if global admin hasn't specified org
49
68
  const empty = {};
50
69
  for (const level of SCOPE_LEVELS) {
51
70
  empty[level] = "";
@@ -58,7 +77,11 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
58
77
  setLoading(true);
59
78
  try {
60
79
  // Fetch WITHOUT defaults - only get actual DB records
61
- const params = new URLSearchParams({ org: org.trim(), include_defaults: "false" });
80
+ const params = new URLSearchParams({ include_defaults: "false" });
81
+ // Only pass org_id for global admins (non-global admins are auto-filtered by API)
82
+ if (isGlobalAdmin && orgId.trim()) {
83
+ params.set("org_id", orgId.trim());
84
+ }
62
85
  const response = await fetch(`${apiBasePath}/scope_management/labels?${params}`);
63
86
  const data = await response.json();
64
87
  if (data.success) {
@@ -87,11 +110,13 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
87
110
  finally {
88
111
  setLoading(false);
89
112
  }
90
- }, [apiBasePath, org]);
91
- // Load labels when org changes
113
+ }, [apiBasePath, orgId, isGlobalAdmin]);
114
+ // Load labels when orgId changes or auth finishes loading
92
115
  useEffect(() => {
93
- void loadLabels();
94
- }, [loadLabels]);
116
+ if (!authResult.loading) {
117
+ void loadLabels();
118
+ }
119
+ }, [loadLabels, authResult.loading]);
95
120
  // Handle label change
96
121
  const handleLabelChange = (level, value) => {
97
122
  setLabels((prev) => (Object.assign(Object.assign({}, prev), { [level]: value })));
@@ -109,10 +134,6 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
109
134
  };
110
135
  // Save all labels - send all non-empty labels to be upserted
111
136
  const handleSave = async () => {
112
- if (!org.trim()) {
113
- toast.error("Organization is required");
114
- return;
115
- }
116
137
  // Collect all non-empty labels
117
138
  const labelsToSave = [];
118
139
  for (const level of SCOPE_LEVELS) {
@@ -126,13 +147,18 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
126
147
  }
127
148
  setSaving(true);
128
149
  try {
150
+ // Build request body - API will use user's org_id for non-global admins
151
+ const body = {
152
+ labels: labelsToSave,
153
+ };
154
+ // For global admins, pass the selected org_id
155
+ if (isGlobalAdmin && orgId.trim()) {
156
+ body.org_id = orgId.trim();
157
+ }
129
158
  const response = await fetch(`${apiBasePath}/scope_management/labels`, {
130
159
  method: "PUT",
131
160
  headers: { "Content-Type": "application/json" },
132
- body: JSON.stringify({
133
- org: org.trim(),
134
- labels: labelsToSave,
135
- }),
161
+ body: JSON.stringify(body),
136
162
  });
137
163
  const data = await response.json();
138
164
  if (data.success) {
@@ -151,8 +177,10 @@ export function ScopeLabelsTab({ className, defaultOrg = "" }) {
151
177
  setSaving(false);
152
178
  }
153
179
  };
154
- return (_jsxs("div", { className: `cls_scope_labels_tab flex flex-col gap-4 w-full ${className || ""}`, children: [_jsxs("div", { className: "cls_scope_labels_header flex items-center justify-between gap-4 flex-wrap", children: [_jsx("div", { className: "cls_scope_labels_header_left flex items-center gap-4", children: _jsxs("div", { className: "cls_scope_labels_org_input flex items-center gap-2", children: [_jsx(Label, { htmlFor: "labels_org", className: "text-sm font-medium", children: "Organization:" }), _jsx(Input, { id: "labels_org", value: org, onChange: (e) => setOrg(e.target.value), placeholder: "Enter organization name", className: "w-[200px]" })] }) }), _jsx("div", { className: "cls_scope_labels_header_right", children: _jsx(Button, { onClick: handleSave, disabled: saving || !hasChanges() || !org.trim(), variant: "default", size: "sm", children: saving ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { className: "h-4 w-4 mr-2" }), "Save Changes"] })) }) })] }), loading ? (_jsx("div", { className: "cls_scope_labels_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : (_jsx("div", { className: "cls_scope_labels_table_container border rounded-lg overflow-auto w-full", children: _jsxs(Table, { className: "cls_scope_labels_table w-full", children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[180px]", children: "Scope Level" }), _jsx(TableHead, { children: "Label" })] }) }), _jsx(TableBody, { children: SCOPE_LEVELS.map((level) => {
180
+ // Check if inputs should be enabled
181
+ const inputsEnabled = !isGlobalAdmin || orgId.trim().length > 0;
182
+ return (_jsxs("div", { className: `cls_scope_labels_tab flex flex-col gap-4 w-full ${className || ""}`, children: [_jsxs("div", { className: "cls_scope_labels_header flex items-center justify-between gap-4 flex-wrap", children: [_jsx("div", { className: "cls_scope_labels_header_left flex items-center gap-4", children: isGlobalAdmin && (_jsxs("div", { className: "cls_scope_labels_org_input flex items-center gap-2", children: [_jsx(Label, { htmlFor: "labels_org_id", className: "text-sm font-medium", children: "Organization ID:" }), _jsx(Input, { id: "labels_org_id", value: orgId, onChange: (e) => setOrgId(e.target.value), placeholder: "Enter org UUID", className: "w-[280px] font-mono text-sm" })] })) }), _jsx("div", { className: "cls_scope_labels_header_right", children: _jsx(Button, { onClick: handleSave, disabled: saving || !hasChanges() || (isGlobalAdmin && !orgId.trim()), variant: "default", size: "sm", children: saving ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Saving..."] })) : (_jsxs(_Fragment, { children: [_jsx(Save, { className: "h-4 w-4 mr-2" }), "Save Changes"] })) }) })] }), loading || authResult.loading ? (_jsx("div", { className: "cls_scope_labels_loading flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) })) : isGlobalAdmin && !orgId.trim() ? (_jsx("div", { className: "cls_scope_labels_info text-sm text-muted-foreground text-center p-4 bg-muted/50 rounded-lg", children: "Enter an organization ID to customize scope labels." })) : (_jsx("div", { className: "cls_scope_labels_table_container border rounded-lg overflow-auto w-full", children: _jsxs(Table, { className: "cls_scope_labels_table w-full", children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { className: "w-[180px]", children: "Scope Level" }), _jsx(TableHead, { children: "Label" })] }) }), _jsx(TableBody, { children: SCOPE_LEVELS.map((level) => {
155
183
  const label = labels[level];
156
- return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-sm", children: level }), _jsx(TableCell, { children: _jsx(Input, { value: label, onChange: (e) => handleLabelChange(level, e.target.value), className: "max-w-[400px]", disabled: !org.trim() }) })] }, level));
157
- }) })] }) })), !org.trim() && (_jsx("div", { className: "cls_scope_labels_info text-sm text-muted-foreground text-center p-4 bg-muted/50 rounded-lg", children: "Enter an organization name to customize scope labels." }))] }));
184
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-mono text-sm", children: level }), _jsx(TableCell, { children: _jsx(Input, { value: label, onChange: (e) => handleLabelChange(level, e.target.value), className: "max-w-[400px]", disabled: !inputsEnabled }) })] }, level));
185
+ }) })] }) }))] }));
158
186
  }
@@ -1 +1 @@
1
- {"version":3,"file":"user_scopes_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/user_scopes_tab.tsx"],"names":[],"mappings":"AAoDA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA6EF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CA8f9D"}
1
+ {"version":3,"file":"user_scopes_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/user_management/components/user_scopes_tab.tsx"],"names":[],"mappings":"AAoDA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AA8EF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CA8f9D"}