@jmruthers/pace-core 0.5.165 → 0.5.167
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataTable-HAATGNJY.js → DataTable-QMER2BPR.js} +3 -3
- package/dist/{chunk-23YYDN24.js → chunk-4AJ2ZWQW.js} +2 -2
- package/dist/{chunk-DDB2M4XO.js → chunk-AXFP27TJ.js} +2 -2
- package/dist/{chunk-Q4NUEMNV.js → chunk-OX3NRR4N.js} +9 -20
- package/dist/chunk-OX3NRR4N.js.map +1 -0
- package/dist/{chunk-GFSYFEVZ.js → chunk-PKYRTRZA.js} +35 -24
- package/dist/chunk-PKYRTRZA.js.map +1 -0
- package/dist/components.js +3 -3
- package/dist/index.js +4 -4
- package/dist/rbac/index.d.ts +10 -3
- package/dist/rbac/index.js +2 -2
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +19 -12
- package/package.json +1 -1
- package/src/__tests__/hooks/usePermissions.test.ts +12 -9
- package/src/components/NavigationMenu/NavigationMenu.tsx +9 -21
- package/src/hooks/__tests__/ServiceHooks.test.tsx +2 -2
- package/src/rbac/hooks/__tests__/usePermissions.integration.test.ts +32 -34
- package/src/rbac/hooks/usePermissions.test.ts +34 -32
- package/src/rbac/hooks/usePermissions.ts +56 -28
- package/dist/chunk-GFSYFEVZ.js.map +0 -1
- package/dist/chunk-Q4NUEMNV.js.map +0 -1
- /package/dist/{DataTable-HAATGNJY.js.map → DataTable-QMER2BPR.js.map} +0 -0
- /package/dist/{chunk-23YYDN24.js.map → chunk-4AJ2ZWQW.js.map} +0 -0
- /package/dist/{chunk-DDB2M4XO.js.map → chunk-AXFP27TJ.js.map} +0 -0
|
@@ -23,9 +23,13 @@ import { getPermissionMap } from '../api';
|
|
|
23
23
|
|
|
24
24
|
// Mock data
|
|
25
25
|
const mockUserId = 'user-123';
|
|
26
|
+
const mockOrgId = 'org-123';
|
|
27
|
+
const mockEventId = 'event-123';
|
|
28
|
+
const mockAppId = 'app-123';
|
|
26
29
|
const mockScope = {
|
|
27
|
-
organisationId:
|
|
28
|
-
eventId:
|
|
30
|
+
organisationId: mockOrgId,
|
|
31
|
+
eventId: mockEventId,
|
|
32
|
+
appId: mockAppId
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
const mockPermissionMap = {
|
|
@@ -47,7 +51,7 @@ describe('usePermissions Hook', () => {
|
|
|
47
51
|
describe('Permission Fetching', () => {
|
|
48
52
|
it('fetches permissions for valid user', async () => {
|
|
49
53
|
|
|
50
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
54
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
51
55
|
|
|
52
56
|
expect(result.current.isLoading).toBe(true);
|
|
53
57
|
expect(result.current.permissions).toEqual({});
|
|
@@ -63,7 +67,7 @@ describe('usePermissions Hook', () => {
|
|
|
63
67
|
const error = new Error('Failed to fetch permissions');
|
|
64
68
|
mockGetPermissionMap.mockRejectedValue(error);
|
|
65
69
|
|
|
66
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
70
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
67
71
|
|
|
68
72
|
await waitFor(() => {
|
|
69
73
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -73,7 +77,7 @@ describe('usePermissions Hook', () => {
|
|
|
73
77
|
});
|
|
74
78
|
|
|
75
79
|
it('returns empty permissions for invalid user', async () => {
|
|
76
|
-
const { result } = renderHook(() => usePermissions('',
|
|
80
|
+
const { result } = renderHook(() => usePermissions('', mockOrgId, mockEventId, mockAppId));
|
|
77
81
|
|
|
78
82
|
await waitFor(() => {
|
|
79
83
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -112,7 +116,7 @@ describe('usePermissions Hook', () => {
|
|
|
112
116
|
});
|
|
113
117
|
|
|
114
118
|
it('hasPermission returns correct boolean', async () => {
|
|
115
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
119
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
116
120
|
|
|
117
121
|
await waitFor(() => {
|
|
118
122
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -126,7 +130,7 @@ describe('usePermissions Hook', () => {
|
|
|
126
130
|
});
|
|
127
131
|
|
|
128
132
|
it('hasAnyPermission works with multiple permissions', async () => {
|
|
129
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
133
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
130
134
|
|
|
131
135
|
await waitFor(() => {
|
|
132
136
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -138,7 +142,7 @@ describe('usePermissions Hook', () => {
|
|
|
138
142
|
});
|
|
139
143
|
|
|
140
144
|
it('hasAllPermissions works with multiple permissions', async () => {
|
|
141
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
145
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
142
146
|
|
|
143
147
|
await waitFor(() => {
|
|
144
148
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -150,7 +154,7 @@ describe('usePermissions Hook', () => {
|
|
|
150
154
|
});
|
|
151
155
|
|
|
152
156
|
it('handles empty permission lists', async () => {
|
|
153
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
157
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
154
158
|
|
|
155
159
|
await waitFor(() => {
|
|
156
160
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -165,7 +169,7 @@ describe('usePermissions Hook', () => {
|
|
|
165
169
|
it('refetch function works correctly', async () => {
|
|
166
170
|
mockGetPermissionMap.mockResolvedValue(mockPermissionMap);
|
|
167
171
|
|
|
168
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
172
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
169
173
|
|
|
170
174
|
await waitFor(() => {
|
|
171
175
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -184,7 +188,7 @@ describe('usePermissions Hook', () => {
|
|
|
184
188
|
.mockResolvedValueOnce(mockPermissionMap)
|
|
185
189
|
.mockRejectedValueOnce(new Error('Refetch error'));
|
|
186
190
|
|
|
187
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
191
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
188
192
|
|
|
189
193
|
await waitFor(() => {
|
|
190
194
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -203,7 +207,7 @@ describe('usePermissions Hook', () => {
|
|
|
203
207
|
it('shows loading state during initial fetch', () => {
|
|
204
208
|
mockGetPermissionMap.mockImplementation(() => new Promise(() => {})); // Never resolves
|
|
205
209
|
|
|
206
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
210
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
207
211
|
|
|
208
212
|
expect(result.current.isLoading).toBe(true);
|
|
209
213
|
expect(result.current.permissions).toEqual({});
|
|
@@ -213,7 +217,7 @@ describe('usePermissions Hook', () => {
|
|
|
213
217
|
it('shows loading state during refetch', async () => {
|
|
214
218
|
mockGetPermissionMap.mockResolvedValue(mockPermissionMap);
|
|
215
219
|
|
|
216
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
220
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
217
221
|
|
|
218
222
|
await waitFor(() => {
|
|
219
223
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -233,7 +237,7 @@ describe('usePermissions Hook', () => {
|
|
|
233
237
|
it('handles non-Error exceptions', async () => {
|
|
234
238
|
mockGetPermissionMap.mockRejectedValue('String error');
|
|
235
239
|
|
|
236
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
240
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
237
241
|
|
|
238
242
|
await waitFor(() => {
|
|
239
243
|
expect(result.current.error).toBeInstanceOf(Error);
|
|
@@ -246,7 +250,7 @@ describe('usePermissions Hook', () => {
|
|
|
246
250
|
.mockRejectedValueOnce(new Error('Initial error'))
|
|
247
251
|
.mockResolvedValueOnce(mockPermissionMap);
|
|
248
252
|
|
|
249
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
253
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
250
254
|
|
|
251
255
|
await waitFor(() => {
|
|
252
256
|
expect(result.current.error).toBeDefined();
|
|
@@ -275,7 +279,7 @@ describe('usePermissions Hook', () => {
|
|
|
275
279
|
|
|
276
280
|
mockGetPermissionMap.mockResolvedValue(superAdminPermissions);
|
|
277
281
|
|
|
278
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
282
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
279
283
|
|
|
280
284
|
await waitFor(() => {
|
|
281
285
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -304,7 +308,7 @@ describe('usePermissions Hook', () => {
|
|
|
304
308
|
|
|
305
309
|
mockGetPermissionMap.mockResolvedValue(orgAdminPermissions);
|
|
306
310
|
|
|
307
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
311
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
308
312
|
|
|
309
313
|
await waitFor(() => {
|
|
310
314
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -333,7 +337,7 @@ describe('usePermissions Hook', () => {
|
|
|
333
337
|
|
|
334
338
|
mockGetPermissionMap.mockResolvedValue(eventAdminPermissions);
|
|
335
339
|
|
|
336
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
340
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
337
341
|
|
|
338
342
|
await waitFor(() => {
|
|
339
343
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -363,7 +367,7 @@ describe('usePermissions Hook', () => {
|
|
|
363
367
|
|
|
364
368
|
mockGetPermissionMap.mockResolvedValue(memberPermissions);
|
|
365
369
|
|
|
366
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
370
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
367
371
|
|
|
368
372
|
await waitFor(() => {
|
|
369
373
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -393,7 +397,7 @@ describe('usePermissions Hook', () => {
|
|
|
393
397
|
|
|
394
398
|
mockGetPermissionMap.mockResolvedValue(participantPermissions);
|
|
395
399
|
|
|
396
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
400
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
397
401
|
|
|
398
402
|
await waitFor(() => {
|
|
399
403
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -465,7 +469,7 @@ describe('usePermissions Hook', () => {
|
|
|
465
469
|
|
|
466
470
|
describe('Edge Cases', () => {
|
|
467
471
|
it('handles null userId', async () => {
|
|
468
|
-
const { result } = renderHook(() => usePermissions(null as any,
|
|
472
|
+
const { result } = renderHook(() => usePermissions(null as any, mockOrgId, mockEventId, mockAppId));
|
|
469
473
|
|
|
470
474
|
await waitFor(() => {
|
|
471
475
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -475,7 +479,7 @@ describe('usePermissions Hook', () => {
|
|
|
475
479
|
});
|
|
476
480
|
|
|
477
481
|
it('handles undefined userId', async () => {
|
|
478
|
-
const { result } = renderHook(() => usePermissions(undefined as any,
|
|
482
|
+
const { result } = renderHook(() => usePermissions(undefined as any, mockOrgId, mockEventId, mockAppId));
|
|
479
483
|
|
|
480
484
|
await waitFor(() => {
|
|
481
485
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -487,7 +491,7 @@ describe('usePermissions Hook', () => {
|
|
|
487
491
|
it('handles empty scope', async () => {
|
|
488
492
|
mockGetPermissionMap.mockResolvedValue(mockPermissionMap);
|
|
489
493
|
|
|
490
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
494
|
+
const { result } = renderHook(() => usePermissions(mockUserId, '', undefined, undefined));
|
|
491
495
|
|
|
492
496
|
// Wait for the hook to complete loading
|
|
493
497
|
await waitFor(() => {
|
|
@@ -506,7 +510,7 @@ describe('usePermissions Hook', () => {
|
|
|
506
510
|
|
|
507
511
|
mockGetPermissionMap.mockResolvedValue(largePermissionMap);
|
|
508
512
|
|
|
509
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
513
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
510
514
|
|
|
511
515
|
await waitFor(() => {
|
|
512
516
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -517,7 +521,7 @@ describe('usePermissions Hook', () => {
|
|
|
517
521
|
it('handles empty permission map', async () => {
|
|
518
522
|
mockGetPermissionMap.mockResolvedValue({});
|
|
519
523
|
|
|
520
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
524
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
521
525
|
|
|
522
526
|
await waitFor(() => {
|
|
523
527
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -532,9 +536,8 @@ describe('usePermissions Hook', () => {
|
|
|
532
536
|
});
|
|
533
537
|
|
|
534
538
|
it('handles scope with empty organisationId', async () => {
|
|
535
|
-
const invalidScope = { organisationId: '', eventId: 'event-123' };
|
|
536
539
|
|
|
537
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
540
|
+
const { result } = renderHook(() => usePermissions(mockUserId, '', 'event-123', undefined));
|
|
538
541
|
|
|
539
542
|
await waitFor(() => {
|
|
540
543
|
expect(result.current.isLoading).toBe(true);
|
|
@@ -544,9 +547,8 @@ describe('usePermissions Hook', () => {
|
|
|
544
547
|
});
|
|
545
548
|
|
|
546
549
|
it('handles scope with whitespace-only organisationId', async () => {
|
|
547
|
-
const invalidScope = { organisationId: ' ', eventId: 'event-123' };
|
|
548
550
|
|
|
549
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
551
|
+
const { result } = renderHook(() => usePermissions(mockUserId, ' ', 'event-123', undefined));
|
|
550
552
|
|
|
551
553
|
await waitFor(() => {
|
|
552
554
|
expect(result.current.isLoading).toBe(true);
|
|
@@ -560,7 +562,7 @@ describe('usePermissions Hook', () => {
|
|
|
560
562
|
it('maintains stable references for same permissions', async () => {
|
|
561
563
|
mockGetPermissionMap.mockResolvedValue(mockPermissionMap);
|
|
562
564
|
|
|
563
|
-
const { result, rerender } = renderHook(() => usePermissions(mockUserId,
|
|
565
|
+
const { result, rerender } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
564
566
|
|
|
565
567
|
await waitFor(() => {
|
|
566
568
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -580,7 +582,7 @@ describe('usePermissions Hook', () => {
|
|
|
580
582
|
it('handles rapid permission checks efficiently', async () => {
|
|
581
583
|
mockGetPermissionMap.mockResolvedValue(mockPermissionMap);
|
|
582
584
|
|
|
583
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
585
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
584
586
|
|
|
585
587
|
await waitFor(() => {
|
|
586
588
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -601,7 +603,7 @@ describe('usePermissions Hook', () => {
|
|
|
601
603
|
|
|
602
604
|
describe('[integration] Cache Invalidation', () => {
|
|
603
605
|
it('refetches after cache invalidation', async () => {
|
|
604
|
-
const { result } = renderHook(() => usePermissions(mockUserId,
|
|
606
|
+
const { result } = renderHook(() => usePermissions(mockUserId, mockOrgId, mockEventId, mockAppId));
|
|
605
607
|
|
|
606
608
|
await waitFor(() => {
|
|
607
609
|
expect(result.current.isLoading).toBe(false);
|
|
@@ -27,13 +27,20 @@ import { getRBACLogger } from '../config';
|
|
|
27
27
|
* Hook to get user's permissions in a scope
|
|
28
28
|
*
|
|
29
29
|
* @param userId - User ID
|
|
30
|
-
* @param
|
|
30
|
+
* @param organisationId - Organisation ID
|
|
31
|
+
* @param eventId - Event ID (optional)
|
|
32
|
+
* @param appId - Application ID (optional)
|
|
31
33
|
* @returns Permission state and methods
|
|
32
34
|
*
|
|
33
35
|
* @example
|
|
34
36
|
* ```tsx
|
|
35
37
|
* function MyComponent() {
|
|
36
|
-
* const { permissions, isLoading, error } = usePermissions(
|
|
38
|
+
* const { permissions, isLoading, error } = usePermissions(
|
|
39
|
+
* userId,
|
|
40
|
+
* organisationId,
|
|
41
|
+
* eventId,
|
|
42
|
+
* appId
|
|
43
|
+
* );
|
|
37
44
|
*
|
|
38
45
|
* if (isLoading) return <div>Loading...</div>;
|
|
39
46
|
* if (error) return <div>Error: {error.message}</div>;
|
|
@@ -47,29 +54,31 @@ import { getRBACLogger } from '../config';
|
|
|
47
54
|
* }
|
|
48
55
|
* ```
|
|
49
56
|
*/
|
|
50
|
-
export function usePermissions(
|
|
57
|
+
export function usePermissions(
|
|
58
|
+
userId: UUID,
|
|
59
|
+
organisationId: string | undefined,
|
|
60
|
+
eventId: string | undefined,
|
|
61
|
+
appId: string | undefined
|
|
62
|
+
) {
|
|
51
63
|
const [permissions, setPermissions] = useState<PermissionMap>({} as PermissionMap);
|
|
52
64
|
const [isLoading, setIsLoading] = useState(true);
|
|
53
65
|
const [error, setError] = useState<Error | null>(null);
|
|
54
66
|
const isFetchingRef = useRef(false);
|
|
55
67
|
const logger = getRBACLogger();
|
|
56
68
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
69
|
+
// Track previous appId to detect when it becomes available
|
|
70
|
+
const prevAppIdRef = useRef<string | undefined>(appId);
|
|
71
|
+
|
|
72
|
+
// Normalize organisationId to empty string if undefined
|
|
73
|
+
const orgId = organisationId || '';
|
|
62
74
|
|
|
63
75
|
// Log when hook is called with new scope (every render)
|
|
64
76
|
// This helps us see if React is calling the hook with updated scope
|
|
65
77
|
logger.warn('[usePermissions] Hook called with scope', {
|
|
66
78
|
userId,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
scopeOrgId: scope.organisationId,
|
|
71
|
-
scopeEventId: scope.eventId,
|
|
72
|
-
scopeAppId: scope.appId,
|
|
79
|
+
organisationId: orgId,
|
|
80
|
+
eventId,
|
|
81
|
+
appId,
|
|
73
82
|
hasAppId: !!appId,
|
|
74
83
|
hasOrganisationId: !!orgId
|
|
75
84
|
});
|
|
@@ -78,17 +87,17 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
78
87
|
React.useEffect(() => {
|
|
79
88
|
logger.warn('[usePermissions] Scope changed (useEffect)', {
|
|
80
89
|
userId,
|
|
81
|
-
organisationId
|
|
82
|
-
eventId
|
|
83
|
-
appId
|
|
84
|
-
hasAppId: !!
|
|
85
|
-
hasOrganisationId: !!
|
|
90
|
+
organisationId,
|
|
91
|
+
eventId,
|
|
92
|
+
appId,
|
|
93
|
+
hasAppId: !!appId,
|
|
94
|
+
hasOrganisationId: !!organisationId
|
|
86
95
|
});
|
|
87
|
-
}, [
|
|
96
|
+
}, [userId, organisationId, eventId, appId]);
|
|
88
97
|
|
|
89
98
|
// Add timeout for missing organisation context (3 seconds)
|
|
90
99
|
useEffect(() => {
|
|
91
|
-
if (!
|
|
100
|
+
if (!orgId || orgId === null || (typeof orgId === 'string' && orgId.trim() === '')) {
|
|
92
101
|
const timeoutId = setTimeout(() => {
|
|
93
102
|
setError(new Error('Organisation context is required for permission checks'));
|
|
94
103
|
setIsLoading(false);
|
|
@@ -100,16 +109,28 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
100
109
|
if (error?.message === 'Organisation context is required for permission checks') {
|
|
101
110
|
setError(null);
|
|
102
111
|
}
|
|
103
|
-
}, [
|
|
112
|
+
}, [organisationId, error]);
|
|
104
113
|
|
|
105
114
|
useEffect(() => {
|
|
106
115
|
const fetchPermissions = async () => {
|
|
116
|
+
// Log appId change detection
|
|
117
|
+
const appIdChanged = prevAppIdRef.current !== appId;
|
|
118
|
+
if (appIdChanged) {
|
|
119
|
+
logger.warn('[usePermissions] appId changed detected!', {
|
|
120
|
+
prevAppId: prevAppIdRef.current,
|
|
121
|
+
newAppId: appId,
|
|
122
|
+
hasAppId: !!appId
|
|
123
|
+
});
|
|
124
|
+
prevAppIdRef.current = appId;
|
|
125
|
+
}
|
|
126
|
+
|
|
107
127
|
logger.warn('[usePermissions] Fetch useEffect triggered', {
|
|
108
128
|
userId,
|
|
109
129
|
orgId,
|
|
110
130
|
eventId,
|
|
111
131
|
appId,
|
|
112
132
|
hasAppId: !!appId,
|
|
133
|
+
appIdChanged,
|
|
113
134
|
isFetching: isFetchingRef.current
|
|
114
135
|
});
|
|
115
136
|
|
|
@@ -176,15 +197,15 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
176
197
|
setIsLoading(true);
|
|
177
198
|
setError(null);
|
|
178
199
|
|
|
179
|
-
// Build scope object
|
|
180
|
-
const
|
|
200
|
+
// Build scope object for API call
|
|
201
|
+
const scope: Scope = {
|
|
181
202
|
organisationId: orgId,
|
|
182
203
|
eventId: eventId,
|
|
183
204
|
appId: appId
|
|
184
205
|
};
|
|
185
206
|
|
|
186
207
|
// Fetch new permissions - don't clear old ones until we have new ones
|
|
187
|
-
const permissionMap = await getPermissionMap({ userId, scope
|
|
208
|
+
const permissionMap = await getPermissionMap({ userId, scope });
|
|
188
209
|
|
|
189
210
|
logger.warn('[usePermissions] Permissions fetched successfully', {
|
|
190
211
|
permissionCount: Object.keys(permissionMap).length,
|
|
@@ -211,7 +232,7 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
211
232
|
};
|
|
212
233
|
|
|
213
234
|
fetchPermissions();
|
|
214
|
-
}, [userId,
|
|
235
|
+
}, [userId, organisationId, eventId, appId]);
|
|
215
236
|
|
|
216
237
|
const hasPermission = useCallback((permission: Permission): boolean => {
|
|
217
238
|
if (permissions['*']) {
|
|
@@ -248,7 +269,7 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
248
269
|
|
|
249
270
|
// Don't fetch permissions if scope is invalid (e.g., organisationId is null/empty)
|
|
250
271
|
// IMPORTANT: Don't clear existing permissions - keep them until we have new ones
|
|
251
|
-
if (!
|
|
272
|
+
if (!orgId || orgId === null || (typeof orgId === 'string' && orgId.trim() === '')) {
|
|
252
273
|
// Keep existing permissions, just mark as loading
|
|
253
274
|
setIsLoading(true);
|
|
254
275
|
setError(null);
|
|
@@ -260,6 +281,13 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
260
281
|
setIsLoading(true);
|
|
261
282
|
setError(null);
|
|
262
283
|
|
|
284
|
+
// Build scope object for API call
|
|
285
|
+
const scope: Scope = {
|
|
286
|
+
organisationId: orgId,
|
|
287
|
+
eventId: eventId,
|
|
288
|
+
appId: appId
|
|
289
|
+
};
|
|
290
|
+
|
|
263
291
|
// Fetch new permissions - don't clear old ones until we have new ones
|
|
264
292
|
const permissionMap = await getPermissionMap({ userId, scope });
|
|
265
293
|
|
|
@@ -276,7 +304,7 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
276
304
|
setIsLoading(false);
|
|
277
305
|
isFetchingRef.current = false;
|
|
278
306
|
}
|
|
279
|
-
}, [userId,
|
|
307
|
+
}, [userId, organisationId, eventId, appId]);
|
|
280
308
|
|
|
281
309
|
// Memoize the return object to prevent unnecessary re-renders
|
|
282
310
|
return useMemo(() => ({
|