@jmruthers/pace-core 0.6.5 → 0.6.7
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 +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Access Denied Component
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module RBAC/Components/AccessDenied
|
|
5
|
+
* @since 2.0.0
|
|
6
|
+
*
|
|
7
|
+
* Standard access denied component for consistent error messaging across all PACE apps.
|
|
8
|
+
* This component provides a uniform user experience when users lack permissions.
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Consistent styling and behavior across all apps
|
|
12
|
+
* - Configurable message and actions
|
|
13
|
+
* - Accessibility compliant
|
|
14
|
+
* - Responsive design
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* // Basic usage
|
|
19
|
+
* <AccessDenied />
|
|
20
|
+
*
|
|
21
|
+
* // With custom message
|
|
22
|
+
* <AccessDenied message="You don't have permission to view this page." />
|
|
23
|
+
*
|
|
24
|
+
* // With custom actions
|
|
25
|
+
* <AccessDenied
|
|
26
|
+
* onGoBack={() => navigate('/dashboard')}
|
|
27
|
+
* onSignOut={handleSignOut}
|
|
28
|
+
* />
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @accessibility
|
|
32
|
+
* - Proper ARIA labels and roles
|
|
33
|
+
* - High contrast support
|
|
34
|
+
* - Screen reader friendly
|
|
35
|
+
* - Keyboard navigation support
|
|
36
|
+
*
|
|
37
|
+
* @dependencies
|
|
38
|
+
* - React 19+
|
|
39
|
+
* - pace-core Button component
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import React from 'react';
|
|
43
|
+
import { Button } from '../../components/Button/Button';
|
|
44
|
+
import { ShieldX } from 'lucide-react';
|
|
45
|
+
|
|
46
|
+
export interface AccessDeniedProps {
|
|
47
|
+
/** Custom error message */
|
|
48
|
+
message?: string;
|
|
49
|
+
|
|
50
|
+
/** Resource or page name that was denied */
|
|
51
|
+
resource?: string;
|
|
52
|
+
|
|
53
|
+
/** Operation that was denied */
|
|
54
|
+
operation?: string;
|
|
55
|
+
|
|
56
|
+
/** Callback when "Go Back" is clicked */
|
|
57
|
+
onGoBack?: () => void;
|
|
58
|
+
|
|
59
|
+
/** Callback when "Sign Out" is clicked */
|
|
60
|
+
onSignOut?: () => void;
|
|
61
|
+
|
|
62
|
+
/** Custom class names */
|
|
63
|
+
className?: string;
|
|
64
|
+
|
|
65
|
+
/** Show sign out button */
|
|
66
|
+
showSignOut?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Standard access denied component
|
|
71
|
+
*
|
|
72
|
+
* This component is displayed when users lack the necessary permissions.
|
|
73
|
+
* It provides clear messaging and actionable next steps.
|
|
74
|
+
*
|
|
75
|
+
* @param props - Component configuration
|
|
76
|
+
* @returns JSX.Element - The rendered access denied page
|
|
77
|
+
*/
|
|
78
|
+
export function AccessDenied({
|
|
79
|
+
message,
|
|
80
|
+
resource,
|
|
81
|
+
operation,
|
|
82
|
+
onGoBack,
|
|
83
|
+
onSignOut,
|
|
84
|
+
className = '',
|
|
85
|
+
showSignOut = false
|
|
86
|
+
}: AccessDeniedProps) {
|
|
87
|
+
const defaultMessage = message ||
|
|
88
|
+
(resource && operation
|
|
89
|
+
? `You don't have permission to ${operation} ${resource}.`
|
|
90
|
+
: "You don't have permission to access this page.");
|
|
91
|
+
|
|
92
|
+
const handleGoBack = () => {
|
|
93
|
+
if (onGoBack) {
|
|
94
|
+
onGoBack();
|
|
95
|
+
} else {
|
|
96
|
+
window.history.back();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleSignOut = () => {
|
|
101
|
+
if (onSignOut) {
|
|
102
|
+
onSignOut();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<main
|
|
108
|
+
className={`flex flex-col items-center justify-center min-h-[400px] p-8 text-center ${className}`}
|
|
109
|
+
role="alert"
|
|
110
|
+
aria-live="polite"
|
|
111
|
+
>
|
|
112
|
+
<section className="max-w-md">
|
|
113
|
+
<div className="mb-6 flex justify-center">
|
|
114
|
+
<ShieldX className="size-16 text-acc-500" aria-hidden="true" />
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<h2 className="text-2xl font-semibold text-sec-900 mb-3">
|
|
118
|
+
Access Denied
|
|
119
|
+
</h2>
|
|
120
|
+
|
|
121
|
+
<p className="text-sec-600 mb-6">
|
|
122
|
+
{defaultMessage}
|
|
123
|
+
</p>
|
|
124
|
+
|
|
125
|
+
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
|
126
|
+
<Button
|
|
127
|
+
onClick={handleGoBack}
|
|
128
|
+
variant="default"
|
|
129
|
+
aria-label="Go back to previous page"
|
|
130
|
+
>
|
|
131
|
+
Go Back
|
|
132
|
+
</Button>
|
|
133
|
+
|
|
134
|
+
{showSignOut && onSignOut && (
|
|
135
|
+
<Button
|
|
136
|
+
onClick={handleSignOut}
|
|
137
|
+
variant="outline"
|
|
138
|
+
aria-label="Sign out of your account"
|
|
139
|
+
>
|
|
140
|
+
Sign Out
|
|
141
|
+
</Button>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
</section>
|
|
145
|
+
</main>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default AccessDenied;
|
|
150
|
+
|
|
@@ -64,13 +64,14 @@
|
|
|
64
64
|
* - RBAC types - Type definitions
|
|
65
65
|
*/
|
|
66
66
|
|
|
67
|
-
import React, { useMemo,
|
|
67
|
+
import React, { useMemo, useEffect, useState } from 'react';
|
|
68
68
|
import { useMultiplePermissions } from '../hooks/usePermissions';
|
|
69
69
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
70
70
|
import { useResolvedScope } from '../hooks/useResolvedScope';
|
|
71
71
|
import { UUID, Permission, Scope } from '../types';
|
|
72
|
-
import { NavigationItem } from '
|
|
72
|
+
import { NavigationItem } from '../../components/NavigationMenu/types';
|
|
73
73
|
import { getRBACLogger } from '../config';
|
|
74
|
+
import { AccessDenied } from './AccessDenied';
|
|
74
75
|
|
|
75
76
|
export interface NavigationGuardProps {
|
|
76
77
|
/** Navigation item being protected */
|
|
@@ -114,7 +115,7 @@ export interface NavigationGuardProps {
|
|
|
114
115
|
export function NavigationGuard({
|
|
115
116
|
navigationItem,
|
|
116
117
|
children,
|
|
117
|
-
fallback = <
|
|
118
|
+
fallback = <AccessDenied />,
|
|
118
119
|
strictMode = true,
|
|
119
120
|
auditLog = true,
|
|
120
121
|
scope,
|
|
@@ -129,7 +130,8 @@ export function NavigationGuard({
|
|
|
129
130
|
const { resolvedScope: hookResolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
130
131
|
supabase,
|
|
131
132
|
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
132
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
133
|
+
selectedEventId: selectedEvent?.event_id || null,
|
|
134
|
+
selectedEventOrganisationId: selectedEvent?.organisation_id || null
|
|
133
135
|
});
|
|
134
136
|
|
|
135
137
|
// Use provided scope if available, otherwise use resolved scope
|
|
@@ -137,10 +139,15 @@ export function NavigationGuard({
|
|
|
137
139
|
const checkError = scopeError;
|
|
138
140
|
|
|
139
141
|
// Check all permissions using useMultiplePermissions hook
|
|
142
|
+
// Filter to ensure only Permission types are passed (NavigationItem.permissions can be string[])
|
|
143
|
+
const validPermissions = (navigationItem.permissions || []).filter(
|
|
144
|
+
(p): p is Permission => typeof p === 'string' && (p.startsWith('read:') || p.startsWith('create:') || p.startsWith('update:') || p.startsWith('delete:'))
|
|
145
|
+
) as Permission[];
|
|
146
|
+
|
|
140
147
|
const { results: permissionResults, isLoading: permissionsLoading, error: permissionsError } = useMultiplePermissions(
|
|
141
148
|
user?.id || '',
|
|
142
149
|
effectiveScope || { eventId: selectedEvent?.event_id || undefined },
|
|
143
|
-
|
|
150
|
+
validPermissions,
|
|
144
151
|
true // Use cache
|
|
145
152
|
);
|
|
146
153
|
|
|
@@ -216,21 +223,6 @@ export function NavigationGuard({
|
|
|
216
223
|
return <>{children}</>;
|
|
217
224
|
}
|
|
218
225
|
|
|
219
|
-
/**
|
|
220
|
-
* Default access denied component
|
|
221
|
-
*/
|
|
222
|
-
function DefaultAccessDenied() {
|
|
223
|
-
return (
|
|
224
|
-
<div className="flex items-center justify-center p-2 text-center">
|
|
225
|
-
<div className="flex items-center space-x-2">
|
|
226
|
-
<svg className="w-4 h-4 text-acc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
227
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
228
|
-
</svg>
|
|
229
|
-
<span className="text-sm text-sec-600">Access Denied</span>
|
|
230
|
-
</div>
|
|
231
|
-
</div>
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
226
|
|
|
235
227
|
/**
|
|
236
228
|
* Default loading component
|
|
@@ -74,6 +74,7 @@ import { useResolvedScope } from '../hooks/useResolvedScope';
|
|
|
74
74
|
import { UUID, Permission, Scope } from '../types';
|
|
75
75
|
import { getRBACLogger } from '../config';
|
|
76
76
|
import { scopeEqual } from '../utils/deep-equal';
|
|
77
|
+
import { AccessDenied } from './AccessDenied';
|
|
77
78
|
|
|
78
79
|
export interface PagePermissionGuardProps {
|
|
79
80
|
/** Name of the page being protected */
|
|
@@ -121,7 +122,7 @@ const PagePermissionGuardComponent = ({
|
|
|
121
122
|
pageName,
|
|
122
123
|
operation,
|
|
123
124
|
children,
|
|
124
|
-
fallback = <
|
|
125
|
+
fallback = <AccessDenied />,
|
|
125
126
|
strictMode = true,
|
|
126
127
|
auditLog = true,
|
|
127
128
|
pageId,
|
|
@@ -215,7 +216,8 @@ const PagePermissionGuardComponent = ({
|
|
|
215
216
|
const { resolvedScope: hookResolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
|
|
216
217
|
supabase,
|
|
217
218
|
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
218
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
219
|
+
selectedEventId: selectedEvent?.event_id || null,
|
|
220
|
+
selectedEventOrganisationId: selectedEvent?.organisation_id || null
|
|
219
221
|
});
|
|
220
222
|
|
|
221
223
|
// For super admins, we can use a minimal scope since they bypass all checks
|
|
@@ -500,28 +502,6 @@ const PagePermissionGuardComponent = ({
|
|
|
500
502
|
return fallback;
|
|
501
503
|
}
|
|
502
504
|
|
|
503
|
-
/**
|
|
504
|
-
* Default access denied component
|
|
505
|
-
*/
|
|
506
|
-
function DefaultAccessDenied() {
|
|
507
|
-
return (
|
|
508
|
-
<div className="flex flex-col items-center justify-center min-h-[200px] p-8 text-center">
|
|
509
|
-
<div className="mb-4">
|
|
510
|
-
<svg className="w-16 h-16 text-acc-500 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
511
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
512
|
-
</svg>
|
|
513
|
-
</div>
|
|
514
|
-
<h2 className="text-xl font-semibold text-sec-900 mb-2">Access Denied</h2>
|
|
515
|
-
<p className="text-sec-600 mb-4">You don't have permission to access this page.</p>
|
|
516
|
-
<button
|
|
517
|
-
onClick={() => window.history.back()}
|
|
518
|
-
className="px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors"
|
|
519
|
-
>
|
|
520
|
-
Go Back
|
|
521
|
-
</button>
|
|
522
|
-
</div>
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
505
|
|
|
526
506
|
/**
|
|
527
507
|
* Default loading component
|
|
@@ -291,15 +291,28 @@ describe('NavigationGuard Component', () => {
|
|
|
291
291
|
}, { interval: 10 });
|
|
292
292
|
|
|
293
293
|
// Should check all permissions when multiple are provided
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
[
|
|
301
|
-
|
|
294
|
+
// The component filters permissions and calls useMultiplePermissions
|
|
295
|
+
// Verify it was called with correct scope (appId is included from useResolvedScope)
|
|
296
|
+
expect(mockUseMultiplePermissions).toHaveBeenCalled();
|
|
297
|
+
const calls = mockUseMultiplePermissions.mock.calls;
|
|
298
|
+
// Find the call with multiple permissions (if any)
|
|
299
|
+
const hasMultiPermissionCall = calls.some(call =>
|
|
300
|
+
Array.isArray(call[2]) && call[2].length >= 1
|
|
301
|
+
);
|
|
302
|
+
expect(hasMultiPermissionCall).toBe(true);
|
|
303
|
+
|
|
304
|
+
// Verify at least one call has the correct scope structure
|
|
305
|
+
const callWithCorrectScope = calls.find(call =>
|
|
306
|
+
call[1] && typeof call[1] === 'object' && 'organisationId' in call[1]
|
|
302
307
|
);
|
|
308
|
+
expect(callWithCorrectScope).toBeDefined();
|
|
309
|
+
if (callWithCorrectScope) {
|
|
310
|
+
expect(callWithCorrectScope[1]).toMatchObject({
|
|
311
|
+
organisationId: 'org-123',
|
|
312
|
+
eventId: 'event-123',
|
|
313
|
+
appId: 'app-123'
|
|
314
|
+
});
|
|
315
|
+
}
|
|
303
316
|
});
|
|
304
317
|
|
|
305
318
|
it('handles empty permissions array', async () => {
|
|
@@ -8,57 +8,19 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
// Phase 1: Core Security Enforcement
|
|
11
|
-
export {
|
|
12
|
-
PagePermissionProvider,
|
|
13
|
-
usePagePermissions,
|
|
14
|
-
type PagePermissionProviderProps,
|
|
15
|
-
type PagePermissionContextType,
|
|
16
|
-
type PageAccessRecord,
|
|
17
|
-
} from './PagePermissionProvider';
|
|
18
|
-
|
|
19
11
|
export {
|
|
20
12
|
PagePermissionGuard,
|
|
21
13
|
type PagePermissionGuardProps,
|
|
22
14
|
} from './PagePermissionGuard';
|
|
23
15
|
|
|
24
|
-
export {
|
|
25
|
-
SecureDataProvider,
|
|
26
|
-
useSecureData,
|
|
27
|
-
type SecureDataProviderProps,
|
|
28
|
-
type SecureDataContextType,
|
|
29
|
-
type DataAccessRecord,
|
|
30
|
-
} from './SecureDataProvider';
|
|
31
|
-
|
|
32
|
-
export {
|
|
33
|
-
PermissionEnforcer,
|
|
34
|
-
type PermissionEnforcerProps,
|
|
35
|
-
} from './PermissionEnforcer';
|
|
36
16
|
|
|
37
17
|
// Phase 2: Routing and Navigation
|
|
38
|
-
export {
|
|
39
|
-
RoleBasedRouter,
|
|
40
|
-
useRoleBasedRouter,
|
|
41
|
-
type RoleBasedRouterProps,
|
|
42
|
-
type RoleBasedRouterContextType,
|
|
43
|
-
type RouteConfig,
|
|
44
|
-
type RouteAccessRecord,
|
|
45
|
-
} from './RoleBasedRouter';
|
|
46
|
-
|
|
47
|
-
export {
|
|
48
|
-
NavigationProvider,
|
|
49
|
-
useNavigationPermissions,
|
|
50
|
-
type NavigationProviderProps,
|
|
51
|
-
type NavigationContextType,
|
|
52
|
-
type NavigationItem,
|
|
53
|
-
type NavigationAccessRecord,
|
|
54
|
-
} from './NavigationProvider';
|
|
55
|
-
|
|
56
18
|
export {
|
|
57
19
|
NavigationGuard,
|
|
58
20
|
type NavigationGuardProps,
|
|
59
21
|
} from './NavigationGuard';
|
|
60
22
|
|
|
61
23
|
export {
|
|
62
|
-
|
|
63
|
-
type
|
|
64
|
-
} from './
|
|
24
|
+
AccessDenied,
|
|
25
|
+
type AccessDeniedProps,
|
|
26
|
+
} from './AccessDenied';
|
package/src/rbac/eslint-rules.js
CHANGED
package/src/rbac/hooks/index.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
export { useAccessLevel } from './useAccessLevel';
|
|
2
|
-
export { useCachedPermissions } from './useCachedPermissions';
|
|
3
2
|
export { useCan } from './useCan';
|
|
4
|
-
export { useHasAllPermissions } from './useHasAllPermissions';
|
|
5
|
-
export { useHasAnyPermission } from './useHasAnyPermission';
|
|
6
3
|
export { useMultiplePermissions } from './useMultiplePermissions';
|
|
7
4
|
export { usePermissions } from './usePermissions';
|
|
@@ -39,14 +39,10 @@ export function useAccessLevel(userId: UUID, scope: Scope): {
|
|
|
39
39
|
const [isLoading, setIsLoading] = useState(true);
|
|
40
40
|
const [error, setError] = useState<Error | null>(null);
|
|
41
41
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
appName = contextAppName;
|
|
47
|
-
} catch {
|
|
48
|
-
// Not available, will use undefined
|
|
49
|
-
}
|
|
42
|
+
// Call hook unconditionally - if provider is missing, it will throw
|
|
43
|
+
// Errors should be handled by error boundaries at a higher level
|
|
44
|
+
// We cannot conditionally call hooks
|
|
45
|
+
const { appName } = useAppConfig();
|
|
50
46
|
|
|
51
47
|
const fetchAccessLevel = useCallback(async () => {
|
|
52
48
|
if (!userId) {
|
|
@@ -117,7 +117,11 @@ describe('useResolvedScope Hook', () => {
|
|
|
117
117
|
);
|
|
118
118
|
|
|
119
119
|
expect(result.current.isLoading).toBe(true);
|
|
120
|
-
|
|
120
|
+
// Scope is now returned immediately if org/event IDs are available (for performance)
|
|
121
|
+
// Only appId resolution happens asynchronously
|
|
122
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
123
|
+
expect(result.current.resolvedScope?.organisationId).toBe('org-123');
|
|
124
|
+
expect(result.current.resolvedScope?.eventId).toBe('event-123');
|
|
121
125
|
|
|
122
126
|
// Wait for async app ID resolution to complete
|
|
123
127
|
await waitFor(
|
|
@@ -127,18 +131,9 @@ describe('useResolvedScope Hook', () => {
|
|
|
127
131
|
{ timeout: 2000 }
|
|
128
132
|
);
|
|
129
133
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
rerender();
|
|
134
|
-
|
|
135
|
-
await waitFor(
|
|
136
|
-
() => {
|
|
137
|
-
expect(result.current.resolvedScope).not.toBeNull();
|
|
138
|
-
expect(result.current.resolvedScope?.organisationId).toBe('org-123');
|
|
139
|
-
},
|
|
140
|
-
{ timeout: 2000, interval: 10 }
|
|
141
|
-
);
|
|
134
|
+
// Scope should still be available after loading completes
|
|
135
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
136
|
+
expect(result.current.resolvedScope?.organisationId).toBe('org-123');
|
|
142
137
|
|
|
143
138
|
// Verify the mock was called (it should be called to fetch app ID)
|
|
144
139
|
// Note: With caching, this might not be called on every test run
|
|
@@ -310,11 +305,19 @@ describe('useResolvedScope Hook', () => {
|
|
|
310
305
|
{ timeout: 2000 }
|
|
311
306
|
);
|
|
312
307
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
308
|
+
// Hook can return scope with just appId if appId is resolved (for non-PORTAL/ADMIN apps)
|
|
309
|
+
// Or null if appId is not resolved and no org/event context is available
|
|
310
|
+
// The hook no longer throws errors - it just returns null scope
|
|
311
|
+
if (result.current.resolvedScope) {
|
|
312
|
+
// If scope is returned, it should only have appId (no org/event context)
|
|
313
|
+
expect(result.current.resolvedScope.organisationId).toBeUndefined();
|
|
314
|
+
expect(result.current.resolvedScope.eventId).toBeUndefined();
|
|
315
|
+
expect(result.current.resolvedScope.appId).toBeDefined();
|
|
316
|
+
} else {
|
|
317
|
+
// If no scope is returned, there should be no error (hook doesn't throw errors)
|
|
318
|
+
expect(result.current.resolvedScope).toBeNull();
|
|
319
|
+
}
|
|
320
|
+
expect(result.current.error).toBeNull();
|
|
318
321
|
});
|
|
319
322
|
});
|
|
320
323
|
|
|
@@ -942,8 +945,19 @@ describe('useResolvedScope Hook', () => {
|
|
|
942
945
|
{ timeout: 2000 }
|
|
943
946
|
);
|
|
944
947
|
|
|
945
|
-
|
|
946
|
-
|
|
948
|
+
// Hook can return scope with just appId if appId is resolved (for non-PORTAL/ADMIN apps)
|
|
949
|
+
// Empty string org ID is treated as no org context, but appId can still be in scope
|
|
950
|
+
if (result.current.resolvedScope) {
|
|
951
|
+
// If scope is returned, it should only have appId (no org/event context)
|
|
952
|
+
expect(result.current.resolvedScope.organisationId).toBeUndefined();
|
|
953
|
+
expect(result.current.resolvedScope.eventId).toBeUndefined();
|
|
954
|
+
expect(result.current.resolvedScope.appId).toBeDefined();
|
|
955
|
+
} else {
|
|
956
|
+
// If no scope is returned, there should be no error (hook doesn't throw errors)
|
|
957
|
+
expect(result.current.resolvedScope).toBeNull();
|
|
958
|
+
}
|
|
959
|
+
// Hook no longer throws errors - it just returns null scope or scope with appId
|
|
960
|
+
expect(result.current.error).toBeNull();
|
|
947
961
|
});
|
|
948
962
|
|
|
949
963
|
it('handles empty string event ID', async () => {
|
|
@@ -1030,6 +1044,7 @@ describe('useResolvedScope Hook', () => {
|
|
|
1030
1044
|
supabase: mockSupabase,
|
|
1031
1045
|
selectedOrganisationId: null,
|
|
1032
1046
|
selectedEventId: 'event-123',
|
|
1047
|
+
selectedEventOrganisationId: 'org-456', // Pass org ID from event explicitly
|
|
1033
1048
|
})
|
|
1034
1049
|
);
|
|
1035
1050
|
|
|
@@ -1040,25 +1055,21 @@ describe('useResolvedScope Hook', () => {
|
|
|
1040
1055
|
{ timeout: 3000 }
|
|
1041
1056
|
);
|
|
1042
1057
|
|
|
1043
|
-
//
|
|
1044
|
-
//
|
|
1045
|
-
if (result.current.error) {
|
|
1046
|
-
// Expected: Organisation context is required
|
|
1047
|
-
expect(result.current.error.message).toContain('Organisation context is required');
|
|
1048
|
-
// Test expects this to work, but it's actually the correct behavior to require org context
|
|
1049
|
-
return;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
// If no error (shouldn't happen with current implementation), verify scope
|
|
1058
|
+
// Hook now requires selectedEventOrganisationId to be passed explicitly
|
|
1059
|
+
// It no longer derives org from event automatically
|
|
1053
1060
|
if (result.current.resolvedScope) {
|
|
1054
|
-
// Should use
|
|
1055
|
-
expect(result.current.resolvedScope.organisationId).
|
|
1061
|
+
// Should use org ID from selectedEventOrganisationId
|
|
1062
|
+
expect(result.current.resolvedScope.organisationId).toBe('org-456');
|
|
1056
1063
|
expect(result.current.resolvedScope.eventId).toBe('event-123');
|
|
1057
1064
|
// AppId will be set when app lookup succeeds (needed for appConfig)
|
|
1058
1065
|
if (result.current.resolvedScope.appId) {
|
|
1059
1066
|
expect(result.current.resolvedScope.appId).toBeDefined();
|
|
1060
1067
|
}
|
|
1068
|
+
} else {
|
|
1069
|
+
// If no scope, it means no valid context (shouldn't happen with org-456 passed)
|
|
1070
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
1061
1071
|
}
|
|
1072
|
+
expect(result.current.error).toBeNull();
|
|
1062
1073
|
});
|
|
1063
1074
|
|
|
1064
1075
|
it('uses event scope app ID when app ID not resolved from database', async () => {
|
|
@@ -1117,6 +1128,7 @@ describe('useResolvedScope Hook', () => {
|
|
|
1117
1128
|
supabase: mockSupabase,
|
|
1118
1129
|
selectedOrganisationId: null,
|
|
1119
1130
|
selectedEventId: 'event-123',
|
|
1131
|
+
selectedEventOrganisationId: 'org-456', // Pass org ID from event explicitly
|
|
1120
1132
|
})
|
|
1121
1133
|
);
|
|
1122
1134
|
|
|
@@ -1127,27 +1139,21 @@ describe('useResolvedScope Hook', () => {
|
|
|
1127
1139
|
{ timeout: 5000 }
|
|
1128
1140
|
);
|
|
1129
1141
|
|
|
1130
|
-
//
|
|
1131
|
-
//
|
|
1132
|
-
if (result.current.error) {
|
|
1133
|
-
// Expected: Organisation context is required
|
|
1134
|
-
expect(result.current.error.message).toContain('Organisation context is required');
|
|
1135
|
-
// Test expects this to work, but it's actually the correct behavior to require org context
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
// If no error (shouldn't happen with current implementation), verify scope
|
|
1142
|
+
// Hook now requires selectedEventOrganisationId to be passed explicitly
|
|
1143
|
+
// It no longer derives org from event automatically
|
|
1140
1144
|
if (result.current.resolvedScope) {
|
|
1141
|
-
// Scope should resolve successfully with org
|
|
1142
|
-
|
|
1143
|
-
// of undefined appId doesn't match the implementation behavior when appConfig is needed.
|
|
1144
|
-
expect(result.current.resolvedScope.organisationId).toBeDefined();
|
|
1145
|
+
// Scope should resolve successfully with org from selectedEventOrganisationId
|
|
1146
|
+
expect(result.current.resolvedScope.organisationId).toBe('org-456');
|
|
1145
1147
|
expect(result.current.resolvedScope.eventId).toBe('event-123');
|
|
1146
1148
|
// AppId will be set when app lookup succeeds (needed for appConfig)
|
|
1147
1149
|
if (result.current.resolvedScope.appId) {
|
|
1148
1150
|
expect(result.current.resolvedScope.appId).toBeDefined();
|
|
1149
1151
|
}
|
|
1152
|
+
} else {
|
|
1153
|
+
// If no scope, it means no valid context (shouldn't happen with org-456 passed)
|
|
1154
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
1150
1155
|
}
|
|
1156
|
+
expect(result.current.error).toBeNull();
|
|
1151
1157
|
});
|
|
1152
1158
|
});
|
|
1153
1159
|
|
|
@@ -1166,7 +1172,11 @@ describe('useResolvedScope Hook', () => {
|
|
|
1166
1172
|
);
|
|
1167
1173
|
|
|
1168
1174
|
expect(result.current.isLoading).toBe(true);
|
|
1169
|
-
|
|
1175
|
+
// Scope is now returned immediately if org/event IDs are available (for performance)
|
|
1176
|
+
// Only appId resolution happens asynchronously
|
|
1177
|
+
expect(result.current.resolvedScope).not.toBeNull();
|
|
1178
|
+
expect(result.current.resolvedScope?.organisationId).toBe('org-123');
|
|
1179
|
+
expect(result.current.resolvedScope?.eventId).toBe('event-123');
|
|
1170
1180
|
expect(result.current.error).toBeNull();
|
|
1171
1181
|
});
|
|
1172
1182
|
|