@jmruthers/pace-core 0.5.141 → 0.5.143
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-EGIN2NKK.js → DataTable-SKCX4SCB.js} +6 -6
- package/dist/{UnifiedAuthProvider-XIQQ7LVU.js → UnifiedAuthProvider-BMJAP6Z7.js} +3 -3
- package/dist/{chunk-22WKWKRX.js → chunk-2AKRP5QZ.js} +4 -4
- package/dist/{chunk-4C7EXCAR.js → chunk-CRGFNQ2L.js} +4 -4
- package/dist/{chunk-WKTQM2IC.js → chunk-E6ZCVF4T.js} +4 -4
- package/dist/{chunk-INQLMHPF.js → chunk-ERGKJX4D.js} +2 -2
- package/dist/{chunk-6LAAY47Q.js → chunk-MSHEVJXS.js} +2 -2
- package/dist/{chunk-MA6EPSGZ.js → chunk-PKW27QVS.js} +2 -2
- package/dist/{chunk-T6JN6LH6.js → chunk-R53TUSFK.js} +3 -3
- package/dist/{chunk-PZV3XZKJ.js → chunk-SFVL7ZFI.js} +5 -5
- package/dist/{chunk-3R472UXR.js → chunk-VOJBGZYI.js} +3 -3
- package/dist/{chunk-ALUN6O3G.js → chunk-VP44VQJ6.js} +25 -14
- package/dist/chunk-VP44VQJ6.js.map +1 -0
- package/dist/{chunk-YCWDTTUK.js → chunk-WM26XK7I.js} +22 -8
- package/dist/chunk-WM26XK7I.js.map +1 -0
- package/dist/components.js +8 -8
- package/dist/hooks.js +7 -7
- package/dist/index.js +11 -11
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +7 -7
- 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 +2 -2
- package/docs/rbac/README.md +11 -5
- package/docs/rbac/event-based-apps.md +872 -0
- package/package.json +1 -1
- package/src/components/NavigationMenu/NavigationMenu.tsx +32 -7
- package/src/services/EventService.ts +29 -8
- package/src/services/__tests__/EventService.test.ts +48 -8
- package/dist/chunk-ALUN6O3G.js.map +0 -1
- package/dist/chunk-YCWDTTUK.js.map +0 -1
- package/src/rbac/docs/event-based-apps.md +0 -285
- /package/dist/{DataTable-EGIN2NKK.js.map → DataTable-SKCX4SCB.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-XIQQ7LVU.js.map → UnifiedAuthProvider-BMJAP6Z7.js.map} +0 -0
- /package/dist/{chunk-22WKWKRX.js.map → chunk-2AKRP5QZ.js.map} +0 -0
- /package/dist/{chunk-4C7EXCAR.js.map → chunk-CRGFNQ2L.js.map} +0 -0
- /package/dist/{chunk-WKTQM2IC.js.map → chunk-E6ZCVF4T.js.map} +0 -0
- /package/dist/{chunk-INQLMHPF.js.map → chunk-ERGKJX4D.js.map} +0 -0
- /package/dist/{chunk-6LAAY47Q.js.map → chunk-MSHEVJXS.js.map} +0 -0
- /package/dist/{chunk-MA6EPSGZ.js.map → chunk-PKW27QVS.js.map} +0 -0
- /package/dist/{chunk-T6JN6LH6.js.map → chunk-R53TUSFK.js.map} +0 -0
- /package/dist/{chunk-PZV3XZKJ.js.map → chunk-SFVL7ZFI.js.map} +0 -0
- /package/dist/{chunk-3R472UXR.js.map → chunk-VOJBGZYI.js.map} +0 -0
package/package.json
CHANGED
|
@@ -437,14 +437,24 @@ export const NavigationMenu = React.forwardRef<
|
|
|
437
437
|
logger.warn('NavigationMenu', 'useRBAC not available, permission filtering disabled');
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
// Get event context state for checking readiness
|
|
441
|
+
// Store the raw value to check if it's undefined (tests without event provider)
|
|
442
|
+
const eventLoadingRaw = authContext?.eventLoading;
|
|
443
|
+
const eventLoading = eventLoadingRaw ?? false;
|
|
444
|
+
const selectedEvent = authContext?.selectedEvent || null;
|
|
445
|
+
// Check org context readiness: use isContextReady if available, otherwise fall back to checking selectedOrganisation
|
|
446
|
+
// This handles both production (with isContextReady) and test scenarios (without it)
|
|
447
|
+
const orgContextReady = authContext?.isContextReady ?? (authContext?.selectedOrganisation?.id ? true : false);
|
|
448
|
+
|
|
440
449
|
// Get resolved scope for permission checks
|
|
450
|
+
// Note: Always call useResolvedScope (hooks must be called unconditionally)
|
|
451
|
+
// When filterByPermissions is false, we'll simply ignore the resolved scope
|
|
441
452
|
const { supabase } = authContext || {};
|
|
442
453
|
const { selectedOrganisation } = authContext || {};
|
|
443
|
-
const selectedEvent = authContext?.selectedEvent || null;
|
|
444
454
|
const { resolvedScope, isLoading: scopeLoading } = useResolvedScope({
|
|
445
|
-
supabase: supabase || null,
|
|
446
|
-
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
447
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
455
|
+
supabase: filterByPermissions ? (supabase || null) : null,
|
|
456
|
+
selectedOrganisationId: filterByPermissions ? (selectedOrganisation?.id || null) : null,
|
|
457
|
+
selectedEventId: filterByPermissions ? (selectedEvent?.event_id || null) : null
|
|
448
458
|
});
|
|
449
459
|
|
|
450
460
|
// Stabilize scope object to prevent unnecessary permission refetches
|
|
@@ -504,9 +514,18 @@ export const NavigationMenu = React.forwardRef<
|
|
|
504
514
|
// Security: If filtering is enabled but we're missing required context or still loading, show NO items
|
|
505
515
|
// This prevents security risk of showing items before permissions are verified
|
|
506
516
|
if (filterByPermissions) {
|
|
507
|
-
//
|
|
508
|
-
|
|
517
|
+
// CRITICAL: Wait for BOTH organisation AND event context to be ready before checking permissions
|
|
518
|
+
// This prevents the error "No organisation or event context available" when event context is still loading
|
|
519
|
+
const isOrgContextReady = orgContextReady && selectedOrganisation?.id;
|
|
520
|
+
// Event context is ready when not loading
|
|
521
|
+
// If eventLoadingRaw is undefined (tests without event provider), consider it ready
|
|
522
|
+
// Only wait for event context if we're actually loading events
|
|
523
|
+
const isEventContextReady = eventLoadingRaw === undefined ? true : !eventLoading;
|
|
524
|
+
|
|
525
|
+
// During initial load or when scope/context is loading, show nothing
|
|
526
|
+
if (!authContext || !rbacContext || scopeLoading || !isOrgContextReady || !isEventContextReady) {
|
|
509
527
|
// Still loading - show nothing to prevent security risk
|
|
528
|
+
// Note: We check both org and event context readiness to prevent premature permission checks
|
|
510
529
|
return [];
|
|
511
530
|
}
|
|
512
531
|
|
|
@@ -711,7 +730,13 @@ export const NavigationMenu = React.forwardRef<
|
|
|
711
730
|
scopeLoading,
|
|
712
731
|
permissionsLoading,
|
|
713
732
|
resolvedScope,
|
|
714
|
-
auditLog
|
|
733
|
+
auditLog,
|
|
734
|
+
// Add event context state to dependencies so we re-check permissions when event context becomes available
|
|
735
|
+
eventLoadingRaw,
|
|
736
|
+
eventLoading,
|
|
737
|
+
selectedEvent,
|
|
738
|
+
orgContextReady,
|
|
739
|
+
selectedOrganisation?.id
|
|
715
740
|
]);
|
|
716
741
|
|
|
717
742
|
// Log navigation access attempts for debugging
|
|
@@ -434,17 +434,38 @@ export class EventService extends BaseService implements IEventService {
|
|
|
434
434
|
return startOfEventDate >= startOfToday;
|
|
435
435
|
});
|
|
436
436
|
|
|
437
|
-
if (futureEvents.length
|
|
438
|
-
|
|
437
|
+
if (futureEvents.length > 0) {
|
|
438
|
+
// Sort by date (ascending) to get the next event
|
|
439
|
+
const sortedFutureEvents = futureEvents.sort((a, b) => {
|
|
440
|
+
const dateA = new Date(a.event_date!);
|
|
441
|
+
const dateB = new Date(b.event_date!);
|
|
442
|
+
return dateA.getTime() - dateB.getTime();
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
return sortedFutureEvents[0];
|
|
439
446
|
}
|
|
440
447
|
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
448
|
+
// Fallback: If no future events found, return the most recent past event
|
|
449
|
+
// This handles cases where users only have access to past events
|
|
450
|
+
const pastEvents = eventsToUse.filter(event => {
|
|
451
|
+
if (!event.event_date) return false;
|
|
452
|
+
const eventDate = new Date(event.event_date);
|
|
453
|
+
const startOfEventDate = new Date(eventDate.getFullYear(), eventDate.getMonth(), eventDate.getDate()).getTime();
|
|
454
|
+
return startOfEventDate < startOfToday;
|
|
446
455
|
});
|
|
447
456
|
|
|
448
|
-
|
|
457
|
+
if (pastEvents.length > 0) {
|
|
458
|
+
// Sort by date (descending) to get the most recent past event
|
|
459
|
+
const sortedPastEvents = pastEvents.sort((a, b) => {
|
|
460
|
+
const dateA = new Date(a.event_date!);
|
|
461
|
+
const dateB = new Date(b.event_date!);
|
|
462
|
+
return dateB.getTime() - dateA.getTime(); // Descending order
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
return sortedPastEvents[0];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// No events found at all
|
|
469
|
+
return null;
|
|
449
470
|
}
|
|
450
471
|
}
|
|
@@ -267,14 +267,20 @@ describe('EventService', () => {
|
|
|
267
267
|
expect(nextEvent).toEqual(mockEvent2); // Future event should be selected
|
|
268
268
|
});
|
|
269
269
|
|
|
270
|
-
it('should return
|
|
271
|
-
const
|
|
270
|
+
it('should return most recent past event when no future events', async () => {
|
|
271
|
+
const pastEvent1: Event = {
|
|
272
272
|
...mockEvent,
|
|
273
|
+
event_id: 'event-past-1',
|
|
273
274
|
event_date: '2020-01-01T00:00:00Z'
|
|
274
275
|
};
|
|
276
|
+
const pastEvent2: Event = {
|
|
277
|
+
...mockEvent,
|
|
278
|
+
event_id: 'event-past-2',
|
|
279
|
+
event_date: '2021-06-15T00:00:00Z' // More recent past event
|
|
280
|
+
};
|
|
275
281
|
|
|
276
282
|
mockSupabase.rpc.mockResolvedValue({
|
|
277
|
-
data: [
|
|
283
|
+
data: [pastEvent1, pastEvent2],
|
|
278
284
|
error: null
|
|
279
285
|
});
|
|
280
286
|
|
|
@@ -290,7 +296,9 @@ describe('EventService', () => {
|
|
|
290
296
|
await service.initialize();
|
|
291
297
|
|
|
292
298
|
const nextEvent = service.getNextEventByDate();
|
|
293
|
-
|
|
299
|
+
// Should return the most recent past event (pastEvent2)
|
|
300
|
+
expect(nextEvent).not.toBeNull();
|
|
301
|
+
expect(nextEvent?.event_id).toBe('event-past-2');
|
|
294
302
|
});
|
|
295
303
|
|
|
296
304
|
it('should persist event selection', async () => {
|
|
@@ -760,15 +768,23 @@ describe('EventService', () => {
|
|
|
760
768
|
expect(nextEvent).toBeNull();
|
|
761
769
|
});
|
|
762
770
|
|
|
763
|
-
it('should return
|
|
764
|
-
const
|
|
771
|
+
it('should return most recent past event when all events are in the past', () => {
|
|
772
|
+
const pastEvent1: Event = {
|
|
765
773
|
...mockEvent,
|
|
774
|
+
event_id: 'event-past-1',
|
|
766
775
|
event_date: '2020-01-01T00:00:00Z'
|
|
767
776
|
};
|
|
777
|
+
const pastEvent2: Event = {
|
|
778
|
+
...mockEvent,
|
|
779
|
+
event_id: 'event-past-2',
|
|
780
|
+
event_date: '2022-03-20T00:00:00Z' // More recent past event
|
|
781
|
+
};
|
|
768
782
|
|
|
769
|
-
const nextEvent = eventService.getNextEventByDate([
|
|
783
|
+
const nextEvent = eventService.getNextEventByDate([pastEvent1, pastEvent2]);
|
|
770
784
|
|
|
771
|
-
|
|
785
|
+
// Should return the most recent past event (pastEvent2)
|
|
786
|
+
expect(nextEvent).not.toBeNull();
|
|
787
|
+
expect(nextEvent?.event_id).toBe('event-past-2');
|
|
772
788
|
});
|
|
773
789
|
|
|
774
790
|
it('should select event on today\'s date', () => {
|
|
@@ -826,6 +842,30 @@ describe('EventService', () => {
|
|
|
826
842
|
expect(nextEvent?.event_date).toBe(futureDate2.toISOString());
|
|
827
843
|
});
|
|
828
844
|
|
|
845
|
+
it('should prefer future events over past events when both exist', () => {
|
|
846
|
+
const today = new Date();
|
|
847
|
+
const pastDate = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000); // 30 days ago
|
|
848
|
+
const futureDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000); // 30 days from now
|
|
849
|
+
|
|
850
|
+
const pastEvent: Event = {
|
|
851
|
+
...mockEvent,
|
|
852
|
+
event_id: 'event-past',
|
|
853
|
+
event_date: pastDate.toISOString()
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const futureEvent: Event = {
|
|
857
|
+
...mockEvent,
|
|
858
|
+
event_id: 'event-future',
|
|
859
|
+
event_date: futureDate.toISOString()
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
const nextEvent = eventService.getNextEventByDate([pastEvent, futureEvent]);
|
|
863
|
+
|
|
864
|
+
// Should prefer future event even if past event is more recent
|
|
865
|
+
expect(nextEvent).not.toBeNull();
|
|
866
|
+
expect(nextEvent?.event_id).toBe('event-future');
|
|
867
|
+
});
|
|
868
|
+
|
|
829
869
|
it('should filter out events without dates', () => {
|
|
830
870
|
const eventWithoutDate: Event = {
|
|
831
871
|
...mockEvent,
|