@jmruthers/pace-core 0.6.2 → 0.6.4
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/CHANGELOG.md +45 -0
- package/cursor-rules/00-pace-core-compliance.mdc +34 -2
- package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
- package/dist/{DataTable-TPTKCX4D.js → DataTable-E7YQZD7D.js} +9 -8
- package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
- package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
- package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-QPXO24B4.js} +5 -4
- package/dist/{api-MVVQZLJI.js → api-6LVZTHDS.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/chunk-36LVWXB2.js +227 -0
- package/dist/chunk-36LVWXB2.js.map +1 -0
- package/dist/{chunk-24UVZUZG.js → chunk-3LPHPB62.js} +129 -387
- package/dist/chunk-3LPHPB62.js.map +1 -0
- package/dist/{chunk-2UOI2FG5.js → chunk-5EC5MEWX.js} +4 -4
- package/dist/{chunk-3XC4CPTD.js → chunk-7JPAB3T5.js} +244 -5727
- package/dist/chunk-7JPAB3T5.js.map +1 -0
- package/dist/{chunk-6J4GEEJR.js → chunk-ATKZM7RX.js} +53 -27
- package/dist/chunk-ATKZM7RX.js.map +1 -0
- package/dist/{chunk-EHMR7VYL.js → chunk-AVMLPIM7.js} +443 -189
- package/dist/chunk-AVMLPIM7.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-NECFR5MM.js → chunk-I6DAQMWX.js} +575 -647
- package/dist/chunk-I6DAQMWX.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
- package/dist/{chunk-XWQCNGTQ.js → chunk-NN6WWZ5U.js} +173 -79
- package/dist/chunk-NN6WWZ5U.js.map +1 -0
- package/dist/{chunk-MMZ7JXPU.js → chunk-OEWDTMG7.js} +13 -21
- package/dist/{chunk-MMZ7JXPU.js.map → chunk-OEWDTMG7.js.map} +1 -1
- package/dist/{chunk-SFZUDBL5.js → chunk-YKRAFF5K.js} +70 -56
- package/dist/chunk-YKRAFF5K.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +12 -13
- package/dist/contextValidator-OOPCLPZW.js +9 -0
- package/dist/contextValidator-OOPCLPZW.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -6
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +21 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +67 -27
- package/dist/rbac/index.js +15 -8
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
- package/dist/utils.js +5 -7
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +14 -16
- package/docs/api/modules.md +3796 -2513
- package/docs/components/context-selector.md +126 -0
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/secure-client-protection.md +330 -0
- package/package.json +10 -5
- package/scripts/audit/core/checks/compliance.cjs +72 -0
- package/scripts/audit/core/checks/dependencies.cjs +568 -28
- package/scripts/audit/core/checks/documentation.cjs +68 -3
- package/scripts/audit/core/checks/environment.cjs +2 -14
- package/scripts/audit/core/checks/error-handling.cjs +47 -6
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/components/RowComponent.tsx +19 -19
- package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
- package/src/components/Dialog/Dialog.tsx +29 -1
- package/src/components/FileDisplay/FileDisplay.tsx +42 -10
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +44 -45
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +157 -36
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +2 -2
- package/src/components/index.ts +5 -5
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useFileDisplay.ts +77 -50
- package/src/index.ts +4 -5
- package/src/providers/services/AuthServiceProvider.tsx +17 -7
- package/src/providers/services/EventServiceProvider.tsx +33 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/adapters.tsx +2 -2
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +178 -132
- package/src/rbac/components/PagePermissionGuard.tsx +38 -10
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
- package/src/rbac/hooks/permissions/useCan.ts +41 -11
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +0 -9
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useSecureSupabase.ts +7 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +103 -16
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +1 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +285 -56
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -0
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/dist/chunk-24UVZUZG.js.map +0 -1
- package/dist/chunk-3XC4CPTD.js.map +0 -1
- package/dist/chunk-6J4GEEJR.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-EHMR7VYL.js.map +0 -1
- package/dist/chunk-NECFR5MM.js.map +0 -1
- package/dist/chunk-SFZUDBL5.js.map +0 -1
- package/dist/chunk-XWQCNGTQ.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -163
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -34
- package/docs/api/interfaces/ButtonProps.md +0 -56
- package/docs/api/interfaces/CalendarProps.md +0 -73
- package/docs/api/interfaces/CardProps.md +0 -69
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -252
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
- package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -296
- package/docs/api/interfaces/FooterProps.md +0 -107
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -56
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -187
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -12
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -121
- package/docs/api/interfaces/UserMenuProps.md +0 -88
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -423
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
- package/src/components/OrganisationSelector/index.ts +0 -9
- /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-E7YQZD7D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-QPXO24B4.js.map} +0 -0
- /package/dist/{api-MVVQZLJI.js.map → api-6LVZTHDS.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-2UOI2FG5.js.map → chunk-5EC5MEWX.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useEvents.ts","../src/components/ErrorBoundary/ErrorBoundaryContext.tsx","../src/components/ErrorBoundary/ErrorBoundary.tsx","../src/components/ErrorBoundary/index.ts","../src/components/PublicLayout/PublicPageProvider.tsx","../src/hooks/useAppConfig.ts","../src/hooks/useOrganisationSecurity.ts"],"sourcesContent":["/**\n * @file Event Hook\n * @package @jmruthers/pace-core\n * @module Hooks\n * @since 0.1.0\n *\n * Convenience hook for accessing event context.\n * This hook provides a simple interface for working with events.\n * \n * Note: This is a convenience wrapper around the EventService.\n * For better performance, consider using useEventService directly.\n */\n\nimport { useMemo, useRef } from 'react';\nimport { useEventService } from './services/useEventService';\nimport { Event } from '../types/event';\n\nexport interface EventContextType {\n events: Event[];\n selectedEvent: Event | null;\n isLoading: boolean;\n error: Error | null;\n setSelectedEvent: (event: Event | null) => void;\n refreshEvents: () => Promise<void>;\n clearEventSelection: () => Promise<void>;\n}\n\n/**\n * Hook to access event context\n * \n * @returns Event context with events, selected event, and helper methods\n * @throws {Error} If used outside EventServiceProvider\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { events, selectedEvent, setSelectedEvent } = useEvents();\n * \n * return (\n * <div>\n * {events.map(event => (\n * <button key={event.id} onClick={() => setSelectedEvent(event)}>\n * {event.event_name}\n * </button>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useEvents(): EventContextType {\n const eventService = useEventService();\n\n // Get current state - service subscription will trigger re-renders when state changes\n const rawEvents = eventService.getEvents();\n const selectedEvent = eventService.getSelectedEvent();\n const isLoading = eventService.isLoading();\n const error = eventService.getError();\n\n // Use refs to track previous values and only create new array reference when events actually change\n const prevEventsRef = useRef<Event[]>([]);\n const prevEventsIdsRef = useRef<string>('');\n \n // Compare event IDs to detect actual changes (more efficient than deep comparison)\n // Do this outside useMemo to avoid dependency on rawEvents array\n const currentEventsIds = rawEvents.map(e => e.event_id || e.id).join(',');\n const eventsChanged = currentEventsIds !== prevEventsIdsRef.current;\n \n // Only create new array reference when events actually changed\n // Update refs synchronously, then memoize based on the ID string\n if (eventsChanged) {\n prevEventsRef.current = rawEvents;\n prevEventsIdsRef.current = currentEventsIds;\n }\n \n // Return stable array reference - only changes when event IDs change\n const events = useMemo(() => {\n return prevEventsRef.current;\n }, [currentEventsIds]);\n\n // Memoize callbacks to prevent unnecessary re-renders in child components\n const setSelectedEventCallback = useMemo(\n () => (event: Event | null) => eventService.setSelectedEvent(event),\n [eventService]\n );\n const refreshEventsCallback = useMemo(\n () => () => eventService.refreshEvents(),\n [eventService]\n );\n const clearEventSelectionCallback = useMemo(\n () => () => eventService.clearEventSelection(),\n [eventService]\n );\n\n // Memoize the return object - only recreate when actual values change\n return useMemo(() => ({\n events,\n selectedEvent,\n isLoading,\n error,\n setSelectedEvent: setSelectedEventCallback,\n refreshEvents: refreshEventsCallback,\n clearEventSelection: clearEventSelectionCallback,\n }), [events, selectedEvent?.event_id, isLoading, error?.message, setSelectedEventCallback, refreshEventsCallback, clearEventSelectionCallback]);\n}\n\n","/**\n * @file Error Boundary Context\n * @package @jmruthers/pace-core\n * @module Components/ErrorBoundary\n * @since 0.6.2\n *\n * Context provider for global error handler configuration in ErrorBoundary components.\n * Allows applications to register a default error handler once, which is then used by\n * all ErrorBoundary instances unless overridden.\n *\n * @example\n * ```tsx\n * // Setup once in main.tsx\n * <ErrorBoundaryProvider\n * defaultErrorHandler={(error, errorInfo, errorId, componentName) => {\n * if (import.meta.env.DEV) {\n * console.error(`[${componentName}] ErrorBoundary caught error:`, error, errorInfo);\n * }\n * errorTracking.captureException(error, {\n * componentStack: errorInfo.componentStack,\n * errorBoundary: true,\n * errorId,\n * componentName,\n * });\n * }}\n * >\n * <App />\n * </ErrorBoundaryProvider>\n *\n * // Usage - no onError needed, uses global handler\n * <ErrorBoundary componentName=\"AppRoot\">\n * <AppContent />\n * </ErrorBoundary>\n * ```\n */\n\nimport React, { createContext, useContext, ReactNode } from 'react';\n\n/**\n * Type definition for the global error handler function\n * @public\n */\nexport type GlobalErrorHandler = (\n error: Error,\n errorInfo: React.ErrorInfo,\n errorId: string,\n componentName: string\n) => void;\n\n/**\n * Context value type for ErrorBoundary configuration\n * @internal\n */\ninterface ErrorBoundaryContextType {\n /** Global error handler that will be used by all ErrorBoundary instances */\n defaultErrorHandler?: GlobalErrorHandler;\n}\n\n/**\n * Context for ErrorBoundary global configuration\n * @internal\n */\nconst ErrorBoundaryContext = createContext<ErrorBoundaryContextType | undefined>(undefined);\n\n/**\n * Props for ErrorBoundaryProvider component\n * @public\n */\nexport interface ErrorBoundaryProviderProps {\n /** Children to wrap with the provider */\n children: ReactNode;\n /** Global error handler that will be used by all ErrorBoundary instances unless overridden */\n defaultErrorHandler?: GlobalErrorHandler;\n}\n\n/**\n * Provider component for ErrorBoundary global error handler configuration\n * \n * Wrap your application with this provider to set a default error handler that will\n * be used by all ErrorBoundary instances unless they provide their own onError prop.\n * \n * @example\n * ```tsx\n * <ErrorBoundaryProvider\n * defaultErrorHandler={(error, errorInfo, errorId, componentName) => {\n * errorTracking.captureException(error, {\n * componentStack: errorInfo.componentStack,\n * errorBoundary: true,\n * errorId,\n * componentName,\n * });\n * }}\n * >\n * <App />\n * </ErrorBoundaryProvider>\n * ```\n * \n * @public\n */\n/**\n * Error boundary provider component.\n * Provides global error handling configuration to child ErrorBoundary components.\n * \n * @param props - Error boundary provider configuration\n * @returns The error boundary provider\n */\nexport function ErrorBoundaryProvider({\n children,\n defaultErrorHandler,\n}: ErrorBoundaryProviderProps) {\n const contextValue: ErrorBoundaryContextType = {\n defaultErrorHandler,\n };\n\n return (\n <ErrorBoundaryContext.Provider value={contextValue}>\n {children}\n </ErrorBoundaryContext.Provider>\n );\n}\n\n/**\n * Hook to access ErrorBoundary context\n * @internal\n */\nexport function useErrorBoundaryContext(): ErrorBoundaryContextType | undefined {\n return useContext(ErrorBoundaryContext);\n}\n\n","/**\n * @file Error Boundary Component\n * @package @jmruthers/pace-core\n * @module Components/ErrorBoundary\n * @since 0.1.0\n *\n * A comprehensive error boundary component that catches JavaScript errors in child components\n * and provides fallback UI with retry functionality and error reporting.\n *\n * Features:\n * - Catches JavaScript errors in component tree\n * - Custom fallback UI with retry functionality\n * - Error reporting and logging\n * - Performance monitoring integration\n * - Retry mechanism with configurable attempts\n * - Development mode error details\n * - Accessibility compliant error display\n * - Component-specific error tracking\n * - Error ID generation for tracking\n *\n * @example\n * ```tsx\n * // Basic error boundary\n * <ErrorBoundary>\n * <MyComponent />\n * </ErrorBoundary>\n * \n * // Using global error handler (recommended for applications)\n * import { ErrorBoundaryProvider, ErrorBoundary } from '@jmruthers/pace-core';\n * \n * // Setup once in main.tsx\n * <ErrorBoundaryProvider\n * defaultErrorHandler={(error, errorInfo, errorId, componentName) => {\n * if (import.meta.env.DEV) {\n * console.error(`[${componentName}] ErrorBoundary caught error:`, error, errorInfo);\n * }\n * errorTracking.captureException(error, {\n * componentStack: errorInfo.componentStack,\n * errorBoundary: true,\n * errorId,\n * componentName,\n * });\n * }}\n * >\n * <App />\n * </ErrorBoundaryProvider>\n * \n * // Then use ErrorBoundary without onError prop - uses global handler\n * <ErrorBoundary componentName=\"AppRoot\">\n * <AppContent />\n * </ErrorBoundary>\n * \n * // Error boundary with custom fallback\n * <ErrorBoundary\n * componentName=\"UserProfile\"\n * fallback={<section>Something went wrong loading the profile.</section>}\n * onError={(error, errorInfo, errorId) => {\n * console.log('Error caught:', errorId, error);\n * }}\n * >\n * <UserProfile />\n * </ErrorBoundary>\n * \n * // Error boundary with retry functionality\n * <ErrorBoundary\n * componentName=\"DataTable\"\n * maxRetries={3}\n * enableRetry={true}\n * enableReporting={true}\n * onError={(error, errorInfo, errorId) => {\n * // Send to error reporting service\n * errorReportingService.report({ error, errorInfo, errorId });\n * }}\n * >\n * <DataTable data={data} />\n * </ErrorBoundary>\n * \n * // Wrapping multiple components\n * <ErrorBoundary componentName=\"App\">\n * <Header />\n * <ErrorBoundary componentName=\"MainContent\">\n * <MainContent />\n * </ErrorBoundary>\n * <Footer />\n * </ErrorBoundary>\n * \n * // Per-instance onError overrides global handler\n * <ErrorBoundary \n * componentName=\"SpecialCase\"\n * onError={(error, errorInfo, errorId) => {\n * // Custom handling for this specific case - global handler won't be called\n * }}\n * >\n * <SpecialComponent />\n * </ErrorBoundary>\n * ```\n *\n * @accessibility\n * - WCAG 2.1 AA compliant\n * - Proper ARIA role=\"alert\" for error announcements\n * - Screen reader friendly error messages\n * - Keyboard accessible retry buttons\n * - High contrast error styling\n * - Clear error identification\n *\n * @performance\n * - Efficient error state management\n * - Performance monitoring integration\n * - Minimal impact on normal rendering\n * - Configurable error reporting\n *\n * @dependencies\n * - React 19+ - Component lifecycle\n * - Performance monitoring utilities\n * - Tailwind CSS - Styling\n */\n\nimport React, { Component, ReactNode } from 'react';\nimport { performanceBudgetMonitor } from '../../utils/performance/performanceBudgets';\nimport { logger } from '../../utils/core/logger';\n\n/**\n * State interface for the ErrorBoundary component\n * @public\n */\nexport interface ErrorBoundaryState {\n /** Whether an error has been caught */\n hasError: boolean;\n /** The error that was caught */\n error?: Error;\n /** Additional error information from React */\n errorInfo?: React.ErrorInfo;\n /** Unique identifier for the error */\n errorId?: string;\n /** Number of retry attempts made */\n retryCount: number;\n}\n\n/**\n * Props interface for the ErrorBoundary component\n * @public\n */\nexport interface ErrorBoundaryProps {\n /** Child components to wrap with error boundary */\n children: ReactNode;\n /** Name of the component for error reporting */\n componentName?: string;\n /** Custom fallback UI to display when error occurs */\n fallback?: ReactNode;\n /** Callback function called when an error is caught */\n onError?: (error: Error, errorInfo: React.ErrorInfo, errorId: string) => void;\n /** Maximum number of retry attempts */\n maxRetries?: number;\n /** Whether to enable retry functionality */\n enableRetry?: boolean;\n /** Whether to enable error reporting */\n enableReporting?: boolean;\n /** Internal: Global error handler from context (not part of public API) */\n _globalErrorHandler?: (error: Error, errorInfo: React.ErrorInfo, errorId: string, componentName: string) => void;\n}\n\n/**\n * ErrorBoundary component\n * Catches JavaScript errors in child components and provides fallback UI\n * \n * @example\n * ```tsx\n * <ErrorBoundary\n * componentName=\"MyComponent\"\n * maxRetries={3}\n * onError={(error, errorInfo, errorId) => {\n * console.log('Error caught:', errorId);\n * }}\n * >\n * <MyComponent />\n * </ErrorBoundary>\n * ```\n */\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n private retryTimeoutId: NodeJS.Timeout | null = null;\n\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { \n hasError: false, \n retryCount: 0 \n };\n }\n\n static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {\n const errorId = `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n return { \n hasError: true, \n error,\n errorId \n };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n const { componentName = 'Unknown Component', onError, enableReporting = true } = this.props;\n const errorId = this.state.errorId!;\n const componentNameForHandler = componentName || 'Unknown Component';\n \n this.setState({ errorInfo });\n \n // Enhanced logging with component name and error ID\n logger.error('ErrorBoundary', `[${componentNameForHandler}] Caught error ${errorId}:`, error, errorInfo);\n \n // Performance monitoring - track error occurrence\n performanceBudgetMonitor.measure('ERROR_BOUNDARY_TRIGGER', 1, {\n componentName: componentNameForHandler,\n errorId,\n errorMessage: error.message,\n stack: error.stack?.substring(0, 200), // Truncated stack trace\n });\n\n // Report error if enabled\n if (enableReporting) {\n this.reportError(errorId, componentNameForHandler);\n }\n \n // Call error handler: prefer prop onError, fall back to global handler from props\n if (onError) {\n onError(error, errorInfo, errorId);\n } else if (this.props._globalErrorHandler) {\n this.props._globalErrorHandler(error, errorInfo, errorId, componentNameForHandler);\n }\n }\n\n private reportError = (errorId: string, componentName: string) => {\n // In production, this would send to error reporting service\n if (import.meta.env.MODE === 'production') {\n // Example: Send to error reporting service\n // errorReportingService.report({ error, errorInfo, errorId, componentName });\n logger.warn('ErrorBoundary', 'Error reporting would be triggered in production:', { errorId, componentName });\n }\n };\n\n private handleRetry = () => {\n const { maxRetries = 3 } = this.props;\n const { retryCount } = this.state;\n\n if (retryCount < maxRetries) {\n logger.debug('ErrorBoundary', `Retrying component render (attempt ${retryCount + 1}/${maxRetries})`);\n \n this.setState(prevState => ({\n hasError: false,\n error: undefined,\n errorInfo: undefined,\n errorId: undefined,\n retryCount: prevState.retryCount + 1\n }));\n }\n };\n\n componentWillUnmount() {\n if (this.retryTimeoutId) {\n clearTimeout(this.retryTimeoutId);\n }\n }\n\n render() {\n if (this.state.hasError) {\n const { \n componentName = 'Component', \n fallback, \n enableRetry = true, \n maxRetries = 3 \n } = this.props;\n const { retryCount, errorId } = this.state;\n\n // Use custom fallback if provided\n if (fallback) {\n return fallback;\n }\n\n // Enhanced error UI with retry functionality\n return (\n <div \n role=\"alert\" \n className=\"p-6 bg-destructive/10 border border-destructive/20 rounded-lg\"\n data-error-boundary={errorId}\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0\">\n <svg className=\"w-5 h-5 text-destructive\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fillRule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z\" clipRule=\"evenodd\" />\n </svg>\n </div>\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"text-destructive\">\n Error in {componentName}\n </h3>\n <p className=\"text-destructive/80\">\n {this.state.error?.message || 'An unexpected error occurred.'}\n </p>\n \n {enableRetry && retryCount < maxRetries && (\n <div className=\"flex gap-3 mb-4\">\n <button\n onClick={this.handleRetry}\n className=\"px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors text-sm font-medium\"\n >\n Retry ({retryCount + 1}/{maxRetries})\n </button>\n <button\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 bg-sec-600 text-main-50 rounded-md hover:bg-sec-700 transition-colors text-sm font-medium\"\n >\n Reload Page\n </button>\n </div>\n )}\n\n {retryCount >= maxRetries && (\n <div className=\"mb-4 p-3 bg-acc-50 border border-acc-200 rounded-md\">\n <p className=\"text-acc-800\">\n Maximum retry attempts reached. Please reload the page or contact support.\n </p>\n <button\n onClick={() => window.location.reload()}\n className=\"mt-2 px-3 py-1 bg-acc-600 text-main-50 rounded text-sm hover:bg-acc-700\"\n >\n Reload Page\n </button>\n </div>\n )}\n\n {import.meta.env.MODE === 'development' && this.state.error && (\n <details className=\"text-sm text-destructive/70\">\n <summary className=\"cursor-pointer font-medium mb-2\">\n Error Details (Development)\n </summary>\n <div className=\"bg-destructive/5 p-3 rounded border\">\n <p className=\"font-mono\">Error ID: {errorId}</p>\n <pre className=\"whitespace-pre-wrap text-xs overflow-auto max-h-32\">\n {this.state.error.toString()}\n {this.state.errorInfo?.componentStack}\n </pre>\n </div>\n </details>\n )}\n </div>\n </div>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n\nexport default ErrorBoundary;\n","/**\n * @file Error Boundary exports\n */\n\n// Re-export ErrorBoundary with automatic context support\nimport { ErrorBoundary as ErrorBoundaryClass } from './ErrorBoundary';\nimport { useErrorBoundaryContext } from './ErrorBoundaryContext';\nimport React from 'react';\nimport type { ErrorBoundaryProps } from './ErrorBoundary';\n\n// Export types and provider\nexport type { ErrorBoundaryProps, ErrorBoundaryState } from './ErrorBoundary';\nexport { ErrorBoundaryProvider } from './ErrorBoundaryContext';\nexport type { ErrorBoundaryProviderProps, GlobalErrorHandler } from './ErrorBoundaryContext';\n\n/**\n * ErrorBoundary component with automatic context support\n * This wrapper automatically uses the global error handler from ErrorBoundaryProvider\n * if no onError prop is provided.\n */\nexport const ErrorBoundary = React.forwardRef<\n ErrorBoundaryClass,\n Omit<ErrorBoundaryProps, '_globalErrorHandler'>\n>((props, ref) => {\n const context = useErrorBoundaryContext();\n const globalErrorHandler = context?.defaultErrorHandler;\n \n return React.createElement(ErrorBoundaryClass, { ...props, ref, _globalErrorHandler: globalErrorHandler });\n});\n\nErrorBoundary.displayName = 'ErrorBoundary';\n\nexport default ErrorBoundary;\n","/**\n * @file Public Page Provider\n * @package @jmruthers/pace-core\n * @module Components/PublicLayout\n * @since 1.0.0\n *\n * A completely isolated provider for public pages that doesn't trigger\n * any authentication context. This ensures public pages work independently\n * of the main application's authentication system.\n *\n * Features:\n * - No authentication required\n * - No organisation context\n * - No event context\n * - Completely isolated from main app context\n * - Environment variable access for public data\n * - Error boundary integration\n *\n * @example\n * ```tsx\n * import { PublicPageProvider } from '@jmruthers/pace-core';\n * \n * const APP_NAME = 'CORE';\n * \n * function PublicApp() {\n * return (\n * <PublicPageProvider appName={APP_NAME}>\n * <Routes>\n * <Route path=\"/events/:eventCode/recipe-grid-report\" element={<PublicRecipePage />} />\n * </Routes>\n * </PublicPageProvider>\n * );\n * }\n * ```\n */\n\nimport React, { createContext, useContext, ReactNode, useMemo } from 'react';\nimport { createClient } from '@supabase/supabase-js';\nimport type { Database } from '../../types/database';\nimport { ErrorBoundary } from '../ErrorBoundary';\nimport { logger } from '../../utils/core/logger';\n\ninterface PublicPageContextType {\n isPublicPage: true;\n supabase: ReturnType<typeof createClient<Database>> | null;\n appName: string | null;\n environment: {\n supabaseUrl: string | null;\n supabaseKey: string | null;\n };\n}\n\n/**\n * Context for public pages.\n * Provides isolated context for public pages without authentication requirements.\n */\nexport const PublicPageContext = createContext<PublicPageContextType | undefined>(undefined);\n\nexport interface PublicPageProviderProps {\n children: ReactNode;\n /** Application name for logo display and branding. Should match the APP_NAME constant used in App.tsx */\n appName?: string;\n}\n\n/**\n * Provider for public pages that completely isolates them from authentication context\n * \n * This provider:\n * - Does not initialize any authentication providers\n * - Provides environment variables for public data access\n * - Includes error boundary for graceful error handling\n * - Is completely separate from the main app context\n * - Provides appName for consistent logo display\n */\nexport function PublicPageProvider({ children, appName }: PublicPageProviderProps) {\n // Get environment variables for public data access\n // Handle both Vite (import.meta.env) and Node.js (process.env) environments\n const getEnvVar = (key: string): string | undefined => {\n // Check Vite environment first (browser)\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n const env = import.meta.env as Record<string, string | undefined>;\n return env[key];\n }\n // Check Node.js environment (server-side)\n if (typeof process !== 'undefined' && process.env) {\n return process.env[key];\n }\n return undefined;\n };\n\n const supabaseUrl = getEnvVar('VITE_SUPABASE_URL') || \n getEnvVar('NEXT_PUBLIC_SUPABASE_URL') || \n null;\n \n const supabaseKey = getEnvVar('VITE_SUPABASE_PUBLISHABLE_KEY') || \n getEnvVar('NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY') ||\n getEnvVar('VITE_SUPABASE_ANON_KEY') ||\n getEnvVar('NEXT_PUBLIC_SUPABASE_ANON_KEY') || \n null;\n\n // Create Supabase client if environment variables are available\n // Note: VITE_SUPABASE_PUBLISHABLE_KEY should be your publishable key (sb_publishable_...)\n const supabase = useMemo(() => {\n if (!supabaseUrl || !supabaseKey) {\n logger.warn('PublicPageProvider', 'Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are set in your environment.');\n return null;\n }\n const client = createClient<Database>(supabaseUrl, supabaseKey);\n return client;\n }, [supabaseUrl, supabaseKey]);\n\n const contextValue: PublicPageContextType = {\n isPublicPage: true,\n supabase,\n appName: appName || null,\n environment: {\n supabaseUrl,\n supabaseKey\n }\n };\n\n return (\n <PublicPageContext.Provider value={contextValue}>\n <ErrorBoundary componentName=\"PublicPageProvider\">\n {children}\n </ErrorBoundary>\n </PublicPageContext.Provider>\n );\n}\n\n/**\n * Hook to access public page context\n * \n * @returns Public page context with environment variables\n */\nexport function usePublicPageContext(): PublicPageContextType {\n const context = useContext(PublicPageContext);\n \n if (!context) {\n throw new Error('usePublicPageContext must be used within a PublicPageProvider');\n }\n \n return context;\n}\n\n/**\n * Hook to check if we're in a public page context\n * \n * @returns True if we're in a public page context\n */\nexport function useIsPublicPage(): boolean {\n const context = useContext(PublicPageContext);\n return context !== undefined;\n}\n","/**\n * @file useAppConfig Hook\n * @package @jmruthers/pace-core\n * @module Hooks/useAppConfig\n * @since 0.4.0\n *\n * Hook for accessing app configuration like direct access support and event requirements.\n * This is a convenience hook that extracts app config from the UnifiedAuthProvider.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { supportsDirectAccess, requiresEvent, isLoading } = useAppConfig();\n * \n * if (isLoading) return <div>Loading...</div>;\n * \n * return (\n * <div>\n * {supportsDirectAccess && (\n * <div>This app supports direct access!</div>\n * )}\n * {requiresEvent && (\n * <EventSelector />\n * )}\n * </div>\n * );\n * }\n * ```\n */\n\nimport { useMemo, useContext } from 'react';\nimport { useUnifiedAuth } from '../providers/services/UnifiedAuthProvider';\nimport { useIsPublicPage, PublicPageContext } from '../components/PublicLayout/PublicPageProvider';\n\nexport interface UseAppConfigReturn {\n supportsDirectAccess: boolean;\n requiresEvent: boolean;\n isLoading: boolean;\n appName: string;\n}\n\n/**\n * Hook to access app configuration\n * Works in both authenticated and public contexts\n * @returns App configuration and loading state\n */\nexport function useAppConfig(): UseAppConfigReturn {\n // Check if we're in a public page context first\n const isPublicPage = useIsPublicPage();\n \n // Try to get appName from PublicPageContext (access context directly to avoid hook rule violations)\n const publicPageContext = useContext(PublicPageContext);\n const hasPublicContext = publicPageContext !== undefined;\n const contextAppName = publicPageContext?.appName || null;\n const getNodeEnvVar = (key: string): string | undefined =>\n typeof process !== 'undefined' && process.env ? process.env[key] : undefined;\n \n if (isPublicPage) {\n const getAppName = (): string => {\n // When tests mock public mode without the provider, default to PACE\n if (!hasPublicContext) {\n return 'PACE';\n }\n \n // Priority 1: Use appName from PublicPageContext (passed from App.tsx)\n if (contextAppName) {\n return contextAppName;\n }\n \n // Priority 2: Check environment variables\n if (typeof import.meta !== 'undefined' && (import.meta as any).env) {\n const env = (import.meta as any).env;\n return env.VITE_APP_NAME || \n env.NEXT_PUBLIC_APP_NAME || \n 'PACE';\n }\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n return import.meta.env.VITE_APP_NAME || \n import.meta.env.NEXT_PUBLIC_APP_NAME || \n 'PACE';\n }\n \n const nodeEnvAppName =\n getNodeEnvVar('VITE_APP_NAME') ||\n getNodeEnvVar('NEXT_PUBLIC_APP_NAME');\n if (nodeEnvAppName) {\n return nodeEnvAppName;\n }\n \n // Priority 3: Default fallback\n return 'PACE';\n };\n \n return useMemo(() => ({\n supportsDirectAccess: false, // Public pages don't support direct access\n requiresEvent: true, // Public pages always require an event\n isLoading: false,\n appName: getAppName()\n }), [contextAppName, hasPublicContext]);\n }\n \n // For authenticated pages, use UnifiedAuthProvider\n try {\n const { appConfig, appName } = useUnifiedAuth();\n return useMemo(() => ({\n supportsDirectAccess: !(appConfig?.requires_event ?? true),\n requiresEvent: appConfig?.requires_event ?? true,\n isLoading: appConfig === null,\n appName\n }), [appConfig?.requires_event, appName]);\n } catch (error) {\n // Fallback if UnifiedAuthProvider is not available\n return useMemo(() => ({\n supportsDirectAccess: false,\n requiresEvent: true,\n isLoading: false,\n appName: contextAppName ||\n getNodeEnvVar('VITE_APP_NAME') ||\n getNodeEnvVar('NEXT_PUBLIC_APP_NAME') ||\n 'PACE'\n }), [contextAppName]);\n }\n} ","/**\n * @file Organisation Security Hook\n * @package @jmruthers/pace-core\n * @module Hooks/OrganisationSecurity\n * @since 0.4.0\n *\n * Security-focused hook for organisation access validation and super admin functionality.\n * Provides utilities for validating user access to organisations and checking permissions.\n */\n\nimport { useCallback, useMemo, useEffect, useState } from 'react';\nimport { useUnifiedAuth } from '../providers';\nimport { useOrganisations } from './useOrganisations';\n// Legacy useRBAC hook removed - use new RBAC system instead\nimport type { OrganisationSecurityError, SuperAdminContext } from '../types/organisation';\nimport type { Permission } from '../rbac/types';\nimport { logger } from '../utils/core/logger';\n\n/**\n * Organisation security hook interface.\n * Provides security utilities for organisation access validation and super admin functionality.\n */\nexport interface OrganisationSecurityHook {\n // Super admin context\n superAdminContext: SuperAdminContext;\n \n // Access validation\n validateOrganisationAccess: (orgId: string) => Promise<boolean>;\n hasMinimumRole: (minRole: string, orgId?: string) => boolean;\n canAccessChildOrganisations: (orgId?: string) => boolean;\n \n // Permission checks\n hasPermission: (permission: string, orgId?: string) => Promise<boolean>;\n getUserPermissions: (orgId?: string) => Promise<string[]>;\n \n // Audit logging\n logOrganisationAccess: (action: string, details?: Record<string, unknown>) => Promise<void>;\n \n // Security utilities\n ensureOrganisationAccess: (orgId: string) => Promise<void>;\n validateUserAccess: (userId: string, orgId: string) => Promise<boolean>;\n}\n\nexport const useOrganisationSecurity = (): OrganisationSecurityHook => {\n const { user, session, supabase } = useUnifiedAuth();\n const { selectedOrganisation, getUserRole, validateOrganisationAccess: validateAccess } = useOrganisations();\n \n // Super admin status - query database for security (user_metadata can be spoofed)\n const [isSuperAdmin, setIsSuperAdmin] = useState<boolean>(false);\n const [isCheckingSuperAdmin, setIsCheckingSuperAdmin] = useState(false);\n\n // Check super admin status from database\n useEffect(() => {\n if (!user || !session || !supabase) {\n setIsSuperAdmin(false);\n return;\n }\n\n const checkSuperAdmin = async () => {\n setIsCheckingSuperAdmin(true);\n try {\n const now = new Date().toISOString();\n const { data, error } = await supabase\n .from('rbac_global_roles')\n .select('role')\n .eq('user_id', user.id)\n .eq('role', 'super_admin')\n .lte('valid_from', now)\n .or(`valid_to.is.null,valid_to.gte.${now}`)\n .limit(1);\n\n setIsSuperAdmin(!error && data && data.length > 0);\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Error checking super admin status:', error);\n setIsSuperAdmin(false);\n } finally {\n setIsCheckingSuperAdmin(false);\n }\n };\n\n checkSuperAdmin();\n }, [user, session, supabase]);\n\n // Super admin context\n const superAdminContext = useMemo((): SuperAdminContext => {\n return {\n isSuperAdmin,\n hasGlobalAccess: isSuperAdmin,\n canManageAllOrganisations: isSuperAdmin\n };\n }, [isSuperAdmin]);\n\n // Validate organisation access with database check\n const validateOrganisationAccess = useCallback(async (orgId: string): Promise<boolean> => {\n if (!user || !session || !supabase) return false;\n \n try {\n // Super admin has access to all organisations\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n // Check organisation membership using consolidated rbac_organisation_roles table\n const { data, error } = await supabase\n .from('rbac_organisation_roles')\n .select('id')\n .eq('user_id', user.id)\n .eq('organisation_id', orgId)\n .eq('status', 'active')\n .is('revoked_at', null)\n .in('role', ['org_admin', 'leader', 'member']) // Only actual members, not supporters\n .single();\n\n if (error) {\n logger.error('useOrganisationSecurity', 'Error validating organisation access:', error);\n return false;\n }\n\n return !!data;\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Exception validating organisation access:', error);\n return false;\n }\n }, [user, session, supabase, superAdminContext.isSuperAdmin]);\n\n // Check if user has minimum role\n const hasMinimumRole = useCallback((minRole: string, orgId?: string): boolean => {\n // Super admin has all roles\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n const targetOrgId = orgId || selectedOrganisation?.id;\n if (!targetOrgId) return false;\n\n const userRole = getUserRole(targetOrgId);\n const roleHierarchy = ['supporter', 'member', 'leader', 'org_admin'];\n \n const userRoleIndex = roleHierarchy.indexOf(userRole);\n const minRoleIndex = roleHierarchy.indexOf(minRole);\n \n return userRoleIndex >= minRoleIndex;\n }, [selectedOrganisation, getUserRole, superAdminContext.isSuperAdmin]);\n\n // Check if user can access child organisations\n const canAccessChildOrganisations = useCallback((orgId?: string): boolean => {\n // Super admin can access all organisations\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n const targetOrgId = orgId || selectedOrganisation?.id;\n if (!targetOrgId) return false;\n\n const userRole = getUserRole(targetOrgId);\n return userRole === 'org_admin';\n }, [selectedOrganisation, getUserRole, superAdminContext.isSuperAdmin]);\n\n // Check specific permission using the new RBAC system\n const hasPermission = useCallback(async (permission: string, orgId?: string): Promise<boolean> => {\n // Super admin has all permissions\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n const targetOrgId = orgId || selectedOrganisation?.id;\n if (!targetOrgId || !user) return false;\n\n try {\n // Use the new RBAC system with caching\n const { isPermittedCached } = await import('../rbac/api');\n \n const scope = {\n organisationId: targetOrgId,\n eventId: user.user_metadata?.eventId || user.app_metadata?.eventId,\n appId: user.user_metadata?.appId || user.app_metadata?.appId,\n };\n\n return await isPermittedCached({\n userId: user.id,\n scope,\n permission: permission as Permission\n });\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Exception checking permission:', error);\n return false;\n }\n }, [selectedOrganisation, user, superAdminContext.isSuperAdmin]);\n\n // Get user's permissions for organisation using the new RBAC system\n const getUserPermissions = useCallback(async (orgId?: string): Promise<string[]> => {\n // Super admin has all permissions\n if (superAdminContext.isSuperAdmin) {\n return ['*']; // All permissions\n }\n\n const targetOrgId = orgId || selectedOrganisation?.id;\n if (!targetOrgId || !user) return [];\n\n try {\n // Use the new RBAC system\n const { getPermissionMap } = await import('../rbac/api');\n \n const scope = {\n organisationId: targetOrgId,\n eventId: user.user_metadata?.eventId || user.app_metadata?.eventId,\n appId: user.user_metadata?.appId || user.app_metadata?.appId,\n };\n\n const permissionMap = await getPermissionMap({\n userId: user.id,\n scope\n });\n \n // Flatten all permissions from all pages\n const allPermissions = Object.entries(permissionMap)\n .filter(([, allowed]) => allowed)\n .map(([permission]) => permission);\n return [...new Set(allPermissions)]; // Remove duplicates\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Exception getting user permissions:', error);\n return [];\n }\n }, [selectedOrganisation, user, getUserRole, superAdminContext.isSuperAdmin]);\n\n // Log organisation access for audit using the new RBAC audit system\n const logOrganisationAccess = useCallback(async (action: string, details?: Record<string, unknown>): Promise<void> => {\n if (!user || !selectedOrganisation) return;\n\n try {\n // Use the new RBAC audit system - only if we have a valid organisation ID\n if (selectedOrganisation.id) {\n const { emitAuditEvent } = await import('../rbac/audit');\n \n await emitAuditEvent({\n type: 'permission_check',\n userId: user.id,\n organisationId: selectedOrganisation.id,\n permission: action,\n decision: true, // Assume access was granted if we're logging it\n source: 'api',\n duration_ms: 0, // No actual permission check performed here\n metadata: details || {}\n });\n }\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Error logging organisation access:', error);\n }\n }, [user, selectedOrganisation]);\n\n // Ensure organisation access (throws if no access)\n const ensureOrganisationAccess = useCallback(async (orgId: string): Promise<void> => {\n const hasAccess = await validateOrganisationAccess(orgId);\n \n if (!hasAccess) {\n const error = new Error(`User does not have access to organisation ${orgId}`) as OrganisationSecurityError;\n error.name = 'OrganisationSecurityError';\n error.code = 'ACCESS_DENIED';\n error.organisationId = orgId;\n error.userId = user?.id;\n throw error;\n }\n }, [validateOrganisationAccess, user]);\n\n // Validate user access (for admin functions)\n const validateUserAccess = useCallback(async (userId: string, orgId: string): Promise<boolean> => {\n if (!supabase) return false;\n\n try {\n // Super admin can validate any user\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n // Regular users can only validate their own access\n if (userId !== user?.id) {\n return false;\n }\n\n return await validateOrganisationAccess(orgId);\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Exception validating user access:', error);\n return false;\n }\n }, [supabase, superAdminContext.isSuperAdmin, user, validateOrganisationAccess]);\n\n return {\n superAdminContext,\n validateOrganisationAccess,\n hasMinimumRole,\n canAccessChildOrganisations,\n hasPermission,\n getUserPermissions,\n logOrganisationAccess,\n ensureOrganisationAccess,\n validateUserAccess\n };\n}; "],"mappings":";;;;;;;;;;;;;AAaA,SAAS,SAAS,cAAc;AAqCzB,SAAS,YAA8B;AAC5C,QAAM,eAAe,gBAAgB;AAGrC,QAAM,YAAY,aAAa,UAAU;AACzC,QAAM,gBAAgB,aAAa,iBAAiB;AACpD,QAAM,YAAY,aAAa,UAAU;AACzC,QAAM,QAAQ,aAAa,SAAS;AAGpC,QAAM,gBAAgB,OAAgB,CAAC,CAAC;AACxC,QAAM,mBAAmB,OAAe,EAAE;AAI1C,QAAM,mBAAmB,UAAU,IAAI,OAAK,EAAE,YAAY,EAAE,EAAE,EAAE,KAAK,GAAG;AACxE,QAAM,gBAAgB,qBAAqB,iBAAiB;AAI5D,MAAI,eAAe;AACjB,kBAAc,UAAU;AACxB,qBAAiB,UAAU;AAAA,EAC7B;AAGA,QAAM,SAAS,QAAQ,MAAM;AAC3B,WAAO,cAAc;AAAA,EACvB,GAAG,CAAC,gBAAgB,CAAC;AAGrB,QAAM,2BAA2B;AAAA,IAC/B,MAAM,CAAC,UAAwB,aAAa,iBAAiB,KAAK;AAAA,IAClE,CAAC,YAAY;AAAA,EACf;AACA,QAAM,wBAAwB;AAAA,IAC5B,MAAM,MAAM,aAAa,cAAc;AAAA,IACvC,CAAC,YAAY;AAAA,EACf;AACA,QAAM,8BAA8B;AAAA,IAClC,MAAM,MAAM,aAAa,oBAAoB;AAAA,IAC7C,CAAC,YAAY;AAAA,EACf;AAGA,SAAO,QAAQ,OAAO;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,kBAAkB;AAAA,IAClB,eAAe;AAAA,IACf,qBAAqB;AAAA,EACvB,IAAI,CAAC,QAAQ,eAAe,UAAU,WAAW,OAAO,SAAS,0BAA0B,uBAAuB,2BAA2B,CAAC;AAChJ;;;ACpEA,SAAgB,eAAe,kBAA6B;AA+ExD;AArDJ,IAAM,uBAAuB,cAAoD,MAAS;AA4CnF,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AACF,GAA+B;AAC7B,QAAM,eAAyC;AAAA,IAC7C;AAAA,EACF;AAEA,SACE,oBAAC,qBAAqB,UAArB,EAA8B,OAAO,cACnC,UACH;AAEJ;AAMO,SAAS,0BAAgE;AAC9E,SAAO,WAAW,oBAAoB;AACxC;;;ACVA,SAAgB,iBAA4B;AAyK5B,gBAAAA,MAIF,YAJE;AA5GT,IAAM,gBAAN,cAA4B,UAAkD;AAAA,EAGnF,YAAY,OAA2B;AACrC,UAAM,KAAK;AAHb,SAAQ,iBAAwC;AAkDhD,SAAQ,cAAc,CAAC,SAAiB,kBAA0B;AAEhE,UAAI,YAAY,IAAI,SAAS,cAAc;AAGzC,eAAO,KAAK,iBAAiB,qDAAqD,EAAE,SAAS,cAAc,CAAC;AAAA,MAC9G;AAAA,IACF;AAEA,SAAQ,cAAc,MAAM;AAC1B,YAAM,EAAE,aAAa,EAAE,IAAI,KAAK;AAChC,YAAM,EAAE,WAAW,IAAI,KAAK;AAE5B,UAAI,aAAa,YAAY;AAC3B,eAAO,MAAM,iBAAiB,sCAAsC,aAAa,CAAC,IAAI,UAAU,GAAG;AAEnG,aAAK,SAAS,gBAAc;AAAA,UAC1B,UAAU;AAAA,UACV,OAAO;AAAA,UACP,WAAW;AAAA,UACX,SAAS;AAAA,UACT,YAAY,UAAU,aAAa;AAAA,QACrC,EAAE;AAAA,MACJ;AAAA,IACF;AAtEE,SAAK,QAAQ;AAAA,MACX,UAAU;AAAA,MACV,YAAY;AAAA,IACd;AAAA,EACF;AAAA,EAEA,OAAO,yBAAyB,OAA2C;AACzE,UAAM,UAAU,SAAS,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAG,CAAC,CAAC;AAC9E,WAAO;AAAA,MACL,UAAU;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,kBAAkB,OAAc,WAA4B;AAC1D,UAAM,EAAE,gBAAgB,qBAAqB,SAAS,kBAAkB,KAAK,IAAI,KAAK;AACtF,UAAM,UAAU,KAAK,MAAM;AAC3B,UAAM,0BAA0B,iBAAiB;AAEjD,SAAK,SAAS,EAAE,UAAU,CAAC;AAG3B,WAAO,MAAM,iBAAiB,IAAI,uBAAuB,kBAAkB,OAAO,KAAK,OAAO,SAAS;AAGvG,6BAAyB,QAAQ,0BAA0B,GAAG;AAAA,MAC5D,eAAe;AAAA,MACf;AAAA,MACA,cAAc,MAAM;AAAA,MACpB,OAAO,MAAM,OAAO,UAAU,GAAG,GAAG;AAAA;AAAA,IACtC,CAAC;AAGD,QAAI,iBAAiB;AACnB,WAAK,YAAY,SAAS,uBAAuB;AAAA,IACnD;AAGA,QAAI,SAAS;AACX,cAAQ,OAAO,WAAW,OAAO;AAAA,IACnC,WAAW,KAAK,MAAM,qBAAqB;AACzC,WAAK,MAAM,oBAAoB,OAAO,WAAW,SAAS,uBAAuB;AAAA,IACnF;AAAA,EACF;AAAA,EA4BA,uBAAuB;AACrB,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,SAAS;AACP,QAAI,KAAK,MAAM,UAAU;AACvB,YAAM;AAAA,QACJ,gBAAgB;AAAA,QAChB;AAAA,QACA,cAAc;AAAA,QACd,aAAa;AAAA,MACf,IAAI,KAAK;AACT,YAAM,EAAE,YAAY,QAAQ,IAAI,KAAK;AAGrC,UAAI,UAAU;AACZ,eAAO;AAAA,MACT;AAGA,aACE,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,uBAAqB;AAAA,UAErB,+BAAC,SAAI,WAAU,0BACb;AAAA,4BAAAA,KAAC,SAAI,WAAU,iBACb,0BAAAA,KAAC,SAAI,WAAU,4BAA2B,SAAQ,aAAY,MAAK,gBACjE,0BAAAA,KAAC,UAAK,UAAS,WAAU,GAAE,qHAAoH,UAAS,WAAU,GACpK,GACF;AAAA,YACA,qBAAC,SAAI,WAAU,kBACb;AAAA,mCAAC,QAAG,WAAU,oBAAmB;AAAA;AAAA,gBACrB;AAAA,iBACZ;AAAA,cACA,gBAAAA,KAAC,OAAE,WAAU,uBACV,eAAK,MAAM,OAAO,WAAW,iCAChC;AAAA,cAEC,eAAe,aAAa,cAC3B,qBAAC,SAAI,WAAU,mBACb;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,SAAS,KAAK;AAAA,oBACd,WAAU;AAAA,oBACX;AAAA;AAAA,sBACS,aAAa;AAAA,sBAAE;AAAA,sBAAE;AAAA,sBAAW;AAAA;AAAA;AAAA,gBACtC;AAAA,gBACA,gBAAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,SAAS,MAAM,OAAO,SAAS,OAAO;AAAA,oBACtC,WAAU;AAAA,oBACX;AAAA;AAAA,gBAED;AAAA,iBACF;AAAA,cAGD,cAAc,cACb,qBAAC,SAAI,WAAU,uDACb;AAAA,gCAAAA,KAAC,OAAE,WAAU,gBAAe,wFAE5B;AAAA,gBACA,gBAAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,SAAS,MAAM,OAAO,SAAS,OAAO;AAAA,oBACtC,WAAU;AAAA,oBACX;AAAA;AAAA,gBAED;AAAA,iBACF;AAAA,cAGD,YAAY,IAAI,SAAS,iBAAiB,KAAK,MAAM,SACpD,qBAAC,aAAQ,WAAU,+BACjB;AAAA,gCAAAA,KAAC,aAAQ,WAAU,mCAAkC,yCAErD;AAAA,gBACA,qBAAC,SAAI,WAAU,uCACb;AAAA,uCAAC,OAAE,WAAU,aAAY;AAAA;AAAA,oBAAW;AAAA,qBAAQ;AAAA,kBAC5C,qBAAC,SAAI,WAAU,sDACZ;AAAA,yBAAK,MAAM,MAAM,SAAS;AAAA,oBAC1B,KAAK,MAAM,WAAW;AAAA,qBACzB;AAAA,mBACF;AAAA,iBACF;AAAA,eAEJ;AAAA,aACF;AAAA;AAAA,MACF;AAAA,IAEJ;AAEA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;;;ACvVA,OAAOC,YAAW;AAaX,IAAMC,iBAAgBD,OAAM,WAGjC,CAAC,OAAO,QAAQ;AAChB,QAAM,UAAU,wBAAwB;AACxC,QAAM,qBAAqB,SAAS;AAEpC,SAAOA,OAAM,cAAc,eAAoB,EAAE,GAAG,OAAO,KAAK,qBAAqB,mBAAmB,CAAC;AAC3G,CAAC;AAEDC,eAAc,cAAc;;;ACM5B,SAAgB,iBAAAC,gBAAe,cAAAC,aAAuB,WAAAC,gBAAe;AACrE,SAAS,oBAAoB;AAsFvB,gBAAAC,YAAA;AAnEC,IAAM,oBAAoBC,eAAiD,MAAS;AAkBpF,SAAS,mBAAmB,EAAE,UAAU,QAAQ,GAA4B;AAGjF,QAAM,YAAY,CAAC,QAAoC;AAErD,QAAI,OAAO,gBAAgB,eAAe,YAAY,KAAK;AACzD,YAAM,MAAM,YAAY;AACxB,aAAO,IAAI,GAAG;AAAA,IAChB;AAEA,QAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AACjD,aAAO,QAAQ,IAAI,GAAG;AAAA,IACxB;AACA,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,UAAU,mBAAmB,KAC9B,UAAU,0BAA0B,KACpC;AAEnB,QAAM,cAAc,UAAU,+BAA+B,KAC1C,UAAU,sCAAsC,KAChD,UAAU,wBAAwB,KAClC,UAAU,+BAA+B,KACzC;AAInB,QAAM,WAAWC,SAAQ,MAAM;AAC7B,QAAI,CAAC,eAAe,CAAC,aAAa;AAChC,aAAO,KAAK,sBAAsB,wIAAwI;AAC1K,aAAO;AAAA,IACT;AACA,UAAM,SAAS,aAAuB,aAAa,WAAW;AAC9D,WAAO;AAAA,EACT,GAAG,CAAC,aAAa,WAAW,CAAC;AAE7B,QAAM,eAAsC;AAAA,IAC1C,cAAc;AAAA,IACd;AAAA,IACA,SAAS,WAAW;AAAA,IACpB,aAAa;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,SACE,gBAAAF,KAAC,kBAAkB,UAAlB,EAA2B,OAAO,cACjC,0BAAAA,KAACG,gBAAA,EAAc,eAAc,sBAC1B,UACH,GACF;AAEJ;AAOO,SAAS,uBAA8C;AAC5D,QAAM,UAAUC,YAAW,iBAAiB;AAE5C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AAEA,SAAO;AACT;AAOO,SAAS,kBAA2B;AACzC,QAAM,UAAUA,YAAW,iBAAiB;AAC5C,SAAO,YAAY;AACrB;;;AC3HA,SAAS,WAAAC,UAAS,cAAAC,mBAAkB;AAgB7B,SAAS,eAAmC;AAEjD,QAAM,eAAe,gBAAgB;AAGrC,QAAM,oBAAoBC,YAAW,iBAAiB;AACtD,QAAM,mBAAmB,sBAAsB;AAC/C,QAAM,iBAAiB,mBAAmB,WAAW;AACrD,QAAM,gBAAgB,CAAC,QACrB,OAAO,YAAY,eAAe,QAAQ,MAAM,QAAQ,IAAI,GAAG,IAAI;AAErE,MAAI,cAAc;AAChB,UAAM,aAAa,MAAc;AAE/B,UAAI,CAAC,kBAAkB;AACrB,eAAO;AAAA,MACT;AAGA,UAAI,gBAAgB;AAClB,eAAO;AAAA,MACT;AAGA,UAAI,OAAO,gBAAgB,eAAgB,YAAoB,KAAK;AAClE,cAAM,MAAO,YAAoB;AACjC,eAAO,IAAI,iBACJ,IAAI,wBACJ;AAAA,MACT;AACA,UAAI,OAAO,gBAAgB,eAAe,YAAY,KAAK;AACzD,eAAO,YAAY,IAAI,iBAChB,YAAY,IAAI,wBAChB;AAAA,MACT;AAEA,YAAM,iBACJ,cAAc,eAAe,KAC7B,cAAc,sBAAsB;AACtC,UAAI,gBAAgB;AAClB,eAAO;AAAA,MACT;AAGA,aAAO;AAAA,IACT;AAEA,WAAOC,SAAQ,OAAO;AAAA,MACpB,sBAAsB;AAAA;AAAA,MACtB,eAAe;AAAA;AAAA,MACf,WAAW;AAAA,MACX,SAAS,WAAW;AAAA,IACtB,IAAI,CAAC,gBAAgB,gBAAgB,CAAC;AAAA,EACxC;AAGA,MAAI;AACF,UAAM,EAAE,WAAW,QAAQ,IAAI,eAAe;AAC9C,WAAOA,SAAQ,OAAO;AAAA,MACpB,sBAAsB,EAAE,WAAW,kBAAkB;AAAA,MACrD,eAAe,WAAW,kBAAkB;AAAA,MAC5C,WAAW,cAAc;AAAA,MACzB;AAAA,IACF,IAAI,CAAC,WAAW,gBAAgB,OAAO,CAAC;AAAA,EAC1C,SAAS,OAAO;AAEd,WAAOA,SAAQ,OAAO;AAAA,MACpB,sBAAsB;AAAA,MACtB,eAAe;AAAA,MACf,WAAW;AAAA,MACX,SAAS,kBACP,cAAc,eAAe,KAC7B,cAAc,sBAAsB,KACpC;AAAA,IACJ,IAAI,CAAC,cAAc,CAAC;AAAA,EACtB;AACF;;;AChHA,SAAS,aAAa,WAAAC,UAAS,WAAW,gBAAgB;AAiCnD,IAAM,0BAA0B,MAAgC;AACrE,QAAM,EAAE,MAAM,SAAS,SAAS,IAAI,eAAe;AACnD,QAAM,EAAE,sBAAsB,aAAa,4BAA4B,eAAe,IAAI,iBAAiB;AAG3G,QAAM,CAAC,cAAc,eAAe,IAAI,SAAkB,KAAK;AAC/D,QAAM,CAAC,sBAAsB,uBAAuB,IAAI,SAAS,KAAK;AAGtE,YAAU,MAAM;AACd,QAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,UAAU;AAClC,sBAAgB,KAAK;AACrB;AAAA,IACF;AAEA,UAAM,kBAAkB,YAAY;AAClC,8BAAwB,IAAI;AAC5B,UAAI;AACF,cAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,mBAAmB,EACxB,OAAO,MAAM,EACb,GAAG,WAAW,KAAK,EAAE,EACrB,GAAG,QAAQ,aAAa,EACxB,IAAI,cAAc,GAAG,EACrB,GAAG,iCAAiC,GAAG,EAAE,EACzC,MAAM,CAAC;AAEV,wBAAgB,CAAC,SAAS,QAAQ,KAAK,SAAS,CAAC;AAAA,MACnD,SAAS,OAAO;AACd,eAAO,MAAM,2BAA2B,sCAAsC,KAAK;AACnF,wBAAgB,KAAK;AAAA,MACvB,UAAE;AACA,gCAAwB,KAAK;AAAA,MAC/B;AAAA,IACF;AAEA,oBAAgB;AAAA,EAClB,GAAG,CAAC,MAAM,SAAS,QAAQ,CAAC;AAG5B,QAAM,oBAAoBC,SAAQ,MAAyB;AACzD,WAAO;AAAA,MACL;AAAA,MACA,iBAAiB;AAAA,MACjB,2BAA2B;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAGjB,QAAM,6BAA6B,YAAY,OAAO,UAAoC;AACxF,QAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,SAAU,QAAO;AAE3C,QAAI;AAEF,UAAI,kBAAkB,cAAc;AAClC,eAAO;AAAA,MACT;AAGA,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,yBAAyB,EAC9B,OAAO,IAAI,EACX,GAAG,WAAW,KAAK,EAAE,EACrB,GAAG,mBAAmB,KAAK,EAC3B,GAAG,UAAU,QAAQ,EACrB,GAAG,cAAc,IAAI,EACrB,GAAG,QAAQ,CAAC,aAAa,UAAU,QAAQ,CAAC,EAC5C,OAAO;AAEV,UAAI,OAAO;AACT,eAAO,MAAM,2BAA2B,yCAAyC,KAAK;AACtF,eAAO;AAAA,MACT;AAEA,aAAO,CAAC,CAAC;AAAA,IACX,SAAS,OAAO;AACd,aAAO,MAAM,2BAA2B,6CAA6C,KAAK;AAC1F,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,MAAM,SAAS,UAAU,kBAAkB,YAAY,CAAC;AAG5D,QAAM,iBAAiB,YAAY,CAAC,SAAiB,UAA4B;AAE/E,QAAI,kBAAkB,cAAc;AAClC,aAAO;AAAA,IACT;AAEA,UAAM,cAAc,SAAS,sBAAsB;AACnD,QAAI,CAAC,YAAa,QAAO;AAEzB,UAAM,WAAW,YAAY,WAAW;AACxC,UAAM,gBAAgB,CAAC,aAAa,UAAU,UAAU,WAAW;AAEnE,UAAM,gBAAgB,cAAc,QAAQ,QAAQ;AACpD,UAAM,eAAe,cAAc,QAAQ,OAAO;AAElD,WAAO,iBAAiB;AAAA,EAC1B,GAAG,CAAC,sBAAsB,aAAa,kBAAkB,YAAY,CAAC;AAGtE,QAAM,8BAA8B,YAAY,CAAC,UAA4B;AAE3E,QAAI,kBAAkB,cAAc;AAClC,aAAO;AAAA,IACT;AAEA,UAAM,cAAc,SAAS,sBAAsB;AACnD,QAAI,CAAC,YAAa,QAAO;AAEzB,UAAM,WAAW,YAAY,WAAW;AACxC,WAAO,aAAa;AAAA,EACtB,GAAG,CAAC,sBAAsB,aAAa,kBAAkB,YAAY,CAAC;AAGtE,QAAM,gBAAgB,YAAY,OAAO,YAAoB,UAAqC;AAEhG,QAAI,kBAAkB,cAAc;AAClC,aAAO;AAAA,IACT;AAEA,UAAM,cAAc,SAAS,sBAAsB;AACnD,QAAI,CAAC,eAAe,CAAC,KAAM,QAAO;AAElC,QAAI;AAEF,YAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,mBAAa;AAExD,YAAM,QAAQ;AAAA,QACZ,gBAAgB;AAAA,QAChB,SAAS,KAAK,eAAe,WAAW,KAAK,cAAc;AAAA,QAC3D,OAAO,KAAK,eAAe,SAAS,KAAK,cAAc;AAAA,MACzD;AAEA,aAAO,MAAM,kBAAkB;AAAA,QAC7B,QAAQ,KAAK;AAAA,QACb;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,aAAO,MAAM,2BAA2B,kCAAkC,KAAK;AAC/E,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,sBAAsB,MAAM,kBAAkB,YAAY,CAAC;AAG/D,QAAM,qBAAqB,YAAY,OAAO,UAAsC;AAElF,QAAI,kBAAkB,cAAc;AAClC,aAAO,CAAC,GAAG;AAAA,IACb;AAEA,UAAM,cAAc,SAAS,sBAAsB;AACnD,QAAI,CAAC,eAAe,CAAC,KAAM,QAAO,CAAC;AAEnC,QAAI;AAEF,YAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,mBAAa;AAEvD,YAAM,QAAQ;AAAA,QACZ,gBAAgB;AAAA,QAChB,SAAS,KAAK,eAAe,WAAW,KAAK,cAAc;AAAA,QAC3D,OAAO,KAAK,eAAe,SAAS,KAAK,cAAc;AAAA,MACzD;AAEA,YAAM,gBAAgB,MAAM,iBAAiB;AAAA,QAC3C,QAAQ,KAAK;AAAA,QACb;AAAA,MACF,CAAC;AAGD,YAAM,iBAAiB,OAAO,QAAQ,aAAa,EAChD,OAAO,CAAC,CAAC,EAAE,OAAO,MAAM,OAAO,EAC/B,IAAI,CAAC,CAAC,UAAU,MAAM,UAAU;AACnC,aAAO,CAAC,GAAG,IAAI,IAAI,cAAc,CAAC;AAAA,IACpC,SAAS,OAAO;AACd,aAAO,MAAM,2BAA2B,uCAAuC,KAAK;AACpF,aAAO,CAAC;AAAA,IACV;AAAA,EACF,GAAG,CAAC,sBAAsB,MAAM,aAAa,kBAAkB,YAAY,CAAC;AAG5E,QAAM,wBAAwB,YAAY,OAAO,QAAgB,YAAqD;AACpH,QAAI,CAAC,QAAQ,CAAC,qBAAsB;AAEpC,QAAI;AAEF,UAAI,qBAAqB,IAAI;AAC3B,cAAM,EAAE,eAAe,IAAI,MAAM,OAAO,qBAAe;AAEvD,cAAM,eAAe;AAAA,UACnB,MAAM;AAAA,UACN,QAAQ,KAAK;AAAA,UACb,gBAAgB,qBAAqB;AAAA,UACrC,YAAY;AAAA,UACZ,UAAU;AAAA;AAAA,UACV,QAAQ;AAAA,UACR,aAAa;AAAA;AAAA,UACb,UAAU,WAAW,CAAC;AAAA,QACxB,CAAC;AAAA,MACH;AAAA,IACF,SAAS,OAAO;AACd,aAAO,MAAM,2BAA2B,sCAAsC,KAAK;AAAA,IACrF;AAAA,EACF,GAAG,CAAC,MAAM,oBAAoB,CAAC;AAG/B,QAAM,2BAA2B,YAAY,OAAO,UAAiC;AACnF,UAAM,YAAY,MAAM,2BAA2B,KAAK;AAExD,QAAI,CAAC,WAAW;AACd,YAAM,QAAQ,IAAI,MAAM,6CAA6C,KAAK,EAAE;AAC5E,YAAM,OAAO;AACb,YAAM,OAAO;AACb,YAAM,iBAAiB;AACvB,YAAM,SAAS,MAAM;AACrB,YAAM;AAAA,IACR;AAAA,EACF,GAAG,CAAC,4BAA4B,IAAI,CAAC;AAGrC,QAAM,qBAAqB,YAAY,OAAO,QAAgB,UAAoC;AAChG,QAAI,CAAC,SAAU,QAAO;AAEtB,QAAI;AAEF,UAAI,kBAAkB,cAAc;AAClC,eAAO;AAAA,MACT;AAGA,UAAI,WAAW,MAAM,IAAI;AACvB,eAAO;AAAA,MACT;AAEA,aAAO,MAAM,2BAA2B,KAAK;AAAA,IAC/C,SAAS,OAAO;AACd,aAAO,MAAM,2BAA2B,qCAAqC,KAAK;AAClF,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,UAAU,kBAAkB,cAAc,MAAM,0BAA0B,CAAC;AAE/E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["jsx","React","ErrorBoundary","createContext","useContext","useMemo","jsx","createContext","useMemo","ErrorBoundary","useContext","useMemo","useContext","useContext","useMemo","useMemo","useMemo"]}
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useEvents.ts","../src/components/ErrorBoundary/ErrorBoundaryContext.tsx","../src/components/ErrorBoundary/ErrorBoundary.tsx","../src/components/ErrorBoundary/index.ts","../src/components/PublicLayout/PublicPageProvider.tsx","../src/hooks/useAppConfig.ts","../src/hooks/useOrganisationSecurity.ts"],"sourcesContent":["/**\n * @file Event Hook\n * @package @jmruthers/pace-core\n * @module Hooks\n * @since 0.1.0\n *\n * Convenience hook for accessing event context.\n * This hook provides a simple interface for working with events.\n * \n * Note: This is a convenience wrapper around the EventService.\n * For better performance, consider using useEventService directly.\n */\n\nimport { useMemo, useRef } from 'react';\nimport { useEventService } from './services/useEventService';\nimport { Event } from '../types/event';\n\nexport interface EventContextType {\n events: Event[];\n selectedEvent: Event | null;\n isLoading: boolean;\n error: Error | null;\n setSelectedEvent: (event: Event | null) => void;\n refreshEvents: () => Promise<void>;\n clearEventSelection: () => Promise<void>;\n}\n\n/**\n * Hook to access event context\n * \n * @returns Event context with events, selected event, and helper methods\n * @throws {Error} If used outside EventServiceProvider\n * \n * @example\n * ```tsx\n * function MyComponent() {\n * const { events, selectedEvent, setSelectedEvent } = useEvents();\n * \n * return (\n * <div>\n * {events.map(event => (\n * <button key={event.id} onClick={() => setSelectedEvent(event)}>\n * {event.event_name}\n * </button>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useEvents(): EventContextType {\n const eventService = useEventService();\n\n // Get current state - service subscription will trigger re-renders when state changes\n const rawEvents = eventService.getEvents();\n const selectedEvent = eventService.getSelectedEvent();\n const isLoading = eventService.isLoading();\n const error = eventService.getError();\n\n // Use refs to track previous values and only create new array reference when events actually change\n const prevEventsRef = useRef<Event[]>([]);\n const prevEventsIdsRef = useRef<string>('');\n \n // Compare event IDs to detect actual changes (more efficient than deep comparison)\n // Do this outside useMemo to avoid dependency on rawEvents array\n const currentEventsIds = rawEvents.map(e => e.event_id || e.id).join(',');\n const eventsChanged = currentEventsIds !== prevEventsIdsRef.current;\n \n // Only create new array reference when events actually changed\n // Update refs synchronously, then memoize based on the ID string\n if (eventsChanged) {\n prevEventsRef.current = rawEvents;\n prevEventsIdsRef.current = currentEventsIds;\n }\n \n // Return stable array reference - only changes when event IDs change\n const events = useMemo(() => {\n return prevEventsRef.current;\n }, [currentEventsIds]);\n\n // Memoize callbacks to prevent unnecessary re-renders in child components\n const setSelectedEventCallback = useMemo(\n () => (event: Event | null) => eventService.setSelectedEvent(event),\n [eventService]\n );\n const refreshEventsCallback = useMemo(\n () => () => eventService.refreshEvents(),\n [eventService]\n );\n const clearEventSelectionCallback = useMemo(\n () => () => eventService.clearEventSelection(),\n [eventService]\n );\n\n // Memoize the return object - only recreate when actual values change\n return useMemo(() => ({\n events,\n selectedEvent,\n isLoading,\n error,\n setSelectedEvent: setSelectedEventCallback,\n refreshEvents: refreshEventsCallback,\n clearEventSelection: clearEventSelectionCallback,\n }), [events, selectedEvent?.event_id, isLoading, error?.message, setSelectedEventCallback, refreshEventsCallback, clearEventSelectionCallback]);\n}\n\n","/**\n * @file Error Boundary Context\n * @package @jmruthers/pace-core\n * @module Components/ErrorBoundary\n * @since 0.6.2\n *\n * Context provider for global error handler configuration in ErrorBoundary components.\n * Allows applications to register a default error handler once, which is then used by\n * all ErrorBoundary instances unless overridden.\n *\n * @example\n * ```tsx\n * // Setup once in main.tsx\n * <ErrorBoundaryProvider\n * defaultErrorHandler={(error, errorInfo, errorId, componentName) => {\n * if (import.meta.env.DEV) {\n * console.error(`[${componentName}] ErrorBoundary caught error:`, error, errorInfo);\n * }\n * errorTracking.captureException(error, {\n * componentStack: errorInfo.componentStack,\n * errorBoundary: true,\n * errorId,\n * componentName,\n * });\n * }}\n * >\n * <App />\n * </ErrorBoundaryProvider>\n *\n * // Usage - no onError needed, uses global handler\n * <ErrorBoundary componentName=\"AppRoot\">\n * <AppContent />\n * </ErrorBoundary>\n * ```\n */\n\nimport React, { createContext, useContext, ReactNode } from 'react';\n\n/**\n * Type definition for the global error handler function\n * @public\n */\nexport type GlobalErrorHandler = (\n error: Error,\n errorInfo: React.ErrorInfo,\n errorId: string,\n componentName: string\n) => void;\n\n/**\n * Context value type for ErrorBoundary configuration\n * @internal\n */\ninterface ErrorBoundaryContextType {\n /** Global error handler that will be used by all ErrorBoundary instances */\n defaultErrorHandler?: GlobalErrorHandler;\n}\n\n/**\n * Context for ErrorBoundary global configuration\n * @internal\n */\nconst ErrorBoundaryContext = createContext<ErrorBoundaryContextType | undefined>(undefined);\n\n/**\n * Props for ErrorBoundaryProvider component\n * @public\n */\nexport interface ErrorBoundaryProviderProps {\n /** Children to wrap with the provider */\n children: ReactNode;\n /** Global error handler that will be used by all ErrorBoundary instances unless overridden */\n defaultErrorHandler?: GlobalErrorHandler;\n}\n\n/**\n * Provider component for ErrorBoundary global error handler configuration\n * \n * Wrap your application with this provider to set a default error handler that will\n * be used by all ErrorBoundary instances unless they provide their own onError prop.\n * \n * @example\n * ```tsx\n * <ErrorBoundaryProvider\n * defaultErrorHandler={(error, errorInfo, errorId, componentName) => {\n * errorTracking.captureException(error, {\n * componentStack: errorInfo.componentStack,\n * errorBoundary: true,\n * errorId,\n * componentName,\n * });\n * }}\n * >\n * <App />\n * </ErrorBoundaryProvider>\n * ```\n * \n * @public\n */\n/**\n * Error boundary provider component.\n * Provides global error handling configuration to child ErrorBoundary components.\n * \n * @param props - Error boundary provider configuration\n * @returns The error boundary provider\n */\nexport function ErrorBoundaryProvider({\n children,\n defaultErrorHandler,\n}: ErrorBoundaryProviderProps) {\n const contextValue: ErrorBoundaryContextType = {\n defaultErrorHandler,\n };\n\n return (\n <ErrorBoundaryContext.Provider value={contextValue}>\n {children}\n </ErrorBoundaryContext.Provider>\n );\n}\n\n/**\n * Hook to access ErrorBoundary context\n * @internal\n */\nexport function useErrorBoundaryContext(): ErrorBoundaryContextType | undefined {\n return useContext(ErrorBoundaryContext);\n}\n\n","/**\n * @file Error Boundary Component\n * @package @jmruthers/pace-core\n * @module Components/ErrorBoundary\n * @since 0.1.0\n *\n * A comprehensive error boundary component that catches JavaScript errors in child components\n * and provides fallback UI with retry functionality and error reporting.\n *\n * Features:\n * - Catches JavaScript errors in component tree\n * - Custom fallback UI with retry functionality\n * - Error reporting and logging\n * - Performance monitoring integration\n * - Retry mechanism with configurable attempts\n * - Development mode error details\n * - Accessibility compliant error display\n * - Component-specific error tracking\n * - Error ID generation for tracking\n *\n * @example\n * ```tsx\n * // Basic error boundary\n * <ErrorBoundary>\n * <MyComponent />\n * </ErrorBoundary>\n * \n * // Using global error handler (recommended for applications)\n * import { ErrorBoundaryProvider, ErrorBoundary } from '@jmruthers/pace-core';\n * \n * // Setup once in main.tsx\n * <ErrorBoundaryProvider\n * defaultErrorHandler={(error, errorInfo, errorId, componentName) => {\n * if (import.meta.env.DEV) {\n * console.error(`[${componentName}] ErrorBoundary caught error:`, error, errorInfo);\n * }\n * errorTracking.captureException(error, {\n * componentStack: errorInfo.componentStack,\n * errorBoundary: true,\n * errorId,\n * componentName,\n * });\n * }}\n * >\n * <App />\n * </ErrorBoundaryProvider>\n * \n * // Then use ErrorBoundary without onError prop - uses global handler\n * <ErrorBoundary componentName=\"AppRoot\">\n * <AppContent />\n * </ErrorBoundary>\n * \n * // Error boundary with custom fallback\n * <ErrorBoundary\n * componentName=\"UserProfile\"\n * fallback={<section>Something went wrong loading the profile.</section>}\n * onError={(error, errorInfo, errorId) => {\n * console.log('Error caught:', errorId, error);\n * }}\n * >\n * <UserProfile />\n * </ErrorBoundary>\n * \n * // Error boundary with retry functionality\n * <ErrorBoundary\n * componentName=\"DataTable\"\n * maxRetries={3}\n * enableRetry={true}\n * enableReporting={true}\n * onError={(error, errorInfo, errorId) => {\n * // Send to error reporting service\n * errorReportingService.report({ error, errorInfo, errorId });\n * }}\n * >\n * <DataTable data={data} />\n * </ErrorBoundary>\n * \n * // Wrapping multiple components\n * <ErrorBoundary componentName=\"App\">\n * <Header />\n * <ErrorBoundary componentName=\"MainContent\">\n * <MainContent />\n * </ErrorBoundary>\n * <Footer />\n * </ErrorBoundary>\n * \n * // Per-instance onError overrides global handler\n * <ErrorBoundary \n * componentName=\"SpecialCase\"\n * onError={(error, errorInfo, errorId) => {\n * // Custom handling for this specific case - global handler won't be called\n * }}\n * >\n * <SpecialComponent />\n * </ErrorBoundary>\n * ```\n *\n * @accessibility\n * - WCAG 2.1 AA compliant\n * - Proper ARIA role=\"alert\" for error announcements\n * - Screen reader friendly error messages\n * - Keyboard accessible retry buttons\n * - High contrast error styling\n * - Clear error identification\n *\n * @performance\n * - Efficient error state management\n * - Performance monitoring integration\n * - Minimal impact on normal rendering\n * - Configurable error reporting\n *\n * @dependencies\n * - React 19+ - Component lifecycle\n * - Performance monitoring utilities\n * - Tailwind CSS - Styling\n */\n\nimport React, { Component, ReactNode } from 'react';\nimport { performanceBudgetMonitor } from '../../utils/performance/performanceBudgets';\nimport { logger } from '../../utils/core/logger';\n\n/**\n * State interface for the ErrorBoundary component\n * @public\n */\nexport interface ErrorBoundaryState {\n /** Whether an error has been caught */\n hasError: boolean;\n /** The error that was caught */\n error?: Error;\n /** Additional error information from React */\n errorInfo?: React.ErrorInfo;\n /** Unique identifier for the error */\n errorId?: string;\n /** Number of retry attempts made */\n retryCount: number;\n}\n\n/**\n * Props interface for the ErrorBoundary component\n * @public\n */\nexport interface ErrorBoundaryProps {\n /** Child components to wrap with error boundary */\n children: ReactNode;\n /** Name of the component for error reporting */\n componentName?: string;\n /** Custom fallback UI to display when error occurs */\n fallback?: ReactNode;\n /** Callback function called when an error is caught */\n onError?: (error: Error, errorInfo: React.ErrorInfo, errorId: string) => void;\n /** Maximum number of retry attempts */\n maxRetries?: number;\n /** Whether to enable retry functionality */\n enableRetry?: boolean;\n /** Whether to enable error reporting */\n enableReporting?: boolean;\n /** Internal: Global error handler from context (not part of public API) */\n _globalErrorHandler?: (error: Error, errorInfo: React.ErrorInfo, errorId: string, componentName: string) => void;\n}\n\n/**\n * ErrorBoundary component\n * Catches JavaScript errors in child components and provides fallback UI\n * \n * @example\n * ```tsx\n * <ErrorBoundary\n * componentName=\"MyComponent\"\n * maxRetries={3}\n * onError={(error, errorInfo, errorId) => {\n * console.log('Error caught:', errorId);\n * }}\n * >\n * <MyComponent />\n * </ErrorBoundary>\n * ```\n */\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {\n private retryTimeoutId: NodeJS.Timeout | null = null;\n\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { \n hasError: false, \n retryCount: 0 \n };\n }\n\n static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {\n const errorId = `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n return { \n hasError: true, \n error,\n errorId \n };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n const { componentName = 'Unknown Component', onError, enableReporting = true } = this.props;\n const errorId = this.state.errorId!;\n const componentNameForHandler = componentName || 'Unknown Component';\n \n this.setState({ errorInfo });\n \n // Enhanced logging with component name and error ID\n logger.error('ErrorBoundary', `[${componentNameForHandler}] Caught error ${errorId}:`, error, errorInfo);\n \n // Performance monitoring - track error occurrence\n performanceBudgetMonitor.measure('ERROR_BOUNDARY_TRIGGER', 1, {\n componentName: componentNameForHandler,\n errorId,\n errorMessage: error.message,\n stack: error.stack?.substring(0, 200), // Truncated stack trace\n });\n\n // Report error if enabled\n if (enableReporting) {\n this.reportError(errorId, componentNameForHandler);\n }\n \n // Call error handler: prefer prop onError, fall back to global handler from props\n if (onError) {\n onError(error, errorInfo, errorId);\n } else if (this.props._globalErrorHandler) {\n this.props._globalErrorHandler(error, errorInfo, errorId, componentNameForHandler);\n }\n }\n\n private reportError = (errorId: string, componentName: string) => {\n // In production, this would send to error reporting service\n if (import.meta.env.MODE === 'production') {\n // Example: Send to error reporting service\n // errorReportingService.report({ error, errorInfo, errorId, componentName });\n logger.warn('ErrorBoundary', 'Error reporting would be triggered in production:', { errorId, componentName });\n }\n };\n\n private handleRetry = () => {\n const { maxRetries = 3 } = this.props;\n const { retryCount } = this.state;\n\n if (retryCount < maxRetries) {\n logger.debug('ErrorBoundary', `Retrying component render (attempt ${retryCount + 1}/${maxRetries})`);\n \n this.setState(prevState => ({\n hasError: false,\n error: undefined,\n errorInfo: undefined,\n errorId: undefined,\n retryCount: prevState.retryCount + 1\n }));\n }\n };\n\n componentWillUnmount() {\n if (this.retryTimeoutId) {\n clearTimeout(this.retryTimeoutId);\n }\n }\n\n render() {\n if (this.state.hasError) {\n const { \n componentName = 'Component', \n fallback, \n enableRetry = true, \n maxRetries = 3 \n } = this.props;\n const { retryCount, errorId } = this.state;\n\n // Use custom fallback if provided\n if (fallback) {\n return fallback;\n }\n\n // Enhanced error UI with retry functionality\n return (\n <div \n role=\"alert\" \n className=\"p-6 bg-destructive/10 border border-destructive/20 rounded-lg\"\n data-error-boundary={errorId}\n >\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0\">\n <svg className=\"w-5 h-5 text-destructive\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fillRule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z\" clipRule=\"evenodd\" />\n </svg>\n </div>\n <div className=\"flex-1 min-w-0\">\n <h3 className=\"text-destructive\">\n Error in {componentName}\n </h3>\n <p className=\"text-destructive/80\">\n {this.state.error?.message || 'An unexpected error occurred.'}\n </p>\n \n {enableRetry && retryCount < maxRetries && (\n <div className=\"flex gap-3 mb-4\">\n <button\n onClick={this.handleRetry}\n className=\"px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors text-sm font-medium\"\n >\n Retry ({retryCount + 1}/{maxRetries})\n </button>\n <button\n onClick={() => window.location.reload()}\n className=\"px-4 py-2 bg-sec-600 text-main-50 rounded-md hover:bg-sec-700 transition-colors text-sm font-medium\"\n >\n Reload Page\n </button>\n </div>\n )}\n\n {retryCount >= maxRetries && (\n <div className=\"mb-4 p-3 bg-acc-50 border border-acc-200 rounded-md\">\n <p className=\"text-acc-800\">\n Maximum retry attempts reached. Please reload the page or contact support.\n </p>\n <button\n onClick={() => window.location.reload()}\n className=\"mt-2 px-3 py-1 bg-acc-600 text-main-50 rounded text-sm hover:bg-acc-700\"\n >\n Reload Page\n </button>\n </div>\n )}\n\n {import.meta.env.MODE === 'development' && this.state.error && (\n <details className=\"text-sm text-destructive/70\">\n <summary className=\"cursor-pointer font-medium mb-2\">\n Error Details (Development)\n </summary>\n <div className=\"bg-destructive/5 p-3 rounded border\">\n <p className=\"font-mono\">Error ID: {errorId}</p>\n <pre className=\"whitespace-pre-wrap text-xs overflow-auto max-h-32\">\n {this.state.error.toString()}\n {this.state.errorInfo?.componentStack}\n </pre>\n </div>\n </details>\n )}\n </div>\n </div>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n\nexport default ErrorBoundary;\n","/**\n * @file Error Boundary exports\n */\n\n// Re-export ErrorBoundary with automatic context support\nimport { ErrorBoundary as ErrorBoundaryClass } from './ErrorBoundary';\nimport { useErrorBoundaryContext } from './ErrorBoundaryContext';\nimport React from 'react';\nimport type { ErrorBoundaryProps } from './ErrorBoundary';\n\n// Export types and provider\nexport type { ErrorBoundaryProps, ErrorBoundaryState } from './ErrorBoundary';\nexport { ErrorBoundaryProvider } from './ErrorBoundaryContext';\nexport type { ErrorBoundaryProviderProps, GlobalErrorHandler } from './ErrorBoundaryContext';\n\n/**\n * ErrorBoundary component with automatic context support\n * This wrapper automatically uses the global error handler from ErrorBoundaryProvider\n * if no onError prop is provided.\n */\nexport const ErrorBoundary = React.forwardRef<\n ErrorBoundaryClass,\n Omit<ErrorBoundaryProps, '_globalErrorHandler'>\n>((props, ref) => {\n const context = useErrorBoundaryContext();\n const globalErrorHandler = context?.defaultErrorHandler;\n \n return React.createElement(ErrorBoundaryClass, { ...props, ref, _globalErrorHandler: globalErrorHandler });\n});\n\nErrorBoundary.displayName = 'ErrorBoundary';\n\nexport default ErrorBoundary;\n","/**\n * @file Public Page Provider\n * @package @jmruthers/pace-core\n * @module Components/PublicLayout\n * @since 1.0.0\n *\n * A completely isolated provider for public pages that doesn't trigger\n * any authentication context. This ensures public pages work independently\n * of the main application's authentication system.\n *\n * Features:\n * - No authentication required\n * - No organisation context\n * - No event context\n * - Completely isolated from main app context\n * - Environment variable access for public data\n * - Error boundary integration\n *\n * @example\n * ```tsx\n * import { PublicPageProvider } from '@jmruthers/pace-core';\n * \n * const APP_NAME = 'CORE';\n * \n * function PublicApp() {\n * return (\n * <PublicPageProvider appName={APP_NAME}>\n * <Routes>\n * <Route path=\"/events/:eventCode/recipe-grid-report\" element={<PublicRecipePage />} />\n * </Routes>\n * </PublicPageProvider>\n * );\n * }\n * ```\n */\n\nimport React, { createContext, useContext, ReactNode, useMemo } from 'react';\nimport { createClient } from '@supabase/supabase-js';\nimport type { Database } from '../../types/database';\nimport { ErrorBoundary } from '../ErrorBoundary';\nimport { logger } from '../../utils/core/logger';\n\ninterface PublicPageContextType {\n isPublicPage: true;\n supabase: ReturnType<typeof createClient<Database>> | null;\n appName: string | null;\n environment: {\n supabaseUrl: string | null;\n supabaseKey: string | null;\n };\n}\n\n/**\n * Context for public pages.\n * Provides isolated context for public pages without authentication requirements.\n */\nexport const PublicPageContext = createContext<PublicPageContextType | undefined>(undefined);\n\nexport interface PublicPageProviderProps {\n children: ReactNode;\n /** Application name for logo display and branding. Should match the APP_NAME constant used in App.tsx */\n appName?: string;\n}\n\n/**\n * Provider for public pages that completely isolates them from authentication context\n * \n * This provider:\n * - Does not initialize any authentication providers\n * - Provides environment variables for public data access\n * - Includes error boundary for graceful error handling\n * - Is completely separate from the main app context\n * - Provides appName for consistent logo display\n */\nexport function PublicPageProvider({ children, appName }: PublicPageProviderProps) {\n // Get environment variables for public data access\n // Handle both Vite (import.meta.env) and Node.js (process.env) environments\n const getEnvVar = (key: string): string | undefined => {\n // Check Vite environment first (browser)\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n const env = import.meta.env as Record<string, string | undefined>;\n return env[key];\n }\n // Check Node.js environment (server-side)\n if (typeof process !== 'undefined' && process.env) {\n return process.env[key];\n }\n return undefined;\n };\n\n const supabaseUrl = getEnvVar('VITE_SUPABASE_URL') || \n getEnvVar('NEXT_PUBLIC_SUPABASE_URL') || \n null;\n \n const supabaseKey = getEnvVar('VITE_SUPABASE_PUBLISHABLE_KEY') || \n getEnvVar('NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY') ||\n getEnvVar('VITE_SUPABASE_ANON_KEY') ||\n getEnvVar('NEXT_PUBLIC_SUPABASE_ANON_KEY') || \n null;\n\n // Create Supabase client if environment variables are available\n // Note: VITE_SUPABASE_PUBLISHABLE_KEY should be your publishable key (sb_publishable_...)\n const supabase = useMemo(() => {\n if (!supabaseUrl || !supabaseKey) {\n logger.warn('PublicPageProvider', 'Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are set in your environment.');\n return null;\n }\n const client = createClient<Database>(supabaseUrl, supabaseKey);\n return client;\n }, [supabaseUrl, supabaseKey]);\n\n const contextValue: PublicPageContextType = {\n isPublicPage: true,\n supabase,\n appName: appName || null,\n environment: {\n supabaseUrl,\n supabaseKey\n }\n };\n\n return (\n <PublicPageContext.Provider value={contextValue}>\n <ErrorBoundary componentName=\"PublicPageProvider\">\n {children}\n </ErrorBoundary>\n </PublicPageContext.Provider>\n );\n}\n\n/**\n * Hook to access public page context\n * \n * @returns Public page context with environment variables\n */\nexport function usePublicPageContext(): PublicPageContextType {\n const context = useContext(PublicPageContext);\n \n if (!context) {\n throw new Error('usePublicPageContext must be used within a PublicPageProvider');\n }\n \n return context;\n}\n\n/**\n * Hook to check if we're in a public page context\n * \n * @returns True if we're in a public page context\n */\nexport function useIsPublicPage(): boolean {\n const context = useContext(PublicPageContext);\n return context !== undefined;\n}\n","/**\n * @file useAppConfig Hook\n * @package @jmruthers/pace-core\n * @module Hooks/useAppConfig\n * @since 0.4.0\n *\n * Hook for accessing app name and loading state.\n * \n * NOTE: Scope configuration (requires_event) is now page-level only (rbac_app_pages.scope_type).\n * Use getPageScopeType() to determine if a specific page requires event or organisation context.\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { appName, isLoading } = useAppConfig();\n * \n * if (isLoading) return <div>Loading...</div>;\n * \n * return <div>App: {appName}</div>;\n * }\n * ```\n */\n\nimport { useMemo, useContext } from 'react';\nimport { useUnifiedAuth } from '../providers/services/UnifiedAuthProvider';\nimport { useIsPublicPage, PublicPageContext } from '../components/PublicLayout/PublicPageProvider';\n\nexport interface UseAppConfigReturn {\n appName: string;\n isLoading: boolean;\n}\n\n/**\n * Hook to access app configuration\n * Works in both authenticated and public contexts\n * @returns App configuration and loading state\n */\nexport function useAppConfig(): UseAppConfigReturn {\n // Check if we're in a public page context first\n const isPublicPage = useIsPublicPage();\n \n // Try to get appName from PublicPageContext (access context directly to avoid hook rule violations)\n const publicPageContext = useContext(PublicPageContext);\n const hasPublicContext = publicPageContext !== undefined;\n const contextAppName = publicPageContext?.appName || null;\n const getNodeEnvVar = (key: string): string | undefined =>\n typeof process !== 'undefined' && process.env ? process.env[key] : undefined;\n \n if (isPublicPage) {\n const getAppName = (): string => {\n // When tests mock public mode without the provider, default to PACE\n if (!hasPublicContext) {\n return 'PACE';\n }\n \n // Priority 1: Use appName from PublicPageContext (passed from App.tsx)\n if (contextAppName) {\n return contextAppName;\n }\n \n // Priority 2: Check environment variables\n if (typeof import.meta !== 'undefined' && (import.meta as any).env) {\n const env = (import.meta as any).env;\n return env.VITE_APP_NAME || \n env.NEXT_PUBLIC_APP_NAME || \n 'PACE';\n }\n if (typeof import.meta !== 'undefined' && import.meta.env) {\n return import.meta.env.VITE_APP_NAME || \n import.meta.env.NEXT_PUBLIC_APP_NAME || \n 'PACE';\n }\n \n const nodeEnvAppName =\n getNodeEnvVar('VITE_APP_NAME') ||\n getNodeEnvVar('NEXT_PUBLIC_APP_NAME');\n if (nodeEnvAppName) {\n return nodeEnvAppName;\n }\n \n // Priority 3: Default fallback\n return 'PACE';\n };\n \n return useMemo(() => ({\n appName: getAppName(),\n isLoading: false\n }), [contextAppName, hasPublicContext]);\n }\n \n // For authenticated pages, use UnifiedAuthProvider\n try {\n const { appName } = useUnifiedAuth();\n return useMemo(() => ({\n appName,\n isLoading: false\n }), [appName]);\n } catch (error) {\n // Fallback if UnifiedAuthProvider is not available\n return useMemo(() => ({\n appName: contextAppName ||\n getNodeEnvVar('VITE_APP_NAME') ||\n getNodeEnvVar('NEXT_PUBLIC_APP_NAME') ||\n 'PACE',\n isLoading: false\n }), [contextAppName]);\n }\n} ","/**\n * @file Organisation Security Hook\n * @package @jmruthers/pace-core\n * @module Hooks/OrganisationSecurity\n * @since 0.4.0\n *\n * Security-focused hook for organisation access validation and super admin functionality.\n * Provides utilities for validating user access to organisations and checking permissions.\n */\n\nimport { useCallback, useMemo, useEffect, useState } from 'react';\nimport { useUnifiedAuth } from '../providers';\nimport { useOrganisations } from './useOrganisations';\n// Legacy useRBAC hook removed - use new RBAC system instead\nimport type { OrganisationSecurityError, SuperAdminContext } from '../types/organisation';\nimport type { Permission } from '../rbac/types';\nimport { logger } from '../utils/core/logger';\n\n/**\n * Organisation security hook interface.\n * Provides security utilities for organisation access validation and super admin functionality.\n */\nexport interface OrganisationSecurityHook {\n // Super admin context\n superAdminContext: SuperAdminContext;\n \n // Access validation\n validateOrganisationAccess: (orgId: string) => Promise<boolean>;\n hasMinimumRole: (minRole: string, orgId?: string) => boolean;\n canAccessChildOrganisations: (orgId?: string) => boolean;\n \n // Permission checks\n hasPermission: (permission: string, orgId?: string) => Promise<boolean>;\n getUserPermissions: (orgId?: string) => Promise<string[]>;\n \n // Audit logging\n logOrganisationAccess: (action: string, details?: Record<string, unknown>) => Promise<void>;\n \n // Security utilities\n ensureOrganisationAccess: (orgId: string) => Promise<void>;\n validateUserAccess: (userId: string, orgId: string) => Promise<boolean>;\n}\n\nexport const useOrganisationSecurity = (): OrganisationSecurityHook => {\n const { user, session, supabase } = useUnifiedAuth();\n const { selectedOrganisation, getUserRole, validateOrganisationAccess: validateAccess } = useOrganisations();\n \n // Super admin status - query database for security (user_metadata can be spoofed)\n const [isSuperAdmin, setIsSuperAdmin] = useState<boolean>(false);\n const [isCheckingSuperAdmin, setIsCheckingSuperAdmin] = useState(false);\n\n // Check super admin status from database\n useEffect(() => {\n if (!user || !session || !supabase) {\n setIsSuperAdmin(false);\n return;\n }\n\n const checkSuperAdmin = async () => {\n setIsCheckingSuperAdmin(true);\n try {\n const now = new Date().toISOString();\n const { data, error } = await supabase\n .from('rbac_global_roles')\n .select('role')\n .eq('user_id', user.id)\n .eq('role', 'super_admin')\n .lte('valid_from', now)\n .or(`valid_to.is.null,valid_to.gte.${now}`)\n .limit(1);\n\n setIsSuperAdmin(!error && data && data.length > 0);\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Error checking super admin status:', error);\n setIsSuperAdmin(false);\n } finally {\n setIsCheckingSuperAdmin(false);\n }\n };\n\n checkSuperAdmin();\n }, [user, session, supabase]);\n\n // Super admin context\n const superAdminContext = useMemo((): SuperAdminContext => {\n return {\n isSuperAdmin,\n hasGlobalAccess: isSuperAdmin,\n canManageAllOrganisations: isSuperAdmin\n };\n }, [isSuperAdmin]);\n\n // Validate organisation access with database check\n const validateOrganisationAccess = useCallback(async (orgId: string): Promise<boolean> => {\n if (!user || !session || !supabase) return false;\n \n try {\n // Super admin has access to all organisations\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n // Check organisation membership using consolidated rbac_organisation_roles table\n const { data, error } = await supabase\n .from('rbac_organisation_roles')\n .select('id')\n .eq('user_id', user.id)\n .eq('organisation_id', orgId)\n .eq('status', 'active')\n .is('revoked_at', null)\n .in('role', ['org_admin', 'leader', 'member']) // Only actual members, not supporters\n .single();\n\n if (error) {\n logger.error('useOrganisationSecurity', 'Error validating organisation access:', error);\n return false;\n }\n\n return !!data;\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Exception validating organisation access:', error);\n return false;\n }\n }, [user, session, supabase, superAdminContext.isSuperAdmin]);\n\n // Check if user has minimum role\n const hasMinimumRole = useCallback((minRole: string, orgId?: string): boolean => {\n // Super admin has all roles\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n const targetOrgId = orgId || selectedOrganisation?.id;\n if (!targetOrgId) return false;\n\n const userRole = getUserRole(targetOrgId);\n const roleHierarchy = ['supporter', 'member', 'leader', 'org_admin'];\n \n const userRoleIndex = roleHierarchy.indexOf(userRole);\n const minRoleIndex = roleHierarchy.indexOf(minRole);\n \n return userRoleIndex >= minRoleIndex;\n }, [selectedOrganisation, getUserRole, superAdminContext.isSuperAdmin]);\n\n // Check if user can access child organisations\n const canAccessChildOrganisations = useCallback((orgId?: string): boolean => {\n // Super admin can access all organisations\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n const targetOrgId = orgId || selectedOrganisation?.id;\n if (!targetOrgId) return false;\n\n const userRole = getUserRole(targetOrgId);\n return userRole === 'org_admin';\n }, [selectedOrganisation, getUserRole, superAdminContext.isSuperAdmin]);\n\n // Check specific permission using the new RBAC system\n const hasPermission = useCallback(async (permission: string, orgId?: string): Promise<boolean> => {\n // Super admin has all permissions\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n const targetOrgId = orgId || selectedOrganisation?.id;\n if (!targetOrgId || !user) return false;\n\n try {\n // Use the new RBAC system with caching\n const { isPermittedCached } = await import('../rbac/api');\n \n const scope = {\n organisationId: targetOrgId,\n eventId: user.user_metadata?.eventId || user.app_metadata?.eventId,\n appId: user.user_metadata?.appId || user.app_metadata?.appId,\n };\n\n return await isPermittedCached({\n userId: user.id,\n scope,\n permission: permission as Permission\n });\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Exception checking permission:', error);\n return false;\n }\n }, [selectedOrganisation, user, superAdminContext.isSuperAdmin]);\n\n // Get user's permissions for organisation using the new RBAC system\n const getUserPermissions = useCallback(async (orgId?: string): Promise<string[]> => {\n // Super admin has all permissions\n if (superAdminContext.isSuperAdmin) {\n return ['*']; // All permissions\n }\n\n const targetOrgId = orgId || selectedOrganisation?.id;\n if (!targetOrgId || !user) return [];\n\n try {\n // Use the new RBAC system\n const { getPermissionMap } = await import('../rbac/api');\n \n const scope = {\n organisationId: targetOrgId,\n eventId: user.user_metadata?.eventId || user.app_metadata?.eventId,\n appId: user.user_metadata?.appId || user.app_metadata?.appId,\n };\n\n const permissionMap = await getPermissionMap({\n userId: user.id,\n scope\n });\n \n // Flatten all permissions from all pages\n const allPermissions = Object.entries(permissionMap)\n .filter(([, allowed]) => allowed)\n .map(([permission]) => permission);\n return [...new Set(allPermissions)]; // Remove duplicates\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Exception getting user permissions:', error);\n return [];\n }\n }, [selectedOrganisation, user, getUserRole, superAdminContext.isSuperAdmin]);\n\n // Log organisation access for audit using the new RBAC audit system\n const logOrganisationAccess = useCallback(async (action: string, details?: Record<string, unknown>): Promise<void> => {\n if (!user || !selectedOrganisation) return;\n\n try {\n // Use the new RBAC audit system - only if we have a valid organisation ID\n if (selectedOrganisation.id) {\n const { emitAuditEvent } = await import('../rbac/audit');\n \n await emitAuditEvent({\n type: 'permission_check',\n userId: user.id,\n organisationId: selectedOrganisation.id,\n permission: action,\n decision: true, // Assume access was granted if we're logging it\n source: 'api',\n duration_ms: 0, // No actual permission check performed here\n metadata: details || {}\n });\n }\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Error logging organisation access:', error);\n }\n }, [user, selectedOrganisation]);\n\n // Ensure organisation access (throws if no access)\n const ensureOrganisationAccess = useCallback(async (orgId: string): Promise<void> => {\n const hasAccess = await validateOrganisationAccess(orgId);\n \n if (!hasAccess) {\n const error = new Error(`User does not have access to organisation ${orgId}`) as OrganisationSecurityError;\n error.name = 'OrganisationSecurityError';\n error.code = 'ACCESS_DENIED';\n error.organisationId = orgId;\n error.userId = user?.id;\n throw error;\n }\n }, [validateOrganisationAccess, user]);\n\n // Validate user access (for admin functions)\n const validateUserAccess = useCallback(async (userId: string, orgId: string): Promise<boolean> => {\n if (!supabase) return false;\n\n try {\n // Super admin can validate any user\n if (superAdminContext.isSuperAdmin) {\n return true;\n }\n\n // Regular users can only validate their own access\n if (userId !== user?.id) {\n return false;\n }\n\n return await validateOrganisationAccess(orgId);\n } catch (error) {\n logger.error('useOrganisationSecurity', 'Exception validating user access:', error);\n return false;\n }\n }, [supabase, superAdminContext.isSuperAdmin, user, validateOrganisationAccess]);\n\n return {\n superAdminContext,\n validateOrganisationAccess,\n hasMinimumRole,\n canAccessChildOrganisations,\n hasPermission,\n getUserPermissions,\n logOrganisationAccess,\n ensureOrganisationAccess,\n validateUserAccess\n };\n}; "],"mappings":"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","names":["jsx","React","ErrorBoundary","createContext","useContext","useMemo","jsx","createContext","useMemo","ErrorBoundary","useContext","useMemo","useContext","useContext","useMemo","useMemo","useMemo"]}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useEvents
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OEWDTMG7.js";
|
|
4
4
|
import {
|
|
5
5
|
isSuperAdmin
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3LPHPB62.js";
|
|
7
7
|
import {
|
|
8
8
|
assertAppId
|
|
9
9
|
} from "./chunk-QXHPKYJV.js";
|
|
@@ -1210,7 +1210,7 @@ function getPublicFileDisplayCacheStats() {
|
|
|
1210
1210
|
}
|
|
1211
1211
|
|
|
1212
1212
|
// src/hooks/useFileDisplay.ts
|
|
1213
|
-
import { useState as useState4, useEffect as useEffect5, useCallback as useCallback4 } from "react";
|
|
1213
|
+
import { useState as useState4, useEffect as useEffect5, useCallback as useCallback4, useRef as useRef3 } from "react";
|
|
1214
1214
|
|
|
1215
1215
|
// src/utils/file-reference/index.ts
|
|
1216
1216
|
var log3 = createLogger("FileReferenceService");
|
|
@@ -1691,14 +1691,20 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1691
1691
|
const [fileCount, setFileCount] = useState4(0);
|
|
1692
1692
|
const [isLoading, setIsLoading] = useState4(false);
|
|
1693
1693
|
const [error, setError] = useState4(null);
|
|
1694
|
+
const isMountedRef = useRef3(true);
|
|
1695
|
+
const safeSetState = (setter, value) => {
|
|
1696
|
+
if (isMountedRef.current) {
|
|
1697
|
+
setter(value);
|
|
1698
|
+
}
|
|
1699
|
+
};
|
|
1694
1700
|
const fetchFiles = useCallback4(async () => {
|
|
1695
1701
|
if (!table_name || !record_id || !supabase) {
|
|
1696
|
-
setFileUrl
|
|
1697
|
-
setFileReference
|
|
1698
|
-
setFileReferences
|
|
1699
|
-
setFileUrls
|
|
1700
|
-
setFileCount
|
|
1701
|
-
setIsLoading
|
|
1702
|
+
safeSetState(setFileUrl, null);
|
|
1703
|
+
safeSetState(setFileReference, null);
|
|
1704
|
+
safeSetState(setFileReferences, []);
|
|
1705
|
+
safeSetState(setFileUrls, /* @__PURE__ */ new Map());
|
|
1706
|
+
safeSetState(setFileCount, 0);
|
|
1707
|
+
safeSetState(setIsLoading, false);
|
|
1702
1708
|
return;
|
|
1703
1709
|
}
|
|
1704
1710
|
if (organisation_id) {
|
|
@@ -1721,31 +1727,31 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1721
1727
|
expiresIn: 3600
|
|
1722
1728
|
});
|
|
1723
1729
|
const regeneratedUrl = signedUrlResult?.url || null;
|
|
1724
|
-
setFileUrl
|
|
1725
|
-
setFileReference
|
|
1726
|
-
setFileReferences
|
|
1727
|
-
setFileUrls
|
|
1728
|
-
setFileCount
|
|
1729
|
-
setIsLoading
|
|
1730
|
-
setError
|
|
1730
|
+
safeSetState(setFileUrl, regeneratedUrl);
|
|
1731
|
+
safeSetState(setFileReference, cachedData.fileReference);
|
|
1732
|
+
safeSetState(setFileReferences, cachedData.fileReferences || []);
|
|
1733
|
+
safeSetState(setFileUrls, cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
1734
|
+
safeSetState(setFileCount, cachedData.fileCount || 0);
|
|
1735
|
+
safeSetState(setIsLoading, false);
|
|
1736
|
+
safeSetState(setError, null);
|
|
1731
1737
|
return;
|
|
1732
1738
|
} catch (err) {
|
|
1733
1739
|
logger.warn("useFileDisplay", "Failed to regenerate signed URL from cache, falling back to fetch:", err);
|
|
1734
1740
|
}
|
|
1735
1741
|
}
|
|
1736
|
-
setFileUrl
|
|
1737
|
-
setFileReference
|
|
1738
|
-
setFileReferences
|
|
1739
|
-
setFileUrls
|
|
1740
|
-
setFileCount
|
|
1741
|
-
setIsLoading
|
|
1742
|
-
setError
|
|
1742
|
+
safeSetState(setFileUrl, cachedData.fileUrl || null);
|
|
1743
|
+
safeSetState(setFileReference, cachedData.fileReference || null);
|
|
1744
|
+
safeSetState(setFileReferences, cachedData.fileReferences || []);
|
|
1745
|
+
safeSetState(setFileUrls, cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
1746
|
+
safeSetState(setFileCount, cachedData.fileCount || 0);
|
|
1747
|
+
safeSetState(setIsLoading, false);
|
|
1748
|
+
safeSetState(setError, null);
|
|
1743
1749
|
return;
|
|
1744
1750
|
}
|
|
1745
1751
|
}
|
|
1746
1752
|
try {
|
|
1747
|
-
setIsLoading
|
|
1748
|
-
setError
|
|
1753
|
+
safeSetState(setIsLoading, true);
|
|
1754
|
+
safeSetState(setError, null);
|
|
1749
1755
|
const service = createFileReferenceService(supabase);
|
|
1750
1756
|
let files = [];
|
|
1751
1757
|
const shouldSearchBothScopes = organisation_id === void 0;
|
|
@@ -1943,11 +1949,11 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1943
1949
|
}
|
|
1944
1950
|
}
|
|
1945
1951
|
if (files.length === 0) {
|
|
1946
|
-
setFileUrl
|
|
1947
|
-
setFileReference
|
|
1948
|
-
setFileReferences
|
|
1949
|
-
setFileUrls
|
|
1950
|
-
setFileCount
|
|
1952
|
+
safeSetState(setFileUrl, null);
|
|
1953
|
+
safeSetState(setFileReference, null);
|
|
1954
|
+
safeSetState(setFileReferences, []);
|
|
1955
|
+
safeSetState(setFileUrls, /* @__PURE__ */ new Map());
|
|
1956
|
+
safeSetState(setFileCount, 0);
|
|
1951
1957
|
if (enableCache) {
|
|
1952
1958
|
authenticatedFileCache.set(cacheKey, {
|
|
1953
1959
|
data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: /* @__PURE__ */ new Map(), fileCount: 0 },
|
|
@@ -1958,11 +1964,11 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1958
1964
|
}
|
|
1959
1965
|
return;
|
|
1960
1966
|
}
|
|
1961
|
-
setFileReferences
|
|
1962
|
-
setFileCount
|
|
1967
|
+
safeSetState(setFileReferences, files);
|
|
1968
|
+
safeSetState(setFileCount, files.length);
|
|
1963
1969
|
if (category && files.length > 0) {
|
|
1964
1970
|
const firstFile = files[0];
|
|
1965
|
-
setFileReference
|
|
1971
|
+
safeSetState(setFileReference, firstFile);
|
|
1966
1972
|
let url = null;
|
|
1967
1973
|
if (firstFile.is_public) {
|
|
1968
1974
|
url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
@@ -1982,7 +1988,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1982
1988
|
});
|
|
1983
1989
|
}
|
|
1984
1990
|
}
|
|
1985
|
-
setFileUrl
|
|
1991
|
+
safeSetState(setFileUrl, url);
|
|
1986
1992
|
} else {
|
|
1987
1993
|
const urlMap = await generateFileUrlsBatch(supabase, files, {
|
|
1988
1994
|
appName: "pace-core",
|
|
@@ -1990,9 +1996,9 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1990
1996
|
userId: organisation_id ? void 0 : record_id,
|
|
1991
1997
|
expiresIn: 3600
|
|
1992
1998
|
});
|
|
1993
|
-
setFileUrls
|
|
1994
|
-
setFileReference
|
|
1995
|
-
setFileUrl
|
|
1999
|
+
safeSetState(setFileUrls, urlMap);
|
|
2000
|
+
safeSetState(setFileReference, null);
|
|
2001
|
+
safeSetState(setFileUrl, null);
|
|
1996
2002
|
}
|
|
1997
2003
|
if (enableCache) {
|
|
1998
2004
|
let cacheData = {
|
|
@@ -2030,28 +2036,36 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
2030
2036
|
} catch (err) {
|
|
2031
2037
|
logger.error("useFileDisplay", "Error fetching files:", err);
|
|
2032
2038
|
const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
2033
|
-
setError
|
|
2034
|
-
setFileUrl
|
|
2035
|
-
setFileReference
|
|
2036
|
-
setFileReferences
|
|
2037
|
-
setFileUrls
|
|
2038
|
-
setFileCount
|
|
2039
|
+
safeSetState(setError, error2);
|
|
2040
|
+
safeSetState(setFileUrl, null);
|
|
2041
|
+
safeSetState(setFileReference, null);
|
|
2042
|
+
safeSetState(setFileReferences, []);
|
|
2043
|
+
safeSetState(setFileUrls, /* @__PURE__ */ new Map());
|
|
2044
|
+
safeSetState(setFileCount, 0);
|
|
2039
2045
|
} finally {
|
|
2040
|
-
setIsLoading
|
|
2046
|
+
safeSetState(setIsLoading, false);
|
|
2041
2047
|
}
|
|
2042
2048
|
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
2043
2049
|
useEffect5(() => {
|
|
2050
|
+
isMountedRef.current = true;
|
|
2044
2051
|
if (table_name && record_id && supabase) {
|
|
2045
|
-
fetchFiles()
|
|
2052
|
+
fetchFiles().catch((err) => {
|
|
2053
|
+
if (isMountedRef.current) {
|
|
2054
|
+
safeSetState(setError, err instanceof Error ? err : new Error("Unknown error"));
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2046
2057
|
} else {
|
|
2047
|
-
setFileUrl
|
|
2048
|
-
setFileReference
|
|
2049
|
-
setFileReferences
|
|
2050
|
-
setFileUrls
|
|
2051
|
-
setFileCount
|
|
2052
|
-
setIsLoading
|
|
2053
|
-
setError
|
|
2058
|
+
safeSetState(setFileUrl, null);
|
|
2059
|
+
safeSetState(setFileReference, null);
|
|
2060
|
+
safeSetState(setFileReferences, []);
|
|
2061
|
+
safeSetState(setFileUrls, /* @__PURE__ */ new Map());
|
|
2062
|
+
safeSetState(setFileCount, 0);
|
|
2063
|
+
safeSetState(setIsLoading, false);
|
|
2064
|
+
safeSetState(setError, null);
|
|
2054
2065
|
}
|
|
2066
|
+
return () => {
|
|
2067
|
+
isMountedRef.current = false;
|
|
2068
|
+
};
|
|
2055
2069
|
}, [table_name, record_id, organisation_id, supabase]);
|
|
2056
2070
|
const refetch = useCallback4(async () => {
|
|
2057
2071
|
if (!table_name || !record_id || !supabase) return;
|
|
@@ -2143,11 +2157,11 @@ function useEventTheme(event) {
|
|
|
2143
2157
|
}
|
|
2144
2158
|
|
|
2145
2159
|
// src/hooks/usePreventTabReload.ts
|
|
2146
|
-
import { useEffect as useEffect7, useRef as
|
|
2160
|
+
import { useEffect as useEffect7, useRef as useRef4 } from "react";
|
|
2147
2161
|
function usePreventTabReload(options = {}) {
|
|
2148
2162
|
const { enabled = true, gracePeriodMs = 2e3 } = options;
|
|
2149
|
-
const isRestoringFromCacheRef =
|
|
2150
|
-
const gracePeriodTimeoutRef =
|
|
2163
|
+
const isRestoringFromCacheRef = useRef4(false);
|
|
2164
|
+
const gracePeriodTimeoutRef = useRef4(null);
|
|
2151
2165
|
useEffect7(() => {
|
|
2152
2166
|
if (!enabled || typeof window === "undefined") return;
|
|
2153
2167
|
const handlePageShow = (event) => {
|
|
@@ -2220,4 +2234,4 @@ export {
|
|
|
2220
2234
|
useEventTheme,
|
|
2221
2235
|
usePreventTabReload
|
|
2222
2236
|
};
|
|
2223
|
-
//# sourceMappingURL=chunk-
|
|
2237
|
+
//# sourceMappingURL=chunk-YKRAFF5K.js.map
|