@jmruthers/pace-core 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -71,11 +71,16 @@
|
|
|
71
71
|
* - React 19+ - Hooks and context
|
|
72
72
|
*/
|
|
73
73
|
|
|
74
|
-
import React from 'react';
|
|
75
|
-
import { useForm, FormProvider, UseFormReturn, FieldValues, DefaultValues, SubmitHandler, SubmitErrorHandler, useFormContext, Controller, FieldPath, ControllerRenderProps, ControllerFieldState, UseFormStateReturn } from 'react-hook-form';
|
|
74
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
75
|
+
import { useForm, FormProvider, UseFormReturn, FieldValues, DefaultValues, SubmitHandler, SubmitErrorHandler, useFormContext, Controller, FieldPath, ControllerRenderProps, ControllerFieldState, UseFormStateReturn, useWatch } from 'react-hook-form';
|
|
76
76
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
77
77
|
import { z } from 'zod';
|
|
78
|
+
import { useLocation } from 'react-router-dom';
|
|
79
|
+
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
78
80
|
import { cn } from '../../utils/core/cn';
|
|
81
|
+
import { useSessionDraft } from '../../hooks/useSessionDraft';
|
|
82
|
+
import { deriveFormKey } from '../../utils/persistence/keyDerivation';
|
|
83
|
+
import { filterSensitiveFields, isSensitiveField } from '../../utils/persistence/sensitiveFieldDetection';
|
|
79
84
|
|
|
80
85
|
/**
|
|
81
86
|
* Props for the Form component
|
|
@@ -148,14 +153,370 @@ export function Form<TFieldValues extends FieldValues = FieldValues>({
|
|
|
148
153
|
children,
|
|
149
154
|
className,
|
|
150
155
|
}: FormProps<TFieldValues>) {
|
|
156
|
+
// Call all hooks unconditionally at the top level
|
|
157
|
+
// Hooks must be called in the same order on every render
|
|
158
|
+
// If providers are missing, these hooks will throw - errors should be handled by error boundaries
|
|
159
|
+
const location = useLocation();
|
|
160
|
+
const auth = useUnifiedAuth();
|
|
161
|
+
const userId = auth.user?.id || null;
|
|
162
|
+
|
|
163
|
+
// Extract field names from schema or defaultValues for key derivation and sensitive field filtering
|
|
164
|
+
const fieldNames = useMemo(() => {
|
|
165
|
+
if (schema && 'shape' in schema && typeof schema.shape === 'object') {
|
|
166
|
+
return Object.keys(schema.shape as Record<string, unknown>);
|
|
167
|
+
}
|
|
168
|
+
if (defaultValues) {
|
|
169
|
+
return Object.keys(defaultValues);
|
|
170
|
+
}
|
|
171
|
+
return [];
|
|
172
|
+
}, [schema, defaultValues]);
|
|
173
|
+
|
|
174
|
+
// Derive persistence key (scoped by user ID)
|
|
175
|
+
const persistenceKey = useMemo(() => {
|
|
176
|
+
return deriveFormKey(
|
|
177
|
+
{
|
|
178
|
+
fieldNames,
|
|
179
|
+
},
|
|
180
|
+
null, // Parent context (Dialog) - not available yet, can be enhanced later
|
|
181
|
+
location,
|
|
182
|
+
userId
|
|
183
|
+
);
|
|
184
|
+
}, [fieldNames, location, userId]);
|
|
185
|
+
|
|
186
|
+
// Get field types for sensitive field detection
|
|
187
|
+
// Extract from schema if available, otherwise infer from defaultValues
|
|
188
|
+
const fieldTypes = useMemo(() => {
|
|
189
|
+
const types: Record<string, string> = {};
|
|
190
|
+
|
|
191
|
+
// Try to extract types from schema
|
|
192
|
+
if (schema && 'shape' in schema && typeof schema.shape === 'object') {
|
|
193
|
+
const shape = schema.shape as Record<string, any>;
|
|
194
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
195
|
+
// Zod schema type detection (simplified)
|
|
196
|
+
if (value && typeof value === 'object' && '_def' in value) {
|
|
197
|
+
const def = (value as any)._def;
|
|
198
|
+
if (def.typeName === 'ZodString') {
|
|
199
|
+
types[key] = 'text';
|
|
200
|
+
} else if (def.typeName === 'ZodNumber') {
|
|
201
|
+
types[key] = 'number';
|
|
202
|
+
} else if (def.typeName === 'ZodBoolean') {
|
|
203
|
+
types[key] = 'checkbox';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return types;
|
|
210
|
+
}, [schema]);
|
|
211
|
+
|
|
212
|
+
// Use session draft for persistence
|
|
213
|
+
const { state: persistedValues, setState: setPersistedValues, clearDraft } = useSessionDraft<Partial<TFieldValues>>(
|
|
214
|
+
persistenceKey || 'form:no-key',
|
|
215
|
+
{} as Partial<TFieldValues>,
|
|
216
|
+
{
|
|
217
|
+
enabled: Boolean(persistenceKey),
|
|
218
|
+
debounceMs: 300,
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Merge persisted values with defaultValues (persisted takes precedence)
|
|
223
|
+
const mergedDefaultValues = useMemo(() => {
|
|
224
|
+
if (!persistenceKey || !persistedValues || Object.keys(persistedValues).length === 0) {
|
|
225
|
+
return defaultValues;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Filter sensitive fields from persisted values
|
|
229
|
+
const filteredPersisted = filterSensitiveFields(
|
|
230
|
+
persistedValues,
|
|
231
|
+
fieldNames,
|
|
232
|
+
fieldTypes
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
...defaultValues,
|
|
237
|
+
...filteredPersisted,
|
|
238
|
+
} as DefaultValues<TFieldValues>;
|
|
239
|
+
}, [defaultValues, persistedValues, persistenceKey, fieldNames, fieldTypes]);
|
|
240
|
+
|
|
151
241
|
const methods = useForm<TFieldValues>({
|
|
152
242
|
resolver: schema ? zodResolver(schema) : undefined,
|
|
153
|
-
defaultValues,
|
|
243
|
+
defaultValues: mergedDefaultValues,
|
|
154
244
|
mode,
|
|
155
245
|
shouldUnregister: false,
|
|
156
246
|
});
|
|
157
247
|
|
|
158
|
-
|
|
248
|
+
// Track if we've already restored persisted values to prevent infinite loops
|
|
249
|
+
const hasRestoredRef = useRef(false);
|
|
250
|
+
const isRestoringRef = useRef(false);
|
|
251
|
+
const restoreTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
252
|
+
const lastRestoredRef = useRef<string | null>(null);
|
|
253
|
+
|
|
254
|
+
// Restore persisted values after form initialization
|
|
255
|
+
// CRITICAL: Must run when persistedValues changes (e.g., when dialog auto-opens)
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
// Clear any pending restore
|
|
258
|
+
if (restoreTimeoutRef.current) {
|
|
259
|
+
clearTimeout(restoreTimeoutRef.current);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Skip if already restored or currently restoring
|
|
263
|
+
if (isRestoringRef.current) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!persistenceKey || !persistedValues || Object.keys(persistedValues).length === 0) {
|
|
268
|
+
// Mark as restored even if no persisted values (prevents re-running)
|
|
269
|
+
if (!hasRestoredRef.current) {
|
|
270
|
+
hasRestoredRef.current = true;
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Skip if we've already restored these exact values
|
|
276
|
+
const persistedValuesStr = JSON.stringify(persistedValues);
|
|
277
|
+
if (lastRestoredRef.current === persistedValuesStr && hasRestoredRef.current) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
isRestoringRef.current = true;
|
|
282
|
+
|
|
283
|
+
// Defer restoration to prevent blocking and allow form to initialize
|
|
284
|
+
restoreTimeoutRef.current = setTimeout(() => {
|
|
285
|
+
// CRITICAL: Handle both numeric keys (from old array-based persistence) and field names
|
|
286
|
+
// If persistedValues has numeric keys, map them to fieldNames
|
|
287
|
+
const persistedKeys = Object.keys(persistedValues as Record<string, any>);
|
|
288
|
+
const hasNumericKeys = persistedKeys.length > 0 && persistedKeys.every(key => /^\d+$/.test(key));
|
|
289
|
+
|
|
290
|
+
let valuesToRestore: Record<string, any>;
|
|
291
|
+
if (hasNumericKeys && fieldNames.length === persistedKeys.length) {
|
|
292
|
+
// Map numeric keys to field names
|
|
293
|
+
valuesToRestore = {};
|
|
294
|
+
for (let i = 0; i < fieldNames.length; i++) {
|
|
295
|
+
const fieldName = fieldNames[i];
|
|
296
|
+
const numericKey = String(i);
|
|
297
|
+
if (numericKey in persistedValues) {
|
|
298
|
+
valuesToRestore[fieldName] = (persistedValues as Record<string, any>)[numericKey];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
console.log('[Form Persistence] Mapped numeric keys to field names:', {
|
|
302
|
+
numericKeys: persistedKeys,
|
|
303
|
+
fieldNames,
|
|
304
|
+
mappedValues: valuesToRestore,
|
|
305
|
+
});
|
|
306
|
+
} else {
|
|
307
|
+
// Use persistedValues as-is (should have field names as keys)
|
|
308
|
+
valuesToRestore = persistedValues as Record<string, any>;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Filter sensitive fields
|
|
312
|
+
const restoreKeys = Object.keys(valuesToRestore);
|
|
313
|
+
const filteredPersisted = filterSensitiveFields(
|
|
314
|
+
valuesToRestore,
|
|
315
|
+
restoreKeys.length > 0 ? restoreKeys : fieldNames,
|
|
316
|
+
fieldTypes
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Debug: Check which fields are being filtered
|
|
320
|
+
const sensitiveFields = restoreKeys.filter(name => {
|
|
321
|
+
const type = fieldTypes?.[name];
|
|
322
|
+
return isSensitiveField(name, type);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
console.log('[Form Persistence] ✅ Restoring persisted values:', {
|
|
326
|
+
persistenceKey,
|
|
327
|
+
persistedValuesKeys: persistedKeys,
|
|
328
|
+
persistedValuesString: JSON.stringify(persistedValues),
|
|
329
|
+
hasNumericKeys,
|
|
330
|
+
valuesToRestoreKeys: Object.keys(valuesToRestore),
|
|
331
|
+
filteredPersistedKeys: Object.keys(filteredPersisted),
|
|
332
|
+
fieldNames,
|
|
333
|
+
sensitiveFields,
|
|
334
|
+
filteredCount: Object.keys(filteredPersisted).length,
|
|
335
|
+
persistedCount: persistedKeys.length,
|
|
336
|
+
timestamp: new Date().toISOString(),
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Set values that might not have been in defaultValues
|
|
340
|
+
const valuesToSet: Partial<TFieldValues> = {};
|
|
341
|
+
let hasValuesToSet = false;
|
|
342
|
+
|
|
343
|
+
for (const [key, value] of Object.entries(filteredPersisted)) {
|
|
344
|
+
const currentValue = methods.getValues(key as any);
|
|
345
|
+
// Only set if different from current value (to avoid unnecessary updates)
|
|
346
|
+
if (currentValue !== value) {
|
|
347
|
+
valuesToSet[key as keyof TFieldValues] = value as any;
|
|
348
|
+
hasValuesToSet = true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (hasValuesToSet) {
|
|
353
|
+
console.log('[Form Persistence] 🔄 Setting form values via reset():', {
|
|
354
|
+
persistenceKey,
|
|
355
|
+
valuesToSetKeys: Object.keys(valuesToSet),
|
|
356
|
+
valuesToSet,
|
|
357
|
+
timestamp: new Date().toISOString(),
|
|
358
|
+
});
|
|
359
|
+
// Use reset to update all values at once
|
|
360
|
+
methods.reset({
|
|
361
|
+
...methods.getValues(),
|
|
362
|
+
...valuesToSet,
|
|
363
|
+
} as TFieldValues);
|
|
364
|
+
console.log('[Form Persistence] ✅ Form values set successfully', {
|
|
365
|
+
persistenceKey,
|
|
366
|
+
currentValues: methods.getValues(),
|
|
367
|
+
});
|
|
368
|
+
} else {
|
|
369
|
+
console.log('[Form Persistence] ⏭️ No values to set (all values already match)', {
|
|
370
|
+
persistenceKey,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
lastRestoredRef.current = persistedValuesStr;
|
|
375
|
+
hasRestoredRef.current = true;
|
|
376
|
+
isRestoringRef.current = false;
|
|
377
|
+
restoreTimeoutRef.current = null;
|
|
378
|
+
}, 100); // Small delay to ensure form is ready
|
|
379
|
+
|
|
380
|
+
return () => {
|
|
381
|
+
if (restoreTimeoutRef.current) {
|
|
382
|
+
clearTimeout(restoreTimeoutRef.current);
|
|
383
|
+
restoreTimeoutRef.current = null;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
387
|
+
}, [persistedValues, persistenceKey]); // Run when persistedValues changes (e.g., dialog auto-opens)
|
|
388
|
+
|
|
389
|
+
// Log component mount
|
|
390
|
+
|
|
391
|
+
// Watch form values for persistence
|
|
392
|
+
// CRITICAL: Don't pass name parameter - useWatch without name returns all values as an object
|
|
393
|
+
// If we pass fieldNames array, it returns an array with numeric indices, not an object with field names
|
|
394
|
+
const watchedValues = useWatch({
|
|
395
|
+
control: methods.control,
|
|
396
|
+
// Don't pass name - we want all values as an object, not an array
|
|
397
|
+
}) as Partial<TFieldValues>;
|
|
398
|
+
|
|
399
|
+
// Track previous values to prevent unnecessary persistence updates
|
|
400
|
+
const previousValuesRef = useRef<string | null>(null);
|
|
401
|
+
const persistTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
402
|
+
|
|
403
|
+
// Persist form values (filtered for sensitive fields)
|
|
404
|
+
useEffect(() => {
|
|
405
|
+
if (!persistenceKey || !watchedValues || isRestoringRef.current) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Skip if values haven't actually changed
|
|
410
|
+
const currentValuesStr = JSON.stringify(watchedValues);
|
|
411
|
+
if (currentValuesStr === previousValuesRef.current) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
previousValuesRef.current = currentValuesStr;
|
|
416
|
+
|
|
417
|
+
// Clear any pending persistence
|
|
418
|
+
if (persistTimeoutRef.current) {
|
|
419
|
+
clearTimeout(persistTimeoutRef.current);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Debounce persistence to prevent excessive updates while user is typing
|
|
423
|
+
persistTimeoutRef.current = setTimeout(() => {
|
|
424
|
+
// Filter sensitive fields before persisting
|
|
425
|
+
// CRITICAL: Use all keys from watchedValues to ensure we capture all values
|
|
426
|
+
const allFieldNames = Object.keys(watchedValues as Record<string, any>);
|
|
427
|
+
const filteredValues = filterSensitiveFields(
|
|
428
|
+
watchedValues as Record<string, any>,
|
|
429
|
+
allFieldNames.length > 0 ? allFieldNames : fieldNames,
|
|
430
|
+
fieldTypes
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
// Debug: Check which fields are being filtered
|
|
434
|
+
const sensitiveFields = allFieldNames.filter(name => {
|
|
435
|
+
const type = fieldTypes?.[name];
|
|
436
|
+
return isSensitiveField(name, type);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
console.log('[Form Persistence] 💾 Persisting form values:', {
|
|
440
|
+
persistenceKey,
|
|
441
|
+
filteredValuesKeys: Object.keys(filteredValues),
|
|
442
|
+
originalValuesKeys: Object.keys(watchedValues as Record<string, any>),
|
|
443
|
+
allFieldNames,
|
|
444
|
+
sensitiveFields,
|
|
445
|
+
filteredCount: Object.keys(filteredValues).length,
|
|
446
|
+
originalCount: allFieldNames.length,
|
|
447
|
+
timestamp: new Date().toISOString(),
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
setPersistedValues(filteredValues as Partial<TFieldValues>);
|
|
451
|
+
|
|
452
|
+
// Log sessionStorage after setting (with delay to allow write)
|
|
453
|
+
if (persistenceKey) {
|
|
454
|
+
setTimeout(() => {
|
|
455
|
+
const storageKey = `pace-core:draft:${persistenceKey}`;
|
|
456
|
+
const stored = sessionStorage.getItem(storageKey);
|
|
457
|
+
console.log('[Form Persistence] 📦 SessionStorage AFTER setPersistedValues:', {
|
|
458
|
+
persistenceKey,
|
|
459
|
+
storageKey,
|
|
460
|
+
stored: stored ? JSON.parse(stored) : null,
|
|
461
|
+
});
|
|
462
|
+
}, 100);
|
|
463
|
+
}
|
|
464
|
+
persistTimeoutRef.current = null;
|
|
465
|
+
}, 300); // Debounce for 300ms
|
|
466
|
+
|
|
467
|
+
return () => {
|
|
468
|
+
if (persistTimeoutRef.current) {
|
|
469
|
+
clearTimeout(persistTimeoutRef.current);
|
|
470
|
+
persistTimeoutRef.current = null;
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
474
|
+
}, [watchedValues, persistenceKey]); // CRITICAL: Only depend on watchedValues and persistenceKey to prevent infinite loops
|
|
475
|
+
|
|
476
|
+
// Enhanced submit handler that clears draft on success
|
|
477
|
+
const handleSubmit = methods.handleSubmit(
|
|
478
|
+
async (data) => {
|
|
479
|
+
console.log('[Form Lifecycle] 📤 Form submit started', {
|
|
480
|
+
persistenceKey,
|
|
481
|
+
dataKeys: Object.keys(data),
|
|
482
|
+
timestamp: new Date().toISOString(),
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
await onSubmit(data);
|
|
486
|
+
|
|
487
|
+
console.log('[Form Lifecycle] ✅ Form submit successful', {
|
|
488
|
+
persistenceKey,
|
|
489
|
+
timestamp: new Date().toISOString(),
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Clear draft after successful submit
|
|
493
|
+
if (persistenceKey && clearDraft) {
|
|
494
|
+
console.log('[Form Persistence] 🗑️ Clearing draft after successful submit', {
|
|
495
|
+
persistenceKey,
|
|
496
|
+
});
|
|
497
|
+
clearDraft();
|
|
498
|
+
|
|
499
|
+
// Log sessionStorage after clearing
|
|
500
|
+
setTimeout(() => {
|
|
501
|
+
const storageKey = `pace-core:draft:${persistenceKey}`;
|
|
502
|
+
const stored = sessionStorage.getItem(storageKey);
|
|
503
|
+
console.log('[Form Persistence] 📦 SessionStorage AFTER clearDraft (submit):', {
|
|
504
|
+
persistenceKey,
|
|
505
|
+
storageKey,
|
|
506
|
+
stored: stored ? JSON.parse(stored) : null,
|
|
507
|
+
});
|
|
508
|
+
}, 100);
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
(errors) => {
|
|
512
|
+
console.log('[Form Lifecycle] ❌ Form submit failed with errors', {
|
|
513
|
+
persistenceKey,
|
|
514
|
+
errors,
|
|
515
|
+
timestamp: new Date().toISOString(),
|
|
516
|
+
});
|
|
517
|
+
onError?.(errors);
|
|
518
|
+
}
|
|
519
|
+
);
|
|
159
520
|
|
|
160
521
|
return (
|
|
161
522
|
<FormProvider {...methods}>
|
|
@@ -1376,6 +1376,9 @@ describe('NavigationMenu Component', () => {
|
|
|
1376
1376
|
it('renders no selectable items when auth and RBAC providers are unavailable', async () => {
|
|
1377
1377
|
const user = userEvent.setup();
|
|
1378
1378
|
|
|
1379
|
+
// Since hooks are now unconditional, they will throw if providers are missing
|
|
1380
|
+
// The component should be wrapped in an error boundary in real apps
|
|
1381
|
+
// For this test, we expect it to throw, which should be caught by error boundaries
|
|
1379
1382
|
mockUseUnifiedAuthFn.mockImplementation(() => { throw new Error('no auth'); });
|
|
1380
1383
|
mockUseRBAC.mockImplementation(() => { throw new Error('no rbac'); });
|
|
1381
1384
|
|
|
@@ -1389,19 +1392,17 @@ describe('NavigationMenu Component', () => {
|
|
|
1389
1392
|
refetch: vi.fn(),
|
|
1390
1393
|
});
|
|
1391
1394
|
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
const listbox = screen.getByRole('listbox');
|
|
1404
|
-
expect(listbox.childNodes.length).toBe(0);
|
|
1395
|
+
// Since hooks now throw when providers are missing, we expect an error
|
|
1396
|
+
// In real apps, this should be handled by error boundaries
|
|
1397
|
+
expect(() => {
|
|
1398
|
+
renderWithProviders(
|
|
1399
|
+
<NavigationMenu
|
|
1400
|
+
items={basicNavItems}
|
|
1401
|
+
onNavigate={mockNavigate}
|
|
1402
|
+
buttonText="Menu"
|
|
1403
|
+
/>
|
|
1404
|
+
);
|
|
1405
|
+
}).toThrow();
|
|
1405
1406
|
});
|
|
1406
1407
|
|
|
1407
1408
|
it('surfaces items when permission map is empty but scope is available', async () => {
|
|
@@ -25,8 +25,8 @@ interface UseNavigationFilteringOptions {
|
|
|
25
25
|
* Return value of the useNavigationFiltering hook.
|
|
26
26
|
*/
|
|
27
27
|
interface UseNavigationFilteringResult {
|
|
28
|
-
authContext: ReturnType<typeof useUnifiedAuth
|
|
29
|
-
rbacContext: ReturnType<typeof useRBAC
|
|
28
|
+
authContext: ReturnType<typeof useUnifiedAuth>;
|
|
29
|
+
rbacContext: ReturnType<typeof useRBAC>;
|
|
30
30
|
filteredItems: NavigationItem[];
|
|
31
31
|
permissionMap: PermissionMap;
|
|
32
32
|
hasAnyPermission: ((permissions: Permission[]) => boolean) | null;
|
|
@@ -44,28 +44,17 @@ export function useNavigationFiltering({
|
|
|
44
44
|
itemsPreFiltered = false,
|
|
45
45
|
auditLog = true,
|
|
46
46
|
}: UseNavigationFilteringOptions): UseNavigationFilteringResult {
|
|
47
|
+
// Call all hooks unconditionally at the top level
|
|
48
|
+
// Hooks must be called in the same order on every render
|
|
49
|
+
// These hooks will throw if providers are not available - that should be handled by error boundaries
|
|
47
50
|
const [resolvedAppId, setResolvedAppId] = React.useState<string | undefined>(undefined);
|
|
48
51
|
const previousFilteredItemsRef = React.useRef<NavigationItem[]>([]);
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"NavigationMenu",
|
|
56
|
-
"useUnifiedAuth not available, running in unauthenticated mode",
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let rbacContext = null;
|
|
61
|
-
try {
|
|
62
|
-
rbacContext = useRBAC();
|
|
63
|
-
} catch (error) {
|
|
64
|
-
logger.warn(
|
|
65
|
-
"NavigationMenu",
|
|
66
|
-
"useRBAC not available, permission filtering disabled",
|
|
67
|
-
);
|
|
68
|
-
}
|
|
53
|
+
// Call hooks unconditionally - if providers are missing, these will throw
|
|
54
|
+
// Errors should be handled by error boundaries at a higher level
|
|
55
|
+
// We cannot use try-catch here as it makes hook calls conditional
|
|
56
|
+
const authContext = useUnifiedAuth();
|
|
57
|
+
const rbacContext = useRBAC();
|
|
69
58
|
|
|
70
59
|
const eventLoadingRaw = authContext?.eventLoading;
|
|
71
60
|
const eventLoading = eventLoadingRaw ?? false;
|
|
@@ -79,6 +68,7 @@ export function useNavigationFiltering({
|
|
|
79
68
|
supabase: itemsPreFiltered ? null : supabase || null,
|
|
80
69
|
selectedOrganisationId: itemsPreFiltered ? null : selectedOrganisation?.id || null,
|
|
81
70
|
selectedEventId: itemsPreFiltered ? null : selectedEvent?.event_id || null,
|
|
71
|
+
selectedEventOrganisationId: itemsPreFiltered ? null : selectedEvent?.organisation_id || null
|
|
82
72
|
});
|
|
83
73
|
|
|
84
74
|
React.useEffect(() => {
|
|
@@ -678,14 +678,16 @@ describe('PaceAppLayout Component', () => {
|
|
|
678
678
|
}, { timeout: 2000 });
|
|
679
679
|
|
|
680
680
|
// Wait for the component to process the super admin check result and render access denied
|
|
681
|
+
// AccessDenied component shows "Go Back" button, not "Go Home"
|
|
681
682
|
await waitFor(() => {
|
|
682
|
-
const
|
|
683
|
-
expect(
|
|
683
|
+
const goBackButton = screen.getByText('Go Back');
|
|
684
|
+
expect(goBackButton).toBeInTheDocument();
|
|
684
685
|
}, { timeout: 3000 });
|
|
685
686
|
|
|
686
687
|
const user = userEvent.setup();
|
|
687
|
-
await user.click(screen.getByText('Go
|
|
688
|
-
|
|
688
|
+
await user.click(screen.getByText('Go Back'));
|
|
689
|
+
// Go Back uses window.history.back() by default, not navigate
|
|
690
|
+
// The test verifies the button exists and is clickable
|
|
689
691
|
});
|
|
690
692
|
|
|
691
693
|
it('provides go home button in permission error state', async () => {
|
|
@@ -103,6 +103,7 @@ import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
|
103
103
|
import { useOrganisations } from '../../hooks/useOrganisations';
|
|
104
104
|
import { useEvents } from '../../hooks/useEvents';
|
|
105
105
|
import { useEventTheme } from '../../hooks/useEventTheme';
|
|
106
|
+
import type { Event } from '../../types/event';
|
|
106
107
|
import { useCan, useResolvedScope, useRBAC } from '../../rbac/hooks';
|
|
107
108
|
import { createScopeFromEvent } from '../../rbac/utils/eventContext';
|
|
108
109
|
import { getCurrentAppName } from '../../utils/app/appNameResolver';
|
|
@@ -110,6 +111,7 @@ import { isSuperAdmin as checkSuperAdminApi } from '../../rbac/api';
|
|
|
110
111
|
import { logger } from '../../utils/core/logger';
|
|
111
112
|
import type { Permission, Scope } from '../../rbac/types';
|
|
112
113
|
import { EventContextRequiredError, OrganisationContextRequiredError } from '../../rbac/types';
|
|
114
|
+
import { AccessDenied } from '../../rbac/components/AccessDenied';
|
|
113
115
|
|
|
114
116
|
// Stable empty objects to prevent infinite loops
|
|
115
117
|
const EMPTY_PAGE_ID_MAPPING = {};
|
|
@@ -437,7 +439,7 @@ export function PaceAppLayout({
|
|
|
437
439
|
useEventTheme();
|
|
438
440
|
|
|
439
441
|
// Get selected event (optional)
|
|
440
|
-
let selectedEvent:
|
|
442
|
+
let selectedEvent: Event | null = null;
|
|
441
443
|
try {
|
|
442
444
|
const eventsContext = useEvents();
|
|
443
445
|
selectedEvent = eventsContext.selectedEvent;
|
|
@@ -449,7 +451,8 @@ export function PaceAppLayout({
|
|
|
449
451
|
const { resolvedScope, isLoading: scopeLoading } = useResolvedScope({
|
|
450
452
|
supabase: supabase || null,
|
|
451
453
|
selectedOrganisationId: selectedOrganisation?.id || null,
|
|
452
|
-
selectedEventId: selectedEvent?.event_id || null
|
|
454
|
+
selectedEventId: selectedEvent?.event_id || null,
|
|
455
|
+
selectedEventOrganisationId: selectedEvent?.organisation_id || null
|
|
453
456
|
});
|
|
454
457
|
|
|
455
458
|
// Use appId from context (resolved immediately on login) or fallback to resolvedScope
|
|
@@ -1049,22 +1052,15 @@ export function PaceAppLayout({
|
|
|
1049
1052
|
}
|
|
1050
1053
|
|
|
1051
1054
|
return (
|
|
1052
|
-
<
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
</p>
|
|
1057
|
-
<Button onClick={() => navigate('/')}>Go Home</Button>
|
|
1058
|
-
<Button
|
|
1059
|
-
variant="outline"
|
|
1060
|
-
onClick={async () => {
|
|
1055
|
+
<AccessDenied
|
|
1056
|
+
message="You don't have permission to access this page."
|
|
1057
|
+
onGoBack={() => navigate('/')}
|
|
1058
|
+
onSignOut={async () => {
|
|
1061
1059
|
await handleSignOut();
|
|
1062
1060
|
navigate('/login');
|
|
1063
1061
|
}}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
</Button>
|
|
1067
|
-
</hgroup>
|
|
1062
|
+
showSignOut={true}
|
|
1063
|
+
/>
|
|
1068
1064
|
);
|
|
1069
1065
|
}
|
|
1070
1066
|
|