hazo_auth 3.0.4 → 4.1.0

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 (82) hide show
  1. package/README.md +228 -8
  2. package/SETUP_CHECKLIST.md +370 -0
  3. package/dist/app/api/hazo_auth/me/route.d.ts +3 -0
  4. package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
  5. package/dist/app/api/hazo_auth/me/route.js +9 -1
  6. package/dist/components/layouts/my_settings/components/profile_picture_library_tab.d.ts.map +1 -1
  7. package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +2 -2
  8. package/dist/components/layouts/profile_stamp_test/index.d.ts +10 -0
  9. package/dist/components/layouts/profile_stamp_test/index.d.ts.map +1 -0
  10. package/dist/components/layouts/profile_stamp_test/index.js +51 -0
  11. package/dist/components/layouts/rbac_test/index.d.ts +15 -0
  12. package/dist/components/layouts/rbac_test/index.d.ts.map +1 -0
  13. package/dist/components/layouts/rbac_test/index.js +378 -0
  14. package/dist/components/layouts/shared/components/password_field.js +1 -1
  15. package/dist/components/layouts/shared/components/profile_stamp.d.ts +58 -0
  16. package/dist/components/layouts/shared/components/profile_stamp.d.ts.map +1 -0
  17. package/dist/components/layouts/shared/components/profile_stamp.js +72 -0
  18. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  19. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
  20. package/dist/components/layouts/shared/components/two_column_auth_layout.js +1 -1
  21. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts +3 -0
  22. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts.map +1 -1
  23. package/dist/components/layouts/shared/hooks/use_auth_status.js +4 -0
  24. package/dist/components/layouts/shared/index.d.ts +2 -0
  25. package/dist/components/layouts/shared/index.d.ts.map +1 -1
  26. package/dist/components/layouts/shared/index.js +1 -0
  27. package/dist/components/layouts/user_management/components/roles_matrix.d.ts +2 -3
  28. package/dist/components/layouts/user_management/components/roles_matrix.d.ts.map +1 -1
  29. package/dist/components/layouts/user_management/components/roles_matrix.js +133 -8
  30. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +12 -0
  31. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -0
  32. package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +291 -0
  33. package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +13 -0
  34. package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -0
  35. package/dist/components/layouts/user_management/components/scope_labels_tab.js +158 -0
  36. package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts +11 -0
  37. package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -0
  38. package/dist/components/layouts/user_management/components/user_scopes_tab.js +267 -0
  39. package/dist/components/layouts/user_management/index.d.ts +9 -2
  40. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  41. package/dist/components/layouts/user_management/index.js +22 -6
  42. package/dist/components/ui/hover-card.d.ts +7 -0
  43. package/dist/components/ui/hover-card.d.ts.map +1 -0
  44. package/dist/components/ui/hover-card.js +29 -0
  45. package/dist/components/ui/index.d.ts +1 -0
  46. package/dist/components/ui/index.d.ts.map +1 -1
  47. package/dist/components/ui/index.js +1 -0
  48. package/dist/components/ui/select.d.ts +14 -0
  49. package/dist/components/ui/select.d.ts.map +1 -0
  50. package/dist/components/ui/select.js +59 -0
  51. package/dist/components/ui/tree-view.d.ts +108 -0
  52. package/dist/components/ui/tree-view.d.ts.map +1 -0
  53. package/dist/components/ui/tree-view.js +194 -0
  54. package/dist/lib/auth/auth_types.d.ts +45 -0
  55. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  56. package/dist/lib/auth/auth_types.js +13 -0
  57. package/dist/lib/auth/hazo_get_auth.server.d.ts +4 -2
  58. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  59. package/dist/lib/auth/hazo_get_auth.server.js +107 -3
  60. package/dist/lib/auth/scope_cache.d.ts +92 -0
  61. package/dist/lib/auth/scope_cache.d.ts.map +1 -0
  62. package/dist/lib/auth/scope_cache.js +171 -0
  63. package/dist/lib/scope_hierarchy_config.server.d.ts +39 -0
  64. package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -0
  65. package/dist/lib/scope_hierarchy_config.server.js +96 -0
  66. package/dist/lib/services/email_service.d.ts.map +1 -1
  67. package/dist/lib/services/email_service.js +7 -2
  68. package/dist/lib/services/profile_picture_service.d.ts +1 -7
  69. package/dist/lib/services/profile_picture_service.d.ts.map +1 -1
  70. package/dist/lib/services/profile_picture_service.js +77 -32
  71. package/dist/lib/services/registration_service.js +1 -1
  72. package/dist/lib/services/scope_labels_service.d.ts +48 -0
  73. package/dist/lib/services/scope_labels_service.d.ts.map +1 -0
  74. package/dist/lib/services/scope_labels_service.js +277 -0
  75. package/dist/lib/services/scope_service.d.ts +114 -0
  76. package/dist/lib/services/scope_service.d.ts.map +1 -0
  77. package/dist/lib/services/scope_service.js +582 -0
  78. package/dist/lib/services/user_scope_service.d.ts +74 -0
  79. package/dist/lib/services/user_scope_service.d.ts.map +1 -0
  80. package/dist/lib/services/user_scope_service.js +415 -0
  81. package/hazo_auth_config.example.ini +1 -1
  82. package/package.json +4 -1
@@ -24,6 +24,9 @@ import { get_filename, get_line_number } from "../../../../lib/utils/api_route_h
24
24
  * email_verified: boolean,
25
25
  * last_logon: string | undefined,
26
26
  * profile_picture_url: string | null,
