@jmruthers/pace-core 0.6.1 → 0.6.3
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 +88 -10
- package/cursor-rules/00-pace-core-compliance.mdc +46 -87
- package/cursor-rules/01-standards-compliance.mdc +16 -47
- package/cursor-rules/02-project-structure.mdc +4 -4
- package/cursor-rules/03-solid-principles.mdc +45 -164
- package/cursor-rules/04-testing-standards.mdc +22 -69
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
- package/cursor-rules/06-code-quality.mdc +42 -125
- package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +18 -0
- package/cursor-rules/README.md +2 -1
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
- package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
- package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
- package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
- package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
- package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
- package/dist/chunk-2T2IG7T7.js.map +1 -0
- package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
- package/dist/chunk-6Z7LTB3D.js.map +1 -0
- package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
- package/dist/chunk-CNCQDFLN.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
- package/dist/chunk-DWUBLJJM.js.map +1 -0
- package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
- package/dist/chunk-HFZBI76P.js.map +1 -0
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
- package/dist/chunk-M7MPQISP.js.map +1 -0
- package/dist/chunk-PQBSKX33.js +7793 -0
- package/dist/chunk-PQBSKX33.js.map +1 -0
- package/dist/chunk-QRPVRXYT.js +226 -0
- package/dist/chunk-QRPVRXYT.js.map +1 -0
- package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
- package/dist/chunk-RWEBCB47.js.map +1 -0
- package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
- package/dist/chunk-YDQHOZNA.js.map +1 -0
- package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
- package/dist/chunk-ZNIWI3UC.js.map +1 -0
- package/dist/components.d.ts +5 -5
- package/dist/components.js +18 -16
- package/dist/components.js.map +1 -1
- package/dist/contextValidator-3JNZKUTX.js +9 -0
- package/dist/contextValidator-3JNZKUTX.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +55 -122
- package/dist/hooks.js +10 -13
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +60 -13
- package/dist/index.js +30 -25
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +210 -139
- package/dist/rbac/index.js +17 -13
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +2 -2
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +17 -19
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +21 -17
- package/docs/api/modules.md +4191 -2967
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/components/context-selector.md +126 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +2 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +2 -24
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/rbac/secure-client-protection.md +330 -0
- package/docs/standards/README.md +1 -0
- package/package.json +4 -3
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +985 -0
- package/scripts/audit/core/checks/documentation.cjs +268 -0
- package/scripts/audit/core/checks/environment.cjs +116 -0
- package/scripts/audit/core/checks/error-handling.cjs +340 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +61 -936
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +14 -0
- package/src/components/Button/Button.tsx +22 -0
- package/src/components/Calendar/Calendar.tsx +8 -2
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/DataTable.tsx +38 -4
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +196 -554
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +8 -0
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +8 -0
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- package/src/components/DataTable/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/Dialog/Dialog.tsx +31 -3
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/FileDisplay/FileDisplay.tsx +74 -28
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +14 -11
- package/src/components/Form/Form.tsx +1 -0
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +59 -49
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +8 -4
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoginForm/LoginForm.tsx +4 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- 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 +13 -11
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +3 -4
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +80 -434
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Toast/Toast.tsx +4 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -2
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +21 -18
- package/src/components/index.ts +7 -7
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +4 -0
- package/src/hooks/public/usePublicEventLogo.ts +4 -0
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +4 -0
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +81 -50
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +14 -0
- package/src/hooks/useFocusTrap.ts +3 -0
- package/src/hooks/useInactivityTracker.ts +3 -0
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +7 -0
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +1 -1
- package/src/index.ts +6 -6
- package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
- package/src/providers/services/AuthServiceProvider.tsx +35 -7
- package/src/providers/services/EventServiceProvider.tsx +51 -5
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +12 -3
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +246 -167
- package/src/rbac/components/NavigationProvider.tsx +4 -1
- package/src/rbac/components/PagePermissionGuard.tsx +185 -17
- package/src/rbac/components/RoleBasedRouter.tsx +5 -1
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +20 -5
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +377 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +64 -66
- package/src/rbac/hooks/usePermissions.ts +14 -995
- 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/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +27 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +294 -68
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +9 -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 +186 -54
- 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/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/dist/chunk-3QRJFVBR.js.map +0 -1
- package/dist/chunk-3XTALGJF.js.map +0 -1
- package/dist/chunk-4N5C5XZU.js.map +0 -1
- package/dist/chunk-4ZC4GX36.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-BYFSK72L.js.map +0 -1
- package/dist/chunk-EXUD6RNJ.js +0 -451
- package/dist/chunk-EXUD6RNJ.js.map +0 -1
- package/dist/chunk-GLK6VM3F.js.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-JBKQ3SAO.js.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-LXQLPRQ2.js.map +0 -1
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-T33XF5ZC.js +0 -12922
- package/dist/chunk-T33XF5ZC.js.map +0 -1
- package/dist/chunk-XM25TVIE.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/ErrorBoundary.md +0 -144
- 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 -160
- 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 -27
- package/docs/api/interfaces/ButtonProps.md +0 -53
- package/docs/api/interfaces/CalendarProps.md +0 -70
- package/docs/api/interfaces/CardProps.md +0 -66
- 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 -249
- 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/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 -293
- package/docs/api/interfaces/FooterProps.md +0 -105
- 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 -53
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -184
- 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 -406
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
- 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 -97
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
- 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 -9
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
- 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 -118
- package/docs/api/interfaces/UserMenuProps.md +0 -86
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -420
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
- package/src/components/OrganisationSelector/index.ts +0 -9
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -681
- /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
- /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
getAccessLevel,
|
|
20
20
|
resolveAppContext,
|
|
21
21
|
getRoleContext,
|
|
22
|
+
getPageScopeType,
|
|
22
23
|
} from '../api';
|
|
23
24
|
import { getRBACLogger } from '../config';
|
|
24
25
|
import { ContextValidator } from '../utils/contextValidator';
|
|
@@ -58,7 +59,6 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
58
59
|
session,
|
|
59
60
|
supabase,
|
|
60
61
|
appName,
|
|
61
|
-
appConfig,
|
|
62
62
|
appId: contextAppId,
|
|
63
63
|
selectedOrganisation,
|
|
64
64
|
isContextReady: orgContextReady,
|
|
@@ -94,30 +94,21 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
// Build initial scope from available context
|
|
97
|
-
//
|
|
98
|
-
// For org-required apps: use selectedOrganisation.id
|
|
97
|
+
// Scope is now page-level only - use whatever context is available
|
|
99
98
|
const initialScope: Scope = {
|
|
100
|
-
organisationId:
|
|
101
|
-
? (selectedEvent?.organisation_id || selectedOrganisation?.id)
|
|
102
|
-
: selectedOrganisation?.id,
|
|
99
|
+
organisationId: selectedEvent?.organisation_id || selectedOrganisation?.id || undefined,
|
|
103
100
|
eventId: selectedEvent?.event_id || undefined,
|
|
104
101
|
appId: undefined
|
|
105
102
|
};
|
|
106
103
|
|
|
107
|
-
// Check if context is ready using ContextValidator
|
|
108
|
-
const contextReady = ContextValidator.isContextReady(
|
|
109
|
-
initialScope,
|
|
110
|
-
appConfig,
|
|
111
|
-
appName,
|
|
112
|
-
!!selectedEvent,
|
|
113
|
-
!!selectedOrganisation
|
|
114
|
-
);
|
|
115
|
-
|
|
116
104
|
// PORTAL/ADMIN special case: context is always ready
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
105
|
+
// For other apps, we need at least one context (org or event) for page-level scope validation
|
|
106
|
+
if (appName !== 'PORTAL' && appName !== 'ADMIN') {
|
|
107
|
+
if (!selectedOrganisation && !selectedEvent) {
|
|
108
|
+
// Wait for context to be available
|
|
109
|
+
setIsLoading(true);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
setIsLoading(true);
|
|
@@ -135,14 +126,8 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
135
126
|
// For PORTAL/ADMIN apps, allow access even if hasAccess is false (users can view their own profile, super admins have global access)
|
|
136
127
|
if (!resolved) {
|
|
137
128
|
if (appName === 'PORTAL' || appName === 'ADMIN') {
|
|
138
|
-
// For PORTAL/ADMIN,
|
|
139
|
-
|
|
140
|
-
const { getAppConfigByName } = await import('../api');
|
|
141
|
-
await getAppConfigByName(appName);
|
|
142
|
-
// We can't get appId from config, but that's OK - use contextAppId or proceed without
|
|
143
|
-
} catch (err) {
|
|
144
|
-
// Proceed without appId for page-level permissions
|
|
145
|
-
}
|
|
129
|
+
// For PORTAL/ADMIN, proceed without appId - it's optional for these apps
|
|
130
|
+
// Use contextAppId if available
|
|
146
131
|
} else {
|
|
147
132
|
throw new Error(`User does not have access to app "${appName}"`);
|
|
148
133
|
}
|
|
@@ -180,11 +165,25 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
180
165
|
appId: appId || contextAppId,
|
|
181
166
|
};
|
|
182
167
|
|
|
183
|
-
// Resolve
|
|
184
|
-
//
|
|
185
|
-
|
|
168
|
+
// Resolve scope based on page-level scope type
|
|
169
|
+
// If pageId is provided, use its scope type; otherwise default to 'organisation'
|
|
170
|
+
let pageScopeType: 'event' | 'organisation' | 'both' = 'organisation';
|
|
171
|
+
if (pageId && scope.appId) {
|
|
172
|
+
try {
|
|
173
|
+
pageScopeType = await getPageScopeType(pageId, scope.appId, appName);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
logger.warn('[useRBAC] Failed to get page scope type, defaulting to organisation', {
|
|
176
|
+
pageId,
|
|
177
|
+
error: error instanceof Error ? error.message : String(error)
|
|
178
|
+
});
|
|
179
|
+
// Default to organisation scope on error
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Resolve required context using page-level scope type
|
|
184
|
+
const validation = await ContextValidator.resolveScopeForPage(
|
|
186
185
|
scope,
|
|
187
|
-
|
|
186
|
+
pageScopeType,
|
|
188
187
|
appName,
|
|
189
188
|
supabase || null
|
|
190
189
|
);
|
|
@@ -196,11 +195,11 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
196
195
|
const resolvedScope = validation.resolvedScope;
|
|
197
196
|
setCurrentScope(resolvedScope);
|
|
198
197
|
|
|
199
|
-
//
|
|
198
|
+
// API calls no longer need appConfig (scope is page-level)
|
|
200
199
|
const [map, roleContext, accessLevel] = await Promise.all([
|
|
201
|
-
getPermissionMap({ userId: user.id as UUID, scope: resolvedScope }
|
|
202
|
-
getRoleContext({ userId: user.id as UUID, scope: resolvedScope }
|
|
203
|
-
getAccessLevel({ userId: user.id as UUID, scope: resolvedScope }
|
|
200
|
+
getPermissionMap({ userId: user.id as UUID, scope: resolvedScope }),
|
|
201
|
+
getRoleContext({ userId: user.id as UUID, scope: resolvedScope }),
|
|
202
|
+
getAccessLevel({ userId: user.id as UUID, scope: resolvedScope }),
|
|
204
203
|
]);
|
|
205
204
|
|
|
206
205
|
setPermissionMap(map);
|
|
@@ -225,7 +224,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
225
224
|
} finally {
|
|
226
225
|
setIsLoading(false);
|
|
227
226
|
}
|
|
228
|
-
}, [appName, logger, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, eventLoading,
|
|
227
|
+
}, [appName, logger, resetState, selectedEvent?.event_id, selectedOrganisation?.id, session, user, eventLoading, orgContextReady, orgLoading]);
|
|
229
228
|
|
|
230
229
|
const hasGlobalPermission = useCallback(
|
|
231
230
|
(permission: string): boolean => {
|
|
@@ -254,7 +253,7 @@ export function useRBAC(pageId?: string): UserRBACContext {
|
|
|
254
253
|
|
|
255
254
|
useEffect(() => {
|
|
256
255
|
loadRBACContext();
|
|
257
|
-
}, [loadRBACContext, appName,
|
|
256
|
+
}, [loadRBACContext, appName, eventLoading, selectedEvent?.event_id, user, session, selectedOrganisation?.id, orgContextReady, orgLoading]);
|
|
258
257
|
|
|
259
258
|
return {
|
|
260
259
|
user,
|
|
@@ -24,14 +24,24 @@ vi.mock('../../utils/app/appNameResolver', () => ({
|
|
|
24
24
|
getCurrentAppName: vi.fn(),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
+
vi.mock('../utils/contextValidator', () => ({
|
|
28
|
+
ContextValidator: {
|
|
29
|
+
resolveScopeForPage: vi.fn(),
|
|
30
|
+
deriveOrgFromEvent: vi.fn(),
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
|
|
27
34
|
import { createScopeFromEvent, getOrganisationFromEvent } from '../utils/eventContext';
|
|
28
35
|
import { getCurrentAppName } from '../../utils/app/appNameResolver';
|
|
29
36
|
import { createMockSupabaseClient } from '../../__tests__/helpers/supabaseMock';
|
|
37
|
+
import { ContextValidator } from '../utils/contextValidator';
|
|
38
|
+
import { OrganisationContextRequiredError } from '../types';
|
|
30
39
|
|
|
31
40
|
describe('useResolvedScope Hook', () => {
|
|
32
41
|
const mockCreateScopeFromEvent = vi.mocked(createScopeFromEvent);
|
|
33
42
|
const mockGetOrganisationFromEvent = vi.mocked(getOrganisationFromEvent);
|
|
34
43
|
const mockGetCurrentAppName = vi.mocked(getCurrentAppName);
|
|
44
|
+
const mockContextValidator = vi.mocked(ContextValidator);
|
|
35
45
|
|
|
36
46
|
let mockSupabase: SupabaseClient<Database>;
|
|
37
47
|
let sharedMockQuery: any;
|
|
@@ -55,11 +65,32 @@ describe('useResolvedScope Hook', () => {
|
|
|
55
65
|
mockSupabase = {
|
|
56
66
|
from: vi.fn().mockReturnValue(sharedMockQuery),
|
|
57
67
|
rpc: vi.fn(),
|
|
68
|
+
auth: {
|
|
69
|
+
getSession: vi.fn().mockResolvedValue({
|
|
70
|
+
data: { session: { access_token: 'test-token' } },
|
|
71
|
+
error: null
|
|
72
|
+
})
|
|
73
|
+
}
|
|
58
74
|
} as any;
|
|
59
75
|
|
|
60
76
|
mockGetCurrentAppName.mockReturnValue('test-app');
|
|
61
77
|
// Default mock for getOrganisationFromEvent
|
|
62
78
|
mockGetOrganisationFromEvent.mockResolvedValue(null);
|
|
79
|
+
// Default mock for ContextValidator - fails validation when no orgId for organisation scope
|
|
80
|
+
mockContextValidator.resolveScopeForPage.mockImplementation(async (scope, scopeType) => {
|
|
81
|
+
if (!scope.organisationId && scopeType === 'organisation') {
|
|
82
|
+
return {
|
|
83
|
+
isValid: false,
|
|
84
|
+
resolvedScope: null,
|
|
85
|
+
error: new OrganisationContextRequiredError()
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
isValid: true,
|
|
90
|
+
resolvedScope: scope,
|
|
91
|
+
error: null
|
|
92
|
+
};
|
|
93
|
+
});
|
|
63
94
|
});
|
|
64
95
|
|
|
65
96
|
afterEach(() => {
|
|
@@ -204,6 +235,13 @@ describe('useResolvedScope Hook', () => {
|
|
|
204
235
|
// Set up mock implementation
|
|
205
236
|
// Note: Don't clear all mocks here as it would clear the getOrganisationFromEvent mock
|
|
206
237
|
mockGetOrganisationFromEvent.mockResolvedValue('org-456');
|
|
238
|
+
// Ensure ContextValidator fails validation for this test (no orgId, but has eventId)
|
|
239
|
+
// This will cause the hook to return scope with just eventId
|
|
240
|
+
mockContextValidator.resolveScopeForPage.mockResolvedValue({
|
|
241
|
+
isValid: false,
|
|
242
|
+
resolvedScope: null,
|
|
243
|
+
error: new OrganisationContextRequiredError()
|
|
244
|
+
});
|
|
207
245
|
(mockSupabase.from as any).mockImplementation((table: string) => {
|
|
208
246
|
if (table === 'event') {
|
|
209
247
|
return eventQueryBuilder;
|
|
@@ -223,39 +261,37 @@ describe('useResolvedScope Hook', () => {
|
|
|
223
261
|
);
|
|
224
262
|
|
|
225
263
|
// Wait for async resolution to complete
|
|
264
|
+
// The hook should set resolvedScope to eventScope when validation fails but eventId exists
|
|
226
265
|
await waitFor(
|
|
227
266
|
() => {
|
|
228
267
|
expect(result.current.isLoading).toBe(false);
|
|
229
268
|
},
|
|
230
|
-
{ timeout: 3000 }
|
|
269
|
+
{ timeout: 3000, interval: 10 }
|
|
231
270
|
);
|
|
232
271
|
|
|
233
|
-
//
|
|
234
|
-
// However, the hook requires organisation context for event-required apps
|
|
235
|
-
// Skip this test as it's testing invalid state (event without org context)
|
|
236
|
-
if (result.current.error) {
|
|
237
|
-
// Expected: Organisation context is required even when deriving from event
|
|
238
|
-
expect(result.current.error.message).toContain('Organisation context is required');
|
|
239
|
-
return; // Test expects this to work, but it's actually invalid state
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Force rerender to pick up ref update
|
|
272
|
+
// Force rerender to pick up ref update (refs don't trigger re-renders)
|
|
243
273
|
rerender();
|
|
244
274
|
|
|
245
|
-
// Wait for stable scope ref to update
|
|
275
|
+
// Wait for stable scope ref to update (happens in useEffect after state update)
|
|
276
|
+
// The scope will have eventId and appId, but no organisationId
|
|
246
277
|
await waitFor(
|
|
247
278
|
() => {
|
|
279
|
+
// Hook should return scope with eventId when validation fails but eventId is present
|
|
248
280
|
expect(result.current.resolvedScope).not.toBeNull();
|
|
249
281
|
},
|
|
250
|
-
{ timeout:
|
|
282
|
+
{ timeout: 2000, interval: 10 }
|
|
251
283
|
);
|
|
252
284
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
285
|
+
// Hook should return scope with eventId even if org derivation hasn't happened yet
|
|
286
|
+
// The organisation will be derived during permission checks
|
|
287
|
+
// When event is provided but validation fails, hook returns scope with just eventId (no error)
|
|
288
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
289
|
+
if (result.current.resolvedScope) {
|
|
290
|
+
expect(result.current.resolvedScope.eventId).toBe('event-123');
|
|
291
|
+
expect(result.current.resolvedScope.appId).toBe('app-123');
|
|
292
|
+
// organisationId may be undefined - will be derived during permission checks
|
|
293
|
+
expect(result.current.error).toBeNull(); // No error when event is provided
|
|
294
|
+
}
|
|
259
295
|
});
|
|
260
296
|
|
|
261
297
|
it('handles no context available', async () => {
|
|
@@ -497,10 +533,20 @@ describe('useResolvedScope Hook', () => {
|
|
|
497
533
|
|
|
498
534
|
describe('Error Handling', () => {
|
|
499
535
|
it('handles error when event scope resolution fails', async () => {
|
|
500
|
-
|
|
501
|
-
|
|
536
|
+
// Ensure appId is resolved so scope is valid
|
|
537
|
+
sharedMockQuery.single.mockResolvedValue({
|
|
538
|
+
data: { id: 'app-123', name: 'test-app', is_active: true },
|
|
539
|
+
error: null,
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// Ensure ContextValidator fails validation (no orgId)
|
|
543
|
+
mockContextValidator.resolveScopeForPage.mockResolvedValue({
|
|
544
|
+
isValid: false,
|
|
545
|
+
resolvedScope: null,
|
|
546
|
+
error: new OrganisationContextRequiredError()
|
|
547
|
+
});
|
|
502
548
|
|
|
503
|
-
const { result } = renderHook(() =>
|
|
549
|
+
const { result, rerender } = renderHook(() =>
|
|
504
550
|
useResolvedScope({
|
|
505
551
|
supabase: mockSupabase,
|
|
506
552
|
selectedOrganisationId: null,
|
|
@@ -515,22 +561,43 @@ describe('useResolvedScope Hook', () => {
|
|
|
515
561
|
{ timeout: 2000 }
|
|
516
562
|
);
|
|
517
563
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
564
|
+
// Force rerender to pick up ref update (refs don't trigger re-renders)
|
|
565
|
+
rerender();
|
|
566
|
+
|
|
567
|
+
// Wait for stable scope ref to update (happens in useEffect after state update)
|
|
568
|
+
await waitFor(
|
|
569
|
+
() => {
|
|
570
|
+
// When event is provided but validation fails, hook returns scope with just eventId
|
|
571
|
+
// appId must be resolved for scope to be valid
|
|
572
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
573
|
+
},
|
|
574
|
+
{ timeout: 2000, interval: 10 }
|
|
523
575
|
);
|
|
576
|
+
|
|
577
|
+
// When event is provided but validation fails, hook returns scope with just eventId
|
|
578
|
+
// (no error - org will be derived during permission checks)
|
|
579
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
580
|
+
if (result.current.resolvedScope) {
|
|
581
|
+
expect(result.current.resolvedScope.eventId).toBe('event-123');
|
|
582
|
+
expect(result.current.error).toBeNull();
|
|
583
|
+
}
|
|
524
584
|
});
|
|
525
585
|
|
|
526
586
|
it('handles error when createScopeFromEvent throws', async () => {
|
|
527
|
-
//
|
|
587
|
+
// Ensure appId is resolved so scope is valid
|
|
528
588
|
sharedMockQuery.single.mockResolvedValue({
|
|
529
|
-
data:
|
|
530
|
-
error:
|
|
589
|
+
data: { id: 'app-123', name: 'test-app', is_active: true },
|
|
590
|
+
error: null,
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// Ensure ContextValidator fails validation (no orgId)
|
|
594
|
+
mockContextValidator.resolveScopeForPage.mockResolvedValue({
|
|
595
|
+
isValid: false,
|
|
596
|
+
resolvedScope: null,
|
|
597
|
+
error: new OrganisationContextRequiredError()
|
|
531
598
|
});
|
|
532
599
|
|
|
533
|
-
const { result } = renderHook(() =>
|
|
600
|
+
const { result, rerender } = renderHook(() =>
|
|
534
601
|
useResolvedScope({
|
|
535
602
|
supabase: mockSupabase,
|
|
536
603
|
selectedOrganisationId: null,
|
|
@@ -545,10 +612,28 @@ describe('useResolvedScope Hook', () => {
|
|
|
545
612
|
{ timeout: 2000 }
|
|
546
613
|
);
|
|
547
614
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
615
|
+
// Force rerender to pick up ref update (refs don't trigger re-renders)
|
|
616
|
+
rerender();
|
|
617
|
+
|
|
618
|
+
// Wait for stable scope ref to update (happens in useEffect after state update)
|
|
619
|
+
await waitFor(
|
|
620
|
+
() => {
|
|
621
|
+
// When event is provided but validation fails, hook returns scope with just eventId
|
|
622
|
+
// appId must be resolved for scope to be valid
|
|
623
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
624
|
+
},
|
|
625
|
+
{ timeout: 2000, interval: 10 }
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
// When event is provided but org derivation fails, hook returns scope with just eventId
|
|
629
|
+
// (no error - org will be derived during permission checks)
|
|
630
|
+
// appId must be present for scope to be valid
|
|
631
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
632
|
+
if (result.current.resolvedScope) {
|
|
633
|
+
expect(result.current.resolvedScope.eventId).toBe('event-123');
|
|
634
|
+
expect(result.current.resolvedScope.appId).toBe('app-123');
|
|
635
|
+
expect(result.current.error).toBeNull();
|
|
636
|
+
}
|
|
552
637
|
});
|
|
553
638
|
|
|
554
639
|
it('handles database error when resolving app ID', async () => {
|
|
@@ -13,8 +13,6 @@ import { useEffect, useState, useRef, useMemo } from 'react';
|
|
|
13
13
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
14
14
|
import type { Database } from '../../types/database';
|
|
15
15
|
import type { Scope } from '../types';
|
|
16
|
-
import { ContextValidator } from '../utils/contextValidator';
|
|
17
|
-
import type { AppConfig } from '../utils/contextValidator';
|
|
18
16
|
import { getCurrentAppName } from '../../utils/app/appNameResolver';
|
|
19
17
|
import { createLogger } from '../../utils/core/logger';
|
|
20
18
|
|
|
@@ -22,12 +20,12 @@ const log = createLogger('useResolvedScope');
|
|
|
22
20
|
|
|
23
21
|
// Cache app config to avoid repeated database queries
|
|
24
22
|
// App config rarely changes during a session, so we can cache it
|
|
25
|
-
const
|
|
23
|
+
const appIdCache = new Map<string, { appId: string; timestamp: number }>();
|
|
26
24
|
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
27
25
|
|
|
28
26
|
// Export function to clear cache (for testing)
|
|
29
27
|
export function clearAppConfigCache(): void {
|
|
30
|
-
|
|
28
|
+
appIdCache.clear();
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
export interface UseResolvedScopeOptions {
|
|
@@ -138,12 +136,11 @@ export function useResolvedScope({
|
|
|
138
136
|
setError(null);
|
|
139
137
|
|
|
140
138
|
try {
|
|
141
|
-
// Get app name and
|
|
139
|
+
// Get app name and resolve appId
|
|
142
140
|
const appName = getCurrentAppName();
|
|
143
141
|
let appId: string | undefined = undefined;
|
|
144
|
-
let appConfig: AppConfig | null = null;
|
|
145
142
|
|
|
146
|
-
// Try to resolve
|
|
143
|
+
// Try to resolve appId from database (with caching)
|
|
147
144
|
// Only query if user is authenticated (RLS policies require authentication)
|
|
148
145
|
if (supabase && appName) {
|
|
149
146
|
try {
|
|
@@ -156,19 +153,18 @@ export function useResolvedScope({
|
|
|
156
153
|
log.debug(`Skipping app resolution for "${appName}" - user not authenticated`);
|
|
157
154
|
} else {
|
|
158
155
|
// Check cache first
|
|
159
|
-
const cached =
|
|
156
|
+
const cached = appIdCache.get(appName);
|
|
160
157
|
const now = Date.now();
|
|
161
158
|
if (cached && (now - cached.timestamp) < CACHE_TTL) {
|
|
162
159
|
appId = cached.appId;
|
|
163
|
-
appConfig = cached.appConfig;
|
|
164
160
|
} else {
|
|
165
161
|
// Cache miss or expired - fetch from database
|
|
166
162
|
const { data: app, error } = await supabase
|
|
167
163
|
.from('rbac_apps')
|
|
168
|
-
.select('id, name,
|
|
164
|
+
.select('id, name, is_active')
|
|
169
165
|
.eq('name', appName)
|
|
170
166
|
.eq('is_active', true)
|
|
171
|
-
.single() as { data: { id: string; name: string;
|
|
167
|
+
.single() as { data: { id: string; name: string; is_active: boolean } | null; error: any };
|
|
172
168
|
|
|
173
169
|
if (error) {
|
|
174
170
|
// HTTP 406 is expected when not authenticated (RLS blocks query)
|
|
@@ -197,9 +193,8 @@ export function useResolvedScope({
|
|
|
197
193
|
}
|
|
198
194
|
} else if (app) {
|
|
199
195
|
appId = app.id;
|
|
200
|
-
appConfig = { requires_event: app.requires_event ?? false };
|
|
201
196
|
// Only cache successful lookups of active apps
|
|
202
|
-
|
|
197
|
+
appIdCache.set(appName, { appId, timestamp: now });
|
|
203
198
|
}
|
|
204
199
|
}
|
|
205
200
|
}
|
|
@@ -208,7 +203,7 @@ export function useResolvedScope({
|
|
|
208
203
|
// Don't log 406 errors as they're expected when not authenticated
|
|
209
204
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
210
205
|
if (!errorMessage.includes('406') && !errorMessage.includes('PGRST116')) {
|
|
211
|
-
log.error('Unexpected error resolving app
|
|
206
|
+
log.error('Unexpected error resolving app ID:', error);
|
|
212
207
|
} else {
|
|
213
208
|
log.debug('App resolution skipped - authentication required');
|
|
214
209
|
}
|
|
@@ -216,44 +211,44 @@ export function useResolvedScope({
|
|
|
216
211
|
}
|
|
217
212
|
|
|
218
213
|
// Build initial scope from available context
|
|
219
|
-
//
|
|
220
|
-
//
|
|
214
|
+
// Scope is now page-level only - use whatever context is available
|
|
215
|
+
// Default to organisation scope if both are available (safest default)
|
|
221
216
|
const initialScope: Scope = {
|
|
222
|
-
organisationId:
|
|
217
|
+
organisationId: selectedOrganisationId || undefined,
|
|
223
218
|
eventId: selectedEventId || undefined,
|
|
224
219
|
appId: appId
|
|
225
220
|
};
|
|
226
221
|
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
222
|
+
// For PORTAL/ADMIN apps, allow scope without org/event
|
|
223
|
+
if (appName === 'PORTAL' || appName === 'ADMIN') {
|
|
224
|
+
if (!cancelled) {
|
|
225
|
+
const optionalContextScope: Scope = {
|
|
226
|
+
organisationId: undefined,
|
|
227
|
+
eventId: undefined,
|
|
228
|
+
appId: appId || undefined
|
|
229
|
+
};
|
|
230
|
+
setResolvedScope(optionalContextScope);
|
|
231
|
+
setError(null);
|
|
232
|
+
setIsLoading(false);
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// For other apps, default to organisation scope validation (safest default)
|
|
238
|
+
// Page-level scope will be validated during permission checks
|
|
239
|
+
// ContextValidator is already imported at the top
|
|
240
|
+
const { ContextValidator } = await import('../utils/contextValidator');
|
|
241
|
+
const validation = await ContextValidator.resolveScopeForPage(
|
|
230
242
|
initialScope,
|
|
231
|
-
|
|
243
|
+
'organisation', // Default to organisation scope when no page context
|
|
232
244
|
appName || undefined,
|
|
233
245
|
supabase
|
|
234
246
|
);
|
|
235
247
|
|
|
236
248
|
if (!validation.isValid) {
|
|
237
|
-
//
|
|
238
|
-
if (appName === 'PORTAL' || appName === 'ADMIN') {
|
|
239
|
-
if (!cancelled) {
|
|
240
|
-
// For PORTAL/ADMIN, we need at least an appId. If we don't have it from the query,
|
|
241
|
-
// we'll set it to undefined and let the component handle it (it can use contextAppId)
|
|
242
|
-
const optionalContextScope: Scope = {
|
|
243
|
-
organisationId: undefined,
|
|
244
|
-
eventId: undefined,
|
|
245
|
-
appId: appId || undefined // appId might be undefined if query failed, that's OK
|
|
246
|
-
};
|
|
247
|
-
setResolvedScope(optionalContextScope);
|
|
248
|
-
setError(null);
|
|
249
|
-
setIsLoading(false);
|
|
250
|
-
}
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// For event-required apps: if validation fails but we have an eventId, return scope with eventId
|
|
249
|
+
// If validation fails but we have an eventId, return scope with eventId
|
|
255
250
|
// The organisation will be derived later during permission checks
|
|
256
|
-
if (
|
|
251
|
+
if (selectedEventId) {
|
|
257
252
|
if (!cancelled) {
|
|
258
253
|
const eventScope: Scope = {
|
|
259
254
|
organisationId: undefined, // Will be derived from event during permission check
|