@jmruthers/pace-core 0.5.165 → 0.5.166
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-Z6IYTME3.js} +3 -3
- package/dist/{chunk-DDB2M4XO.js → chunk-5PH366QI.js} +2 -2
- package/dist/{chunk-GFSYFEVZ.js → chunk-BPIFVNEO.js} +24 -24
- package/dist/chunk-BPIFVNEO.js.map +1 -0
- package/dist/{chunk-Q4NUEMNV.js → chunk-JIRBF5HR.js} +6 -20
- package/dist/chunk-JIRBF5HR.js.map +1 -0
- package/dist/{chunk-23YYDN24.js → chunk-SVMR7EVX.js} +2 -2
- 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/modules.md +17 -10
- package/package.json +1 -1
- package/src/__tests__/hooks/usePermissions.test.ts +12 -9
- package/src/components/NavigationMenu/NavigationMenu.tsx +4 -22
- 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 +41 -28
- package/dist/chunk-GFSYFEVZ.js.map +0 -1
- package/dist/chunk-Q4NUEMNV.js.map +0 -1
- /package/dist/{DataTable-HAATGNJY.js.map → DataTable-Z6IYTME3.js.map} +0 -0
- /package/dist/{chunk-DDB2M4XO.js.map → chunk-5PH366QI.js.map} +0 -0
- /package/dist/{chunk-23YYDN24.js.map → chunk-SVMR7EVX.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,28 @@ 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
|
-
const orgId = scope.organisationId || '';
|
|
60
|
-
const eventId = scope.eventId;
|
|
61
|
-
const appId = scope.appId;
|
|
69
|
+
// Normalize organisationId to empty string if undefined
|
|
70
|
+
const orgId = organisationId || '';
|
|
62
71
|
|
|
63
72
|
// Log when hook is called with new scope (every render)
|
|
64
73
|
// This helps us see if React is calling the hook with updated scope
|
|
65
74
|
logger.warn('[usePermissions] Hook called with scope', {
|
|
66
75
|
userId,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
scopeOrgId: scope.organisationId,
|
|
71
|
-
scopeEventId: scope.eventId,
|
|
72
|
-
scopeAppId: scope.appId,
|
|
76
|
+
organisationId: orgId,
|
|
77
|
+
eventId,
|
|
78
|
+
appId,
|
|
73
79
|
hasAppId: !!appId,
|
|
74
80
|
hasOrganisationId: !!orgId
|
|
75
81
|
});
|
|
@@ -78,17 +84,17 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
78
84
|
React.useEffect(() => {
|
|
79
85
|
logger.warn('[usePermissions] Scope changed (useEffect)', {
|
|
80
86
|
userId,
|
|
81
|
-
organisationId:
|
|
82
|
-
eventId
|
|
83
|
-
appId
|
|
84
|
-
hasAppId: !!
|
|
85
|
-
hasOrganisationId: !!
|
|
87
|
+
organisationId: orgId,
|
|
88
|
+
eventId,
|
|
89
|
+
appId,
|
|
90
|
+
hasAppId: !!appId,
|
|
91
|
+
hasOrganisationId: !!orgId
|
|
86
92
|
});
|
|
87
|
-
}, [
|
|
93
|
+
}, [userId, orgId, eventId, appId]);
|
|
88
94
|
|
|
89
95
|
// Add timeout for missing organisation context (3 seconds)
|
|
90
96
|
useEffect(() => {
|
|
91
|
-
if (!
|
|
97
|
+
if (!orgId || orgId === null || (typeof orgId === 'string' && orgId.trim() === '')) {
|
|
92
98
|
const timeoutId = setTimeout(() => {
|
|
93
99
|
setError(new Error('Organisation context is required for permission checks'));
|
|
94
100
|
setIsLoading(false);
|
|
@@ -100,7 +106,7 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
100
106
|
if (error?.message === 'Organisation context is required for permission checks') {
|
|
101
107
|
setError(null);
|
|
102
108
|
}
|
|
103
|
-
}, [
|
|
109
|
+
}, [orgId, error]);
|
|
104
110
|
|
|
105
111
|
useEffect(() => {
|
|
106
112
|
const fetchPermissions = async () => {
|
|
@@ -176,15 +182,15 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
176
182
|
setIsLoading(true);
|
|
177
183
|
setError(null);
|
|
178
184
|
|
|
179
|
-
// Build scope object
|
|
180
|
-
const
|
|
185
|
+
// Build scope object for API call
|
|
186
|
+
const scope: Scope = {
|
|
181
187
|
organisationId: orgId,
|
|
182
188
|
eventId: eventId,
|
|
183
189
|
appId: appId
|
|
184
190
|
};
|
|
185
191
|
|
|
186
192
|
// Fetch new permissions - don't clear old ones until we have new ones
|
|
187
|
-
const permissionMap = await getPermissionMap({ userId, scope
|
|
193
|
+
const permissionMap = await getPermissionMap({ userId, scope });
|
|
188
194
|
|
|
189
195
|
logger.warn('[usePermissions] Permissions fetched successfully', {
|
|
190
196
|
permissionCount: Object.keys(permissionMap).length,
|
|
@@ -211,7 +217,7 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
211
217
|
};
|
|
212
218
|
|
|
213
219
|
fetchPermissions();
|
|
214
|
-
}, [userId,
|
|
220
|
+
}, [userId, orgId, eventId, appId]);
|
|
215
221
|
|
|
216
222
|
const hasPermission = useCallback((permission: Permission): boolean => {
|
|
217
223
|
if (permissions['*']) {
|
|
@@ -248,7 +254,7 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
248
254
|
|
|
249
255
|
// Don't fetch permissions if scope is invalid (e.g., organisationId is null/empty)
|
|
250
256
|
// IMPORTANT: Don't clear existing permissions - keep them until we have new ones
|
|
251
|
-
if (!
|
|
257
|
+
if (!orgId || orgId === null || (typeof orgId === 'string' && orgId.trim() === '')) {
|
|
252
258
|
// Keep existing permissions, just mark as loading
|
|
253
259
|
setIsLoading(true);
|
|
254
260
|
setError(null);
|
|
@@ -260,6 +266,13 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
260
266
|
setIsLoading(true);
|
|
261
267
|
setError(null);
|
|
262
268
|
|
|
269
|
+
// Build scope object for API call
|
|
270
|
+
const scope: Scope = {
|
|
271
|
+
organisationId: orgId,
|
|
272
|
+
eventId: eventId,
|
|
273
|
+
appId: appId
|
|
274
|
+
};
|
|
275
|
+
|
|
263
276
|
// Fetch new permissions - don't clear old ones until we have new ones
|
|
264
277
|
const permissionMap = await getPermissionMap({ userId, scope });
|
|
265
278
|
|
|
@@ -276,7 +289,7 @@ export function usePermissions(userId: UUID, scope: Scope) {
|
|
|
276
289
|
setIsLoading(false);
|
|
277
290
|
isFetchingRef.current = false;
|
|
278
291
|
}
|
|
279
|
-
}, [userId,
|
|
292
|
+
}, [userId, orgId, eventId, appId]);
|
|
280
293
|
|
|
281
294
|
// Memoize the return object to prevent unnecessary re-renders
|
|
282
295
|
return useMemo(() => ({
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/rbac/hooks/useRBAC.ts","../src/rbac/hooks/useResolvedScope.ts","../src/rbac/utils/eventContext.ts","../src/rbac/hooks/usePermissions.ts","../src/rbac/hooks/useResourcePermissions.ts","../src/rbac/hooks/useRoleManagement.ts"],"sourcesContent":["/**\n * @file RBAC Hook\n * @package @jmruthers/pace-core\n * @module RBAC/Hooks\n * @since 0.3.0\n *\n * A React hook that provides access to the RBAC (Role-Based Access Control) system\n * through the hardened RBAC engine API. The hook defers all permission and role\n * resolution to the shared engine to ensure consistent security behaviour across\n * applications.\n */\n\nimport { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';\nimport { useOrganisations } from '../../hooks/useOrganisations';\nimport { useEvents } from '../../hooks/useEvents';\nimport {\n getPermissionMap,\n getAccessLevel,\n resolveAppContext,\n getRoleContext,\n} from '../api';\nimport { getRBACLogger } from '../config';\nimport type {\n UserRBACContext,\n GlobalRole,\n OrganisationRole,\n EventAppRole,\n Permission,\n Scope,\n PermissionMap,\n UUID,\n} from '../types';\n\nfunction mapAccessLevelToEventRole(level: string | null): EventAppRole | null {\n switch (level) {\n case 'viewer':\n return 'viewer';\n case 'participant':\n return 'participant';\n case 'planner':\n return 'planner';\n case 'admin':\n case 'super':\n return 'event_admin';\n default:\n return null;\n }\n}\n\nexport function useRBAC(pageId?: string): UserRBACContext {\n const logger = getRBACLogger();\n // Get all context from UnifiedAuth - it already provides selectedOrganisation, isContextReady, selectedEvent, and eventLoading\n // This is more reliable than calling useOrganisations()/useEvents() separately which might throw or return stale values\n const { \n user, \n session, \n appName, \n appConfig,\n selectedOrganisation,\n isContextReady: orgContextReady,\n organisationLoading: orgLoading,\n selectedEvent,\n eventLoading\n } = useUnifiedAuth();\n \n // Check if app requires event context\n // IMPORTANT: If appConfig is null initially, default to true (safer for event-based apps)\n // This prevents premature loading when appConfig hasn't loaded yet\n const requiresEvent = appConfig?.requires_event ?? (appConfig === null ? true : false);\n \n // Only log hook initialization if user is authenticated to avoid noise during login\n // Log hook initialization for debugging (use warn level so it's always visible)\n // Also use direct console.log as fallback to ensure visibility\n if (user && session) {\n const hookInitLog = {\n appName,\n requiresEvent,\n appConfig: appConfig ? JSON.stringify(appConfig) : 'null',\n hasUser: !!user,\n hasSession: !!session,\n hasSelectedEvent: !!selectedEvent,\n eventLoading,\n selectedEventId: selectedEvent?.event_id,\n hasSelectedOrganisation: !!selectedOrganisation,\n organisationId: selectedOrganisation?.id,\n orgContextReady,\n orgLoading\n };\n logger.warn('[useRBAC] Hook initialized', hookInitLog);\n // Direct console.log as fallback to ensure we can see if hook is called\n console.warn('[useRBAC] Hook initialized (direct log)', hookInitLog);\n }\n\n const [globalRole, setGlobalRole] = useState<GlobalRole | null>(null);\n const [organisationRole, setOrganisationRole] = useState<OrganisationRole | null>(null);\n const [eventAppRole, setEventAppRole] = useState<EventAppRole | null>(null);\n const [permissionMap, setPermissionMap] = useState<PermissionMap>({} as PermissionMap);\n const [currentScope, setCurrentScope] = useState<Scope | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const resetState = useCallback(() => {\n setGlobalRole(null);\n setOrganisationRole(null);\n setEventAppRole(null);\n setPermissionMap({} as PermissionMap);\n setCurrentScope(null);\n }, []);\n\n const loadRBACContext = useCallback(async () => {\n // Early return if user is not authenticated - don't do anything\n if (!user || !session) {\n resetState();\n setIsLoading(false);\n return;\n }\n\n // Wait for organisation context to be ready before loading RBAC\n // This is critical - without organisation ID, RPC calls can't resolve permissions\n if (orgLoading || !orgContextReady || !selectedOrganisation?.id) {\n setIsLoading(true);\n logger.warn('[useRBAC] Waiting for organisation context before loading RBAC context', {\n orgLoading,\n orgContextReady,\n hasSelectedOrganisation: !!selectedOrganisation,\n organisationId: selectedOrganisation?.id,\n appName\n });\n return;\n }\n\n // For event-based apps, wait for event context to be ready before loading RBAC\n // This prevents NetworkError when RPC calls are made before event context is available\n if (requiresEvent) {\n if (eventLoading || !selectedEvent) {\n // Event context not ready yet - don't load RBAC yet\n // This prevents premature RPC calls that can cause NetworkError\n // Set loading state so React knows we're waiting\n setIsLoading(true);\n logger.warn('[useRBAC] Waiting for event context before loading RBAC context', {\n eventLoading,\n hasSelectedEvent: !!selectedEvent,\n appName,\n selectedEventId: selectedEvent?.event_id,\n organisationId: selectedOrganisation?.id\n });\n return;\n }\n }\n\n setIsLoading(true);\n setError(null);\n\n logger.warn('[useRBAC] Loading RBAC context', {\n appName,\n requiresEvent,\n hasSelectedEvent: !!selectedEvent,\n selectedEventId: selectedEvent?.event_id,\n organisationId: selectedOrganisation?.id\n });\n\n try {\n let appId: UUID | undefined;\n if (appName) {\n // Wrap RPC call in try-catch to handle NetworkError gracefully\n try {\n const resolved = await resolveAppContext({ userId: user.id as UUID, appName });\n if (!resolved || !resolved.hasAccess) {\n throw new Error(`User does not have access to app \"${appName}\"`);\n }\n appId = resolved.appId;\n } catch (rpcError: any) {\n // Handle NetworkError - might be due to timing issue\n if (rpcError?.message?.includes('NetworkError') || rpcError?.message?.includes('fetch')) {\n logger.warn('[useRBAC] NetworkError resolving app context - may be timing issue, will retry when context is ready', {\n appName,\n error: rpcError.message,\n requiresEvent,\n eventLoading,\n hasSelectedEvent: !!selectedEvent\n });\n // Don't throw - let it retry when dependencies change\n setIsLoading(false);\n return;\n }\n // Re-throw other errors\n throw rpcError;\n }\n }\n\n const scope: Scope = {\n organisationId: selectedOrganisation?.id,\n eventId: selectedEvent?.event_id || undefined,\n appId,\n };\n \n // Log scope to verify it's built correctly\n logger.warn('[useRBAC] Building scope for RBAC context', {\n organisationId: scope.organisationId,\n eventId: scope.eventId,\n appId: scope.appId,\n hasOrganisationId: !!scope.organisationId,\n hasEventId: !!scope.eventId\n });\n \n setCurrentScope(scope);\n\n const [map, roleContext, accessLevel] = await Promise.all([\n getPermissionMap({ userId: user.id as UUID, scope }),\n getRoleContext({ userId: user.id as UUID, scope }),\n getAccessLevel({ userId: user.id as UUID, scope }),\n ]);\n\n setPermissionMap(map);\n setGlobalRole(roleContext.globalRole);\n setOrganisationRole(roleContext.organisationRole);\n setEventAppRole(roleContext.eventAppRole || mapAccessLevelToEventRole(accessLevel));\n \n logger.warn('[useRBAC] RBAC context loaded successfully', {\n appName,\n permissionCount: Object.keys(map).length,\n globalRole: roleContext.globalRole,\n organisationRole: roleContext.organisationRole,\n eventAppRole: roleContext.eventAppRole || mapAccessLevelToEventRole(accessLevel)\n });\n } catch (err) {\n const handledError = err instanceof Error ? err : new Error('Failed to load RBAC context');\n logger.error('[useRBAC] Error loading RBAC context:', handledError);\n setError(handledError);\n resetState();\n } finally {\n setIsLoading(false);\n }\n }, [appName, logger, resetState, selectedEvent, selectedEvent?.event_id, selectedOrganisation, selectedOrganisation?.id, session, user, requiresEvent, eventLoading, appConfig, orgContextReady, orgLoading]);\n\n const hasGlobalPermission = useCallback(\n (permission: string): boolean => {\n if (globalRole === 'super_admin' || permissionMap['*']) {\n return true;\n }\n\n if (permission === 'super_admin') {\n return globalRole === 'super_admin';\n }\n\n if (permission === 'org_admin') {\n return organisationRole === 'org_admin';\n }\n\n return permissionMap[permission as Permission] === true;\n },\n [globalRole, organisationRole, permissionMap],\n );\n\n const isSuperAdmin = useMemo(() => globalRole === 'super_admin' || permissionMap['*'] === true, [globalRole, permissionMap]);\n const isOrgAdmin = useMemo(() => organisationRole === 'org_admin' || isSuperAdmin, [organisationRole, isSuperAdmin]);\n const isEventAdmin = useMemo(() => eventAppRole === 'event_admin' || isSuperAdmin, [eventAppRole, isSuperAdmin]);\n const canManageOrganisation = useMemo(() => isSuperAdmin || organisationRole === 'org_admin', [isSuperAdmin, organisationRole]);\n const canManageEvent = useMemo(() => isSuperAdmin || eventAppRole === 'event_admin', [isSuperAdmin, eventAppRole]);\n\n useEffect(() => {\n // Log when effect runs to help debug\n logger.warn('[useRBAC] useEffect triggered - calling loadRBACContext', {\n appName,\n requiresEvent,\n eventLoading,\n hasSelectedEvent: !!selectedEvent,\n hasUser: !!user,\n hasSession: !!session,\n hasSelectedOrganisation: !!selectedOrganisation,\n organisationId: selectedOrganisation?.id,\n orgContextReady,\n orgLoading\n });\n loadRBACContext();\n }, [loadRBACContext, appName, requiresEvent, eventLoading, selectedEvent, user, session, selectedOrganisation, orgContextReady, orgLoading]);\n\n return {\n user,\n globalRole,\n organisationRole,\n eventAppRole,\n hasGlobalPermission,\n isSuperAdmin,\n isOrgAdmin,\n isEventAdmin,\n canManageOrganisation,\n canManageEvent,\n isLoading,\n error,\n };\n}\n","/**\n * @file useResolvedScope Hook\n * @package @jmruthers/pace-core\n * @module RBAC/Hooks\n * @since 1.0.0\n * \n * Shared hook for resolving RBAC scope from various contexts.\n * This hook is used by both DataTable and PagePermissionGuard to ensure\n * consistent scope resolution logic.\n */\n\nimport { useEffect, useState, useRef } from 'react';\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport type { Database } from '../../types/database';\nimport type { Scope } from '../types';\nimport { createScopeFromEvent } from '../utils/eventContext';\nimport { getCurrentAppName } from '../../utils/app/appNameResolver';\nimport { createLogger } from '../../utils/core/logger';\n\nconst log = createLogger('useResolvedScope');\n\nexport interface UseResolvedScopeOptions {\n /** Supabase client instance */\n supabase: SupabaseClient<Database> | null;\n /** Selected organisation ID */\n selectedOrganisationId: string | null;\n /** Selected event ID */\n selectedEventId: string | null;\n}\n\nexport interface UseResolvedScopeReturn {\n /** Resolved scope, or null if not yet resolved */\n resolvedScope: Scope | null;\n /** Whether the scope resolution is in progress */\n isLoading: boolean;\n /** Error if scope resolution failed */\n error: Error | null;\n}\n\n/**\n * Resolves RBAC scope from organisation and event context\n * \n * This hook handles the complex logic of determining the correct RBAC scope\n * based on available context (organisation, event, app). It ensures consistent\n * scope resolution across the application.\n * \n * @param options - Hook options\n * @returns Resolved scope and loading state\n * \n * @example\n * ```tsx\n * const { resolvedScope, isLoading } = useResolvedScope({\n * supabase,\n * selectedOrganisationId,\n * selectedEventId\n * });\n * \n * if (isLoading) return <Loading />;\n * if (!resolvedScope) return <Error />;\n * \n * const permission = useCan(userId, resolvedScope, permission);\n * ```\n */\nexport function useResolvedScope({\n supabase,\n selectedOrganisationId,\n selectedEventId\n}: UseResolvedScopeOptions): UseResolvedScopeReturn {\n const [resolvedScope, setResolvedScope] = useState<Scope | null>(null);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n \n // Use a ref to track the stable scope and only update it when it actually changes\n const stableScopeRef = useRef<{ organisationId: string; appId: string; eventId: string | undefined }>({ \n organisationId: '', \n appId: '', \n eventId: undefined \n });\n \n // Update stable scope ref in useEffect to avoid updates during render\n useEffect(() => {\n if (resolvedScope && resolvedScope.organisationId) {\n const newScope = {\n organisationId: resolvedScope.organisationId,\n appId: resolvedScope.appId,\n eventId: resolvedScope.eventId\n };\n \n // Only update if the scope has actually changed\n if (stableScopeRef.current.organisationId !== newScope.organisationId ||\n stableScopeRef.current.eventId !== newScope.eventId ||\n stableScopeRef.current.appId !== newScope.appId) {\n stableScopeRef.current = {\n organisationId: newScope.organisationId,\n appId: newScope.appId || '',\n eventId: newScope.eventId\n };\n }\n } else if (!resolvedScope) {\n // Reset to empty scope when no resolved scope\n stableScopeRef.current = { organisationId: '', appId: '', eventId: undefined };\n }\n }, [resolvedScope]);\n \n const stableScope = stableScopeRef.current;\n \n useEffect(() => {\n let cancelled = false;\n \n const resolveScope = async () => {\n setIsLoading(true);\n setError(null);\n \n try {\n // Get app ID from package.json or environment\n let appId: string | undefined = undefined;\n \n // Try to resolve from database\n if (supabase) {\n const appName = getCurrentAppName();\n if (appName) {\n try {\n const { data: app, error } = await supabase\n .from('rbac_apps')\n .select('id, name, is_active')\n .eq('name', appName)\n .eq('is_active', true)\n .single() as { data: { id: string; name: string; is_active: boolean } | null; error: any };\n \n if (error) {\n // Check if app exists but is inactive\n const { data: inactiveApp } = await supabase\n .from('rbac_apps')\n .select('id, name, is_active')\n .eq('name', appName)\n .single() as { data: { id: string; name: string; is_active: boolean } | null };\n \n if (inactiveApp) {\n log.error(`App \"${appName}\" exists but is inactive (is_active: ${inactiveApp.is_active})`);\n } else {\n log.error(`App \"${appName}\" not found in rbac_apps table`);\n }\n } else if (app) {\n appId = app.id;\n }\n } catch (error) {\n log.error('Unexpected error resolving app ID:', error);\n }\n }\n }\n\n // Resolve scope based on available context\n\n // If we have both organisation and event, use them directly\n if (selectedOrganisationId && selectedEventId) {\n if (!cancelled) {\n setResolvedScope({\n organisationId: selectedOrganisationId,\n eventId: selectedEventId,\n appId: appId\n });\n setIsLoading(false);\n }\n return;\n }\n\n // If we only have organisation, use it\n if (selectedOrganisationId) {\n if (!cancelled) {\n setResolvedScope({\n organisationId: selectedOrganisationId,\n eventId: selectedEventId || undefined,\n appId: appId\n });\n setIsLoading(false);\n }\n return;\n }\n\n // If we only have event, resolve organisation from event\n if (selectedEventId && supabase) {\n try {\n const eventScope = await createScopeFromEvent(supabase, selectedEventId, appId);\n if (!eventScope) {\n log.error('Could not resolve organization from event context');\n if (!cancelled) {\n setResolvedScope(null);\n setError(new Error('Could not resolve organisation from event context'));\n setIsLoading(false);\n }\n return;\n }\n // Preserve the resolved app ID\n if (!cancelled) {\n setResolvedScope({\n ...eventScope,\n appId: appId || eventScope.appId\n });\n setIsLoading(false);\n }\n } catch (err) {\n log.error('Error resolving scope from event:', err);\n if (!cancelled) {\n setResolvedScope(null);\n setError(err as Error);\n setIsLoading(false);\n }\n }\n return;\n }\n\n // No context available\n log.error('No organisation or event context available');\n if (!cancelled) {\n setResolvedScope(null);\n setError(new Error('No organisation or event context available'));\n setIsLoading(false);\n }\n } catch (err) {\n if (!cancelled) {\n setError(err as Error);\n setIsLoading(false);\n }\n }\n };\n\n resolveScope();\n \n return () => {\n cancelled = true;\n };\n }, [selectedOrganisationId, selectedEventId, supabase]);\n \n return {\n resolvedScope: stableScope.organisationId ? stableScope as Scope : null,\n isLoading,\n error\n };\n}\n","/**\n * Event Context Utilities for RBAC\n * @package @jmruthers/pace-core\n * @module RBAC/EventContext\n * @since 1.0.0\n * \n * This module provides utilities for event-based RBAC operations where\n * the organization context is derived from the event context.\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../../types/database';\nimport { UUID, Scope } from '../types';\n\n/**\n * Get organization ID from event ID\n * \n * @param supabase - Supabase client\n * @param eventId - Event ID\n * @returns Promise resolving to organization ID or null\n */\nexport async function getOrganisationFromEvent(\n supabase: SupabaseClient<Database>,\n eventId: string\n): Promise<UUID | null> {\n const { data, error } = await supabase\n .from('event')\n .select('organisation_id')\n .eq('event_id', eventId)\n .single() as { data: { organisation_id: string } | null; error: any };\n\n if (error || !data) {\n return null;\n }\n\n return data.organisation_id;\n}\n\n/**\n * Create a complete scope from event context\n * \n * @param supabase - Supabase client\n * @param eventId - Event ID\n * @param appId - Optional app ID\n * @returns Promise resolving to complete scope\n */\nexport async function createScopeFromEvent(\n supabase: SupabaseClient<Database>,\n eventId: string,\n appId?: UUID\n): Promise<Scope | null> {\n const organisationId = await getOrganisationFromEvent(supabase, eventId);\n \n if (!organisationId) {\n return null;\n }\n\n return {\n organisationId,\n eventId,\n appId\n };\n}\n\n/**\n * Check if a scope is event-based (has eventId but no explicit organisationId)\n * \n * @param scope - Permission scope\n * @returns True if scope is event-based\n */\nexport function isEventBasedScope(scope: Scope): boolean {\n return !scope.organisationId && !!scope.eventId;\n}\n\n/**\n * Validate that an event-based scope has the required context\n * \n * @param scope - Permission scope\n * @returns True if scope is valid for event-based operations\n */\nexport function isValidEventBasedScope(scope: Scope): boolean {\n return isEventBasedScope(scope) && !!scope.eventId;\n}\n","/**\n * @file RBAC Permission Hooks\n * @package @jmruthers/pace-core\n * @module RBAC/Hooks\n * @since 1.0.0\n * \n * This module provides React hooks for RBAC functionality.\n */\n\nimport React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';\nimport { \n UUID, \n Scope, \n Permission, \n PermissionMap\n} from '../types';\nimport { AccessLevel as AccessLevelType } from '../types';\nimport { \n getAccessLevel, \n getPermissionMap, \n isPermitted,\n isPermittedCached \n} from '../api';\nimport { getRBACLogger } from '../config';\n\n/**\n * Hook to get user's permissions in a scope\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @returns Permission state and methods\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading, error } = usePermissions(userId, scope);\n * \n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {permissions['read:users'] && <UserList />}\n * {permissions['create:users'] && <CreateUserButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function usePermissions(userId: UUID, scope: Scope) {\n const [permissions, setPermissions] = useState<PermissionMap>({} as PermissionMap);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const isFetchingRef = useRef(false);\n const logger = getRBACLogger();\n \n // Extract individual values to ensure React detects changes\n // This is critical - React compares these values, not the object reference\n const orgId = scope.organisationId || '';\n const eventId = scope.eventId;\n const appId = scope.appId;\n \n // Log when hook is called with new scope (every render)\n // This helps us see if React is calling the hook with updated scope\n logger.warn('[usePermissions] Hook called with scope', {\n userId,\n extractedOrgId: orgId,\n extractedEventId: eventId,\n extractedAppId: appId,\n scopeOrgId: scope.organisationId,\n scopeEventId: scope.eventId,\n scopeAppId: scope.appId,\n hasAppId: !!appId,\n hasOrganisationId: !!orgId\n });\n \n // Log when scope changes to verify React detects the change\n React.useEffect(() => {\n logger.warn('[usePermissions] Scope changed (useEffect)', {\n userId,\n organisationId: scope.organisationId,\n eventId: scope.eventId,\n appId: scope.appId,\n hasAppId: !!scope.appId,\n hasOrganisationId: !!scope.organisationId\n });\n }, [scope.organisationId, scope.eventId, scope.appId, userId]);\n\n // Add timeout for missing organisation context (3 seconds)\n useEffect(() => {\n if (!scope.organisationId || scope.organisationId === null || (typeof scope.organisationId === 'string' && scope.organisationId.trim() === '')) {\n const timeoutId = setTimeout(() => {\n setError(new Error('Organisation context is required for permission checks'));\n setIsLoading(false);\n }, 3000); // 3 seconds - typical permission check is < 1 second\n \n return () => clearTimeout(timeoutId);\n }\n // Clear error if organisation context becomes available\n if (error?.message === 'Organisation context is required for permission checks') {\n setError(null);\n }\n }, [scope.organisationId, error]);\n\n useEffect(() => {\n const fetchPermissions = async () => {\n logger.warn('[usePermissions] Fetch useEffect triggered', {\n userId,\n orgId,\n eventId,\n appId,\n hasAppId: !!appId,\n isFetching: isFetchingRef.current\n });\n\n // Prevent multiple simultaneous fetches\n if (isFetchingRef.current) {\n logger.warn('[usePermissions] Skipping fetch - already fetching', {\n userId,\n scope: {\n organisationId: orgId,\n eventId: eventId,\n appId: appId\n }\n });\n return;\n }\n\n if (!userId) {\n logger.warn('[usePermissions] Skipping fetch - no userId');\n setPermissions({} as PermissionMap);\n setIsLoading(false);\n return;\n }\n\n // Don't fetch permissions if scope is invalid (e.g., organisationId is null/empty)\n // Wait for organisation context to resolve\n // IMPORTANT: Don't clear existing permissions here - keep them until we have new ones\n if (!orgId || orgId === null || (typeof orgId === 'string' && orgId.trim() === '')) {\n logger.warn('[usePermissions] Skipping fetch - no orgId', {\n orgId,\n hasOrgId: !!orgId\n });\n // Keep existing permissions, just mark as loading\n setIsLoading(true);\n setError(null);\n return;\n }\n\n // CRITICAL: getPermissionMap requires appId to fetch pages and permissions\n // If appId is missing, it returns an empty map\n // Wait for appId to be available before fetching\n if (!appId) {\n logger.warn('[usePermissions] Waiting for appId before fetching permissions', {\n userId,\n organisationId: orgId,\n eventId: eventId,\n hasAppId: false\n });\n setIsLoading(true);\n setError(null);\n return;\n }\n logger.warn('[usePermissions] Fetching permissions', {\n userId,\n scope: {\n organisationId: orgId,\n eventId: eventId,\n appId: appId\n },\n hasAppId: !!appId\n });\n\n try {\n isFetchingRef.current = true;\n setIsLoading(true);\n setError(null);\n \n // Build scope object from extracted values\n const scopeForFetch: Scope = {\n organisationId: orgId,\n eventId: eventId,\n appId: appId\n };\n \n // Fetch new permissions - don't clear old ones until we have new ones\n const permissionMap = await getPermissionMap({ userId, scope: scopeForFetch });\n \n logger.warn('[usePermissions] Permissions fetched successfully', {\n permissionCount: Object.keys(permissionMap).length,\n hasWildcard: !!permissionMap['*'],\n scope: {\n organisationId: orgId,\n eventId: eventId,\n appId: appId\n }\n });\n \n // Only update permissions if fetch was successful\n setPermissions(permissionMap);\n } catch (err) {\n // On error, keep existing permissions but set error state\n // This prevents the UI from losing all items when there's a transient error\n logger.error('[usePermissions] Failed to fetch permissions:', err);\n setError(err instanceof Error ? err : new Error('Failed to fetch permissions'));\n // Don't clear permissions on error - keep what we had\n } finally {\n setIsLoading(false);\n isFetchingRef.current = false;\n }\n };\n\n fetchPermissions();\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\n\n const hasPermission = useCallback((permission: Permission): boolean => {\n if (permissions['*']) {\n return true;\n }\n return permissions[permission] === true;\n }, [permissions]);\n\n const hasAnyPermission = useCallback((permissionList: Permission[]): boolean => {\n if (permissions['*']) {\n return true;\n }\n return permissionList.some(p => permissions[p] === true);\n }, [permissions]);\n\n const hasAllPermissions = useCallback((permissionList: Permission[]): boolean => {\n if (permissions['*']) {\n return true;\n }\n return permissionList.every(p => permissions[p] === true);\n }, [permissions]);\n\n const refetch = useCallback(async () => {\n // Prevent multiple simultaneous fetches\n if (isFetchingRef.current) {\n return;\n }\n\n if (!userId) {\n setPermissions({} as PermissionMap);\n setIsLoading(false);\n return;\n }\n\n // Don't fetch permissions if scope is invalid (e.g., organisationId is null/empty)\n // IMPORTANT: Don't clear existing permissions - keep them until we have new ones\n if (!scope.organisationId || scope.organisationId === null || (typeof scope.organisationId === 'string' && scope.organisationId.trim() === '')) {\n // Keep existing permissions, just mark as loading\n setIsLoading(true);\n setError(null);\n return;\n }\n\n try {\n isFetchingRef.current = true;\n setIsLoading(true);\n setError(null);\n \n // Fetch new permissions - don't clear old ones until we have new ones\n const permissionMap = await getPermissionMap({ userId, scope });\n \n // Only update permissions if fetch was successful\n setPermissions(permissionMap);\n } catch (err) {\n // On error, keep existing permissions but set error state\n // This prevents the UI from losing all items when there's a transient error\n const logger = getRBACLogger();\n logger.error('Failed to refetch permissions:', err);\n setError(err instanceof Error ? err : new Error('Failed to fetch permissions'));\n // Don't clear permissions on error - keep what we had\n } finally {\n setIsLoading(false);\n isFetchingRef.current = false;\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\n\n // Memoize the return object to prevent unnecessary re-renders\n return useMemo(() => ({\n permissions,\n isLoading,\n error,\n hasPermission,\n hasAnyPermission,\n hasAllPermissions,\n refetch\n }), [permissions, isLoading, error, hasPermission, hasAnyPermission, hasAllPermissions, refetch]);\n}\n\n/**\n * Hook to check if user can perform an action\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @param permission - Permission to check\n * @param pageId - Optional page ID\n * @param useCache - Whether to use cached results\n * @returns Permission check state and methods\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { can, isLoading, error } = useCan(userId, scope, 'read:users');\n * \n * if (isLoading) return <div>Checking permission...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return can ? <UserList /> : <div>Access denied</div>;\n * }\n * ```\n */\nexport function useCan(\n userId: UUID, \n scope: Scope, \n permission: Permission, \n pageId?: UUID,\n useCache: boolean = true\n) {\n const [can, setCan] = useState<boolean>(false);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n // Validate scope parameter - handle undefined/null scope gracefully\n const isValidScope = scope && typeof scope === 'object';\n const organisationId = isValidScope ? scope.organisationId : undefined;\n const eventId = isValidScope ? scope.eventId : undefined;\n const appId = isValidScope ? scope.appId : undefined;\n\n // Add timeout for missing organisation context (3 seconds)\n useEffect(() => {\n if (!isValidScope || !organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === '')) {\n const timeoutId = setTimeout(() => {\n setError(new Error('Organisation context is required for permission checks'));\n setIsLoading(false);\n setCan(false);\n }, 3000); // 3 seconds - typical permission check is < 1 second\n \n return () => clearTimeout(timeoutId);\n }\n // Clear error if organisation context becomes available\n if (error?.message === 'Organisation context is required for permission checks') {\n setError(null);\n }\n }, [isValidScope, organisationId, error]);\n\n // Use refs to track the last values to prevent unnecessary re-runs\n const lastUserIdRef = useRef<UUID | null>(null);\n const lastScopeRef = useRef<string | null>(null);\n const lastPermissionRef = useRef<Permission | null>(null);\n const lastPageIdRef = useRef<UUID | undefined | null>(null);\n const lastUseCacheRef = useRef<boolean | null>(null);\n\n useEffect(() => {\n // Create a scope key to track changes - use safe property access\n const scopeKey = isValidScope ? `${organisationId}-${eventId}-${appId}` : 'invalid-scope';\n \n // Only run if something has actually changed\n if (\n lastUserIdRef.current !== userId ||\n lastScopeRef.current !== scopeKey ||\n lastPermissionRef.current !== permission ||\n lastPageIdRef.current !== pageId ||\n lastUseCacheRef.current !== useCache\n ) {\n lastUserIdRef.current = userId;\n lastScopeRef.current = scopeKey;\n lastPermissionRef.current = permission;\n lastPageIdRef.current = pageId;\n lastUseCacheRef.current = useCache;\n \n // Inline the permission check logic to avoid useCallback dependency issues\n const checkPermission = async () => {\n if (!userId) {\n setCan(false);\n setIsLoading(false);\n return;\n }\n\n // Validate scope before accessing properties\n if (!isValidScope) {\n setIsLoading(true);\n setCan(false);\n setError(null);\n // Timeout is handled in separate useEffect\n return;\n }\n\n // Don't check permissions if scope is invalid (e.g., organisationId is null/empty)\n // Wait for organisation context to resolve\n if (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === '')) {\n setIsLoading(true);\n setCan(false);\n setError(null);\n // Timeout is handled in separate useEffect (Phase 1.4)\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n // Create a valid scope object for the API call\n const validScope: Scope = {\n organisationId,\n ...(eventId ? { eventId } : {}),\n ...(appId ? { appId } : {})\n };\n \n const result = useCache \n ? await isPermittedCached({ userId, scope: validScope, permission, pageId })\n : await isPermitted({ userId, scope: validScope, permission, pageId });\n \n setCan(result);\n } catch (err) {\n const logger = getRBACLogger();\n logger.error('Permission check error:', { permission, error: err });\n setError(err instanceof Error ? err : new Error('Failed to check permission'));\n setCan(false);\n } finally {\n setIsLoading(false);\n }\n };\n \n checkPermission();\n }\n }, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache]);\n\n const refetch = useCallback(async () => {\n if (!userId) {\n setCan(false);\n setIsLoading(false);\n return;\n }\n\n // Validate scope before accessing properties\n if (!isValidScope) {\n setCan(false);\n setIsLoading(true);\n setError(null);\n return;\n }\n\n // Don't check permissions if scope is invalid (e.g., organisationId is null/empty)\n if (!organisationId || organisationId === null || (typeof organisationId === 'string' && organisationId.trim() === '')) {\n setCan(false);\n setIsLoading(true);\n setError(null);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n // Create a valid scope object for the API call\n const validScope: Scope = {\n organisationId,\n ...(eventId ? { eventId } : {}),\n ...(appId ? { appId } : {})\n };\n \n const result = useCache \n ? await isPermittedCached({ userId, scope: validScope, permission, pageId })\n : await isPermitted({ userId, scope: validScope, permission, pageId });\n \n setCan(result);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permission'));\n setCan(false);\n } finally {\n setIsLoading(false);\n }\n }, [userId, isValidScope, organisationId, eventId, appId, permission, pageId, useCache]);\n\n // Memoize the return object to prevent unnecessary re-renders\n return useMemo(() => ({\n can,\n isLoading,\n error,\n refetch\n }), [can, isLoading, error, refetch]);\n}\n\n/**\n * Hook to get user's access level in a scope\n * \n * @param userId - User ID\n * @param scope - Scope for access level checking\n * @returns Access level state and methods\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { accessLevel, isLoading, error } = useAccessLevel(userId, scope);\n * \n * if (isLoading) return <div>Loading access level...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * Access Level: {accessLevel}\n * {accessLevel >= AccessLevel.ADMIN && <AdminPanel />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useAccessLevel(userId: UUID, scope: Scope): {\n accessLevel: AccessLevelType;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [accessLevel, setAccessLevel] = useState<AccessLevelType>('viewer');\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchAccessLevel = useCallback(async () => {\n if (!userId) {\n setAccessLevel('viewer');\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const level = await getAccessLevel({ userId, scope });\n setAccessLevel(level);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch access level'));\n setAccessLevel('viewer');\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\n\n useEffect(() => {\n fetchAccessLevel();\n }, [fetchAccessLevel]);\n\n // Memoize the return object to prevent unnecessary re-renders\n return useMemo(() => ({\n accessLevel,\n isLoading,\n error,\n refetch: fetchAccessLevel\n }), [accessLevel, isLoading, error, fetchAccessLevel]);\n}\n\n/**\n * Hook to check multiple permissions at once\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @param permissions - Array of permissions to check\n * @param useCache - Whether to use cached results\n * @returns Multiple permission check results\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { results, isLoading, error } = useMultiplePermissions(\n * userId, \n * scope, \n * ['read:users', 'create:users', 'update:users']\n * );\n * \n * if (isLoading) return <div>Checking permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {results['read:users'] && <UserList />}\n * {results['create:users'] && <CreateUserButton />}\n * {results['update:users'] && <EditUserButton />}\n * </div>\n * );\n * }\n * ```\n */\nexport function useMultiplePermissions(\n userId: UUID, \n scope: Scope, \n permissions: Permission[],\n useCache: boolean = true\n): {\n results: Record<Permission, boolean>;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [results, setResults] = useState<Record<Permission, boolean>>({} as Record<Permission, boolean>);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const checkPermissions = useCallback(async () => {\n if (!userId || permissions.length === 0) {\n setResults({} as Record<Permission, boolean>);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const permissionResults: Record<Permission, boolean> = {} as Record<Permission, boolean>;\n \n // Check each permission\n for (const permission of permissions) {\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission })\n : await isPermitted({ userId, scope, permission });\n permissionResults[permission] = result;\n }\n \n setResults(permissionResults);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permissions'));\n setResults({} as Record<Permission, boolean>);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);\n\n useEffect(() => {\n checkPermissions();\n }, [checkPermissions]);\n\n // Memoize the return object to prevent unnecessary re-renders\n return useMemo(() => ({\n results,\n isLoading,\n error,\n refetch: checkPermissions\n }), [results, isLoading, error, checkPermissions]);\n}\n\n/**\n * Hook to check if user has any of the specified permissions\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @param permissions - Array of permissions to check\n * @param useCache - Whether to use cached results\n * @returns Whether user has any of the permissions\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { hasAny, isLoading, error } = useHasAnyPermission(\n * userId, \n * scope, \n * ['read:users', 'create:users']\n * );\n * \n * if (isLoading) return <div>Checking permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return hasAny ? <UserManagementPanel /> : <div>No user permissions</div>;\n * }\n * ```\n */\nexport function useHasAnyPermission(\n userId: UUID, \n scope: Scope, \n permissions: Permission[],\n useCache: boolean = true\n): {\n hasAny: boolean;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [hasAny, setHasAny] = useState<boolean>(false);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const checkAnyPermission = useCallback(async () => {\n if (!userId || permissions.length === 0) {\n setHasAny(false);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n let hasAnyPermission = false;\n \n for (const permission of permissions) {\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission })\n : await isPermitted({ userId, scope, permission });\n \n if (result) {\n hasAnyPermission = true;\n break;\n }\n }\n \n setHasAny(hasAnyPermission);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permissions'));\n setHasAny(false);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);\n\n useEffect(() => {\n checkAnyPermission();\n }, [checkAnyPermission]);\n\n // Memoize the return object to prevent unnecessary re-renders\n return useMemo(() => ({\n hasAny,\n isLoading,\n error,\n refetch: checkAnyPermission\n }), [hasAny, isLoading, error, checkAnyPermission]);\n}\n\n/**\n * Hook to check if user has all of the specified permissions\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @param permissions - Array of permissions to check\n * @param useCache - Whether to use cached results\n * @returns Whether user has all of the permissions\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { hasAll, isLoading, error } = useHasAllPermissions(\n * userId, \n * scope, \n * ['read:users', 'create:users', 'update:users']\n * );\n * \n * if (isLoading) return <div>Checking permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return hasAll ? <FullUserManagementPanel /> : <div>Insufficient permissions</div>;\n * }\n * ```\n */\nexport function useHasAllPermissions(\n userId: UUID, \n scope: Scope, \n permissions: Permission[],\n useCache: boolean = true\n): {\n hasAll: boolean;\n isLoading: boolean;\n error: Error | null;\n refetch: () => Promise<void>;\n} {\n const [hasAll, setHasAll] = useState<boolean>(false);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const checkAllPermissions = useCallback(async () => {\n if (!userId || permissions.length === 0) {\n setHasAll(false);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n let hasAllPermissions = true;\n \n for (const permission of permissions) {\n const result = useCache \n ? await isPermittedCached({ userId, scope, permission })\n : await isPermitted({ userId, scope, permission });\n \n if (!result) {\n hasAllPermissions = false;\n break;\n }\n }\n \n setHasAll(hasAllPermissions);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to check permissions'));\n setHasAll(false);\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId, permissions, useCache]);\n\n useEffect(() => {\n checkAllPermissions();\n }, [checkAllPermissions]);\n\n // Memoize the return object to prevent unnecessary re-renders\n return useMemo(() => ({\n hasAll,\n isLoading,\n error,\n refetch: checkAllPermissions\n }), [hasAll, isLoading, error, checkAllPermissions]);\n}\n\n/**\n * Hook to get cached permissions with TTL management\n * \n * @param userId - User ID\n * @param scope - Scope for permission checking\n * @returns Cached permission state and methods\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { permissions, isLoading, error, invalidateCache } = useCachedPermissions(userId, scope);\n * \n * if (isLoading) return <div>Loading cached permissions...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * \n * return (\n * <div>\n * {permissions['read:users'] && <UserList />}\n * <button onClick={invalidateCache}>Refresh Permissions</button>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCachedPermissions(userId: UUID, scope: Scope): {\n permissions: PermissionMap;\n isLoading: boolean;\n error: Error | null;\n invalidateCache: () => void;\n refetch: () => Promise<void>;\n} {\n const [permissions, setPermissions] = useState<PermissionMap>({} as PermissionMap);\n const [isLoading, setIsLoading] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchCachedPermissions = useCallback(async () => {\n if (!userId) {\n setPermissions({} as PermissionMap);\n setIsLoading(false);\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n \n const permissionMap = await getPermissionMap({ userId, scope });\n setPermissions(permissionMap);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch cached permissions'));\n } finally {\n setIsLoading(false);\n }\n }, [userId, scope.organisationId, scope.eventId, scope.appId]);\n\n const invalidateCache = useCallback(() => {\n // This would typically invalidate the cache in the actual implementation\n // For now, we'll just refetch\n fetchCachedPermissions();\n }, [fetchCachedPermissions]);\n\n useEffect(() => {\n fetchCachedPermissions();\n }, [fetchCachedPermissions]);\n\n // Memoize the return object to prevent unnecessary re-renders\n return useMemo(() => ({\n permissions,\n isLoading,\n error,\n invalidateCache,\n refetch: fetchCachedPermissions\n }), [permissions, isLoading, error, invalidateCache, fetchCachedPermissions]);\n}\n","/**\n * @file useResourcePermissions Hook\n * @package @jmruthers/pace-core\n * @module RBAC/Hooks\n * @since 1.0.0\n * \n * Hook to check permissions for a specific resource type.\n * This hook centralizes the common pattern of checking create/update/delete/read\n * permissions, eliminating ~30 lines of boilerplate code per hook usage.\n * \n * @example\n * ```tsx\n * import { useResourcePermissions } from '@jmruthers/pace-core/rbac';\n * \n * function ContactsHook() {\n * const { canCreate, canUpdate, canDelete } = useResourcePermissions('contacts');\n * \n * const addContact = async (data: ContactData) => {\n * if (!canCreate('contacts')) {\n * throw new Error(\"Permission denied: You do not have permission to create contacts.\");\n * }\n * // ... perform mutation\n * };\n * }\n * ```\n * \n * @example\n * ```tsx\n * // With read permissions enabled\n * const { canRead } = useResourcePermissions('contacts', { enableRead: true });\n * \n * if (!canRead('contacts')) {\n * return <PermissionDenied />;\n * }\n * ```\n * \n * @security\n * - Requires organisation context (handled by useResolvedScope)\n * - All permission checks are scoped to the current organisation/event/app context\n * - Missing user context results in all permissions being denied\n */\n\nimport { useMemo } from 'react';\nimport { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';\nimport { useOrganisations } from '../../hooks/useOrganisations';\nimport { useEvents } from '../../hooks/useEvents';\nimport { useResolvedScope } from './useResolvedScope';\nimport { useCan } from './usePermissions';\nimport type { Scope } from '../types';\n\nexport interface UseResourcePermissionsOptions {\n /** Whether to check read permissions (default: false) */\n enableRead?: boolean;\n /** Whether scope resolution is required (default: true) */\n requireScope?: boolean;\n}\n\nexport interface ResourcePermissions {\n /** Check if user can create resources of this type */\n canCreate: (resource: string) => boolean;\n /** Check if user can update resources of this type */\n canUpdate: (resource: string) => boolean;\n /** Check if user can delete resources of this type */\n canDelete: (resource: string) => boolean;\n /** Check if user can read resources of this type */\n canRead: (resource: string) => boolean;\n /** The resolved scope object (for advanced use cases) */\n scope: Scope;\n /** Whether any permission check is currently loading */\n isLoading: boolean;\n /** Error from any permission check or scope resolution */\n error: Error | null;\n}\n\n/**\n * Hook to check permissions for a specific resource\n * \n * This hook encapsulates the common pattern of checking create/update/delete/read\n * permissions for a resource type. It handles scope resolution, user context,\n * and provides a simple API for permission checking.\n * \n * @param resource - The resource name (e.g., 'contacts', 'risks', 'journal')\n * @param options - Optional configuration\n * @param options.enableRead - Whether to check read permissions (default: false)\n * @param options.requireScope - Whether scope resolution is required (default: true)\n * @returns Object with permission check functions and scope\n * \n * @example\n * ```tsx\n * function useContacts() {\n * const { canCreate, canUpdate, canDelete } = useResourcePermissions('contacts');\n * \n * const addContact = async (data: ContactData) => {\n * if (!canCreate('contacts')) {\n * throw new Error(\"Permission denied\");\n * }\n * // ... perform mutation\n * };\n * }\n * ```\n */\nexport function useResourcePermissions(\n resource: string,\n options: UseResourcePermissionsOptions = {}\n): ResourcePermissions {\n const { enableRead = false, requireScope = true } = options;\n\n // Get user and supabase client from UnifiedAuth\n const { user, supabase } = useUnifiedAuth();\n \n // Get selected organisation\n const { selectedOrganisation } = useOrganisations();\n \n // Get selected event (optional - wrap in try/catch)\n let selectedEvent: { event_id: string } | null = null;\n try {\n const eventsContext = useEvents();\n selectedEvent = eventsContext.selectedEvent;\n } catch (error) {\n // Event provider not available - continue without event context\n // This is expected in some apps that don't use events\n }\n\n // Resolve scope for permission checks\n const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({\n supabase,\n selectedOrganisationId: selectedOrganisation?.id || null,\n selectedEventId: selectedEvent?.event_id || null\n });\n\n // Create fallback scope if resolvedScope is not available\n const scope: Scope = resolvedScope || {\n organisationId: selectedOrganisation?.id || '',\n eventId: selectedEvent?.event_id || undefined,\n appId: undefined\n };\n\n // Permission checks for create, update, delete\n const { can: canCreateResult, isLoading: createLoading, error: createError } = useCan(\n user?.id || '',\n scope,\n `create:${resource}` as const,\n undefined, // pageId\n true // useCache\n );\n\n const { can: canUpdateResult, isLoading: updateLoading, error: updateError } = useCan(\n user?.id || '',\n scope,\n `update:${resource}` as const,\n undefined, // pageId\n true // useCache\n );\n\n const { can: canDeleteResult, isLoading: deleteLoading, error: deleteError } = useCan(\n user?.id || '',\n scope,\n `delete:${resource}` as const,\n undefined, // pageId\n true // useCache\n );\n\n // Optional read permission check\n const { can: canReadResult, isLoading: readLoading, error: readError } = useCan(\n user?.id || '',\n scope,\n `read:${resource}` as const,\n undefined, // pageId\n true // useCache\n );\n\n // Aggregate loading states - any permission check or scope resolution loading\n const isLoading = useMemo(() => {\n return scopeLoading || createLoading || updateLoading || deleteLoading || (enableRead && readLoading);\n }, [scopeLoading, createLoading, updateLoading, deleteLoading, readLoading, enableRead]);\n\n // Aggregate errors - prefer scope error, then any permission error\n const error = useMemo(() => {\n if (scopeError) return scopeError;\n if (createError) return createError;\n if (updateError) return updateError;\n if (deleteError) return deleteError;\n if (enableRead && readError) return readError;\n return null;\n }, [scopeError, createError, updateError, deleteError, readError, enableRead]);\n\n // Return wrapper functions that take resource name and return permission result\n // Note: The resource parameter in the function is for consistency with the API,\n // but we're checking permissions for the resource passed to the hook\n return useMemo(() => ({\n canCreate: (res: string) => {\n // For now, we only check the resource passed to the hook\n // Future enhancement could support checking different resources\n if (res !== resource) {\n return false;\n }\n return canCreateResult;\n },\n canUpdate: (res: string) => {\n if (res !== resource) {\n return false;\n }\n return canUpdateResult;\n },\n canDelete: (res: string) => {\n if (res !== resource) {\n return false;\n }\n return canDeleteResult;\n },\n canRead: (res: string) => {\n if (!enableRead) {\n return true; // If read checking is disabled, allow read\n }\n if (res !== resource) {\n return false;\n }\n return canReadResult;\n },\n scope,\n isLoading,\n error\n }), [\n resource,\n canCreateResult,\n canUpdateResult,\n canDeleteResult,\n canReadResult,\n enableRead,\n scope,\n isLoading,\n error\n ]);\n}\n\n","/**\n * @file RBAC Role Management Hook\n * @package @jmruthers/pace-core\n * @module RBAC/Hooks\n * @since 2.1.0\n *\n * React hook for managing RBAC roles safely using RPC functions.\n * This hook provides a secure, type-safe interface for granting and revoking roles\n * that ensures proper audit trails and security checks.\n *\n * @example\n * ```tsx\n * import { useRoleManagement } from '@jmruthers/pace-core/rbac';\n *\n * function UserRolesComponent() {\n * const { revokeEventAppRole, grantEventAppRole, isLoading, error } = useRoleManagement();\n *\n * const handleRevokeRole = async (roleId: string, roleData: EventAppRoleData) => {\n * const result = await revokeEventAppRole({\n * userId: roleData.user_id,\n * organisationId: roleData.organisation_id,\n * eventId: roleData.event_id,\n * appId: roleData.app_id,\n * role: roleData.role\n * });\n *\n * if (result.success) {\n * toast({ title: 'Role revoked successfully' });\n * } else {\n * toast({ title: 'Failed to revoke role', variant: 'destructive' });\n * }\n * };\n *\n * return <button onClick={() => handleRevokeRole(roleId, roleData)}>Revoke Role</button>;\n * }\n * ```\n */\n\nimport { useState, useCallback } from 'react';\nimport { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';\nimport type { UUID } from '../types';\n\nexport interface EventAppRoleData {\n user_id: UUID;\n organisation_id: UUID;\n event_id: string;\n app_id: UUID;\n role: 'viewer' | 'participant' | 'planner' | 'event_admin';\n}\n\nexport interface RevokeEventAppRoleParams extends EventAppRoleData {\n revoked_by?: UUID;\n}\n\nexport interface GrantEventAppRoleParams extends EventAppRoleData {\n granted_by?: UUID;\n valid_from?: string;\n valid_to?: string | null;\n}\n\nexport interface RoleManagementResult {\n success: boolean;\n message?: string;\n error?: string;\n roleId?: UUID;\n}\n\nexport function useRoleManagement() {\n const { user, supabase } = useUnifiedAuth();\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n if (!supabase) {\n throw new Error('useRoleManagement requires a Supabase client. Ensure UnifiedAuthProvider is configured.');\n }\n\n /**\n * Revoke an event app role using the secure RPC function\n * \n * This function uses the `revoke_event_app_role` RPC which:\n * - Runs with SECURITY DEFINER privileges\n * - Includes proper permission checks\n * - Automatically populates audit fields (revoked_by, timestamps)\n * - Complies with Row-Level Security policies\n * \n * @param params - Role revocation parameters\n * @returns Promise resolving to operation result\n */\n const revokeEventAppRole = useCallback(async (\n params: RevokeEventAppRoleParams\n ): Promise<RoleManagementResult> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const { data, error: rpcError } = await supabase.rpc('revoke_event_app_role', {\n p_user_id: params.user_id,\n p_organisation_id: params.organisation_id,\n p_event_id: params.event_id,\n p_app_id: params.app_id,\n p_role: params.role,\n p_revoked_by: params.revoked_by || user?.id || undefined\n });\n\n if (rpcError) {\n throw new Error(rpcError.message || 'Failed to revoke role');\n }\n\n return {\n success: data === true,\n message: data === true ? 'Role revoked successfully' : 'No role found to revoke',\n error: data === false ? 'No matching role found' : undefined\n };\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';\n setError(err instanceof Error ? err : new Error(errorMessage));\n return {\n success: false,\n error: errorMessage\n };\n } finally {\n setIsLoading(false);\n }\n }, [user?.id]);\n\n /**\n * Grant an event app role using the secure RPC function\n * \n * This function uses the `grant_event_app_role` RPC which:\n * - Runs with SECURITY DEFINER privileges\n * - Includes proper permission checks\n * - Automatically populates audit fields (granted_by, timestamps)\n * - Complies with Row-Level Security policies\n * \n * @param params - Role grant parameters\n * @returns Promise resolving to operation result with role ID\n */\n const grantEventAppRole = useCallback(async (\n params: GrantEventAppRoleParams\n ): Promise<RoleManagementResult> => {\n setIsLoading(true);\n setError(null);\n\n try {\n const { data, error: rpcError } = await supabase.rpc('grant_event_app_role', {\n p_user_id: params.user_id,\n p_organisation_id: params.organisation_id,\n p_event_id: params.event_id,\n p_app_id: params.app_id,\n p_role: params.role,\n p_granted_by: params.granted_by || user?.id || undefined,\n p_valid_from: params.valid_from,\n p_valid_to: params.valid_to\n });\n\n if (rpcError) {\n throw new Error(rpcError.message || 'Failed to grant role');\n }\n\n if (!data) {\n return {\n success: false,\n error: 'Failed to grant role - no role ID returned'\n };\n }\n\n return {\n success: true,\n message: 'Role granted successfully',\n roleId: data as UUID\n };\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';\n setError(err instanceof Error ? err : new Error(errorMessage));\n return {\n success: false,\n error: errorMessage\n };\n } finally {\n setIsLoading(false);\n }\n }, [user?.id]);\n\n /**\n * Revoke an event app role by role ID (alternative method)\n * \n * This fetches the role by ID first to get the required context (role name, event_id, app_id),\n * then uses the unified `rbac_role_revoke` function to revoke it.\n * \n * @param roleId - The role ID to revoke\n * @returns Promise resolving to operation result\n */\n const revokeRoleById = useCallback(async (\n roleId: UUID\n ): Promise<RoleManagementResult> => {\n setIsLoading(true);\n setError(null);\n\n try {\n // First, fetch the role by ID to get the required context\n const { data: roleData, error: fetchError } = await supabase\n .from('rbac_event_app_roles')\n .select('user_id, role, event_id, app_id')\n .eq('id', roleId)\n .single();\n\n if (fetchError || !roleData) {\n throw new Error(fetchError?.message || 'Role not found');\n }\n\n // Construct context_id in the format required by rbac_role_revoke: \"event_id:app_id\"\n const contextId = `${roleData.event_id}:${roleData.app_id}`;\n\n // Now call rbac_role_revoke with the required parameters\n const { data, error: rpcError } = await supabase.rpc('rbac_role_revoke', {\n p_user_id: roleData.user_id,\n p_role_type: 'event_app',\n p_role_name: roleData.role,\n p_context_id: contextId,\n p_revoked_by: user?.id || undefined\n });\n\n if (rpcError) {\n throw new Error(rpcError.message || 'Failed to revoke role');\n }\n\n // rbac_role_revoke returns a table with success, message, revoked_count, error_code\n const result = Array.isArray(data) && data.length > 0 ? data[0] : null;\n\n return {\n success: result?.success === true,\n message: result?.message || undefined,\n error: result?.success === false ? (result?.message || result?.error_code || 'Unknown error') : undefined\n };\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred';\n setError(err instanceof Error ? err : new Error(errorMessage));\n return {\n success: false,\n error: errorMessage\n };\n } finally {\n setIsLoading(false);\n }\n }, [user?.id, supabase]);\n\n return {\n revokeEventAppRole,\n grantEventAppRole,\n revokeRoleById,\n isLoading,\n error\n };\n}\n\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect","error","useState","useEffect","useCallback","useMemo","useRef","useState","useRef","useEffect","useCallback","logger","useMemo","useMemo","error","useMemo","useState","useCallback"]}
|