@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
|
@@ -15,6 +15,7 @@ import { renderWithProviders } from '../../__tests__/helpers/test-utils';
|
|
|
15
15
|
import { Logger, LogLevel } from '../../utils/core/logger';
|
|
16
16
|
import { clearPalette } from '../../theming/runtime';
|
|
17
17
|
import { EventServiceContext } from '../../providers/services/EventServiceProvider';
|
|
18
|
+
import { UnifiedAuthContext } from '../../providers/services/UnifiedAuthProvider';
|
|
18
19
|
|
|
19
20
|
// Mock React Router
|
|
20
21
|
const mockNavigate = vi.fn();
|
|
@@ -47,36 +48,66 @@ const mockSupabase = {
|
|
|
47
48
|
// Mock the UnifiedAuthProvider
|
|
48
49
|
const mockAuthContext = {
|
|
49
50
|
user: null as User | null,
|
|
51
|
+
session: null,
|
|
50
52
|
isAuthenticated: false,
|
|
51
53
|
isLoading: false,
|
|
54
|
+
authLoading: false,
|
|
52
55
|
authError: null as Error | null,
|
|
56
|
+
error: null,
|
|
53
57
|
hasRole: vi.fn(),
|
|
54
58
|
getUserRole: vi.fn(),
|
|
55
59
|
signIn: vi.fn(),
|
|
56
60
|
signOut: vi.fn(),
|
|
61
|
+
signUp: vi.fn(),
|
|
62
|
+
resetPassword: vi.fn(),
|
|
63
|
+
updatePassword: vi.fn(),
|
|
57
64
|
refreshSession: vi.fn(),
|
|
58
65
|
supabase: mockSupabase,
|
|
59
66
|
appName: 'Test App',
|
|
67
|
+
appId: undefined,
|
|
60
68
|
hasErrors: false,
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
// Organisation context
|
|
70
|
+
selectedOrganisation: null,
|
|
71
|
+
selectedOrganisationId: null,
|
|
72
|
+
organisations: [],
|
|
73
|
+
userMemberships: [],
|
|
74
|
+
organisationLoading: false,
|
|
75
|
+
organisationError: null,
|
|
76
|
+
hasValidOrganisationContext: false,
|
|
77
|
+
isContextReady: false,
|
|
78
|
+
switchOrganisation: vi.fn(),
|
|
79
|
+
validateOrganisationAccess: vi.fn(),
|
|
80
|
+
refreshOrganisations: vi.fn(),
|
|
81
|
+
ensureOrganisationContext: vi.fn(),
|
|
82
|
+
isOrganisationSecure: vi.fn(),
|
|
83
|
+
getPrimaryOrganisation: vi.fn(),
|
|
84
|
+
// Event context
|
|
85
|
+
events: [],
|
|
86
|
+
selectedEvent: null,
|
|
87
|
+
selectedEventId: null,
|
|
88
|
+
eventLoading: false,
|
|
89
|
+
eventError: null,
|
|
90
|
+
setSelectedEvent: vi.fn(),
|
|
91
|
+
refreshEvents: vi.fn(),
|
|
67
92
|
// Inactivity context
|
|
68
93
|
isIdle: false,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
timeRemaining: 0,
|
|
95
|
+
showWarning: false,
|
|
96
|
+
showInactivityWarning: false,
|
|
97
|
+
inactivityTimeRemaining: 0,
|
|
98
|
+
isTracking: false,
|
|
99
|
+
resetActivity: vi.fn(),
|
|
100
|
+
startTracking: vi.fn(),
|
|
101
|
+
stopTracking: vi.fn(),
|
|
102
|
+
handleIdleLogout: vi.fn(),
|
|
103
|
+
handleStaySignedIn: vi.fn(),
|
|
104
|
+
handleSignOutNow: vi.fn(),
|
|
105
|
+
// Session restoration
|
|
106
|
+
sessionRestoration: { state: 'idle', error: null },
|
|
107
|
+
sessionRestorationTimedOut: false,
|
|
108
|
+
sessionRestorationTimeoutMs: 5000,
|
|
73
109
|
};
|
|
74
110
|
|
|
75
|
-
// Mock the useUnifiedAuth hook - needs to match the actual import path
|
|
76
|
-
vi.mock('../../providers', () => ({
|
|
77
|
-
useUnifiedAuth: () => mockAuthContext,
|
|
78
|
-
UnifiedAuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
|
79
|
-
}));
|
|
80
111
|
|
|
81
112
|
vi.mock('../../theming/runtime', () => ({
|
|
82
113
|
clearPalette: vi.fn(),
|
|
@@ -105,6 +136,16 @@ const resetAuthContext = () => {
|
|
|
105
136
|
mockAuthContext.supabase = mockSupabase;
|
|
106
137
|
};
|
|
107
138
|
|
|
139
|
+
// Helper to render with UnifiedAuthContext
|
|
140
|
+
const renderWithAuthContext = (ui: React.ReactElement, options: { withRouter?: boolean } = {}) => {
|
|
141
|
+
return renderWithProviders(
|
|
142
|
+
<UnifiedAuthContext.Provider value={mockAuthContext as any}>
|
|
143
|
+
{ui}
|
|
144
|
+
</UnifiedAuthContext.Provider>,
|
|
145
|
+
options
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
|
|
108
149
|
describe('PaceLoginPage Component', () => {
|
|
109
150
|
let originalMode: string | undefined;
|
|
110
151
|
|
|
@@ -141,7 +182,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
141
182
|
|
|
142
183
|
describe('Side Effects', () => {
|
|
143
184
|
it('clears theme palette on mount and when login route is active', () => {
|
|
144
|
-
|
|
185
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
145
186
|
|
|
146
187
|
expect(clearPalette).toHaveBeenCalled();
|
|
147
188
|
});
|
|
@@ -151,9 +192,11 @@ describe('PaceLoginPage Component', () => {
|
|
|
151
192
|
window.history.pushState({}, '', '/login');
|
|
152
193
|
|
|
153
194
|
renderWithProviders(
|
|
154
|
-
<
|
|
155
|
-
<
|
|
156
|
-
|
|
195
|
+
<UnifiedAuthContext.Provider value={mockAuthContext as any}>
|
|
196
|
+
<EventServiceContext.Provider value={{ eventService: { restorePersistedEvent } as any }}>
|
|
197
|
+
<PaceLoginPage appName="Test App" />
|
|
198
|
+
</EventServiceContext.Provider>
|
|
199
|
+
</UnifiedAuthContext.Provider>
|
|
157
200
|
);
|
|
158
201
|
|
|
159
202
|
await waitFor(() => {
|
|
@@ -165,19 +208,19 @@ describe('PaceLoginPage Component', () => {
|
|
|
165
208
|
// Basic rendering tests
|
|
166
209
|
describe('Rendering', () => {
|
|
167
210
|
it('renders with default props', () => {
|
|
168
|
-
|
|
211
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
169
212
|
|
|
170
213
|
expect(screen.getByLabelText('Test App Login Page')).toBeInTheDocument();
|
|
171
214
|
});
|
|
172
215
|
|
|
173
216
|
it('renders with custom app name', () => {
|
|
174
|
-
|
|
217
|
+
renderWithAuthContext(<PaceLoginPage appName="My Application" />, { withRouter: false });
|
|
175
218
|
|
|
176
219
|
expect(screen.getByLabelText('My Application Login Page')).toBeInTheDocument();
|
|
177
220
|
});
|
|
178
221
|
|
|
179
222
|
it('renders with custom redirect path', () => {
|
|
180
|
-
|
|
223
|
+
renderWithAuthContext(
|
|
181
224
|
<PaceLoginPage
|
|
182
225
|
appName="Test App"
|
|
183
226
|
onSuccessRedirectPath="/dashboard"
|
|
@@ -188,7 +231,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
188
231
|
});
|
|
189
232
|
|
|
190
233
|
it('renders app logo with correct attributes', () => {
|
|
191
|
-
|
|
234
|
+
renderWithAuthContext(<PaceLoginPage appName="TestApp" />, { withRouter: false });
|
|
192
235
|
|
|
193
236
|
const logo = screen.getByAltText('TestApp logo');
|
|
194
237
|
expect(logo).toBeInTheDocument();
|
|
@@ -197,13 +240,13 @@ describe('PaceLoginPage Component', () => {
|
|
|
197
240
|
});
|
|
198
241
|
|
|
199
242
|
it('renders LoginForm component', () => {
|
|
200
|
-
|
|
243
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
201
244
|
|
|
202
245
|
expect(screen.getByTestId('login-form')).toBeInTheDocument();
|
|
203
246
|
});
|
|
204
247
|
|
|
205
248
|
it('passes correct props to LoginForm', () => {
|
|
206
|
-
|
|
249
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
207
250
|
|
|
208
251
|
// Check that LoginForm receives the app name
|
|
209
252
|
expect(screen.getByText('Sign in to Test App')).toBeInTheDocument();
|
|
@@ -215,7 +258,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
215
258
|
it('handles loading state', () => {
|
|
216
259
|
mockAuthContext.isLoading = true;
|
|
217
260
|
|
|
218
|
-
|
|
261
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
219
262
|
|
|
220
263
|
expect(screen.getByLabelText('Test App Login Page')).toBeInTheDocument();
|
|
221
264
|
});
|
|
@@ -225,7 +268,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
225
268
|
mockAuthContext.isLoading = false;
|
|
226
269
|
mockAuthContext.hasRole.mockReturnValue(false);
|
|
227
270
|
|
|
228
|
-
|
|
271
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
229
272
|
|
|
230
273
|
expect(screen.getByLabelText('Test App Login Page')).toBeInTheDocument();
|
|
231
274
|
});
|
|
@@ -233,7 +276,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
233
276
|
it('handles authentication error', () => {
|
|
234
277
|
mockAuthContext.authError = new Error('Authentication failed');
|
|
235
278
|
|
|
236
|
-
|
|
279
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
237
280
|
|
|
238
281
|
expect(screen.getByText('Authentication failed')).toBeInTheDocument();
|
|
239
282
|
expect(screen.getByText('Authentication failed')).toHaveClass('text-destructive');
|
|
@@ -243,7 +286,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
243
286
|
const errorMessage = 'Invalid credentials';
|
|
244
287
|
mockAuthContext.authError = new Error(errorMessage);
|
|
245
288
|
|
|
246
|
-
|
|
289
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
247
290
|
|
|
248
291
|
const errorElement = screen.getByText(errorMessage);
|
|
249
292
|
expect(errorElement).toBeInTheDocument();
|
|
@@ -260,7 +303,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
260
303
|
(authErr as any).code = 'session_missing';
|
|
261
304
|
mockAuthContext.authError = authErr;
|
|
262
305
|
|
|
263
|
-
|
|
306
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
264
307
|
|
|
265
308
|
expect(screen.queryByText(/Auth session missing/i)).toBeNull();
|
|
266
309
|
});
|
|
@@ -278,11 +321,13 @@ describe('PaceLoginPage Component', () => {
|
|
|
278
321
|
vi.mocked(isSuperAdmin).mockResolvedValue(true);
|
|
279
322
|
|
|
280
323
|
renderWithProviders(
|
|
281
|
-
<
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
324
|
+
<UnifiedAuthContext.Provider value={mockAuthContext as any}>
|
|
325
|
+
<PaceLoginPage
|
|
326
|
+
appName="Test App"
|
|
327
|
+
onSuccessRedirectPath="/admin"
|
|
328
|
+
requireAppAccess={true}
|
|
329
|
+
/>
|
|
330
|
+
</UnifiedAuthContext.Provider>
|
|
286
331
|
);
|
|
287
332
|
|
|
288
333
|
await waitFor(() => {
|
|
@@ -295,7 +340,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
295
340
|
mockAuthContext.isLoading = false;
|
|
296
341
|
mockAuthContext.hasRole.mockReturnValue(false);
|
|
297
342
|
|
|
298
|
-
|
|
343
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
299
344
|
|
|
300
345
|
await waitFor(() => {
|
|
301
346
|
expect(mockNavigate).not.toHaveBeenCalled();
|
|
@@ -319,10 +364,12 @@ describe('PaceLoginPage Component', () => {
|
|
|
319
364
|
console.error = vi.fn();
|
|
320
365
|
|
|
321
366
|
renderWithProviders(
|
|
322
|
-
<
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
367
|
+
<UnifiedAuthContext.Provider value={mockAuthContext as any}>
|
|
368
|
+
<PaceLoginPage
|
|
369
|
+
appName="Test App"
|
|
370
|
+
requireAppAccess={true}
|
|
371
|
+
/>
|
|
372
|
+
</UnifiedAuthContext.Provider>
|
|
326
373
|
);
|
|
327
374
|
|
|
328
375
|
await waitFor(() => {
|
|
@@ -340,7 +387,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
340
387
|
const user = userEvent.setup();
|
|
341
388
|
mockAuthContext.signIn.mockResolvedValue({ error: null });
|
|
342
389
|
|
|
343
|
-
|
|
390
|
+
renderWithAuthContext(
|
|
344
391
|
<PaceLoginPage
|
|
345
392
|
appName="Test App"
|
|
346
393
|
onSuccessRedirectPath="/dashboard"
|
|
@@ -369,7 +416,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
369
416
|
const signInError = new Error('Invalid credentials');
|
|
370
417
|
mockAuthContext.signIn.mockResolvedValue({ error: signInError });
|
|
371
418
|
|
|
372
|
-
|
|
419
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
373
420
|
|
|
374
421
|
const emailInput = screen.getByLabelText('Email');
|
|
375
422
|
const passwordInput = screen.getByLabelText('Password');
|
|
@@ -399,7 +446,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
399
446
|
|
|
400
447
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
401
448
|
|
|
402
|
-
|
|
449
|
+
renderWithAuthContext(
|
|
403
450
|
<PaceLoginPage appName="Test App" requireAppAccess={false} />,
|
|
404
451
|
{ withRouter: false }
|
|
405
452
|
);
|
|
@@ -430,7 +477,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
430
477
|
});
|
|
431
478
|
mockAuthContext.signIn.mockReturnValue(signInPromise);
|
|
432
479
|
|
|
433
|
-
|
|
480
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
434
481
|
|
|
435
482
|
const emailInput = screen.getByLabelText('Email');
|
|
436
483
|
const passwordInput = screen.getByLabelText('Password');
|
|
@@ -459,14 +506,14 @@ describe('PaceLoginPage Component', () => {
|
|
|
459
506
|
// Error handling tests
|
|
460
507
|
describe('Error Handling', () => {
|
|
461
508
|
it('handles missing app name gracefully', () => {
|
|
462
|
-
|
|
509
|
+
renderWithAuthContext(<PaceLoginPage appName="" />, { withRouter: false });
|
|
463
510
|
|
|
464
511
|
// Check that the component renders even with empty app name
|
|
465
512
|
expect(screen.getByTestId('login-form')).toBeInTheDocument();
|
|
466
513
|
});
|
|
467
514
|
|
|
468
515
|
it('handles undefined redirect path', () => {
|
|
469
|
-
|
|
516
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" onSuccessRedirectPath={undefined} />, { withRouter: false });
|
|
470
517
|
|
|
471
518
|
expect(screen.getByLabelText('Test App Login Page')).toBeInTheDocument();
|
|
472
519
|
});
|
|
@@ -478,7 +525,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
478
525
|
|
|
479
526
|
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
480
527
|
|
|
481
|
-
|
|
528
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
482
529
|
|
|
483
530
|
const emailInput = screen.getByLabelText('Email');
|
|
484
531
|
const passwordInput = screen.getByLabelText('Password');
|
|
@@ -502,14 +549,14 @@ describe('PaceLoginPage Component', () => {
|
|
|
502
549
|
// Accessibility tests
|
|
503
550
|
describe('Accessibility', () => {
|
|
504
551
|
it('has proper ARIA attributes', () => {
|
|
505
|
-
|
|
552
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
506
553
|
|
|
507
554
|
const main = screen.getByLabelText('Test App Login Page');
|
|
508
555
|
expect(main).toHaveAttribute('aria-label', 'Test App Login Page');
|
|
509
556
|
});
|
|
510
557
|
|
|
511
558
|
it('has proper semantic structure', () => {
|
|
512
|
-
|
|
559
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
513
560
|
|
|
514
561
|
expect(screen.getByLabelText('Test App Login Page')).toBeInTheDocument();
|
|
515
562
|
expect(screen.getByRole('img')).toBeInTheDocument();
|
|
@@ -517,7 +564,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
517
564
|
});
|
|
518
565
|
|
|
519
566
|
it('has accessible form elements', () => {
|
|
520
|
-
|
|
567
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
521
568
|
|
|
522
569
|
expect(screen.getByLabelText('Email')).toBeInTheDocument();
|
|
523
570
|
expect(screen.getByLabelText('Password')).toBeInTheDocument();
|
|
@@ -527,7 +574,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
527
574
|
it('announces errors to screen readers', () => {
|
|
528
575
|
mockAuthContext.authError = new Error('Authentication failed');
|
|
529
576
|
|
|
530
|
-
|
|
577
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
531
578
|
|
|
532
579
|
const errorElement = screen.getByText('Authentication failed');
|
|
533
580
|
expect(errorElement).toBeInTheDocument();
|
|
@@ -537,7 +584,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
537
584
|
// Integration tests
|
|
538
585
|
describe('Integration', () => {
|
|
539
586
|
it('integrates with LoginForm component', () => {
|
|
540
|
-
|
|
587
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
541
588
|
|
|
542
589
|
// Check that LoginForm is rendered with correct props
|
|
543
590
|
expect(screen.getByTestId('login-form')).toBeInTheDocument();
|
|
@@ -552,7 +599,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
552
599
|
});
|
|
553
600
|
mockAuthContext.signIn.mockReturnValue(signInPromise);
|
|
554
601
|
|
|
555
|
-
|
|
602
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
556
603
|
|
|
557
604
|
const emailInput = screen.getByLabelText('Email');
|
|
558
605
|
const passwordInput = screen.getByLabelText('Password');
|
|
@@ -574,21 +621,21 @@ describe('PaceLoginPage Component', () => {
|
|
|
574
621
|
// Layout and styling tests
|
|
575
622
|
describe('Layout and Styling', () => {
|
|
576
623
|
it('has correct main container classes', () => {
|
|
577
|
-
|
|
624
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
578
625
|
|
|
579
626
|
const main = screen.getByLabelText('Test App Login Page');
|
|
580
627
|
expect(main).toHaveClass('min-h-screen', 'grid', 'mx-auto', 'w-fit', 'content-center', 'justify-items-center', 'gap-y-8');
|
|
581
628
|
});
|
|
582
629
|
|
|
583
630
|
it('renders logo with correct styling', () => {
|
|
584
|
-
|
|
631
|
+
renderWithAuthContext(<PaceLoginPage appName="TestApp" />, { withRouter: false });
|
|
585
632
|
|
|
586
633
|
const logo = screen.getByAltText('TestApp logo');
|
|
587
634
|
expect(logo).toHaveClass('h-48');
|
|
588
635
|
});
|
|
589
636
|
|
|
590
637
|
it('renders LoginForm with correct width class', () => {
|
|
591
|
-
|
|
638
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
592
639
|
|
|
593
640
|
const form = screen.getByTestId('login-form');
|
|
594
641
|
expect(form.closest('.w-md')).toBeInTheDocument();
|
|
@@ -600,7 +647,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
600
647
|
it('handles empty form submission', async () => {
|
|
601
648
|
const user = userEvent.setup();
|
|
602
649
|
|
|
603
|
-
|
|
650
|
+
renderWithAuthContext(<PaceLoginPage appName="Test App" />, { withRouter: false });
|
|
604
651
|
|
|
605
652
|
const submitButton = screen.getByRole('button', { name: /sign in/i });
|
|
606
653
|
await user.click(submitButton);
|
|
@@ -612,7 +659,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
612
659
|
it('handles very long app names', () => {
|
|
613
660
|
const longAppName = 'A'.repeat(100);
|
|
614
661
|
|
|
615
|
-
|
|
662
|
+
renderWithAuthContext(<PaceLoginPage appName={longAppName} />, { withRouter: false });
|
|
616
663
|
|
|
617
664
|
expect(screen.getByLabelText(`${longAppName} Login Page`)).toBeInTheDocument();
|
|
618
665
|
});
|
|
@@ -620,7 +667,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
620
667
|
it('handles special characters in app name', () => {
|
|
621
668
|
const specialAppName = 'Test & Co. (Ltd.)';
|
|
622
669
|
|
|
623
|
-
|
|
670
|
+
renderWithAuthContext(<PaceLoginPage appName={specialAppName} />, { withRouter: false });
|
|
624
671
|
|
|
625
672
|
expect(screen.getByLabelText(`${specialAppName} Login Page`)).toBeInTheDocument();
|
|
626
673
|
});
|
|
@@ -655,7 +702,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
655
702
|
};
|
|
656
703
|
mockAuthContext.supabase = missingAppSupabase as any;
|
|
657
704
|
|
|
658
|
-
|
|
705
|
+
renderWithAuthContext(
|
|
659
706
|
<PaceLoginPage appName="Test App" requireAppAccess />
|
|
660
707
|
);
|
|
661
708
|
|
|
@@ -727,7 +774,7 @@ describe('PaceLoginPage Component', () => {
|
|
|
727
774
|
|
|
728
775
|
mockAuthContext.supabase = supabaseWithNoOrg as any;
|
|
729
776
|
|
|
730
|
-
|
|
777
|
+
renderWithAuthContext(
|
|
731
778
|
<PaceLoginPage appName="Test App" requireAppAccess />
|
|
732
779
|
);
|
|
733
780
|
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
|
|
124
124
|
import React, { useEffect, useState, useContext } from 'react';
|
|
125
125
|
import { useNavigate, useLocation } from 'react-router-dom';
|
|
126
|
-
import {
|
|
126
|
+
import { UnifiedAuthContext } from '../../providers/services/UnifiedAuthProvider';
|
|
127
127
|
import { isSuperAdmin } from '../../rbac/api';
|
|
128
128
|
import { LoginForm } from '../LoginForm';
|
|
129
129
|
import { Button, Input, Label } from '..';
|
|
@@ -172,8 +172,11 @@ export const PaceLoginPage: React.FC<PaceLoginPageProps> = ({
|
|
|
172
172
|
onSuccessRedirectPath = '/',
|
|
173
173
|
requireAppAccess = false
|
|
174
174
|
}) => {
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
// Call all hooks unconditionally at the top level
|
|
176
|
+
// Hooks must be called in the same order on every render
|
|
177
|
+
// Use useContext directly instead of useUnifiedAuth() to avoid throwing
|
|
178
|
+
// if context isn't available yet (e.g., during initial render)
|
|
179
|
+
const authContext = useContext(UnifiedAuthContext);
|
|
177
180
|
const navigate = useNavigate();
|
|
178
181
|
const location = useLocation();
|
|
179
182
|
const [isSigningIn, setIsSigningIn] = useState(false);
|
|
@@ -185,6 +188,15 @@ export const PaceLoginPage: React.FC<PaceLoginPageProps> = ({
|
|
|
185
188
|
const eventServiceContext = useContext(EventServiceContext);
|
|
186
189
|
const eventService = eventServiceContext?.eventService || null;
|
|
187
190
|
|
|
191
|
+
// Destructure auth context values with defaults to handle missing context
|
|
192
|
+
// These are used in useEffect dependency arrays, so they must always be defined
|
|
193
|
+
const signIn = authContext?.signIn || (async () => ({ error: null }));
|
|
194
|
+
const isAuthenticated = authContext?.isAuthenticated ?? false;
|
|
195
|
+
const isLoading = authContext?.isLoading ?? false;
|
|
196
|
+
const authError = authContext?.authError ?? null;
|
|
197
|
+
const user = authContext?.user ?? null;
|
|
198
|
+
const supabase = authContext?.supabase ?? null;
|
|
199
|
+
|
|
188
200
|
// Clear any active event theme when login page mounts
|
|
189
201
|
// This ensures the login screen always uses default colors
|
|
190
202
|
useEffect(() => {
|
|
@@ -356,6 +368,18 @@ export const PaceLoginPage: React.FC<PaceLoginPageProps> = ({
|
|
|
356
368
|
}
|
|
357
369
|
};
|
|
358
370
|
|
|
371
|
+
// Handle missing auth context - show loading state
|
|
372
|
+
// This check happens after all hooks are called
|
|
373
|
+
if (!authContext) {
|
|
374
|
+
return (
|
|
375
|
+
<main className="min-h-screen flex items-center justify-center">
|
|
376
|
+
<section className="text-center">
|
|
377
|
+
<p>Loading...</p>
|
|
378
|
+
</section>
|
|
379
|
+
</main>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
359
383
|
return (
|
|
360
384
|
<main className="min-h-screen grid mx-auto w-fit content-center justify-items-center gap-y-8" aria-label={`${appName} Login Page`}>
|
|
361
385
|
<img
|
|
@@ -266,6 +266,67 @@ describe('PasswordChangeForm', () => {
|
|
|
266
266
|
|
|
267
267
|
expect(mockOnSubmit).not.toHaveBeenCalled();
|
|
268
268
|
});
|
|
269
|
+
|
|
270
|
+
it('calls onSuccess callback when password change succeeds', async () => {
|
|
271
|
+
const user = userEvent.setup();
|
|
272
|
+
const mockOnSuccess = vi.fn();
|
|
273
|
+
mockOnSubmit.mockResolvedValue({});
|
|
274
|
+
|
|
275
|
+
render(<PasswordChangeForm {...baseProps} onSuccess={mockOnSuccess} />);
|
|
276
|
+
|
|
277
|
+
const newPasswordInput = screen.getByLabelText('New Password');
|
|
278
|
+
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
279
|
+
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
280
|
+
|
|
281
|
+
await user.type(newPasswordInput, 'newpassword123');
|
|
282
|
+
await user.type(confirmPasswordInput, 'newpassword123');
|
|
283
|
+
await user.click(submitButton);
|
|
284
|
+
|
|
285
|
+
await waitFor(() => {
|
|
286
|
+
expect(mockOnSuccess).toHaveBeenCalledTimes(1);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('does not call onSuccess callback when password change fails', async () => {
|
|
291
|
+
const user = userEvent.setup();
|
|
292
|
+
const mockOnSuccess = vi.fn();
|
|
293
|
+
mockOnSubmit.mockResolvedValue({ error: { message: 'Password too weak' } });
|
|
294
|
+
|
|
295
|
+
render(<PasswordChangeForm {...baseProps} onSuccess={mockOnSuccess} />);
|
|
296
|
+
|
|
297
|
+
const newPasswordInput = screen.getByLabelText('New Password');
|
|
298
|
+
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
299
|
+
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
300
|
+
|
|
301
|
+
await user.type(newPasswordInput, 'newpassword123');
|
|
302
|
+
await user.type(confirmPasswordInput, 'newpassword123');
|
|
303
|
+
await user.click(submitButton);
|
|
304
|
+
|
|
305
|
+
await waitFor(() => {
|
|
306
|
+
expect(screen.getByText('Password too weak')).toBeInTheDocument();
|
|
307
|
+
expect(mockOnSuccess).not.toHaveBeenCalled();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('works without onSuccess callback (backward compatibility)', async () => {
|
|
312
|
+
const user = userEvent.setup();
|
|
313
|
+
mockOnSubmit.mockResolvedValue({});
|
|
314
|
+
|
|
315
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
316
|
+
|
|
317
|
+
const newPasswordInput = screen.getByLabelText('New Password');
|
|
318
|
+
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
319
|
+
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
320
|
+
|
|
321
|
+
await user.type(newPasswordInput, 'newpassword123');
|
|
322
|
+
await user.type(confirmPasswordInput, 'newpassword123');
|
|
323
|
+
await user.click(submitButton);
|
|
324
|
+
|
|
325
|
+
await waitFor(() => {
|
|
326
|
+
expect(mockOnSubmit).toHaveBeenCalled();
|
|
327
|
+
});
|
|
328
|
+
// Should not throw error when onSuccess is not provided
|
|
329
|
+
});
|
|
269
330
|
});
|
|
270
331
|
|
|
271
332
|
describe('Loading States', () => {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* }}
|
|
48
48
|
* />
|
|
49
49
|
*
|
|
50
|
-
* // Password change form in a modal
|
|
50
|
+
* // Password change form in a modal with onSuccess callback
|
|
51
51
|
* <Modal isOpen={showPasswordChange} onClose={() => setShowPasswordChange(false)}>
|
|
52
52
|
* <ModalContent>
|
|
53
53
|
* <ModalHeader>
|
|
@@ -58,13 +58,15 @@
|
|
|
58
58
|
* onSubmit={async (values) => {
|
|
59
59
|
* const result = await changePassword(values.newPassword);
|
|
60
60
|
* if (result.success) {
|
|
61
|
-
* setShowPasswordChange(false);
|
|
62
|
-
* toast.success('Password changed successfully');
|
|
63
61
|
* return {};
|
|
64
62
|
* } else {
|
|
65
63
|
* return { error: { message: result.error } };
|
|
66
64
|
* }
|
|
67
65
|
* }}
|
|
66
|
+
* onSuccess={() => {
|
|
67
|
+
* setShowPasswordChange(false);
|
|
68
|
+
* toast.success('Password changed successfully');
|
|
69
|
+
* }}
|
|
68
70
|
* />
|
|
69
71
|
* </ModalBody>
|
|
70
72
|
* </ModalContent>
|
|
@@ -124,10 +126,11 @@ export interface PasswordChangeFormError {
|
|
|
124
126
|
*/
|
|
125
127
|
export interface PasswordChangeFormProps {
|
|
126
128
|
onSubmit: (values: PasswordChangeFormValues) => Promise<{ error?: PasswordChangeFormError }>;
|
|
129
|
+
onSuccess?: () => void;
|
|
127
130
|
className?: string;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
export function PasswordChangeForm({ onSubmit, className }: PasswordChangeFormProps) {
|
|
133
|
+
export function PasswordChangeForm({ onSubmit, onSuccess, className }: PasswordChangeFormProps) {
|
|
131
134
|
const [newPassword, setNewPassword] = useState('');
|
|
132
135
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
133
136
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -151,6 +154,9 @@ export function PasswordChangeForm({ onSubmit, className }: PasswordChangeFormPr
|
|
|
151
154
|
const result = await onSubmit({ newPassword, confirmPassword });
|
|
152
155
|
if (result && result.error) {
|
|
153
156
|
setError(result.error.message || 'Failed to change password.');
|
|
157
|
+
} else {
|
|
158
|
+
// Success - call onSuccess callback
|
|
159
|
+
onSuccess?.();
|
|
154
160
|
}
|
|
155
161
|
} catch (err) {
|
|
156
162
|
const errorObj = err instanceof Error ? err : new Error('An unexpected error occurred');
|
|
@@ -163,23 +169,23 @@ export function PasswordChangeForm({ onSubmit, className }: PasswordChangeFormPr
|
|
|
163
169
|
return (
|
|
164
170
|
<form onSubmit={handleSubmit} className={cn('space-y-4', className)}>
|
|
165
171
|
{error && (
|
|
166
|
-
|
|
172
|
+
<p className="grid place-items-center text-center size-full" role="alert">
|
|
167
173
|
{error}
|
|
168
|
-
</
|
|
174
|
+
</p>
|
|
169
175
|
)}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<Input
|
|
176
|
+
<Label htmlFor="new-password" className="block mb-4">New Password
|
|
177
|
+
<Input
|
|
173
178
|
id="new-password"
|
|
174
179
|
type="password"
|
|
175
180
|
value={newPassword}
|
|
176
181
|
onChange={(e) => setNewPassword(e.target.value)}
|
|
177
182
|
required
|
|
178
183
|
disabled={isSubmitting}
|
|
184
|
+
className="mt-2"
|
|
179
185
|
/>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<Label htmlFor="confirm-password">Confirm Password
|
|
186
|
+
</Label>
|
|
187
|
+
|
|
188
|
+
<Label htmlFor="confirm-password" className="block mb-4">Confirm Password
|
|
183
189
|
<Input
|
|
184
190
|
id="confirm-password"
|
|
185
191
|
type="password"
|
|
@@ -187,8 +193,9 @@ export function PasswordChangeForm({ onSubmit, className }: PasswordChangeFormPr
|
|
|
187
193
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
188
194
|
required
|
|
189
195
|
disabled={isSubmitting}
|
|
196
|
+
className="mt-2"
|
|
190
197
|
/>
|
|
191
|
-
|
|
198
|
+
</Label>
|
|
192
199
|
<Button
|
|
193
200
|
type="submit"
|
|
194
201
|
className="w-full"
|