@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
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* consistent scope resolution logic.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { useEffect, useState,
|
|
12
|
+
import { useEffect, useState, 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';
|
|
@@ -35,6 +35,8 @@ export interface UseResolvedScopeOptions {
|
|
|
35
35
|
selectedOrganisationId: string | null;
|
|
36
36
|
/** Selected event ID */
|
|
37
37
|
selectedEventId: string | null;
|
|
38
|
+
/** Selected event organisation ID (from selectedEvent.organisation_id) - allows immediate context without querying */
|
|
39
|
+
selectedEventOrganisationId?: string | null;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export interface UseResolvedScopeReturn {
|
|
@@ -73,72 +75,42 @@ export interface UseResolvedScopeReturn {
|
|
|
73
75
|
export function useResolvedScope({
|
|
74
76
|
supabase,
|
|
75
77
|
selectedOrganisationId,
|
|
76
|
-
selectedEventId
|
|
78
|
+
selectedEventId,
|
|
79
|
+
selectedEventOrganisationId
|
|
77
80
|
}: UseResolvedScopeOptions): UseResolvedScopeReturn {
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
// Use a ref to track the stable scope and only update it when it actually changes
|
|
83
|
-
const stableScopeRef = useRef<{ organisationId: string; appId: string; eventId: string | undefined }>({
|
|
84
|
-
organisationId: '',
|
|
85
|
-
appId: '',
|
|
86
|
-
eventId: undefined
|
|
87
|
-
});
|
|
81
|
+
// Get immediate context (synchronous) - allows secure client creation immediately
|
|
82
|
+
const immediateOrganisationId = selectedEventOrganisationId || selectedOrganisationId || undefined;
|
|
83
|
+
const immediateEventId = selectedEventId || undefined;
|
|
88
84
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (resolvedScope) {
|
|
93
|
-
const newScope = {
|
|
94
|
-
organisationId: resolvedScope.organisationId || '',
|
|
95
|
-
appId: resolvedScope.appId || '',
|
|
96
|
-
eventId: resolvedScope.eventId
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
// Only update if the scope has actually changed
|
|
100
|
-
if (stableScopeRef.current.organisationId !== newScope.organisationId ||
|
|
101
|
-
stableScopeRef.current.eventId !== newScope.eventId ||
|
|
102
|
-
stableScopeRef.current.appId !== newScope.appId) {
|
|
103
|
-
stableScopeRef.current = {
|
|
104
|
-
organisationId: newScope.organisationId,
|
|
105
|
-
appId: newScope.appId,
|
|
106
|
-
eventId: newScope.eventId
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
} else {
|
|
110
|
-
// Reset to empty scope when no resolved scope
|
|
111
|
-
stableScopeRef.current = { organisationId: '', appId: '', eventId: undefined };
|
|
112
|
-
}
|
|
113
|
-
}, [resolvedScope]);
|
|
85
|
+
const [appId, setAppId] = useState<string | undefined>(undefined);
|
|
86
|
+
const [isResolvingAppId, setIsResolvingAppId] = useState(false);
|
|
87
|
+
const [error, setError] = useState<Error | null>(null);
|
|
114
88
|
|
|
115
89
|
// Get app name to check if it's PORTAL (needed for return logic)
|
|
116
90
|
const appName = getCurrentAppName();
|
|
117
91
|
|
|
118
|
-
|
|
119
|
-
|
|
92
|
+
// Resolve appId in background (non-blocking)
|
|
120
93
|
useEffect(() => {
|
|
121
94
|
let cancelled = false;
|
|
122
95
|
|
|
123
|
-
const
|
|
124
|
-
// OPTIMIZATION: If all inputs are null/undefined,
|
|
125
|
-
// This indicates pre-filtered mode where we don't need to resolve scope
|
|
96
|
+
const resolveAppId = async () => {
|
|
97
|
+
// OPTIMIZATION: If all inputs are null/undefined, skip appId resolution
|
|
126
98
|
if (!supabase && !selectedOrganisationId && !selectedEventId) {
|
|
127
99
|
if (!cancelled) {
|
|
128
|
-
|
|
129
|
-
|
|
100
|
+
setAppId(undefined);
|
|
101
|
+
setIsResolvingAppId(false);
|
|
130
102
|
setError(null);
|
|
131
103
|
}
|
|
132
104
|
return;
|
|
133
105
|
}
|
|
134
106
|
|
|
135
|
-
|
|
107
|
+
setIsResolvingAppId(true);
|
|
136
108
|
setError(null);
|
|
137
109
|
|
|
138
110
|
try {
|
|
139
111
|
// Get app name and resolve appId
|
|
140
112
|
const appName = getCurrentAppName();
|
|
141
|
-
let
|
|
113
|
+
let resolvedAppId: string | undefined = undefined;
|
|
142
114
|
|
|
143
115
|
// Try to resolve appId from database (with caching)
|
|
144
116
|
// Only query if user is authenticated (RLS policies require authentication)
|
|
@@ -156,7 +128,7 @@ export function useResolvedScope({
|
|
|
156
128
|
const cached = appIdCache.get(appName);
|
|
157
129
|
const now = Date.now();
|
|
158
130
|
if (cached && (now - cached.timestamp) < CACHE_TTL) {
|
|
159
|
-
|
|
131
|
+
resolvedAppId = cached.appId;
|
|
160
132
|
} else {
|
|
161
133
|
// Cache miss or expired - fetch from database
|
|
162
134
|
const { data: app, error } = await supabase
|
|
@@ -172,7 +144,7 @@ export function useResolvedScope({
|
|
|
172
144
|
if (error.code === '406' || error.code === 'PGRST116' || error.message?.includes('406')) {
|
|
173
145
|
log.debug(`App resolution blocked by RLS for "${appName}" - user may not be authenticated`);
|
|
174
146
|
// Don't cache - will retry after authentication
|
|
175
|
-
|
|
147
|
+
resolvedAppId = undefined;
|
|
176
148
|
} else {
|
|
177
149
|
// Check if app exists but is inactive
|
|
178
150
|
const { data: inactiveApp } = await supabase
|
|
@@ -184,17 +156,17 @@ export function useResolvedScope({
|
|
|
184
156
|
if (inactiveApp) {
|
|
185
157
|
log.error(`App "${appName}" exists but is inactive (is_active: ${inactiveApp.is_active})`);
|
|
186
158
|
// Don't cache inactive apps - set appId to undefined
|
|
187
|
-
|
|
159
|
+
resolvedAppId = undefined;
|
|
188
160
|
} else {
|
|
189
161
|
log.error(`App "${appName}" not found in rbac_apps table`, { error });
|
|
190
162
|
// Don't cache missing apps - set appId to undefined
|
|
191
|
-
|
|
163
|
+
resolvedAppId = undefined;
|
|
192
164
|
}
|
|
193
165
|
}
|
|
194
166
|
} else if (app) {
|
|
195
|
-
|
|
167
|
+
resolvedAppId = app.id;
|
|
196
168
|
// Only cache successful lookups of active apps
|
|
197
|
-
appIdCache.set(appName, { appId, timestamp: now });
|
|
169
|
+
appIdCache.set(appName, { appId: resolvedAppId, timestamp: now });
|
|
198
170
|
}
|
|
199
171
|
}
|
|
200
172
|
}
|
|
@@ -210,119 +182,65 @@ export function useResolvedScope({
|
|
|
210
182
|
}
|
|
211
183
|
}
|
|
212
184
|
|
|
213
|
-
//
|
|
214
|
-
// Scope is now page-level only - use whatever context is available
|
|
215
|
-
// Default to organisation scope if both are available (safest default)
|
|
216
|
-
const initialScope: Scope = {
|
|
217
|
-
organisationId: selectedOrganisationId || undefined,
|
|
218
|
-
eventId: selectedEventId || undefined,
|
|
219
|
-
appId: appId
|
|
220
|
-
};
|
|
221
|
-
|
|
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(
|
|
242
|
-
initialScope,
|
|
243
|
-
'organisation', // Default to organisation scope when no page context
|
|
244
|
-
appName || undefined,
|
|
245
|
-
supabase
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
if (!validation.isValid) {
|
|
249
|
-
// If validation fails but we have an eventId, return scope with eventId
|
|
250
|
-
// The organisation will be derived later during permission checks
|
|
251
|
-
if (selectedEventId) {
|
|
252
|
-
if (!cancelled) {
|
|
253
|
-
const eventScope: Scope = {
|
|
254
|
-
organisationId: undefined, // Will be derived from event during permission check
|
|
255
|
-
eventId: selectedEventId,
|
|
256
|
-
appId: appId || undefined
|
|
257
|
-
};
|
|
258
|
-
setResolvedScope(eventScope);
|
|
259
|
-
setError(null); // Don't set error - let permission check handle derivation
|
|
260
|
-
setIsLoading(false);
|
|
261
|
-
}
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (!cancelled) {
|
|
266
|
-
setResolvedScope(null);
|
|
267
|
-
setError(validation.error || new Error('Context validation failed'));
|
|
268
|
-
setIsLoading(false);
|
|
269
|
-
}
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Set resolved scope
|
|
185
|
+
// Update appId state when resolved
|
|
274
186
|
if (!cancelled) {
|
|
275
|
-
|
|
187
|
+
setAppId(resolvedAppId);
|
|
188
|
+
setIsResolvingAppId(false);
|
|
276
189
|
setError(null);
|
|
277
|
-
setIsLoading(false);
|
|
278
190
|
}
|
|
279
191
|
} catch (err) {
|
|
280
192
|
if (!cancelled) {
|
|
281
193
|
setError(err as Error);
|
|
282
|
-
|
|
194
|
+
setIsResolvingAppId(false);
|
|
283
195
|
}
|
|
284
196
|
}
|
|
285
197
|
};
|
|
286
198
|
|
|
287
|
-
|
|
199
|
+
resolveAppId();
|
|
288
200
|
|
|
289
201
|
return () => {
|
|
290
202
|
cancelled = true;
|
|
291
203
|
};
|
|
292
|
-
}, [selectedOrganisationId, selectedEventId
|
|
293
|
-
|
|
294
|
-
// Return scope if it has appId (for PORTAL/ADMIN) or organisationId (for other apps)
|
|
295
|
-
// For PORTAL/ADMIN, always return a scope (even if empty) so components can use contextAppId
|
|
296
|
-
const allowsOptionalContexts = appName === 'PORTAL' || appName === 'ADMIN';
|
|
297
|
-
const hasValidScope = allowsOptionalContexts
|
|
298
|
-
? true // PORTAL/ADMIN always have valid scope (even without org/event/appId)
|
|
299
|
-
: (stableScope.appId || stableScope.organisationId);
|
|
204
|
+
}, [supabase, selectedOrganisationId, selectedEventId]);
|
|
300
205
|
|
|
301
|
-
//
|
|
302
|
-
// This
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
206
|
+
// Build scope immediately with synchronous context + async appId
|
|
207
|
+
// This allows secure client creation immediately while appId resolves
|
|
208
|
+
const immediateScope: Scope | null = useMemo(() => {
|
|
209
|
+
// For PORTAL/ADMIN apps, allow scope without org/event
|
|
210
|
+
if (appName === 'PORTAL' || appName === 'ADMIN') {
|
|
211
|
+
return {
|
|
212
|
+
organisationId: undefined,
|
|
213
|
+
eventId: undefined,
|
|
214
|
+
appId: appId || undefined
|
|
215
|
+
};
|
|
306
216
|
}
|
|
307
217
|
|
|
308
|
-
// Build scope
|
|
218
|
+
// Build scope with immediate context
|
|
309
219
|
const scope: Scope = {};
|
|
310
|
-
if (
|
|
311
|
-
scope.organisationId =
|
|
220
|
+
if (immediateOrganisationId) {
|
|
221
|
+
scope.organisationId = immediateOrganisationId;
|
|
312
222
|
}
|
|
313
|
-
if (
|
|
314
|
-
scope.eventId =
|
|
223
|
+
if (immediateEventId) {
|
|
224
|
+
scope.eventId = immediateEventId;
|
|
315
225
|
}
|
|
316
|
-
if (
|
|
317
|
-
scope.appId =
|
|
226
|
+
if (appId) {
|
|
227
|
+
scope.appId = appId;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// For non-PORTAL/ADMIN apps, require at least orgId or appId
|
|
231
|
+
if (!scope.organisationId && !scope.appId) {
|
|
232
|
+
return null;
|
|
318
233
|
}
|
|
319
234
|
|
|
320
235
|
return scope;
|
|
321
|
-
}, [
|
|
236
|
+
}, [immediateOrganisationId, immediateEventId, appId, appName]);
|
|
322
237
|
|
|
238
|
+
// Return scope immediately - appId resolves in background
|
|
239
|
+
// Navigation and permissions will wait for appId (correct behavior)
|
|
240
|
+
// But secure client can be created immediately, allowing data queries to run
|
|
323
241
|
return {
|
|
324
|
-
resolvedScope:
|
|
325
|
-
isLoading,
|
|
242
|
+
resolvedScope: immediateScope,
|
|
243
|
+
isLoading: isResolvingAppId, // Only true while appId resolves
|
|
326
244
|
error
|
|
327
245
|
};
|
|
328
246
|
}
|
|
@@ -11,6 +11,7 @@ import { renderHook, waitFor } from '@testing-library/react';
|
|
|
11
11
|
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
12
12
|
import { useResourcePermissions } from './useResourcePermissions';
|
|
13
13
|
import type { Scope } from '../types';
|
|
14
|
+
import { isSuperAdmin } from '../api';
|
|
14
15
|
|
|
15
16
|
// Mock dependencies
|
|
16
17
|
vi.mock('../../providers/services/UnifiedAuthProvider', () => ({
|
|
@@ -33,6 +34,12 @@ vi.mock('./usePermissions', () => ({
|
|
|
33
34
|
useCan: vi.fn(),
|
|
34
35
|
}));
|
|
35
36
|
|
|
37
|
+
vi.mock('../api', () => ({
|
|
38
|
+
isSuperAdmin: vi.fn(),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const mockIsSuperAdmin = vi.mocked(isSuperAdmin);
|
|
42
|
+
|
|
36
43
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
37
44
|
import { useOrganisations } from '../../hooks/useOrganisations';
|
|
38
45
|
import { useEvents } from '../../hooks/useEvents';
|
|
@@ -89,11 +96,14 @@ describe('useResourcePermissions Hook', () => {
|
|
|
89
96
|
} as any);
|
|
90
97
|
|
|
91
98
|
mockUseResolvedScope.mockReturnValue({
|
|
92
|
-
resolvedScope: mockScope,
|
|
99
|
+
resolvedScope: mockScope, // Use correct property name from UseResolvedScopeReturn interface
|
|
93
100
|
isLoading: false,
|
|
94
101
|
error: null,
|
|
95
102
|
});
|
|
96
103
|
|
|
104
|
+
// Mock isSuperAdmin to resolve immediately (prevents isLoading from being true due to super admin check)
|
|
105
|
+
mockIsSuperAdmin.mockResolvedValue(false);
|
|
106
|
+
|
|
97
107
|
// Default useCan mocks - all permissions allowed
|
|
98
108
|
mockUseCan.mockReturnValue({
|
|
99
109
|
can: true,
|
|
@@ -117,9 +127,16 @@ describe('useResourcePermissions Hook', () => {
|
|
|
117
127
|
expect(result.current.canRead).toBeTypeOf('function');
|
|
118
128
|
});
|
|
119
129
|
|
|
120
|
-
it('returns scope object', () => {
|
|
130
|
+
it('returns scope object', async () => {
|
|
121
131
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
122
132
|
|
|
133
|
+
// Wait for super admin check to complete
|
|
134
|
+
await waitFor(() => {
|
|
135
|
+
expect(result.current.isLoading).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// The hook returns resolvedScope if available, otherwise fallback scope
|
|
139
|
+
// Since mockScope has appId, it should be returned as-is
|
|
123
140
|
expect(result.current.scope).toEqual(mockScope);
|
|
124
141
|
});
|
|
125
142
|
|
|
@@ -130,24 +147,42 @@ describe('useResourcePermissions Hook', () => {
|
|
|
130
147
|
supabase: mockSupabase,
|
|
131
148
|
selectedOrganisationId: 'org-123',
|
|
132
149
|
selectedEventId: 'event-123',
|
|
150
|
+
selectedEventOrganisationId: null, // Mock event doesn't have organisation_id, so it becomes null
|
|
133
151
|
});
|
|
134
152
|
});
|
|
135
153
|
|
|
136
|
-
it('calls useCan for each permission type', () => {
|
|
154
|
+
it('calls useCan for each permission type', async () => {
|
|
137
155
|
renderHook(() => useResourcePermissions('contacts'));
|
|
138
156
|
|
|
139
|
-
|
|
140
|
-
|
|
157
|
+
// Wait for super admin check to complete
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(mockUseCan).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// The hook calls useCan for each permission type (create, update, delete, read)
|
|
163
|
+
// Note: It may be called multiple times due to re-renders during super admin check,
|
|
164
|
+
// so we verify that all four permission types are checked rather than exact call count
|
|
165
|
+
const calls = mockUseCan.mock.calls;
|
|
166
|
+
expect(calls.length).toBeGreaterThanOrEqual(4);
|
|
167
|
+
|
|
168
|
+
// When appId is available in resolvedScope, permission strings include page. prefix
|
|
141
169
|
// and resource name is passed as pageId to enable page permission checks
|
|
142
170
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
143
171
|
'user-123',
|
|
144
|
-
mockScope,
|
|
172
|
+
mockScope, // resolvedScope is used when available
|
|
145
173
|
'create:page.contacts',
|
|
146
|
-
'contacts', // pageId is resource name when appId is available
|
|
174
|
+
'contacts', // pageId is resource name when appId is available in resolvedScope
|
|
147
175
|
true,
|
|
148
|
-
|
|
176
|
+
false, // precomputedSuperAdmin (resolved after async check)
|
|
149
177
|
undefined // appName
|
|
150
178
|
);
|
|
179
|
+
|
|
180
|
+
// Verify all four permission types are checked
|
|
181
|
+
const permissions = calls.map(call => call[2]); // permission is the 3rd argument
|
|
182
|
+
expect(permissions).toContain('create:page.contacts');
|
|
183
|
+
expect(permissions).toContain('update:page.contacts');
|
|
184
|
+
expect(permissions).toContain('delete:page.contacts');
|
|
185
|
+
expect(permissions).toContain('read:page.contacts');
|
|
151
186
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
152
187
|
'user-123',
|
|
153
188
|
mockScope,
|
|
@@ -192,9 +227,10 @@ describe('useResourcePermissions Hook', () => {
|
|
|
192
227
|
expect(result.current.canCreate('contacts')).toBe(true);
|
|
193
228
|
});
|
|
194
229
|
|
|
195
|
-
it('returns false when user cannot create resource', () => {
|
|
230
|
+
it('returns false when user cannot create resource', async () => {
|
|
196
231
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
197
|
-
|
|
232
|
+
// When appId is available in resolvedScope, permission format is 'create:page.contacts'
|
|
233
|
+
if (permission === 'create:page.contacts' || permission === 'create:contacts') {
|
|
198
234
|
return {
|
|
199
235
|
can: false,
|
|
200
236
|
isLoading: false,
|
|
@@ -212,6 +248,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
212
248
|
|
|
213
249
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
214
250
|
|
|
251
|
+
// Wait for super admin check to complete
|
|
252
|
+
await waitFor(() => {
|
|
253
|
+
expect(result.current.isLoading).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
|
|
215
256
|
expect(result.current.canCreate('contacts')).toBe(false);
|
|
216
257
|
expect(result.current.canUpdate('contacts')).toBe(true);
|
|
217
258
|
expect(result.current.canDelete('contacts')).toBe(true);
|
|
@@ -233,9 +274,10 @@ describe('useResourcePermissions Hook', () => {
|
|
|
233
274
|
expect(result.current.canRead('contacts')).toBe(true);
|
|
234
275
|
});
|
|
235
276
|
|
|
236
|
-
it('checks read permissions when enableRead is true', () => {
|
|
277
|
+
it('checks read permissions when enableRead is true', async () => {
|
|
237
278
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
238
|
-
|
|
279
|
+
// When appId is available in resolvedScope, permission format is 'read:page.contacts'
|
|
280
|
+
if (permission === 'read:page.contacts' || permission === 'read:contacts') {
|
|
239
281
|
return {
|
|
240
282
|
can: true,
|
|
241
283
|
isLoading: false,
|
|
@@ -255,12 +297,18 @@ describe('useResourcePermissions Hook', () => {
|
|
|
255
297
|
useResourcePermissions('contacts', { enableRead: true })
|
|
256
298
|
);
|
|
257
299
|
|
|
300
|
+
// Wait for super admin check to complete
|
|
301
|
+
await waitFor(() => {
|
|
302
|
+
expect(result.current.isLoading).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
258
305
|
expect(result.current.canRead('contacts')).toBe(true);
|
|
259
306
|
});
|
|
260
307
|
|
|
261
|
-
it('returns false for read when permission is denied and enableRead is true', () => {
|
|
308
|
+
it('returns false for read when permission is denied and enableRead is true', async () => {
|
|
262
309
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
263
|
-
|
|
310
|
+
// When appId is available in resolvedScope, permission format is 'read:page.contacts'
|
|
311
|
+
if (permission === 'read:page.contacts' || permission === 'read:contacts') {
|
|
264
312
|
return {
|
|
265
313
|
can: false,
|
|
266
314
|
isLoading: false,
|
|
@@ -280,6 +328,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
280
328
|
useResourcePermissions('contacts', { enableRead: true })
|
|
281
329
|
);
|
|
282
330
|
|
|
331
|
+
// Wait for super admin check to complete
|
|
332
|
+
await waitFor(() => {
|
|
333
|
+
expect(result.current.isLoading).toBe(false);
|
|
334
|
+
});
|
|
335
|
+
|
|
283
336
|
expect(result.current.canRead('contacts')).toBe(false);
|
|
284
337
|
});
|
|
285
338
|
});
|
|
@@ -399,7 +452,7 @@ describe('useResourcePermissions Hook', () => {
|
|
|
399
452
|
});
|
|
400
453
|
|
|
401
454
|
describe('Missing User Context', () => {
|
|
402
|
-
it('handles missing user gracefully', () => {
|
|
455
|
+
it('handles missing user gracefully', async () => {
|
|
403
456
|
mockUseUnifiedAuth.mockReturnValue({
|
|
404
457
|
user: null,
|
|
405
458
|
supabase: mockSupabase,
|
|
@@ -407,15 +460,20 @@ describe('useResourcePermissions Hook', () => {
|
|
|
407
460
|
|
|
408
461
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
409
462
|
|
|
463
|
+
// Wait for super admin check to complete (will be false when user is null)
|
|
464
|
+
await waitFor(() => {
|
|
465
|
+
expect(result.current.isLoading).toBe(false);
|
|
466
|
+
});
|
|
467
|
+
|
|
410
468
|
// When user is null, userId is empty string, but scope and permissions are still checked
|
|
411
|
-
// Since mockScope has appId, permission strings include page. prefix
|
|
469
|
+
// Since resolvedScope (mockScope) has appId, permission strings include page. prefix
|
|
412
470
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
413
471
|
'',
|
|
414
|
-
mockScope,
|
|
472
|
+
mockScope, // resolvedScope is used when available
|
|
415
473
|
'create:page.contacts',
|
|
416
|
-
'contacts', // pageId is resource name when appId is available in
|
|
474
|
+
'contacts', // pageId is resource name when appId is available in resolvedScope
|
|
417
475
|
true,
|
|
418
|
-
|
|
476
|
+
false, // precomputedSuperAdmin (false when user is null)
|
|
419
477
|
undefined // appName
|
|
420
478
|
);
|
|
421
479
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
@@ -454,11 +512,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
454
512
|
throw new Error('Event provider not available');
|
|
455
513
|
});
|
|
456
514
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
515
|
+
mockUseResolvedScope.mockReturnValue({
|
|
516
|
+
resolvedScope: mockScope,
|
|
517
|
+
isLoading: false, // Scope is resolved
|
|
518
|
+
error: null,
|
|
519
|
+
});
|
|
462
520
|
|
|
463
521
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
464
522
|
|
|
@@ -528,12 +586,20 @@ describe('useResourcePermissions Hook', () => {
|
|
|
528
586
|
expect(result.current.isLoading).toBe(true);
|
|
529
587
|
});
|
|
530
588
|
|
|
531
|
-
it('excludes read loading state when enableRead is false', () => {
|
|
589
|
+
it('excludes read loading state when enableRead is false', async () => {
|
|
590
|
+
// Ensure scope is resolved (not loading) - this prevents scopeLoading from contributing to isLoading
|
|
591
|
+
mockUseResolvedScope.mockReturnValue({
|
|
592
|
+
resolvedScope: mockScope, // Use correct property name
|
|
593
|
+
isLoading: false,
|
|
594
|
+
error: null,
|
|
595
|
+
});
|
|
596
|
+
|
|
532
597
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
533
|
-
|
|
598
|
+
// Read permission check should not contribute to isLoading when enableRead is false
|
|
599
|
+
if (permission === 'read:page.contacts' || permission === 'read:contacts') {
|
|
534
600
|
return {
|
|
535
601
|
can: false,
|
|
536
|
-
isLoading: true,
|
|
602
|
+
isLoading: true, // Read is loading, but should be excluded
|
|
537
603
|
error: null,
|
|
538
604
|
refetch: vi.fn(),
|
|
539
605
|
} as any;
|
|
@@ -546,9 +612,12 @@ describe('useResourcePermissions Hook', () => {
|
|
|
546
612
|
} as any;
|
|
547
613
|
});
|
|
548
614
|
|
|
549
|
-
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
615
|
+
const { result } = renderHook(() => useResourcePermissions('contacts', { enableRead: false }));
|
|
550
616
|
|
|
551
|
-
|
|
617
|
+
// Wait for super admin check to complete
|
|
618
|
+
await waitFor(() => {
|
|
619
|
+
expect(result.current.isLoading).toBe(false);
|
|
620
|
+
});
|
|
552
621
|
});
|
|
553
622
|
});
|
|
554
623
|
|
|
@@ -566,10 +635,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
566
635
|
expect(result.current.error).toEqual(scopeError);
|
|
567
636
|
});
|
|
568
637
|
|
|
569
|
-
it('aggregates errors from permission checks', () => {
|
|
638
|
+
it('aggregates errors from permission checks', async () => {
|
|
570
639
|
const permissionError = new Error('Permission check failed');
|
|
571
640
|
mockUseCan.mockImplementation((userId, scope, permission) => {
|
|
572
|
-
|
|
641
|
+
// When appId is available in resolvedScope, permission format is 'create:page.contacts'
|
|
642
|
+
if (permission === 'create:page.contacts' || permission === 'create:contacts') {
|
|
573
643
|
return {
|
|
574
644
|
can: false,
|
|
575
645
|
isLoading: false,
|
|
@@ -587,6 +657,11 @@ describe('useResourcePermissions Hook', () => {
|
|
|
587
657
|
|
|
588
658
|
const { result } = renderHook(() => useResourcePermissions('contacts'));
|
|
589
659
|
|
|
660
|
+
// Wait for super admin check to complete
|
|
661
|
+
await waitFor(() => {
|
|
662
|
+
expect(result.current.isLoading).toBe(false);
|
|
663
|
+
});
|
|
664
|
+
|
|
590
665
|
expect(result.current.error).toEqual(permissionError);
|
|
591
666
|
});
|
|
592
667
|
|
|
@@ -630,17 +705,22 @@ describe('useResourcePermissions Hook', () => {
|
|
|
630
705
|
});
|
|
631
706
|
|
|
632
707
|
describe('Options', () => {
|
|
633
|
-
it('respects enableRead option', () => {
|
|
708
|
+
it('respects enableRead option', async () => {
|
|
634
709
|
renderHook(() => useResourcePermissions('contacts', { enableRead: true }));
|
|
635
710
|
|
|
636
|
-
//
|
|
711
|
+
// Wait for super admin check to complete
|
|
712
|
+
await waitFor(() => {
|
|
713
|
+
expect(mockUseCan).toHaveBeenCalled();
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Should call useCan for read permission with page. prefix when appId is available in resolvedScope
|
|
637
717
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
638
718
|
expect.any(String),
|
|
639
719
|
expect.any(Object),
|
|
640
720
|
'read:page.contacts',
|
|
641
|
-
'contacts', // pageId is resource name when appId is available
|
|
721
|
+
'contacts', // pageId is resource name when appId is available in resolvedScope
|
|
642
722
|
true,
|
|
643
|
-
|
|
723
|
+
false, // precomputedSuperAdmin (resolved after async check)
|
|
644
724
|
undefined // appName
|
|
645
725
|
);
|
|
646
726
|
});
|
|
@@ -654,16 +734,22 @@ describe('useResourcePermissions Hook', () => {
|
|
|
654
734
|
});
|
|
655
735
|
|
|
656
736
|
describe('Different Resources', () => {
|
|
657
|
-
it('works with different resource names', () => {
|
|
737
|
+
it('works with different resource names', async () => {
|
|
658
738
|
renderHook(() => useResourcePermissions('risks'));
|
|
659
739
|
|
|
740
|
+
// Wait for super admin check to complete
|
|
741
|
+
await waitFor(() => {
|
|
742
|
+
expect(mockUseCan).toHaveBeenCalled();
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// When appId is available in resolvedScope, permission format includes page. prefix
|
|
660
746
|
expect(mockUseCan).toHaveBeenCalledWith(
|
|
661
747
|
expect.any(String),
|
|
662
748
|
expect.any(Object),
|
|
663
749
|
'create:page.risks',
|
|
664
|
-
'risks', // pageId is resource name when appId is available
|
|
750
|
+
'risks', // pageId is resource name when appId is available in resolvedScope
|
|
665
751
|
true,
|
|
666
|
-
|
|
752
|
+
false, // precomputedSuperAdmin (resolved after async check)
|
|
667
753
|
undefined // appName
|
|
668
754
|
);
|
|
669
755
|
expect(mockUseCan).toHaveBeenCalledWith(
|