27
+ * profile_image: string | null, // alias for profile_picture_url
28
+ * avatar_url: string | null, // alias for profile_picture_url
29
+ * image: string | null, // alias for profile_picture_url
27
30
  * profile_source: "upload" | "library" | "gravatar" | "custom" | undefined,
28
31
  * user: { id, email_address, name, is_active, profile_picture_url },
29
32
  * permissions: string[],
@@ -63,6 +66,7 @@ export async function GET(request) {
63
66
  const profile_source_db = user_db.profile_source;
64
67
  const profile_source_ui = profile_source_db ? map_db_source_to_ui(profile_source_db) : undefined;
65
68
  // Return unified format with all fields
69
+ const profile_pic = auth_result.user.profile_picture_url;
66
70
  return NextResponse.json({
67
71
  authenticated: true,
68
72
  // Top-level fields for backward compatibility
@@ -71,7 +75,11 @@ export async function GET(request) {
71
75
  name: auth_result.user.name,
72
76
  email_verified: user_db.email_verified === true,
73
77
  last_logon: user_db.last_logon || undefined,
74
- profile_picture_url: auth_result.user.profile_picture_url,
78
+ profile_picture_url: profile_pic,
79
+ // Aliases for profile_picture_url (for consuming app compatibility)
80
+ profile_image: profile_pic,
81
+ avatar_url: profile_pic,
82
+ image: profile_pic,
75
83
  profile_source: profile_source_ui,
76
84
  // Permissions and user object (always included)
77
85
  user: auth_result.user,
@@ -1 +1 @@
1
- {"version":3,"file":"profile_picture_library_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/my_settings/components/profile_picture_library_tab.tsx"],"names":[],"mappings":"AAeA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAGF;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,QAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,EAAE,6BAA6B,2CAwQ/B"}
1
+ {"version":3,"file":"profile_picture_library_tab.d.ts","sourceRoot":"","sources":["../../../../../src/components/layouts/my_settings/components/profile_picture_library_tab.tsx"],"names":[],"mappings":"AAeA,MAAM,MAAM,6BAA6B,GAAG;IAC1C,UAAU,EAAE,OAAO,CAAC;IACpB,kBAAkB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3C,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,uBAAuB,EAAE,MAAM,CAAC;IAChC,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC;AAGF;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,UAAU,EACV,kBAAkB,EAClB,aAAa,EACb,QAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,GACxB,EAAE,6BAA6B,2CAyQ/B"}
@@ -128,9 +128,9 @@ export function ProfilePictureLibraryTab({ useLibrary, onUseLibraryChange, onPho
128
128
  };
129
129
  return (_jsxs("div", { className: "cls_profile_picture_library_tab flex flex-col gap-4", children: [_jsxs("div", { className: "cls_profile_picture_library_tab_switch flex items-center gap-3", children: [_jsx(Switch, { id: "use-library", checked: useLibrary, onCheckedChange: onUseLibraryChange, disabled: disabled, className: "cls_profile_picture_library_tab_switch_input", "aria-label": "Use library photo" }), _jsxs(Label, { htmlFor: "use-library", className: "cls_profile_picture_library_tab_switch_label text-sm font-medium text-[var(--hazo-text-secondary)] cursor-pointer", children: ["Use library photo", _jsx(HazoUITooltip, { message: libraryTooltipMessage, iconSize: tooltipIconSizeSmall, side: "top" })] })] }), _jsxs("div", { className: "cls_profile_picture_library_tab_content grid grid-cols-12 gap-4", children: [_jsxs("div", { className: "cls_profile_picture_library_tab_categories_container flex flex-col gap-2 col-span-3", children: [_jsx(Label, { className: "cls_profile_picture_library_tab_categories_label text-sm font-medium text-[var(--hazo-text-secondary)]", children: "Categories" }), loadingCategories ? (_jsx("div", { className: "cls_profile_picture_library_tab_loading flex items-center justify-center p-8 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px]", children: _jsx(Loader2, { className: "h-6 w-6 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }) })) : categories.length > 0 ? (_jsx(VerticalTabs, { value: selectedCategory || categories[0], onValueChange: setSelectedCategory, className: "cls_profile_picture_library_tab_vertical_tabs", children: _jsx(VerticalTabsList, { className: "cls_profile_picture_library_tab_vertical_tabs_list w-full", children: categories.map((category) => (_jsx(VerticalTabsTrigger, { value: category, className: "cls_profile_picture_library_tab_vertical_tabs_trigger w-full justify-start", children: category }, category))) }) })) : (_jsx("div", { className: "cls_profile_picture_library_tab_no_categories flex items-center justify-center p-8 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px]", children: _jsx("p", { className: "cls_profile_picture_library_tab_no_categories_text text-sm text-[var(--hazo-text-muted)]", children: "No categories available" }) }))] }), _jsxs("div", { className: "cls_profile_picture_library_tab_photos_container flex flex-col gap-2 col-span-6", children: [_jsx(Label, { className: "cls_profile_picture_library_tab_photos_label text-sm font-medium text-[var(--hazo-text-secondary)]", children: "Photos" }), loadingPhotos ? (_jsx("div", { className: "cls_profile_picture_library_tab_photos_loading flex items-center justify-center p-8 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px]", children: _jsx(Loader2, { className: "h-6 w-6 text-[var(--hazo-text-subtle)] animate-spin", "aria-hidden": "true" }) })) : photos.length > 0 ? (_jsx("div", { className: `cls_profile_picture_library_tab_photos_grid grid ${getGridColumnsClass(libraryPhotoGridColumns)} gap-3 overflow-y-auto p-4 border border-[var(--hazo-border)] rounded-lg bg-[var(--hazo-bg-subtle)] min-h-[400px] max-h-[400px]`, children: photos.map((photoUrl) => (_jsx("button", { type: "button", onClick: () => handlePhotoClick(photoUrl), className: `
130
130
  cls_profile_picture_library_tab_photo_thumbnail
131
- aspect-square rounded-lg overflow-hidden border-2 transition-colors cursor-pointer
131
+ w-full aspect-square rounded-lg overflow-hidden border-2 transition-colors cursor-pointer
132
132
  ${selectedPhoto === photoUrl ? "border-blue-500 ring-2 ring-blue-200" : "border-[var(--hazo-border)] hover:border-[var(--hazo-border-emphasis)]"}
133
- `, "aria-label": `Select photo ${photoUrl.split('/').pop()}`, children: _jsx("img", { src: photoUrl, alt: `Library photo ${photoUrl.split('/').pop()}`, className: "cls_profile_picture_library_tab_photo_thumbnail_image w-full h-full object-cover", loading: "lazy", onError: (e) => {
133
+ `, style: { minHeight: '80px', minWidth: '80px' }, "aria-label": `Select photo ${photoUrl.split('/').pop()}`, children: _jsx("img", { src: photoUrl, alt: `Library photo ${photoUrl.split('/').pop()}`, className: "cls_profile_picture_library_tab_photo_thumbnail_image w-full h-full object-cover", loading: "lazy", onError: (e) => {
134
134
  // Fallback if image fails to load
135
135
  const target = e.target;
136
136
  target.style.display = 'none';
@@ -0,0 +1,10 @@
1
+ export type ProfileStampTestLayoutProps = {
2
+ className?: string;
3
+ };
4
+ /**
5
+ * ProfileStampTestLayout - Test page for ProfileStamp component
6
+ * Demonstrates various scenarios and configurations
7
+ */
8
+ export declare function ProfileStampTestLayout({ className }: ProfileStampTestLayoutProps): import("react/jsx-runtime").JSX.Element;
9
+ export default ProfileStampTestLayout;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/profile_stamp_test/index.tsx"],"names":[],"mappings":"AAYA,MAAM,MAAM,2BAA2B,GAAG;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAGF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,SAAS,EAAE,EAAE,2BAA2B,2CAkNhF;AAED,eAAe,sBAAsB,CAAC"}
@@ -0,0 +1,51 @@
1
+ // file_description: Test page layout for ProfileStamp component demonstrating various scenarios
2
+ // section: client_directive
3
+ "use client";
4
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
+ // section: imports
6
+ import { useState } from "react";
7
+ import { ProfileStamp } from "../shared/components/profile_stamp";
8
+ import { use_auth_status } from "../shared/hooks/use_auth_status";
9
+ import { Button } from "../../ui/button";
10
+ import { Card } from "../../ui/card";
11
+ // section: component
12
+ /**
13
+ * ProfileStampTestLayout - Test page for ProfileStamp component
14
+ * Demonstrates various scenarios and configurations
15
+ */
16
+ export function ProfileStampTestLayout({ className }) {
17
+ const authStatus = use_auth_status();
18
+ const [showCustomFields, setShowCustomFields] = useState(true);
19
+ // Sample custom fields for testing
20
+ const sampleCustomFields = [
21
+ { label: "Role", value: "Administrator" },
22
+ { label: "Department", value: "Engineering" },
23
+ { label: "Joined", value: "Jan 2024" },
24
+ ];
25
+ return (_jsxs("div", { className: `cls_profile_stamp_test_layout w-full max-w-4xl mx-auto p-6 ${className || ""}`, children: [_jsx("h1", { className: "text-2xl font-bold mb-6", children: "ProfileStamp Component Test" }), _jsxs(Card, { className: "p-4 mb-6", children: [_jsx("h2", { className: "text-lg font-semibold mb-2", children: "Current Auth Status" }), _jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [_jsx("div", { children: "Authenticated:" }), _jsx("div", { className: authStatus.authenticated ? "text-green-600" : "text-red-600", children: authStatus.authenticated ? "Yes" : "No" }), _jsx("div", { children: "User ID:" }), _jsx("div", { children: authStatus.user_id || "N/A" }), _jsx("div", { children: "Name:" }), _jsx("div", { children: authStatus.name || "N/A" }), _jsx("div", { children: "Email:" }), _jsx("div", { children: authStatus.email || "N/A" }), _jsx("div", { children: "Profile Picture URL:" }), _jsx("div", { className: "truncate", children: authStatus.profile_picture_url || "N/A" }), _jsx("div", { children: "Profile Source:" }), _jsx("div", { children: authStatus.profile_source || "N/A" })] })] }), _jsxs(Card, { className: "p-4 mb-6", children: [_jsx("h2", { className: "text-lg font-semibold mb-4", children: "Size Variants" }), _jsxs("div", { className: "flex items-end gap-6", children: [_jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(ProfileStamp, { size: "sm" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "sm (24px)" })] }), _jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(ProfileStamp, { size: "default" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "default (32px)" })] }), _jsxs("div", { className: "flex flex-col items-center gap-2", children: [_jsx(ProfileStamp, { size: "lg" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "lg (40px)" })] })] })] }), _jsxs(Card, { className: "p-4 mb-6", children: [_jsx("h2", { className: "text-lg font-semibold mb-4", children: "With Custom Fields" }), _jsx("div", { className: "flex items-center gap-4 mb-4", children: _jsx(Button, { variant: showCustomFields ? "default" : "outline", size: "sm", onClick: () => setShowCustomFields(!showCustomFields), children: showCustomFields ? "Hide Custom Fields" : "Show Custom Fields" }) }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx(ProfileStamp, { size: "lg", custom_fields: showCustomFields ? sampleCustomFields : [] }), _jsxs("span", { className: "text-sm text-muted-foreground", children: ["Hover to see ", showCustomFields ? "name, email, and custom fields" : "name and email only"] })] }), showCustomFields && (_jsx("div", { className: "mt-4 p-3 bg-muted rounded-md", children: _jsxs("div", { className: "text-xs font-mono", children: ["custom_fields=", JSON.stringify(sampleCustomFields, null, 2)] }) }))] }), _jsxs(Card, { className: "p-4 mb-6", children: [_jsx("h2", { className: "text-lg font-semibold mb-4", children: "Display Options" }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx(ProfileStamp, { show_name: true, show_email: true }), _jsx("span", { className: "text-sm text-muted-foreground", children: "show_name=true, show_email=true (default)" })] }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx(ProfileStamp, { show_name: true, show_email: false }), _jsx("span", { className: "text-sm text-muted-foreground", children: "show_name=true, show_email=false" })] }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx(ProfileStamp, { show_name: false, show_email: true }), _jsx("span", { className: "text-sm text-muted-foreground", children: "show_name=false, show_email=true" })] }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx(ProfileStamp, { show_name: false, show_email: false }), _jsx("span", { className: "text-sm text-muted-foreground", children: "show_name=false, show_email=false (no hover card)" })] })] })] }), _jsxs(Card, { className: "p-4 mb-6", children: [_jsx("h2", { className: "text-lg font-semibold mb-4", children: "Inline Usage Example (Note Attribution)" }), _jsx("div", { className: "border rounded-lg p-4 bg-background", children: _jsxs("div", { className: "flex items-start gap-3", children: [_jsx(ProfileStamp, { size: "default" }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx("span", { className: "font-medium text-sm", children: authStatus.name || "User" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "2 hours ago" })] }), _jsx("p", { className: "text-sm text-foreground", children: "This is an example note with a ProfileStamp component showing who added it. Hover over the profile picture to see more details about the user." })] })] }) })] }), _jsxs(Card, { className: "p-4 mb-6", children: [_jsx("h2", { className: "text-lg font-semibold mb-4", children: "Comment Thread Example" }), _jsx("div", { className: "space-y-4", children: [
26
+ { time: "3 hours ago", text: "Great progress on the project!" },
27
+ { time: "2 hours ago", text: "I agree, the new features look amazing." },
28
+ { time: "1 hour ago", text: "Let me know if you need any help with the deployment." },
29
+ ].map((comment, index) => (_jsxs("div", { className: "flex items-start gap-3 border-b pb-4 last:border-b-0", children: [_jsx(ProfileStamp, { size: "sm", custom_fields: [{ label: "Comment", value: `#${index + 1}` }] }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx("span", { className: "font-medium text-sm", children: authStatus.name || "User" }), _jsx("span", { className: "text-xs text-muted-foreground", children: comment.time })] }), _jsx("p", { className: "text-sm text-foreground", children: comment.text })] })] }, index))) })] }), _jsxs(Card, { className: "p-4 mb-6", children: [_jsx("h2", { className: "text-lg font-semibold mb-4", children: "API Response Fields" }), _jsx("p", { className: "text-sm text-muted-foreground mb-3", children: "The /api/hazo_auth/me endpoint now returns these profile picture fields:" }), _jsx("div", { className: "p-3 bg-muted rounded-md font-mono text-xs", children: _jsx("pre", { children: `{
30
+ "profile_picture_url": "${authStatus.profile_picture_url || "null"}",
31
+ "profile_image": "${authStatus.profile_image || "null"}", // alias
32
+ "avatar_url": "${authStatus.avatar_url || "null"}", // alias
33
+ "image": "${authStatus.image || "null"}" // alias
34
+ }` }) })] }), _jsxs(Card, { className: "p-4", children: [_jsx("h2", { className: "text-lg font-semibold mb-4", children: "Usage Code Example" }), _jsx("div", { className: "p-3 bg-muted rounded-md font-mono text-xs overflow-x-auto", children: _jsx("pre", { children: `// Basic usage
35
+ import { ProfileStamp } from "hazo_auth/client";
36
+
37
+ <ProfileStamp />
38
+
39
+ // With all options
40
+ <ProfileStamp
41
+ size="lg"
42
+ show_name={true}
43
+ show_email={true}
44
+ custom_fields={[
45
+ { label: "Role", value: "Admin" },
46
+ { label: "Department", value: "IT" }
47
+ ]}
48
+ className="my-custom-class"
49
+ />` }) })] })] }));
50
+ }
51
+ export default ProfileStampTestLayout;
@@ -0,0 +1,15 @@
1
+ export type RbacTestLayoutProps = {
2
+ className?: string;
3
+ /** Whether HRBAC is enabled (passed from server) */
4
+ hrbacEnabled?: boolean;
5
+ /** Default organization for HRBAC scopes */
6
+ defaultOrg?: string;
7
+ };
8
+ /**
9
+ * RBAC/HRBAC Test layout component
10
+ * Allows testing permissions and scope access for different users
11
+ * @param props - Component props
12
+ * @returns RBAC test layout component
13
+ */
14
+ export declare function RbacTestLayout({ className, hrbacEnabled, defaultOrg, }: RbacTestLayoutProps): import("react/jsx-runtime").JSX.Element;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/rbac_test/index.tsx"],"names":[],"mappings":"AA2CA,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA+GF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,YAAoB,EACpB,UAAe,GAChB,EAAE,mBAAmB,2CAw3BrB"}
@@ -0,0 +1,378 @@
1
+ // file_description: RBAC/HRBAC Test layout component for testing role-based and hierarchical access control
2
+ // section: client_directive
3
+ "use client";
4
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
5
+ // section: imports
6
+ import { useState, useEffect, useMemo, useCallback } from "react";
7
+ import { Button } from "../../ui/button";
8
+ import { Label } from "../../ui/label";
9
+ import { Checkbox } from "../../ui/checkbox";
10
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "../../ui/card";
11
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../../ui/select";
12
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs";
13
+ import { TreeView } from "../../ui/tree-view";
14
+ import { Avatar, AvatarFallback, AvatarImage } from "../../ui/avatar";
15
+ import { Loader2, Play, AlertCircle, CheckCircle, XCircle, Shield, Building2, FolderTree, User, RefreshCw, } from "lucide-react";
16
+ import { useHazoAuthConfig } from "../../../contexts/hazo_auth_provider";
17
+ import { use_hazo_auth } from "../shared/hooks/use_hazo_auth";
18
+ import { toast } from "sonner";
19
+ const SCOPE_LEVEL_LABELS = {
20
+ hazo_scopes_l1: "Level 1",
21
+ hazo_scopes_l2: "Level 2",
22
+ hazo_scopes_l3: "Level 3",
23
+ hazo_scopes_l4: "Level 4",
24
+ hazo_scopes_l5: "Level 5",
25
+ hazo_scopes_l6: "Level 6",
26
+ hazo_scopes_l7: "Level 7",
27
+ };
28
+ // Convert ScopeTreeNode to TreeDataItem format for selection
29
+ function convertToTreeData(nodes) {
30
+ return nodes.map((node) => {
31
+ const hasChildren = node.children && node.children.length > 0;
32
+ const item = {
33
+ id: node.id,
34
+ name: `${node.name} (${node.seq})`,
35
+ icon: Building2,
36
+ scopeData: node,
37
+ };
38
+ if (hasChildren) {
39
+ item.children = convertToTreeData(node.children);
40
+ }
41
+ return item;
42
+ });
43
+ }
44
+ // Get user initials for avatar fallback
45
+ function getUserInitials(user) {
46
+ var _a, _b;
47
+ if (user.name) {
48
+ const parts = user.name.trim().split(" ");
49
+ if (parts.length >= 2) {
50
+ return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
51
+ }
52
+ return ((_a = user.name[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || "";
53
+ }
54
+ return ((_b = user.email_address[0]) === null || _b === void 0 ? void 0 : _b.toUpperCase()) || "?";
55
+ }
56
+ // section: component
57
+ /**
58
+ * RBAC/HRBAC Test layout component
59
+ * Allows testing permissions and scope access for different users
60
+ * @param props - Component props
61
+ * @returns RBAC test layout component
62
+ */
63
+ export function RbacTestLayout({ className, hrbacEnabled = false, defaultOrg = "", }) {
64
+ var _a;
65
+ const { apiBasePath } = useHazoAuthConfig();
66
+ const authResult = use_hazo_auth();
67
+ // Users state
68
+ const [users, setUsers] = useState([]);
69
+ const [usersLoading, setUsersLoading] = useState(true);
70
+ const [selectedUserId, setSelectedUserId] = useState("");
71
+ const [selectedUser, setSelectedUser] = useState(null);
72
+ // Selected user's permissions and scopes
73
+ const [userPermissions, setUserPermissions] = useState([]);
74
+ const [userScopes, setUserScopes] = useState([]);
75
+ const [userDataLoading, setUserDataLoading] = useState(false);
76
+ // Available permissions state
77
+ const [availablePermissions, setAvailablePermissions] = useState([]);
78
+ const [permissionsLoading, setPermissionsLoading] = useState(true);
79
+ // RBAC test state
80
+ const [selectedPermissions, setSelectedPermissions] = useState([]);
81
+ const [rbacTesting, setRbacTesting] = useState(false);
82
+ const [rbacResult, setRbacResult] = useState(null);
83
+ // HRBAC scope tree state
84
+ const [scopeTree, setScopeTree] = useState([]);
85
+ const [treeLoading, setTreeLoading] = useState(false);
86
+ const [selectedTreeItem, setSelectedTreeItem] = useState();
87
+ // HRBAC test state
88
+ const [hrbacPermissions, setHrbacPermissions] = useState([]);
89
+ const [hrbacTesting, setHrbacTesting] = useState(false);
90
+ const [hrbacResult, setHrbacResult] = useState(null);
91
+ // Load users
92
+ useEffect(() => {
93
+ const loadUsers = async () => {
94
+ var _a;
95
+ setUsersLoading(true);
96
+ try {
97
+ const response = await fetch(`${apiBasePath}/user_management/users`);
98
+ const data = await response.json();
99
+ if (data.success) {
100
+ setUsers(data.users || []);
101
+ // Select current user by default if available
102
+ if ((_a = authResult.user) === null || _a === void 0 ? void 0 : _a.id) {
103
+ setSelectedUserId(authResult.user.id);
104
+ }
105
+ }
106
+ }
107
+ catch (error) {
108
+ toast.error("Failed to load users");
109
+ }
110
+ finally {
111
+ setUsersLoading(false);
112
+ }
113
+ };
114
+ void loadUsers();
115
+ }, [apiBasePath, (_a = authResult.user) === null || _a === void 0 ? void 0 : _a.id]);
116
+ // Update selected user when ID changes
117
+ useEffect(() => {
118
+ if (selectedUserId) {
119
+ const user = users.find((u) => u.id === selectedUserId);
120
+ setSelectedUser(user || null);
121
+ }
122
+ else {
123
+ setSelectedUser(null);
124
+ }
125
+ }, [selectedUserId, users]);
126
+ // Load selected user's permissions and scopes
127
+ const loadUserData = useCallback(async () => {
128
+ if (!selectedUserId) {
129
+ setUserPermissions([]);
130
+ setUserScopes([]);
131
+ return;
132
+ }
133
+ setUserDataLoading(true);
134
+ try {
135
+ // Step 1: Get user's assigned role IDs
136
+ const userRolesResponse = await fetch(`${apiBasePath}/user_management/users/roles?user_id=${selectedUserId}`);
137
+ const userRolesData = await userRolesResponse.json();
138
+ if (userRolesData.success && Array.isArray(userRolesData.role_ids) && userRolesData.role_ids.length > 0) {
139
+ // Step 2: Get all roles with their permissions
140
+ const rolesResponse = await fetch(`${apiBasePath}/user_management/roles`);
141
+ const rolesData = await rolesResponse.json();
142
+ if (rolesData.success && Array.isArray(rolesData.roles)) {
143
+ // Step 3: Filter to user's roles and extract permissions
144
+ const userRoleIds = new Set(userRolesData.role_ids);
145
+ const allPermissions = new Set();
146
+ for (const role of rolesData.roles) {
147
+ if (userRoleIds.has(role.role_id)) {
148
+ if (role.permissions && Array.isArray(role.permissions)) {
149
+ role.permissions.forEach((p) => allPermissions.add(p));
150
+ }
151
+ }
152
+ }
153
+ setUserPermissions(Array.from(allPermissions));
154
+ }
155
+ else {
156
+ setUserPermissions([]);
157
+ }
158
+ }
159
+ else {
160
+ setUserPermissions([]);
161
+ }
162
+ // Load user scopes if HRBAC is enabled
163
+ if (hrbacEnabled) {
164
+ const scopesResponse = await fetch(`${apiBasePath}/user_management/users/scopes?user_id=${selectedUserId}&include_effective=true`);
165
+ const scopesData = await scopesResponse.json();
166
+ if (scopesData.success) {
167
+ setUserScopes(scopesData.direct_scopes || []);
168
+ }
169
+ else {
170
+ setUserScopes([]);
171
+ }
172
+ }
173
+ }
174
+ catch (error) {
175
+ toast.error("Failed to load user data");
176
+ setUserPermissions([]);
177
+ setUserScopes([]);
178
+ }
179
+ finally {
180
+ setUserDataLoading(false);
181
+ }
182
+ }, [apiBasePath, selectedUserId, hrbacEnabled]);
183
+ useEffect(() => {
184
+ void loadUserData();
185
+ }, [loadUserData]);
186
+ // Load available permissions
187
+ useEffect(() => {
188
+ const loadPermissions = async () => {
189
+ setPermissionsLoading(true);
190
+ try {
191
+ const response = await fetch(`${apiBasePath}/user_management/permissions`);
192
+ const data = await response.json();
193
+ if (data.success) {
194
+ const dbPerms = data.db_permissions.map((p) => ({
195
+ id: p.id,
196
+ permission_name: p.permission_name,
197
+ description: p.description,
198
+ source: "db",
199
+ }));
200
+ const configPerms = data.config_permissions.map((name) => ({
201
+ id: 0,
202
+ permission_name: name,
203
+ description: "",
204
+ source: "config",
205
+ }));
206
+ // Dedupe by permission_name, preferring db source
207
+ const permMap = new Map();
208
+ for (const p of [...configPerms, ...dbPerms]) {
209
+ permMap.set(p.permission_name, p);
210
+ }
211
+ setAvailablePermissions(Array.from(permMap.values()));
212
+ }
213
+ }
214
+ catch (error) {
215
+ toast.error("Failed to load permissions");
216
+ }
217
+ finally {
218
+ setPermissionsLoading(false);
219
+ }
220
+ };
221
+ void loadPermissions();
222
+ }, [apiBasePath]);
223
+ // Load scope tree
224
+ const loadScopeTree = useCallback(async () => {
225
+ if (!hrbacEnabled)
226
+ return;
227
+ setTreeLoading(true);
228
+ try {
229
+ const params = new URLSearchParams({ action: "tree_all" });
230
+ const response = await fetch(`${apiBasePath}/scope_management/scopes?${params}`);
231
+ const data = await response.json();
232
+ if (data.success) {
233
+ setScopeTree(data.trees || []);
234
+ }
235
+ else {
236
+ setScopeTree([]);
237
+ }
238
+ }
239
+ catch (error) {
240
+ setScopeTree([]);
241
+ }
242
+ finally {
243
+ setTreeLoading(false);
244
+ }
245
+ }, [apiBasePath, hrbacEnabled]);
246
+ useEffect(() => {
247
+ void loadScopeTree();
248
+ }, [loadScopeTree]);
249
+ // Convert tree to TreeDataItem format
250
+ const treeData = useMemo(() => {
251
+ return convertToTreeData(scopeTree);
252
+ }, [scopeTree]);
253
+ // Handle tree item selection
254
+ const handleTreeSelectChange = (item) => {
255
+ setSelectedTreeItem(item);
256
+ };
257
+ // Handle RBAC permission toggle
258
+ const handlePermissionToggle = (permission, checked) => {
259
+ if (checked) {
260
+ setSelectedPermissions((prev) => [...prev, permission]);
261
+ }
262
+ else {
263
+ setSelectedPermissions((prev) => prev.filter((p) => p !== permission));
264
+ }
265
+ };
266
+ // Handle HRBAC permission toggle
267
+ const handleHrbacPermissionToggle = (permission, checked) => {
268
+ if (checked) {
269
+ setHrbacPermissions((prev) => [...prev, permission]);
270
+ }
271
+ else {
272
+ setHrbacPermissions((prev) => prev.filter((p) => p !== permission));
273
+ }
274
+ };
275
+ // Run RBAC test
276
+ const handleRunRbacTest = async () => {
277
+ if (!selectedUserId) {
278
+ toast.error("Please select a user");
279
+ return;
280
+ }
281
+ setRbacTesting(true);
282
+ setRbacResult(null);
283
+ try {
284
+ const params = new URLSearchParams();
285
+ params.append("test_user_id", selectedUserId);
286
+ selectedPermissions.forEach((p) => {
287
+ params.append("required_permissions", p);
288
+ });
289
+ const response = await fetch(`${apiBasePath}/rbac_test?${params}`);
290
+ const data = await response.json();
291
+ setRbacResult(data);
292
+ }
293
+ catch (error) {
294
+ setRbacResult({
295
+ success: false,
296
+ authenticated: false,
297
+ permission_ok: false,
298
+ error: error instanceof Error ? error.message : "Unknown error",
299
+ });
300
+ }
301
+ finally {
302
+ setRbacTesting(false);
303
+ }
304
+ };
305
+ // Run HRBAC test
306
+ const handleRunHrbacTest = async () => {
307
+ if (!selectedUserId) {
308
+ toast.error("Please select a user");
309
+ return;
310
+ }
311
+ if (!(selectedTreeItem === null || selectedTreeItem === void 0 ? void 0 : selectedTreeItem.scopeData)) {
312
+ toast.error("Please select a scope from the tree");
313
+ return;
314
+ }
315
+ setHrbacTesting(true);
316
+ setHrbacResult(null);
317
+ try {
318
+ const params = new URLSearchParams();
319
+ params.append("test_user_id", selectedUserId);
320
+ params.append("scope_type", selectedTreeItem.scopeData.level);
321
+ params.append("scope_id", selectedTreeItem.scopeData.id);
322
+ hrbacPermissions.forEach((p) => {
323
+ params.append("required_permissions", p);
324
+ });
325
+ const response = await fetch(`${apiBasePath}/rbac_test?${params}`);
326
+ const data = await response.json();
327
+ setHrbacResult(data);
328
+ }
329
+ catch (error) {
330
+ setHrbacResult({
331
+ success: false,
332
+ authenticated: false,
333
+ permission_ok: false,
334
+ error: error instanceof Error ? error.message : "Unknown error",
335
+ });
336
+ }
337
+ finally {
338
+ setHrbacTesting(false);
339
+ }
340
+ };
341
+ // Clear RBAC test
342
+ const handleClearRbac = () => {
343
+ setSelectedPermissions([]);
344
+ setRbacResult(null);
345
+ };
346
+ // Clear HRBAC test
347
+ const handleClearHrbac = () => {
348
+ setSelectedTreeItem(undefined);
349
+ setHrbacPermissions([]);
350
+ setHrbacResult(null);
351
+ };
352
+ if (authResult.loading) {
353
+ return (_jsx("div", { className: "cls_rbac_test_layout flex items-center justify-center p-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-slate-400" }) }));
354
+ }
355
+ if (!authResult.authenticated) {
356
+ return (_jsxs("div", { className: "cls_rbac_test_layout flex flex-col items-center justify-center p-8 gap-4", children: [_jsx(AlertCircle, { className: "h-12 w-12 text-red-500" }), _jsx("h1", { className: "text-xl font-semibold", children: "Authentication Required" }), _jsx("p", { className: "text-muted-foreground", children: "Please log in to access the RBAC test tool." })] }));
357
+ }
358
+ // Check for admin_test_access permission
359
+ if (!authResult.permissions.includes("admin_test_access")) {
360
+ return (_jsxs("div", { className: "cls_rbac_test_layout flex flex-col items-center justify-center p-8 gap-4", children: [_jsx(Shield, { className: "h-12 w-12 text-amber-500" }), _jsx("h1", { className: "text-xl font-semibold", children: "Access Denied" }), _jsxs("p", { className: "text-muted-foreground text-center", children: ["You need the ", _jsx("code", { className: "bg-muted px-1 py-0.5 rounded", children: "admin_test_access" }), " ", "permission to use the RBAC test tool."] })] }));
361
+ }
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
+ ? "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
365
+ ? "border-green-200 bg-green-50/50"
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
+ ? "text-muted-foreground"
368
+ : hrbacResult.scope_ok
369
+ ? "text-green-600"
370
+ : "text-red-600"}`, children: hrbacResult.scope_ok === undefined
371
+ ? "N/A"
372
+ : hrbacResult.scope_ok
373
+ ? "Yes"
374
+ : "No" })] }), _jsxs("div", { children: [_jsx(Label, { className: "text-muted-foreground text-xs", children: "Access Via" }), _jsx("p", { className: "text-sm", children: hrbacResult.scope_access_via
375
+ ? `${hrbacResult.scope_access_via.scope_seq}`
376
+ : "N/A" })] })] }), hrbacResult.scope_access_via && (_jsx("div", { className: "bg-green-100 border border-green-200 rounded p-3", children: _jsxs("p", { className: "text-green-700 text-sm", children: ["Access granted via scope:", " ", _jsx("strong", { children: hrbacResult.scope_access_via.scope_seq }), " (", hrbacResult.scope_access_via.scope_type, ")"] }) })), hrbacResult.missing_permissions &&
377
+ hrbacResult.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: hrbacResult.missing_permissions.map((p) => (_jsx("span", { className: "px-2 py-0.5 bg-red-100 text-red-700 rounded text-xs", children: p }, p))) })] })), _jsxs("details", { className: "text-sm", children: [_jsx("summary", { className: "cursor-pointer text-muted-foreground hover:text-foreground", children: "Show raw response" }), _jsx("pre", { className: "mt-2 bg-muted p-3 rounded text-xs overflow-auto", children: JSON.stringify(hrbacResult, null, 2) })] })] })] }))] })) })] })] }));
378
+ }
@@ -9,5 +9,5 @@ import { Input } from "../../../ui/input";
9
9
  import { FieldErrorMessage } from "./field_error_message";
10
10
  // section: component
11
11
  export function PasswordField({ inputId, ariaLabel, value, placeholder, autoComplete, isVisible, onChange, onToggleVisibility, errorMessage, }) {
12
- return (_jsxs("div", { className: "cls_password_field_wrapper", children: [_jsxs("div", { className: "relative", children: [_jsx(Input, { id: inputId, type: isVisible ? "text" : "password", value: value, onChange: (event) => onChange(event.target.value), autoComplete: autoComplete, placeholder: placeholder, "aria-label": ariaLabel, className: "cls_password_field_input pr-11" }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": `${isVisible ? "Hide" : "Show"} ${ariaLabel.toLowerCase()}`, onClick: onToggleVisibility, className: "cls_password_field_toggle absolute right-1 top-1/2 -translate-y-1/2 text-slate-600 hover:text-slate-900", children: isVisible ? (_jsx(EyeOff, { className: "h-4 w-4", "aria-hidden": "true" })) : (_jsx(Eye, { className: "h-4 w-4", "aria-hidden": "true" })) })] }), errorMessage ? (_jsx("div", { className: "mt-1 min-h-0", children: _jsx(FieldErrorMessage, { message: errorMessage }) })) : null] }));
12
+ return (_jsxs("div", { className: "cls_password_field_wrapper", children: [_jsxs("div", { className: "relative", children: [_jsx(Input, { id: inputId, type: isVisible ? "text" : "password", value: value, onChange: (event) => onChange(event.target.value), autoComplete: autoComplete, placeholder: placeholder, "aria-label": ariaLabel, className: "cls_password_field_input pr-11" }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": `${isVisible ? "Hide" : "Show"} ${ariaLabel.toLowerCase()}`, onClick: onToggleVisibility, className: "cls_password_field_toggle absolute right-0.5 top-1/2 h-9 w-9 -translate-y-1/2 text-slate-600 hover:bg-transparent hover:text-slate-900", children: isVisible ? (_jsx(EyeOff, { className: "h-4 w-4", "aria-hidden": "true" })) : (_jsx(Eye, { className: "h-4 w-4", "aria-hidden": "true" })) })] }), errorMessage ? (_jsx("div", { className: "mt-1 min-h-0", children: _jsx(FieldErrorMessage, { message: errorMessage }) })) : null] }));
13
13
  }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Custom field to display in the hover card
3
+ */
4
+ export type ProfileStampCustomField = {
5
+ label: string;
6
+ value: string;
7
+ };
8
+ /**
9
+ * Props for the ProfileStamp component
10
+ */
11
+ export type ProfileStampProps = {
12
+ /**
13
+ * Size variant for the avatar
14
+ * - sm: h-6 w-6 (24px)
15
+ * - default: h-8 w-8 (32px)
16
+ * - lg: h-10 w-10 (40px)
17
+ */
18
+ size?: "sm" | "default" | "lg";
19
+ /**
20
+ * Custom fields to display in the hover card
21
+ */
22
+ custom_fields?: ProfileStampCustomField[];
23
+ /**
24
+ * Additional CSS classes to apply to the wrapper
25
+ */
26
+ className?: string;
27
+ /**
28
+ * Whether to show the user's name in the hover card (default: true)
29
+ */
30
+ show_name?: boolean;
31
+ /**
32
+ * Whether to show the user's email in the hover card (default: true)
33
+ */
34
+ show_email?: boolean;
35
+ };
36
+ /**
37
+ * ProfileStamp component - displays a circular profile picture with a hover card
38
+ * showing the user's name, email, and any custom fields.
39
+ *
40
+ * Use this component to add profile attribution to notes, comments, or any
41
+ * user-generated content in your application.
42
+ *
43
+ * @example
44
+ * // Basic usage
45
+ * <ProfileStamp />
46
+ *
47
+ * @example
48
+ * // With custom fields
49
+ * <ProfileStamp
50
+ * size="lg"
51
+ * custom_fields={[
52
+ * { label: "Role", value: "Admin" },
53
+ * { label: "Department", value: "Engineering" }
54
+ * ]}
55
+ * />
56
+ */
57
+ export declare function ProfileStamp({ size, custom_fields, className, show_name, show_email, }: ProfileStampProps): import("react/jsx-runtime").JSX.Element;
58
+ //# sourceMappingURL=profile_stamp.d.ts.map