hazo_auth 4.4.1 → 4.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -5
- package/SETUP_CHECKLIST.md +1 -1
- package/cli-src/lib/auth/auth_types.ts +22 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +25 -1
- package/cli-src/lib/auth/session_token_validator.edge.ts +1 -0
- package/cli-src/lib/config/default_config.ts +36 -0
- package/cli-src/lib/navbar_config.server.ts +129 -0
- package/cli-src/lib/scope_hierarchy_config.server.ts +3 -14
- package/cli-src/lib/services/registration_service.ts +12 -0
- package/cli-src/lib/services/scope_labels_service.ts +21 -21
- package/cli-src/lib/services/scope_service.ts +15 -11
- package/cli-src/lib/services/session_token_service.ts +1 -0
- package/cli-src/lib/ui_shell_config.server.ts +15 -0
- package/cli-src/lib/user_types_config.server.ts +178 -0
- package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/me/route.js +17 -0
- package/dist/app/api/hazo_auth/org_management/orgs/route.d.ts +26 -0
- package/dist/app/api/hazo_auth/org_management/orgs/route.d.ts.map +1 -0
- package/dist/app/api/hazo_auth/org_management/orgs/route.js +315 -0
- package/dist/app/api/hazo_auth/user_management/users/route.d.ts +11 -1
- package/dist/app/api/hazo_auth/user_management/users/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/user_management/users/route.js +121 -16
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.d.ts.map +1 -1
- package/dist/components/layouts/my_settings/components/profile_picture_library_tab.js +8 -14
- package/dist/components/layouts/rbac_test/index.d.ts +1 -3
- package/dist/components/layouts/rbac_test/index.d.ts.map +1 -1
- package/dist/components/layouts/rbac_test/index.js +2 -2
- package/dist/components/layouts/shared/components/auth_navbar.d.ts +26 -0
- package/dist/components/layouts/shared/components/auth_navbar.d.ts.map +1 -0
- package/dist/components/layouts/shared/components/auth_navbar.js +14 -0
- package/dist/components/layouts/shared/components/auth_page_shell.d.ts +3 -1
- package/dist/components/layouts/shared/components/auth_page_shell.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/auth_page_shell.js +17 -2
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.d.ts +6 -1
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/standalone_layout_wrapper.js +7 -2
- package/dist/components/layouts/shared/index.d.ts +2 -0
- package/dist/components/layouts/shared/index.d.ts.map +1 -1
- package/dist/components/layouts/shared/index.js +1 -0
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts +3 -2
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/scope_hierarchy_tab.js +45 -18
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts +3 -2
- package/dist/components/layouts/user_management/components/scope_labels_tab.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/scope_labels_tab.js +48 -20
- package/dist/components/layouts/user_management/components/user_scopes_tab.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/user_scopes_tab.js +1 -1
- package/dist/components/layouts/user_management/index.d.ts +11 -3
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +52 -5
- package/dist/components/ui/user-type-badge.d.ts +23 -0
- package/dist/components/ui/user-type-badge.d.ts.map +1 -0
- package/dist/components/ui/user-type-badge.js +42 -0
- package/dist/lib/auth/auth_types.d.ts +17 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +11 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +21 -1
- package/dist/lib/config/default_config.d.ts +60 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +34 -0
- package/dist/lib/navbar_config.server.d.ts +36 -0
- package/dist/lib/navbar_config.server.d.ts.map +1 -0
- package/dist/lib/navbar_config.server.js +45 -0
- package/dist/lib/scope_hierarchy_config.server.d.ts +3 -7
- package/dist/lib/scope_hierarchy_config.server.d.ts.map +1 -1
- package/dist/lib/scope_hierarchy_config.server.js +1 -10
- package/dist/lib/services/registration_service.d.ts.map +1 -1
- package/dist/lib/services/registration_service.js +8 -0
- package/dist/lib/services/scope_labels_service.d.ts +7 -7
- package/dist/lib/services/scope_labels_service.d.ts.map +1 -1
- package/dist/lib/services/scope_labels_service.js +20 -20
- package/dist/lib/services/scope_service.d.ts +8 -5
- package/dist/lib/services/scope_service.d.ts.map +1 -1
- package/dist/lib/services/scope_service.js +9 -8
- package/dist/lib/ui_shell_config.server.d.ts +5 -0
- package/dist/lib/ui_shell_config.server.d.ts.map +1 -1
- package/dist/lib/ui_shell_config.server.js +5 -0
- package/dist/lib/user_types_config.server.d.ts +56 -0
- package/dist/lib/user_types_config.server.d.ts.map +1 -0
- package/dist/lib/user_types_config.server.js +100 -0
- package/dist/server/routes/index.d.ts +1 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +2 -0
- package/dist/server/routes/org_management_orgs.d.ts +2 -0
- package/dist/server/routes/org_management_orgs.d.ts.map +1 -0
- package/dist/server/routes/org_management_orgs.js +2 -0
- package/hazo_auth_config.example.ini +9 -0
- package/package.json +1 -1
- package/cli-src/server/logging/logger_service.ts +0 -56
- /package/public/profile_pictures/library/Cars/{050 - citroe/314/210n_c3.jpeg" → 050 - citro/303/253n_c3.jpeg"} +0 -0
- /package/public/profile_pictures/library/Cars/{064 - lamborghini_huraca/314/201n.jpeg" → 064 - lamborghini_hurac/303/241n.jpeg"} +0 -0
- /package/public/profile_pictures/library/Cars/{099 - citroe/314/210n_2cv_(classic).jpeg" → 099 - citro/303/253n_2cv_(classic).jpeg"} +0 -0
- /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,13 @@ 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
|
+
}[];
|
|
17
|
+
multi_tenancy_enabled: boolean;
|
|
11
18
|
users: {
|
|
12
19
|
id: unknown;
|
|
13
20
|
name: {} | null;
|
|
@@ -18,10 +25,13 @@ export declare function GET(request: NextRequest): Promise<NextResponse<{
|
|
|
18
25
|
created_at: {} | null;
|
|
19
26
|
profile_picture_url: {} | null;
|
|
20
27
|
profile_source: {} | null;
|
|
28
|
+
user_type: string | null;
|
|
29
|
+
org_id: string | null | undefined;
|
|
30
|
+
root_org_id: string | null | undefined;
|
|
21
31
|
}[];
|
|
22
32
|
}>>;
|
|
23
33
|
/**
|
|
24
|
-
* PATCH - Update user (deactivate: set is_active to false)
|
|
34
|
+
* PATCH - Update user (deactivate: set is_active to false, assign org, etc.)
|
|
25
35
|
*/
|
|
26
36
|
export declare function PATCH(request: NextRequest): Promise<NextResponse<{
|
|
27
37
|
error: string;
|
|
@@ -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;
|
|
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;AAsBxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;IA+E7C;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;IA2L/C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;IA2E9C"}
|
|
@@ -8,6 +8,11 @@ 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";
|
|
12
|
+
import { is_multi_tenancy_enabled } from "../../../../../lib/multi_tenancy_config.server";
|
|
13
|
+
import { get_org_by_id, can_add_user_to_org, } from "../../../../../lib/services/org_service";
|
|
14
|
+
import { get_org_cache } from "../../../../../lib/auth/org_cache";
|
|
15
|
+
import { get_multi_tenancy_config } from "../../../../../lib/multi_tenancy_config.server";
|
|
11
16
|
// section: route_config
|
|
12
17
|
export const dynamic = 'force-dynamic';
|
|
13
18
|
// section: api_handler
|
|
@@ -32,8 +37,22 @@ export async function GET(request) {
|
|
|
32
37
|
line_number: get_line_number(),
|
|
33
38
|
user_count: users.length,
|
|
34
39
|
});
|
|
40
|
+
// Check if user types feature is enabled
|
|
41
|
+
const user_types_enabled = is_user_types_enabled();
|
|
42
|
+
const available_user_types = user_types_enabled
|
|
43
|
+
? get_all_user_types().map((t) => ({
|
|
44
|
+
key: t.key,
|
|
45
|
+
label: t.label,
|
|
46
|
+
badge_color: t.badge_color,
|
|
47
|
+
}))
|
|
48
|
+
: [];
|
|
49
|
+
// Check if multi-tenancy is enabled
|
|
50
|
+
const multi_tenancy_enabled = is_multi_tenancy_enabled();
|
|
35
51
|
return NextResponse.json({
|
|
36
52
|
success: true,
|
|
53
|
+
user_types_enabled,
|
|
54
|
+
available_user_types,
|
|
55
|
+
multi_tenancy_enabled,
|
|
37
56
|
users: users.map((user) => ({
|
|
38
57
|
id: user.id,
|
|
39
58
|
name: user.name || null,
|
|
@@ -44,6 +63,10 @@ export async function GET(request) {
|
|
|
44
63
|
created_at: user.created_at || null,
|
|
45
64
|
profile_picture_url: user.profile_picture_url || null,
|
|
46
65
|
profile_source: user.profile_source || null,
|
|
66
|
+
user_type: user.user_type || null,
|
|
67
|
+
// Include org info when multi-tenancy is enabled
|
|
68
|
+
org_id: multi_tenancy_enabled ? user.org_id || null : undefined,
|
|
69
|
+
root_org_id: multi_tenancy_enabled ? user.root_org_id || null : undefined,
|
|
47
70
|
})),
|
|
48
71
|
}, { status: 200 });
|
|
49
72
|
}
|
|
@@ -60,30 +83,89 @@ export async function GET(request) {
|
|
|
60
83
|
}
|
|
61
84
|
}
|
|
62
85
|
/**
|
|
63
|
-
* PATCH - Update user (deactivate: set is_active to false)
|
|
86
|
+
* PATCH - Update user (deactivate: set is_active to false, assign org, etc.)
|
|
64
87
|
*/
|
|
65
88
|
export async function PATCH(request) {
|
|
66
89
|
const logger = create_app_logger();
|
|
67
90
|
try {
|
|
68
91
|
const body = await request.json();
|
|
69
|
-
const { user_id, is_active } = body;
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
const { user_id, is_active, user_type, org_id } = body;
|
|
93
|
+
// user_id is always required
|
|
94
|
+
if (!user_id) {
|
|
95
|
+
return NextResponse.json({ error: "user_id is required" }, { status: 400 });
|
|
72
96
|
}
|
|
97
|
+
// Build update object based on what's provided
|
|
98
|
+
const update_data = {
|
|
99
|
+
changed_at: new Date().toISOString(),
|
|
100
|
+
};
|
|
73
101
|
const hazoConnect = get_hazo_connect_instance();
|
|
102
|
+
// Handle is_active if provided
|
|
103
|
+
if (typeof is_active === "boolean") {
|
|
104
|
+
update_data.is_active = is_active;
|
|
105
|
+
}
|
|
106
|
+
// Handle user_type if provided (only when feature is enabled)
|
|
107
|
+
if (user_type !== undefined) {
|
|
108
|
+
const config = get_user_types_config();
|
|
109
|
+
if (config.enable_user_types) {
|
|
110
|
+
// Allow null to clear the type, or validate type exists
|
|
111
|
+
if (user_type === null || user_type === "") {
|
|
112
|
+
update_data.user_type = null;
|
|
113
|
+
}
|
|
114
|
+
else if (config.user_types.has(user_type)) {
|
|
115
|
+
update_data.user_type = user_type;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
return NextResponse.json({ error: "Invalid user type" }, { status: 400 });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Handle org_id if provided (only when multi-tenancy is enabled)
|
|
123
|
+
if (org_id !== undefined) {
|
|
124
|
+
if (!is_multi_tenancy_enabled()) {
|
|
125
|
+
return NextResponse.json({ error: "Multi-tenancy is not enabled" }, { status: 400 });
|
|
126
|
+
}
|
|
127
|
+
// Allow null to clear the org assignment
|
|
128
|
+
if (org_id === null || org_id === "") {
|
|
129
|
+
update_data.org_id = null;
|
|
130
|
+
update_data.root_org_id = null;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Validate org exists
|
|
134
|
+
const org_result = await get_org_by_id(hazoConnect, org_id);
|
|
135
|
+
if (!org_result.success || !org_result.org) {
|
|
136
|
+
return NextResponse.json({ error: "Organization not found" }, { status: 400 });
|
|
137
|
+
}
|
|
138
|
+
const org = org_result.org;
|
|
139
|
+
// Check if org is active
|
|
140
|
+
if (org.active === false) {
|
|
141
|
+
return NextResponse.json({ error: "Cannot assign user to inactive organization" }, { status: 400 });
|
|
142
|
+
}
|
|
143
|
+
// Check user limit
|
|
144
|
+
const limit_check = await can_add_user_to_org(hazoConnect, org_id);
|
|
145
|
+
if (limit_check.success && !limit_check.can_add) {
|
|
146
|
+
return NextResponse.json({ error: limit_check.reason || "Organization user limit reached" }, { status: 400 });
|
|
147
|
+
}
|
|
148
|
+
// Set org_id and calculate root_org_id
|
|
149
|
+
update_data.org_id = org_id;
|
|
150
|
+
update_data.root_org_id = org.root_org_id || org_id;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Ensure there's something to update besides changed_at
|
|
154
|
+
if (Object.keys(update_data).length === 1) {
|
|
155
|
+
return NextResponse.json({ error: "No valid fields to update" }, { status: 400 });
|
|
156
|
+
}
|
|
74
157
|
const users_service = createCrudService(hazoConnect, "hazo_users");
|
|
75
|
-
// Update user
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Invalidate user cache after deactivation
|
|
82
|
-
if (is_active === false) {
|
|
158
|
+
// Update user
|
|
159
|
+
await users_service.updateById(user_id, update_data);
|
|
160
|
+
// Invalidate caches
|
|
161
|
+
let cache_invalidated = false;
|
|
162
|
+
// Invalidate user auth cache if user deactivated or org changed
|
|
163
|
+
if (is_active === false || org_id !== undefined) {
|
|
83
164
|
try {
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
165
|
+
const auth_config = get_auth_utility_config();
|
|
166
|
+
const auth_cache = get_auth_cache(auth_config.cache_max_users, auth_config.cache_ttl_minutes, auth_config.cache_max_age_minutes);
|
|
167
|
+
auth_cache.invalidate_user(user_id);
|
|
168
|
+
cache_invalidated = true;
|
|
87
169
|
}
|
|
88
170
|
catch (cache_error) {
|
|
89
171
|
// Log but don't fail user update if cache invalidation fails
|
|
@@ -96,11 +178,34 @@ export async function PATCH(request) {
|
|
|
96
178
|
});
|
|
97
179
|
}
|
|
98
180
|
}
|
|
181
|
+
// If org changed, also invalidate org cache for old and new orgs
|
|
182
|
+
if (org_id !== undefined && is_multi_tenancy_enabled()) {
|
|
183
|
+
try {
|
|
184
|
+
const mt_config = get_multi_tenancy_config();
|
|
185
|
+
const org_cache = get_org_cache(mt_config.org_cache_max_entries, mt_config.org_cache_ttl_minutes);
|
|
186
|
+
// Invalidate for the new org if set
|
|
187
|
+
if (org_id && typeof org_id === "string") {
|
|
188
|
+
org_cache.invalidate(org_id);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (cache_error) {
|
|
192
|
+
// Log but don't fail user update if cache invalidation fails
|
|
193
|
+
const cache_error_message = cache_error instanceof Error ? cache_error.message : "Unknown error";
|
|
194
|
+
logger.warn("user_management_org_cache_invalidation_failed", {
|
|
195
|
+
filename: get_filename(),
|
|
196
|
+
line_number: get_line_number(),
|
|
197
|
+
user_id,
|
|
198
|
+
org_id,
|
|
199
|
+
error: cache_error_message,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
99
203
|
logger.info("user_management_user_updated", {
|
|
100
204
|
filename: get_filename(),
|
|
101
205
|
line_number: get_line_number(),
|
|
102
206
|
user_id,
|
|
103
|
-
|
|
207
|
+
updated_fields: Object.keys(update_data).filter((k) => k !== "changed_at"),
|
|
208
|
+
cache_invalidated,
|
|
104
209
|
});
|
|
105
210
|
return NextResponse.json({ success: true }, { status: 200 });
|
|
106
211
|
}
|
|
@@ -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,
|
|
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
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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:
|
|
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,
|
|
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;
|
|
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"}
|