@jmruthers/pace-core 0.6.4 → 0.6.6
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/core-usage-manifest.json +93 -0
- package/cursor-rules/00-pace-core-compliance.mdc +128 -26
- package/cursor-rules/01-standards-compliance.mdc +49 -8
- package/cursor-rules/02-project-structure.mdc +6 -0
- package/cursor-rules/03-solid-principles.mdc +2 -0
- package/cursor-rules/04-testing-standards.mdc +2 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
- package/cursor-rules/06-code-quality.mdc +2 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
- package/cursor-rules/08-markup-quality.mdc +52 -27
- package/cursor-rules/09-rbac-compliance.mdc +462 -0
- package/cursor-rules/10-error-handling-patterns.mdc +179 -0
- package/cursor-rules/11-performance-optimization.mdc +169 -0
- package/cursor-rules/12-ci-cd-integration.mdc +150 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
- package/dist/DataTable-LRJL4IRV.js +15 -0
- package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
- package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
- package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/chunk-4T7OBVTU.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-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
- package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
- package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
- package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/chunk-OHIK3MIO.js +994 -0
- package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
- package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
- package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
- package/dist/components.d.ts +6 -6
- package/dist/components.js +57 -267
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +22 -0
- package/dist/eslint-rules/rules/compliance.cjs +348 -0
- package/dist/eslint-rules/rules/components.cjs +113 -0
- package/dist/eslint-rules/rules/imports.cjs +102 -0
- package/dist/eslint-rules/rules/rbac.cjs +790 -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 +5 -5
- package/dist/hooks.js +62 -270
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +36 -26
- package/dist/index.js +87 -690
- 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 +124 -594
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- 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-B-K_5VnO.d.ts} +4 -0
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +16 -6
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +454 -930
- package/docs/api-reference/components.md +3 -1
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/best-practices/accessibility.md +6 -3
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/permission-enforcement.md +4 -0
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +12 -3
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/secure-client-protection.md +0 -34
- package/docs/standards/00-pace-core-compliance.md +967 -0
- package/docs/standards/01-standards-compliance.md +188 -0
- package/docs/standards/02-project-structure.md +985 -0
- package/docs/standards/03-solid-principles.md +39 -0
- package/docs/standards/04-testing-standards.md +36 -0
- package/docs/standards/05-bug-reports-and-features.md +27 -0
- package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
- package/docs/standards/07-tech-stack-compliance.md +30 -0
- package/docs/standards/08-markup-quality.md +345 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
- package/docs/standards/10-error-handling-patterns.md +401 -0
- package/docs/standards/11-performance-optimization.md +348 -0
- package/docs/standards/12-ci-cd-integration.md +370 -0
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
- package/docs/standards/README.md +62 -33
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +20 -4
- package/package.json +31 -21
- package/scripts/audit/audit-compliance.cjs +1295 -0
- package/scripts/audit/audit-components.cjs +260 -0
- package/scripts/audit/audit-dependencies.cjs +395 -0
- package/scripts/audit/audit-rbac.cjs +954 -0
- package/scripts/audit/audit-standards.cjs +1268 -0
- package/scripts/audit/index.cjs +1898 -194
- package/scripts/install-cursor-rules.cjs +259 -8
- package/scripts/validate-master.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
- 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-utils.test.tsx +3 -3
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/ContextSelector/ContextSelector.tsx +42 -39
- package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
- package/src/components/DataTable/components/DataTableBody.tsx +55 -31
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +7 -2
- package/src/components/DataTable/components/ImportModal.tsx +4 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/core/DataTableContext.tsx +1 -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 -0
- package/src/components/DateTimeField/DateTimeField.tsx +20 -20
- package/src/components/DateTimeField/README.md +5 -2
- package/src/components/Dialog/Dialog.test.tsx +361 -318
- package/src/components/Dialog/Dialog.tsx +1154 -323
- package/src/components/Dialog/index.ts +3 -3
- package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
- package/src/components/FileDisplay/FileDisplay.tsx +28 -22
- package/src/components/Form/Form.test.tsx +9 -10
- package/src/components/Form/Form.tsx +369 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
- package/src/components/LoginForm/LoginForm.tsx +2 -2
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
- package/src/components/PaceAppLayout/README.md +10 -9
- package/src/components/PaceAppLayout/test-setup.tsx +40 -31
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
- package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
- package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
- package/src/components/Select/Select.tsx +23 -21
- package/src/components/Select/types.ts +1 -1
- package/src/components/UserMenu/UserMenu.test.tsx +38 -6
- package/src/components/UserMenu/UserMenu.tsx +39 -34
- package/src/components/index.ts +3 -4
- package/src/eslint-rules/index.cjs +22 -0
- package/src/eslint-rules/rules/compliance.cjs +348 -0
- package/src/eslint-rules/rules/components.cjs +113 -0
- package/src/eslint-rules/rules/imports.cjs +102 -0
- package/src/eslint-rules/rules/rbac.cjs +790 -0
- package/src/eslint-rules/utils/helpers.cjs +42 -0
- package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- 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 +62 -190
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +19 -9
- package/src/hooks/useAppConfig.ts +26 -24
- package/src/hooks/useEventTheme.test.ts +211 -233
- package/src/hooks/useEventTheme.ts +19 -28
- package/src/hooks/useEvents.ts +11 -7
- package/src/hooks/useKeyboardShortcuts.ts +1 -1
- package/src/hooks/useOrganisationPermissions.ts +9 -11
- package/src/hooks/useOrganisations.ts +13 -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 +16 -1
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
- package/src/rbac/README.md +20 -20
- 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/useRBAC.test.ts +21 -3
- package/src/rbac/hooks/useRBAC.ts +4 -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 +241 -60
- package/src/rbac/hooks/useResourcePermissions.ts +182 -63
- 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/permissions.ts +17 -17
- package/src/rbac/utils/contextValidator.ts +45 -7
- package/src/services/AuthService.ts +132 -23
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +155 -58
- 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 +4 -0
- package/src/types/database.generated.ts +4733 -3809
- 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/formatting/formatTime.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/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/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-E7YQZD7D.js +0 -175
- package/dist/DataTable-E7YQZD7D.js.map +0 -1
- package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
- package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
- package/dist/api-6LVZTHDS.js +0 -52
- package/dist/api-6LVZTHDS.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-36LVWXB2.js +0 -227
- package/dist/chunk-36LVWXB2.js.map +0 -1
- package/dist/chunk-3LPHPB62.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-5EC5MEWX.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6SOIHG6Z.js.map +0 -1
- package/dist/chunk-7JPAB3T5.js.map +0 -1
- package/dist/chunk-ATKZM7RX.js +0 -2053
- package/dist/chunk-ATKZM7RX.js.map +0 -1
- package/dist/chunk-AVMLPIM7.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.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-I6DAQMWX.js.map +0 -1
- package/dist/chunk-J36DSWQK.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-NN6WWZ5U.js.map +0 -1
- package/dist/chunk-OEWDTMG7.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-YKRAFF5K.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-OOPCLPZW.js +0 -9
- package/dist/contextValidator-OOPCLPZW.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/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/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/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/eslint-rules/pace-core-compliance.cjs +0 -510
- 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
|
@@ -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
|
|
|
@@ -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
|
}
|