hazo_auth 4.4.1 → 4.5.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 (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 +7 -0
  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 +48 -10
  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
@@ -0,0 +1,315 @@
1
+ // file_description: API route for organization management (list, create, update, soft delete)
2
+ // section: imports
3
+ import { NextResponse } from "next/server";
4
+ import { get_hazo_connect_instance } from "../../../../../lib/hazo_connect_instance.server";
5
+ import { hazo_get_auth } from "../../../../../lib/auth/hazo_get_auth.server";
6
+ import { create_app_logger } from "../../../../../lib/app_logger";
7
+ import { get_filename, get_line_number } from "../../../../../lib/utils/api_route_helpers";
8
+ import { is_multi_tenancy_enabled } from "../../../../../lib/multi_tenancy_config.server";
9
+ import { get_orgs, create_org, update_org, soft_delete_org, get_org_tree, check_user_org_access, } from "../../../../../lib/services/org_service";
10
+ // section: route_config
11
+ export const dynamic = "force-dynamic";
12
+ // section: constants
13
+ const PERMISSION_ORG_MANAGEMENT = "hazo_perm_org_management";
14
+ const PERMISSION_GLOBAL_ADMIN = "hazo_org_global_admin";
15
+ // section: helpers
16
+ async function check_permission(request) {
17
+ const auth_result = await hazo_get_auth(request, {
18
+ required_permissions: [PERMISSION_ORG_MANAGEMENT],
19
+ strict: false,
20
+ });
21
+ if (!auth_result.authenticated) {
22
+ return {
23
+ authorized: false,
24
+ error: NextResponse.json({ error: "Authentication required" }, { status: 401 }),
25
+ };
26
+ }
27
+ if (!auth_result.permission_ok) {
28
+ return {
29
+ authorized: false,
30
+ error: NextResponse.json({ error: "Permission denied", missing_permissions: auth_result.missing_permissions }, { status: 403 }),
31
+ };
32
+ }
33
+ // Check if user has global admin permission
34
+ const is_global_admin = auth_result.permissions.includes(PERMISSION_GLOBAL_ADMIN);
35
+ return {
36
+ authorized: true,
37
+ user_id: auth_result.user.id,
38
+ user_org_id: auth_result.user.org_id || null,
39
+ user_root_org_id: auth_result.user.root_org_id || null,
40
+ is_global_admin,
41
+ };
42
+ }
43
+ // section: api_handler
44
+ /**
45
+ * GET - Fetch organizations
46
+ * Query params:
47
+ * - action: 'list' | 'tree' (default: 'list')
48
+ * - include_inactive: boolean (default: false)
49
+ * - root_org_id: string (optional, filter by root org - required unless global admin)
50
+ */
51
+ export async function GET(request) {
52
+ var _a;
53
+ const logger = create_app_logger();
54
+ try {
55
+ // Check if multi-tenancy is enabled
56
+ if (!is_multi_tenancy_enabled()) {
57
+ return NextResponse.json({ error: "Multi-tenancy is not enabled", code: "MULTI_TENANCY_DISABLED" }, { status: 400 });
58
+ }
59
+ // Check permission
60
+ const perm_check = await check_permission(request);
61
+ if (!perm_check.authorized) {
62
+ return perm_check.error;
63
+ }
64
+ const { searchParams } = new URL(request.url);
65
+ const action = searchParams.get("action") || "list";
66
+ const include_inactive = searchParams.get("include_inactive") === "true";
67
+ let root_org_id = searchParams.get("root_org_id") || undefined;
68
+ const adapter = get_hazo_connect_instance();
69
+ // If not global admin, restrict to user's org tree
70
+ if (!perm_check.is_global_admin) {
71
+ // User must have an org to view orgs
72
+ if (!perm_check.user_root_org_id && !perm_check.user_org_id) {
73
+ return NextResponse.json({ error: "You are not assigned to any organization" }, { status: 403 });
74
+ }
75
+ // Force filter to user's root org
76
+ root_org_id = perm_check.user_root_org_id || perm_check.user_org_id || undefined;
77
+ }
78
+ if (action === "tree") {
79
+ // Return hierarchical tree structure
80
+ const result = await get_org_tree(adapter, root_org_id, include_inactive);
81
+ if (!result.success) {
82
+ return NextResponse.json({ error: result.error }, { status: 500 });
83
+ }
84
+ return NextResponse.json({
85
+ success: true,
86
+ tree: result.tree,
87
+ });
88
+ }
89
+ // List organizations
90
+ const result = await get_orgs(adapter, {
91
+ root_org_id,
92
+ include_inactive,
93
+ });
94
+ if (!result.success) {
95
+ return NextResponse.json({ error: result.error }, { status: 500 });
96
+ }
97
+ logger.info("org_management_orgs_fetched", {
98
+ filename: get_filename(),
99
+ line_number: get_line_number(),
100
+ root_org_id,
101
+ count: ((_a = result.orgs) === null || _a === void 0 ? void 0 : _a.length) || 0,
102
+ is_global_admin: perm_check.is_global_admin,
103
+ });
104
+ return NextResponse.json({
105
+ success: true,
106
+ orgs: result.orgs,
107
+ });
108
+ }
109
+ catch (error) {
110
+ const error_message = error instanceof Error ? error.message : "Unknown error";
111
+ logger.error("org_management_orgs_fetch_error", {
112
+ filename: get_filename(),
113
+ line_number: get_line_number(),
114
+ error_message,
115
+ });
116
+ return NextResponse.json({ error: "Failed to fetch organizations" }, { status: 500 });
117
+ }
118
+ }
119
+ /**
120
+ * POST - Create a new organization
121
+ * Body: { name: string, user_limit?: number, parent_org_id?: string }
122
+ */
123
+ export async function POST(request) {
124
+ var _a;
125
+ const logger = create_app_logger();
126
+ try {
127
+ // Check if multi-tenancy is enabled
128
+ if (!is_multi_tenancy_enabled()) {
129
+ return NextResponse.json({ error: "Multi-tenancy is not enabled", code: "MULTI_TENANCY_DISABLED" }, { status: 400 });
130
+ }
131
+ // Check permission
132
+ const perm_check = await check_permission(request);
133
+ if (!perm_check.authorized) {
134
+ return perm_check.error;
135
+ }
136
+ const body = await request.json();
137
+ const { name, user_limit, parent_org_id } = body;
138
+ // Validate required fields
139
+ if (!name || typeof name !== "string" || name.trim().length === 0) {
140
+ return NextResponse.json({ error: "name is required and must be a non-empty string" }, { status: 400 });
141
+ }
142
+ const adapter = get_hazo_connect_instance();
143
+ // If not global admin, verify user has access to parent org
144
+ if (!perm_check.is_global_admin && parent_org_id) {
145
+ const access_check = await check_user_org_access(adapter, perm_check.user_org_id || null, perm_check.user_root_org_id || null, parent_org_id);
146
+ if (!access_check.has_access) {
147
+ return NextResponse.json({ error: "You do not have access to the parent organization" }, { status: 403 });
148
+ }
149
+ }
150
+ // If not global admin and no parent_org_id, they can't create root orgs
151
+ if (!perm_check.is_global_admin && !parent_org_id) {
152
+ return NextResponse.json({ error: "Only global admins can create root organizations" }, { status: 403 });
153
+ }
154
+ const result = await create_org(adapter, {
155
+ name: name.trim(),
156
+ user_limit: typeof user_limit === "number" ? user_limit : 0,
157
+ parent_org_id,
158
+ created_by: perm_check.user_id,
159
+ });
160
+ if (!result.success) {
161
+ return NextResponse.json({ error: result.error }, { status: 400 });
162
+ }
163
+ logger.info("org_management_org_created", {
164
+ filename: get_filename(),
165
+ line_number: get_line_number(),
166
+ org_id: (_a = result.org) === null || _a === void 0 ? void 0 : _a.id,
167
+ name: name.trim(),
168
+ parent_org_id,
169
+ created_by: perm_check.user_id,
170
+ });
171
+ return NextResponse.json({
172
+ success: true,
173
+ org: result.org,
174
+ }, { status: 201 });
175
+ }
176
+ catch (error) {
177
+ const error_message = error instanceof Error ? error.message : "Unknown error";
178
+ logger.error("org_management_org_create_error", {
179
+ filename: get_filename(),
180
+ line_number: get_line_number(),
181
+ error_message,
182
+ });
183
+ return NextResponse.json({ error: "Failed to create organization" }, { status: 500 });
184
+ }
185
+ }
186
+ /**
187
+ * PATCH - Update an existing organization
188
+ * Body: { org_id: string, name?: string, user_limit?: number }
189
+ */
190
+ export async function PATCH(request) {
191
+ const logger = create_app_logger();
192
+ try {
193
+ // Check if multi-tenancy is enabled
194
+ if (!is_multi_tenancy_enabled()) {
195
+ return NextResponse.json({ error: "Multi-tenancy is not enabled", code: "MULTI_TENANCY_DISABLED" }, { status: 400 });
196
+ }
197
+ // Check permission
198
+ const perm_check = await check_permission(request);
199
+ if (!perm_check.authorized) {
200
+ return perm_check.error;
201
+ }
202
+ const body = await request.json();
203
+ const { org_id, name, user_limit } = body;
204
+ // Validate required fields
205
+ if (!org_id || typeof org_id !== "string") {
206
+ return NextResponse.json({ error: "org_id is required" }, { status: 400 });
207
+ }
208
+ const adapter = get_hazo_connect_instance();
209
+ // If not global admin, verify user has access to this org
210
+ if (!perm_check.is_global_admin) {
211
+ const access_check = await check_user_org_access(adapter, perm_check.user_org_id || null, perm_check.user_root_org_id || null, org_id);
212
+ if (!access_check.has_access) {
213
+ return NextResponse.json({ error: "You do not have access to this organization" }, { status: 403 });
214
+ }
215
+ }
216
+ // Build update data
217
+ const update_data = {
218
+ changed_by: perm_check.user_id,
219
+ };
220
+ if (name !== undefined) {
221
+ if (typeof name !== "string" || name.trim().length === 0) {
222
+ return NextResponse.json({ error: "name must be a non-empty string" }, { status: 400 });
223
+ }
224
+ update_data.name = name.trim();
225
+ }
226
+ if (user_limit !== undefined) {
227
+ if (typeof user_limit !== "number" || user_limit < 0) {
228
+ return NextResponse.json({ error: "user_limit must be a non-negative number" }, { status: 400 });
229
+ }
230
+ update_data.user_limit = user_limit;
231
+ }
232
+ if (update_data.name === undefined && update_data.user_limit === undefined) {
233
+ return NextResponse.json({ error: "No update data provided" }, { status: 400 });
234
+ }
235
+ const result = await update_org(adapter, org_id, update_data);
236
+ if (!result.success) {
237
+ return NextResponse.json({ error: result.error }, { status: 400 });
238
+ }
239
+ logger.info("org_management_org_updated", {
240
+ filename: get_filename(),
241
+ line_number: get_line_number(),
242
+ org_id,
243
+ changed_by: perm_check.user_id,
244
+ });
245
+ return NextResponse.json({
246
+ success: true,
247
+ org: result.org,
248
+ });
249
+ }
250
+ catch (error) {
251
+ const error_message = error instanceof Error ? error.message : "Unknown error";
252
+ logger.error("org_management_org_update_error", {
253
+ filename: get_filename(),
254
+ line_number: get_line_number(),
255
+ error_message,
256
+ });
257
+ return NextResponse.json({ error: "Failed to update organization" }, { status: 500 });
258
+ }
259
+ }
260
+ /**
261
+ * DELETE - Soft delete an organization (sets active = false)
262
+ * Query params: org_id
263
+ */
264
+ export async function DELETE(request) {
265
+ const logger = create_app_logger();
266
+ try {
267
+ // Check if multi-tenancy is enabled
268
+ if (!is_multi_tenancy_enabled()) {
269
+ return NextResponse.json({ error: "Multi-tenancy is not enabled", code: "MULTI_TENANCY_DISABLED" }, { status: 400 });
270
+ }
271
+ // Check permission
272
+ const perm_check = await check_permission(request);
273
+ if (!perm_check.authorized) {
274
+ return perm_check.error;
275
+ }
276
+ const { searchParams } = new URL(request.url);
277
+ const org_id = searchParams.get("org_id");
278
+ if (!org_id) {
279
+ return NextResponse.json({ error: "org_id query parameter is required" }, { status: 400 });
280
+ }
281
+ const adapter = get_hazo_connect_instance();
282
+ // If not global admin, verify user has access to this org
283
+ if (!perm_check.is_global_admin) {
284
+ const access_check = await check_user_org_access(adapter, perm_check.user_org_id || null, perm_check.user_root_org_id || null, org_id);
285
+ if (!access_check.has_access) {
286
+ return NextResponse.json({ error: "You do not have access to this organization" }, { status: 403 });
287
+ }
288
+ }
289
+ // Soft delete (set active = false)
290
+ const result = await soft_delete_org(adapter, org_id, perm_check.user_id);
291
+ if (!result.success) {
292
+ return NextResponse.json({ error: result.error }, { status: 400 });
293
+ }
294
+ logger.info("org_management_org_deactivated", {
295
+ filename: get_filename(),
296
+ line_number: get_line_number(),
297
+ org_id,
298
+ deactivated_by: perm_check.user_id,
299
+ });
300
+ return NextResponse.json({
301
+ success: true,
302
+ org: result.org,
303
+ message: "Organization deactivated successfully",
304
+ });
305
+ }
306
+ catch (error) {
307
+ const error_message = error instanceof Error ? error.message : "Unknown error";
308
+ logger.error("org_management_org_deactivate_error", {
309
+ filename: get_filename(),
310
+ line_number: get_line_number(),
311
+ error_message,
312
+ });
313
+ return NextResponse.json({ error: "Failed to deactivate organization" }, { status: 500 });
314
+ }
315
+ }
@@ -8,6 +8,12 @@ export declare function GET(request: NextRequest): Promise<NextResponse<{
8
8
  error: string;
9
9
  }> | NextResponse<{
10
10
  success: boolean;
11
+ user_types_enabled: boolean;
12
+ available_user_types: {
13
+ key: string;
14
+ label: string;
15
+ badge_color: string;
16
+ }[];
11
17
  users: {
12
18
  id: unknown;
13
19
  name: {} | null;
@@ -18,6 +24,7 @@ export declare function GET(request: NextRequest): Promise<NextResponse<{
18
24
  created_at: {} | null;
19
25
  profile_picture_url: {} | null;
20
26
  profile_source: {} | null;
27
+ user_type: string | null;
21
28
  }[];
22
29
  }>>;
23
30
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/app/api/hazo_auth/user_management/users/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;IA2D7C;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;IA0E/C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;IA2E9C"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/app/api/hazo_auth/user_management/users/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAexD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;IAwE7C;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;IA2G/C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;IA2E9C"}
@@ -8,6 +8,7 @@ import { get_filename, get_line_number } from "../../../../../lib/utils/api_rout
8
8
  import { request_password_reset } from "../../../../../lib/services/password_reset_service";
9
9
  import { get_auth_cache } from "../../../../../lib/auth/auth_cache";
10
10
  import { get_auth_utility_config } from "../../../../../lib/auth_utility_config.server";
11
+ import { is_user_types_enabled, get_all_user_types, get_user_types_config, } from "../../../../../lib/user_types_config.server";
11
12
  // section: route_config
12
13
  export const dynamic = 'force-dynamic';
13
14
  // section: api_handler
@@ -32,8 +33,19 @@ export async function GET(request) {
32
33
  line_number: get_line_number(),
33
34
  user_count: users.length,
34
35
  });
