@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.
Files changed (158) hide show
  1. package/dist/{DataTable-EGIN2NKK.js → DataTable-SKCX4SCB.js} +6 -6
  2. package/dist/{UnifiedAuthProvider-XIQQ7LVU.js → UnifiedAuthProvider-BMJAP6Z7.js} +3 -3
  3. package/dist/{chunk-22WKWKRX.js → chunk-2AKRP5QZ.js} +4 -4
  4. package/dist/{chunk-4C7EXCAR.js → chunk-CRGFNQ2L.js} +4 -4
  5. package/dist/{chunk-WKTQM2IC.js → chunk-E6ZCVF4T.js} +4 -4
  6. package/dist/{chunk-INQLMHPF.js → chunk-ERGKJX4D.js} +2 -2
  7. package/dist/{chunk-6LAAY47Q.js → chunk-MSHEVJXS.js} +2 -2
  8. package/dist/{chunk-MA6EPSGZ.js → chunk-PKW27QVS.js} +2 -2
  9. package/dist/{chunk-T6JN6LH6.js → chunk-R53TUSFK.js} +3 -3
  10. package/dist/{chunk-PZV3XZKJ.js → chunk-SFVL7ZFI.js} +5 -5
  11. package/dist/{chunk-3R472UXR.js → chunk-VOJBGZYI.js} +3 -3
  12. package/dist/{chunk-ALUN6O3G.js → chunk-VP44VQJ6.js} +25 -14
  13. package/dist/chunk-VP44VQJ6.js.map +1 -0
  14. package/dist/{chunk-YCWDTTUK.js → chunk-WM26XK7I.js} +22 -8
  15. package/dist/chunk-WM26XK7I.js.map +1 -0
  16. package/dist/components.js +8 -8
  17. package/dist/hooks.js +7 -7
  18. package/dist/index.js +11 -11
  19. package/dist/providers.js +2 -2
  20. package/dist/rbac/index.js +7 -7
  21. package/dist/utils.js +1 -1
  22. package/docs/api/classes/ColumnFactory.md +1 -1
  23. package/docs/api/classes/ErrorBoundary.md +1 -1
  24. package/docs/api/classes/InvalidScopeError.md +1 -1
  25. package/docs/api/classes/MissingUserContextError.md +1 -1
  26. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  27. package/docs/api/classes/PermissionDeniedError.md +1 -1
  28. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  29. package/docs/api/classes/RBACAuditManager.md +1 -1
  30. package/docs/api/classes/RBACCache.md +1 -1
  31. package/docs/api/classes/RBACEngine.md +1 -1
  32. package/docs/api/classes/RBACError.md +1 -1
  33. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  34. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  35. package/docs/api/classes/StorageUtils.md +1 -1
  36. package/docs/api/enums/FileCategory.md +1 -1
  37. package/docs/api/interfaces/AggregateConfig.md +1 -1
  38. package/docs/api/interfaces/BadgeProps.md +1 -1
  39. package/docs/api/interfaces/ButtonProps.md +1 -1
  40. package/docs/api/interfaces/CalendarProps.md +1 -1
  41. package/docs/api/interfaces/CardProps.md +1 -1
  42. package/docs/api/interfaces/ColorPalette.md +1 -1
  43. package/docs/api/interfaces/ColorShade.md +1 -1
  44. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  45. package/docs/api/interfaces/DataRecord.md +1 -1
  46. package/docs/api/interfaces/DataTableAction.md +1 -1
  47. package/docs/api/interfaces/DataTableColumn.md +1 -1
  48. package/docs/api/interfaces/DataTableProps.md +1 -1
  49. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  50. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  51. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  52. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  53. package/docs/api/interfaces/EventLogoProps.md +1 -1
  54. package/docs/api/interfaces/ExportColumn.md +1 -1
  55. package/docs/api/interfaces/ExportOptions.md +1 -1
  56. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  57. package/docs/api/interfaces/FileMetadata.md +1 -1
  58. package/docs/api/interfaces/FileReference.md +1 -1
  59. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  60. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  61. package/docs/api/interfaces/FileUploadProps.md +1 -1
  62. package/docs/api/interfaces/FooterProps.md +1 -1
  63. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  64. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  65. package/docs/api/interfaces/InputProps.md +1 -1
  66. package/docs/api/interfaces/LabelProps.md +1 -1
  67. package/docs/api/interfaces/LoginFormProps.md +1 -1
  68. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  69. package/docs/api/interfaces/NavigationContextType.md +1 -1
  70. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  71. package/docs/api/interfaces/NavigationItem.md +1 -1
  72. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  73. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  74. package/docs/api/interfaces/Organisation.md +1 -1
  75. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  76. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  77. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  78. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  79. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  80. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  81. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  82. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  83. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  84. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  85. package/docs/api/interfaces/PaletteData.md +1 -1
  86. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  87. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  88. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  89. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  90. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  91. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  92. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  93. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  94. package/docs/api/interfaces/RBACConfig.md +1 -1
  95. package/docs/api/interfaces/RBACLogger.md +1 -1
  96. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  97. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  98. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  99. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  100. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  101. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  102. package/docs/api/interfaces/RouteConfig.md +1 -1
  103. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  104. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  105. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  106. package/docs/api/interfaces/StorageConfig.md +1 -1
  107. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  108. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  109. package/docs/api/interfaces/StorageListOptions.md +1 -1
  110. package/docs/api/interfaces/StorageListResult.md +1 -1
  111. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  112. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  113. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  114. package/docs/api/interfaces/StyleImport.md +1 -1
  115. package/docs/api/interfaces/SwitchProps.md +1 -1
  116. package/docs/api/interfaces/TabsContentProps.md +1 -1
  117. package/docs/api/interfaces/TabsListProps.md +1 -1
  118. package/docs/api/interfaces/TabsProps.md +1 -1
  119. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  120. package/docs/api/interfaces/TextareaProps.md +1 -1
  121. package/docs/api/interfaces/ToastActionElement.md +1 -1
  122. package/docs/api/interfaces/ToastProps.md +1 -1
  123. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  124. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  125. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  126. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  127. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  128. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  129. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  130. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  131. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  132. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  133. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  134. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  135. package/docs/api/interfaces/UserEventAccess.md +1 -1
  136. package/docs/api/interfaces/UserMenuProps.md +1 -1
  137. package/docs/api/interfaces/UserProfile.md +1 -1
  138. package/docs/api/modules.md +2 -2
  139. package/docs/rbac/README.md +11 -5
  140. package/docs/rbac/event-based-apps.md +872 -0
  141. package/package.json +1 -1
  142. package/src/components/NavigationMenu/NavigationMenu.tsx +32 -7
  143. package/src/services/EventService.ts +29 -8
  144. package/src/services/__tests__/EventService.test.ts +48 -8
  145. package/dist/chunk-ALUN6O3G.js.map +0 -1
  146. package/dist/chunk-YCWDTTUK.js.map +0 -1
  147. package/src/rbac/docs/event-based-apps.md +0 -285
  148. /package/dist/{DataTable-EGIN2NKK.js.map → DataTable-SKCX4SCB.js.map} +0 -0
  149. /package/dist/{UnifiedAuthProvider-XIQQ7LVU.js.map → UnifiedAuthProvider-BMJAP6Z7.js.map} +0 -0
  150. /package/dist/{chunk-22WKWKRX.js.map → chunk-2AKRP5QZ.js.map} +0 -0
  151. /package/dist/{chunk-4C7EXCAR.js.map → chunk-CRGFNQ2L.js.map} +0 -0
  152. /package/dist/{chunk-WKTQM2IC.js.map → chunk-E6ZCVF4T.js.map} +0 -0
  153. /package/dist/{chunk-INQLMHPF.js.map → chunk-ERGKJX4D.js.map} +0 -0
  154. /package/dist/{chunk-6LAAY47Q.js.map → chunk-MSHEVJXS.js.map} +0 -0
  155. /package/dist/{chunk-MA6EPSGZ.js.map → chunk-PKW27QVS.js.map} +0 -0
  156. /package/dist/{chunk-T6JN6LH6.js.map → chunk-R53TUSFK.js.map} +0 -0
  157. /package/dist/{chunk-PZV3XZKJ.js.map → chunk-SFVL7ZFI.js.map} +0 -0
  158. /package/dist/{chunk-3R472UXR.js.map → chunk-VOJBGZYI.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmruthers/pace-core",
3
- "version": "0.5.141",
3
+ "version": "0.5.143",
4
4
  "description": "Clean, modern React component library with Tailwind v4 styling and native utilities",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -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
- // During initial load or when scope is loading, show nothing
508
- if (!authContext || !rbacContext || scopeLoading || !resolvedScope?.organisationId) {
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 === 0) {
438
- return null;
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
- // Sort by date (ascending) to get the next event
442
- const sortedFutureEvents = futureEvents.sort((a, b) => {
443
- const dateA = new Date(a.event_date!);
444
- const dateB = new Date(b.event_date!);
445
- return dateA.getTime() - dateB.getTime();
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
- return sortedFutureEvents[0];
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 null when no future events', async () => {
271
- const pastEvent: Event = {
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: [pastEvent],
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
- expect(nextEvent).toBeNull();
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 null when all events are in the past', () => {
764
- const pastEvent: Event = {
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([pastEvent]);
783
+ const nextEvent = eventService.getNextEventByDate([pastEvent1, pastEvent2]);
770
784
 
771
- expect(nextEvent).toBeNull();
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,