36
+ // Check if user types feature is enabled
37
+ const user_types_enabled = is_user_types_enabled();
38
+ const available_user_types = user_types_enabled
39
+ ? get_all_user_types().map((t) => ({
40
+ key: t.key,
41
+ label: t.label,
42
+ badge_color: t.badge_color,
43
+ }))
44
+ : [];
35
45
  return NextResponse.json({
36
46
  success: true,
47
+ user_types_enabled,
48
+ available_user_types,
37
49
  users: users.map((user) => ({
38
50
  id: user.id,
39
51
  name: user.name || null,
@@ -44,6 +56,7 @@ export async function GET(request) {
44
56
  created_at: user.created_at || null,
45
57
  profile_picture_url: user.profile_picture_url || null,
46
58
  profile_source: user.profile_source || null,
59
+ user_type: user.user_type || null,
47
60
  })),
48
61
  }, { status: 200 });
49
62
  }
@@ -66,18 +79,43 @@ export async function PATCH(request) {
66
79
  const logger = create_app_logger();
67
80
  try {
68
81
  const body = await request.json();
69
- const { user_id, is_active } = body;
70
- if (!user_id || typeof is_active !== "boolean") {
71
- return NextResponse.json({ error: "user_id and is_active (boolean) are required" }, { status: 400 });
82
+ const { user_id, is_active, user_type } = body;
83
+ // user_id is always required
84
+ if (!user_id) {
85
+ return NextResponse.json({ error: "user_id is required" }, { status: 400 });
86
+ }
87
+ // Build update object based on what's provided
88
+ const update_data = {
89
+ changed_at: new Date().toISOString(),
90
+ };
91
+ // Handle is_active if provided
92
+ if (typeof is_active === "boolean") {
93
+ update_data.is_active = is_active;
94
+ }
95
+ // Handle user_type if provided (only when feature is enabled)
96
+ if (user_type !== undefined) {
97
+ const config = get_user_types_config();
98
+ if (config.enable_user_types) {
99
+ // Allow null to clear the type, or validate type exists
100
+ if (user_type === null || user_type === "") {
101
+ update_data.user_type = null;
102
+ }
103
+ else if (config.user_types.has(user_type)) {
104
+ update_data.user_type = user_type;
105
+ }
106
+ else {
107
+ return NextResponse.json({ error: "Invalid user type" }, { status: 400 });
108
+ }
109
+ }
110
+ }
111
+ // Ensure there's something to update besides changed_at
112
+ if (Object.keys(update_data).length === 1) {
113
+ return NextResponse.json({ error: "No valid fields to update" }, { status: 400 });
72
114
  }
73
115
  const hazoConnect = get_hazo_connect_instance();
74
116
  const users_service = createCrudService(hazoConnect, "hazo_users");
75
- // Update user with changed_at timestamp
76
- const now = new Date().toISOString();
77
- await users_service.updateById(user_id, {
78
- is_active,
79
- changed_at: now,
80
- });
117
+ // Update user
118
+ await users_service.updateById(user_id, update_data);
81
119
  // Invalidate user cache after deactivation
82
120
  if (is_active === false) {
83
121
  try {
@@ -100,7 +138,7 @@ export async function PATCH(request) {
100
138
  filename: get_filename(),
101
139
  line_number: get_line_number(),
102
140
  user_id,
103
- is_active,
141
+ updated_fields: Object.keys(update_data).filter((k) => k !== "changed_at"),
104
142
  });
105
143
  return NextResponse.json({ success: true }, { status: 200 });
106
144
  }
@@ -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,2CAyQ/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,2CAsQ/B"}
@@ -112,25 +112,19 @@ export function ProfilePictureLibraryTab({ useLibrary, onUseLibraryChange, onPho
112
112
  const getInitials = () => {
113
113
  return "L";
114
114
  };
115
- // Map column count to Tailwind grid class
116
- const getGridColumnsClass = (columns) => {
117
- const columnMap = {
118
- 1: "grid-cols-1",
119
- 2: "grid-cols-2",
120
- 3: "grid-cols-3",
121
- 4: "grid-cols-4",
122
- 5: "grid-cols-5",
123
- 6: "grid-cols-6",
124
- 7: "grid-cols-7",
125
- 8: "grid-cols-8",
115
+ // Get inline style for grid columns (using inline styles instead of Tailwind classes
116
+ // because dynamic classes like grid-cols-4 may not be included in consuming app's CSS bundle)
117
+ const getGridColumnsStyle = (columns) => {
118
+ const validColumns = Math.min(Math.max(columns, 1), 8);
119
+ return {
120
+ gridTemplateColumns: `repeat(${validColumns}, minmax(0, 1fr))`,
126
121
  };
127
- return columnMap[columns] || "grid-cols-4";
128
122
  };
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: `
123
+ 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 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]", style: getGridColumnsStyle(libraryPhotoGridColumns), children: photos.map((photoUrl) => (_jsx("button", { type: "button", onClick: () => handlePhotoClick(photoUrl), className: `
130
124
  cls_profile_picture_library_tab_photo_thumbnail
131
125
  w-full aspect-square rounded-lg overflow-hidden border-2 transition-colors cursor-pointer
132
126
  ${selectedPhoto === photoUrl ? "border-blue-500 ring-2 ring-blue-200" : "border-[var(--hazo-border)] hover:border-[var(--hazo-border-emphasis)]"}
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) => {
127
+ `, style: { minHeight: '80px', minWidth: '80px', aspectRatio: '1/1' }, "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
128
  // Fallback if image fails to load
135
129
  const target = e.target;
136
130
  target.style.display = 'none';
@@ -2,8 +2,6 @@ export type RbacTestLayoutProps = {
2
2
  className?: string;
3
3
  /** Whether HRBAC is enabled (passed from server) */
4
4
  hrbacEnabled?: boolean;
5
- /** Default organization for HRBAC scopes */
6
- defaultOrg?: string;
7
5
  };
8
6
  /**
9
7
  * RBAC/HRBAC Test layout component
@@ -11,5 +9,5 @@ export type RbacTestLayoutProps = {
11
9
  * @param props - Component props
12
10
  * @returns RBAC test layout component
13
11
  */
14
- export declare function RbacTestLayout({ className, hrbacEnabled, defaultOrg, }: RbacTestLayoutProps): import("react/jsx-runtime").JSX.Element;
12
+ export declare function RbacTestLayout({ className, hrbacEnabled, }: RbacTestLayoutProps): import("react/jsx-runtime").JSX.Element;
15
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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"}
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;CACxB,CAAC;AAgHF;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,YAAoB,GACrB,EAAE,mBAAmB,2CAw3BrB"}
@@ -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
+ }