@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.
Files changed (387) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/core-usage-manifest.json +93 -0
  4. package/cursor-rules/00-pace-core-compliance.mdc +128 -26
  5. package/cursor-rules/01-standards-compliance.mdc +49 -8
  6. package/cursor-rules/02-project-structure.mdc +6 -0
  7. package/cursor-rules/03-solid-principles.mdc +2 -0
  8. package/cursor-rules/04-testing-standards.mdc +2 -0
  9. package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
  10. package/cursor-rules/06-code-quality.mdc +2 -0
  11. package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
  12. package/cursor-rules/08-markup-quality.mdc +52 -27
  13. package/cursor-rules/09-rbac-compliance.mdc +462 -0
  14. package/cursor-rules/10-error-handling-patterns.mdc +179 -0
  15. package/cursor-rules/11-performance-optimization.mdc +169 -0
  16. package/cursor-rules/12-ci-cd-integration.mdc +150 -0
  17. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  18. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
  19. package/dist/DataTable-LRJL4IRV.js +15 -0
  20. package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
  21. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  22. package/dist/api-Y4MQWOFW.js +4 -0
  23. package/dist/audit-MYQXYZFU.js +3 -0
  24. package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
  25. package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
  26. package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
  27. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  28. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  29. package/dist/chunk-4T7OBVTU.js +62 -0
  30. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  31. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  32. package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
  33. package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
  34. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  35. package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
  36. package/dist/chunk-C7NSAPTL.js +1 -0
  37. package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
  38. package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
  39. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  40. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  41. package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
  42. package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
  43. package/dist/chunk-MBADTM7L.js +64 -0
  44. package/dist/chunk-OHIK3MIO.js +994 -0
  45. package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
  46. package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
  47. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  48. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  49. package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
  50. package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
  51. package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
  52. package/dist/components.d.ts +6 -6
  53. package/dist/components.js +57 -267
  54. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  55. package/dist/eslint-rules/index.cjs +22 -0
  56. package/dist/eslint-rules/rules/compliance.cjs +348 -0
  57. package/dist/eslint-rules/rules/components.cjs +113 -0
  58. package/dist/eslint-rules/rules/imports.cjs +102 -0
  59. package/dist/eslint-rules/rules/rbac.cjs +790 -0
  60. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  61. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  62. package/dist/hooks.d.ts +5 -5
  63. package/dist/hooks.js +62 -270
  64. package/dist/icons/index.d.ts +1 -0
  65. package/dist/icons/index.js +1 -0
  66. package/dist/index.d.ts +36 -26
  67. package/dist/index.js +87 -690
  68. package/dist/providers.d.ts +2 -2
  69. package/dist/providers.js +8 -35
  70. package/dist/rbac/eslint-rules.d.ts +46 -44
  71. package/dist/rbac/eslint-rules.js +7 -4
  72. package/dist/rbac/index.d.ts +124 -594
  73. package/dist/rbac/index.js +14 -207
  74. package/dist/styles/index.js +2 -12
  75. package/dist/theming/runtime.js +3 -19
  76. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  77. package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
  78. package/dist/types-t9H8qKRw.d.ts +55 -0
  79. package/dist/types.d.ts +1 -1
  80. package/dist/types.js +7 -94
  81. package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
  82. package/dist/utils.d.ts +24 -117
  83. package/dist/utils.js +54 -392
  84. package/docs/README.md +16 -6
  85. package/docs/api/README.md +4 -402
  86. package/docs/api/modules.md +454 -930
  87. package/docs/api-reference/components.md +3 -1
  88. package/docs/api-reference/deprecated.md +31 -6
  89. package/docs/api-reference/rpc-functions.md +78 -3
  90. package/docs/best-practices/accessibility.md +6 -3
  91. package/docs/getting-started/cursor-rules.md +3 -23
  92. package/docs/getting-started/dependencies.md +650 -0
  93. package/docs/getting-started/installation-guide.md +20 -7
  94. package/docs/getting-started/quick-start.md +23 -12
  95. package/docs/implementation-guides/permission-enforcement.md +4 -0
  96. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  97. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  98. package/docs/rbac/README.md +12 -3
  99. package/docs/rbac/edge-functions-guide.md +376 -0
  100. package/docs/rbac/secure-client-protection.md +0 -34
  101. package/docs/standards/00-pace-core-compliance.md +967 -0
  102. package/docs/standards/01-standards-compliance.md +188 -0
  103. package/docs/standards/02-project-structure.md +985 -0
  104. package/docs/standards/03-solid-principles.md +39 -0
  105. package/docs/standards/04-testing-standards.md +36 -0
  106. package/docs/standards/05-bug-reports-and-features.md +27 -0
  107. package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
  108. package/docs/standards/07-tech-stack-compliance.md +30 -0
  109. package/docs/standards/08-markup-quality.md +345 -0
  110. package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
  111. package/docs/standards/10-error-handling-patterns.md +401 -0
  112. package/docs/standards/11-performance-optimization.md +348 -0
  113. package/docs/standards/12-ci-cd-integration.md +370 -0
  114. package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
  115. package/docs/standards/README.md +62 -33
  116. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  117. package/eslint-config-pace-core.cjs +20 -4
  118. package/package.json +31 -21
  119. package/scripts/audit/audit-compliance.cjs +1295 -0
  120. package/scripts/audit/audit-components.cjs +260 -0
  121. package/scripts/audit/audit-dependencies.cjs +395 -0
  122. package/scripts/audit/audit-rbac.cjs +954 -0
  123. package/scripts/audit/audit-standards.cjs +1268 -0
  124. package/scripts/audit/index.cjs +1898 -194
  125. package/scripts/install-cursor-rules.cjs +259 -8
  126. package/scripts/validate-master.js +1 -1
  127. package/src/__tests__/fixtures/supabase.ts +1 -1
  128. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
  129. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  130. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  131. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
  132. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  133. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  134. package/src/__tests__/public-recipe-view.test.ts +38 -9
  135. package/src/components/Button/Button.tsx +5 -1
  136. package/src/components/ContextSelector/ContextSelector.tsx +42 -39
  137. package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
  138. package/src/components/DataTable/components/DataTableBody.tsx +55 -31
  139. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  140. package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
  141. package/src/components/DataTable/components/EditFields.tsx +23 -3
  142. package/src/components/DataTable/components/EditableRow.tsx +7 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +4 -6
  144. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  145. package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
  146. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
  147. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
  148. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  149. package/src/components/DataTable/core/DataTableContext.tsx +1 -1
  150. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  151. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  152. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  153. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  154. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  155. package/src/components/DataTable/types.ts +5 -0
  156. package/src/components/DateTimeField/DateTimeField.tsx +20 -20
  157. package/src/components/DateTimeField/README.md +5 -2
  158. package/src/components/Dialog/Dialog.test.tsx +361 -318
  159. package/src/components/Dialog/Dialog.tsx +1154 -323
  160. package/src/components/Dialog/index.ts +3 -3
  161. package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
  162. package/src/components/FileDisplay/FileDisplay.tsx +28 -22
  163. package/src/components/Form/Form.test.tsx +9 -10
  164. package/src/components/Form/Form.tsx +369 -9
  165. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
  166. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
  167. package/src/components/LoginForm/LoginForm.tsx +2 -2
  168. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  169. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
  170. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  171. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  172. package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
  173. package/src/components/PaceAppLayout/README.md +10 -9
  174. package/src/components/PaceAppLayout/test-setup.tsx +40 -31
  175. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  176. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  177. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
  178. package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
  179. package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
  180. package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
  181. package/src/components/Select/Select.tsx +23 -21
  182. package/src/components/Select/types.ts +1 -1
  183. package/src/components/UserMenu/UserMenu.test.tsx +38 -6
  184. package/src/components/UserMenu/UserMenu.tsx +39 -34
  185. package/src/components/index.ts +3 -4
  186. package/src/eslint-rules/index.cjs +22 -0
  187. package/src/eslint-rules/rules/compliance.cjs +348 -0
  188. package/src/eslint-rules/rules/components.cjs +113 -0
  189. package/src/eslint-rules/rules/imports.cjs +102 -0
  190. package/src/eslint-rules/rules/rbac.cjs +790 -0
  191. package/src/eslint-rules/utils/helpers.cjs +42 -0
  192. package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
  193. package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
  194. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  195. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  196. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  197. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  198. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  199. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  200. package/src/hooks/public/usePublicEvent.ts +62 -190
  201. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  202. package/src/hooks/public/usePublicEventLogo.ts +19 -9
  203. package/src/hooks/useAppConfig.ts +26 -24
  204. package/src/hooks/useEventTheme.test.ts +211 -233
  205. package/src/hooks/useEventTheme.ts +19 -28
  206. package/src/hooks/useEvents.ts +11 -7
  207. package/src/hooks/useKeyboardShortcuts.ts +1 -1
  208. package/src/hooks/useOrganisationPermissions.ts +9 -11
  209. package/src/hooks/useOrganisations.ts +13 -7
  210. package/src/hooks/useQueryCache.ts +0 -1
  211. package/src/hooks/useSessionDraft.ts +380 -0
  212. package/src/hooks/useSessionRestoration.ts +3 -1
  213. package/src/icons/index.ts +27 -0
  214. package/src/index.ts +16 -1
  215. package/src/providers/OrganisationProvider.tsx +23 -14
  216. package/src/providers/services/EventServiceProvider.tsx +1 -24
  217. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  218. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
  219. package/src/rbac/README.md +20 -20
  220. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  221. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  222. package/src/rbac/adapters.tsx +7 -295
  223. package/src/rbac/api.test.ts +44 -56
  224. package/src/rbac/api.ts +10 -17
  225. package/src/rbac/cache-invalidation.ts +0 -1
  226. package/src/rbac/compliance/index.ts +10 -0
  227. package/src/rbac/compliance/pattern-detector.ts +553 -0
  228. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  229. package/src/rbac/components/AccessDenied.tsx +150 -0
  230. package/src/rbac/components/NavigationGuard.tsx +12 -20
  231. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  232. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  233. package/src/rbac/components/index.ts +3 -41
  234. package/src/rbac/eslint-rules.js +1 -1
  235. package/src/rbac/hooks/index.ts +0 -3
  236. package/src/rbac/hooks/permissions/index.ts +0 -3
  237. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  238. package/src/rbac/hooks/usePermissions.ts +0 -3
  239. package/src/rbac/hooks/useRBAC.test.ts +21 -3
  240. package/src/rbac/hooks/useRBAC.ts +4 -3
  241. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  242. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  243. package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
  244. package/src/rbac/hooks/useResourcePermissions.ts +182 -63
  245. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  246. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  247. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  248. package/src/rbac/index.ts +7 -9
  249. package/src/rbac/permissions.ts +17 -17
  250. package/src/rbac/utils/contextValidator.ts +45 -7
  251. package/src/services/AuthService.ts +132 -23
  252. package/src/services/EventService.ts +4 -97
  253. package/src/services/InactivityService.ts +155 -58
  254. package/src/services/OrganisationService.ts +7 -44
  255. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  256. package/src/services/base/BaseService.ts +0 -3
  257. package/src/styles/core.css +4 -0
  258. package/src/types/database.generated.ts +4733 -3809
  259. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  260. package/src/utils/context/organisationContext.test.ts +13 -28
  261. package/src/utils/context/organisationContext.ts +21 -52
  262. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  263. package/src/utils/file-reference/index.ts +39 -15
  264. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  265. package/src/utils/formatting/formatTime.test.ts +3 -2
  266. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  267. package/src/utils/index.ts +4 -1
  268. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  269. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  270. package/src/utils/persistence/keyDerivation.ts +304 -0
  271. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  272. package/src/utils/security/secureStorage.ts +5 -5
  273. package/src/utils/storage/helpers.ts +3 -3
  274. package/src/utils/supabase/createBaseClient.ts +147 -0
  275. package/src/utils/timezone/timezone.test.ts +1 -2
  276. package/src/utils/timezone/timezone.ts +1 -1
  277. package/src/utils/validation/csrf.ts +4 -4
  278. package/cursor-rules/CHANGELOG.md +0 -119
  279. package/cursor-rules/README.md +0 -192
  280. package/dist/DataTable-E7YQZD7D.js +0 -175
  281. package/dist/DataTable-E7YQZD7D.js.map +0 -1
  282. package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
  283. package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
  284. package/dist/api-6LVZTHDS.js +0 -52
  285. package/dist/api-6LVZTHDS.js.map +0 -1
  286. package/dist/audit-V53FV5AG.js +0 -17
  287. package/dist/audit-V53FV5AG.js.map +0 -1
  288. package/dist/chunk-36LVWXB2.js +0 -227
  289. package/dist/chunk-36LVWXB2.js.map +0 -1
  290. package/dist/chunk-3LPHPB62.js.map +0 -1
  291. package/dist/chunk-5DRSZLL2.js.map +0 -1
  292. package/dist/chunk-5EC5MEWX.js.map +0 -1
  293. package/dist/chunk-63FOKYGO.js.map +0 -1
  294. package/dist/chunk-6SOIHG6Z.js.map +0 -1
  295. package/dist/chunk-7JPAB3T5.js.map +0 -1
  296. package/dist/chunk-ATKZM7RX.js +0 -2053
  297. package/dist/chunk-ATKZM7RX.js.map +0 -1
  298. package/dist/chunk-AVMLPIM7.js.map +0 -1
  299. package/dist/chunk-DGUM43GV.js.map +0 -1
  300. package/dist/chunk-E66EQZE6.js.map +0 -1
  301. package/dist/chunk-FFQEQTNW.js.map +0 -1
  302. package/dist/chunk-FMUCXFII.js.map +0 -1
  303. package/dist/chunk-G37KK66H.js.map +0 -1
  304. package/dist/chunk-I6DAQMWX.js.map +0 -1
  305. package/dist/chunk-J36DSWQK.js.map +0 -1
  306. package/dist/chunk-KQCRWDSA.js +0 -1
  307. package/dist/chunk-KQCRWDSA.js.map +0 -1
  308. package/dist/chunk-L4OXEN46.js.map +0 -1
  309. package/dist/chunk-LMC26NLJ.js +0 -84
  310. package/dist/chunk-LMC26NLJ.js.map +0 -1
  311. package/dist/chunk-M43Y4SSO.js.map +0 -1
  312. package/dist/chunk-M7MPQISP.js.map +0 -1
  313. package/dist/chunk-NN6WWZ5U.js.map +0 -1
  314. package/dist/chunk-OEWDTMG7.js.map +0 -1
  315. package/dist/chunk-PWLANIRT.js.map +0 -1
  316. package/dist/chunk-QXHPKYJV.js.map +0 -1
  317. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  318. package/dist/chunk-YKRAFF5K.js.map +0 -1
  319. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  320. package/dist/components.js.map +0 -1
  321. package/dist/contextValidator-OOPCLPZW.js +0 -9
  322. package/dist/contextValidator-OOPCLPZW.js.map +0 -1
  323. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  324. package/dist/hooks.js.map +0 -1
  325. package/dist/index.js.map +0 -1
  326. package/dist/providers.js.map +0 -1
  327. package/dist/rbac/eslint-rules.js.map +0 -1
  328. package/dist/rbac/index.js.map +0 -1
  329. package/dist/styles/index.js.map +0 -1
  330. package/dist/theming/runtime.js.map +0 -1
  331. package/dist/types.js.map +0 -1
  332. package/dist/utils.js.map +0 -1
  333. package/docs/standards/01-architecture-standard.md +0 -44
  334. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  335. package/docs/standards/03-component-standard.md +0 -32
  336. package/docs/standards/05-security-standard.md +0 -44
  337. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  338. package/docs/standards/pace-core-compliance.md +0 -432
  339. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  340. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  341. package/scripts/audit/core/checks/bundle.cjs +0 -142
  342. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  343. package/scripts/audit/core/checks/config.cjs +0 -54
  344. package/scripts/audit/core/checks/coverage.cjs +0 -84
  345. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  346. package/scripts/audit/core/checks/documentation.cjs +0 -268
  347. package/scripts/audit/core/checks/environment.cjs +0 -116
  348. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  349. package/scripts/audit/core/checks/forms.cjs +0 -172
  350. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  351. package/scripts/audit/core/checks/hooks.cjs +0 -334
  352. package/scripts/audit/core/checks/imports.cjs +0 -244
  353. package/scripts/audit/core/checks/performance.cjs +0 -325
  354. package/scripts/audit/core/checks/routes.cjs +0 -117
  355. package/scripts/audit/core/checks/state.cjs +0 -130
  356. package/scripts/audit/core/checks/structure.cjs +0 -65
  357. package/scripts/audit/core/checks/style.cjs +0 -584
  358. package/scripts/audit/core/checks/testing.cjs +0 -122
  359. package/scripts/audit/core/checks/typescript.cjs +0 -61
  360. package/scripts/audit/core/scanner.cjs +0 -199
  361. package/scripts/audit/core/utils.cjs +0 -137
  362. package/scripts/audit/reporters/console.cjs +0 -151
  363. package/scripts/audit/reporters/json.cjs +0 -54
  364. package/scripts/audit/reporters/markdown.cjs +0 -124
  365. package/scripts/audit-consuming-app.cjs +0 -86
  366. package/src/eslint-rules/pace-core-compliance.cjs +0 -510
  367. package/src/eslint-rules/pace-core-compliance.js +0 -638
  368. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  369. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  370. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  371. package/src/rbac/components/NavigationProvider.tsx +0 -345
  372. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  373. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  374. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  375. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  376. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  377. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  378. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  379. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  380. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  381. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  382. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  383. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  384. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  385. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  386. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  387. 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
- // RBAC context
62
- globalRole: null,
63
- organisationRole: null,
64
- eventAppRole: null,
65
- rbacLoading: false,
66
- rbacError: null,
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
- timeUntilIdle: 0,
70
- resetInactivityTimer: vi.fn(),
71
- inactivityLoading: false,
72
- inactivityError: null,
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- <EventServiceContext.Provider value={{ eventService: { restorePersistedEvent } as any }}>
155
- <PaceLoginPage appName="Test App" />
156
- </EventServiceContext.Provider>
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="My Application" />, { withRouter: false });
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
- renderWithProviders(
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
- renderWithProviders(<PaceLoginPage appName="TestApp" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- <PaceLoginPage
282
- appName="Test App"
283
- onSuccessRedirectPath="/admin"
284
- requireAppAccess={true}
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- <PaceLoginPage
323
- appName="Test App"
324
- requireAppAccess={true}
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
- renderWithProviders(
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" onSuccessRedirectPath={undefined} />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="TestApp" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName="Test App" />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName={longAppName} />, { withRouter: false });
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
- renderWithProviders(<PaceLoginPage appName={specialAppName} />, { withRouter: false });
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
- renderWithProviders(
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
- renderWithProviders(
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 { useUnifiedAuth } from '../../providers';
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
- const { signIn, isAuthenticated, isLoading, authError, user, supabase } = useUnifiedAuth();
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
- <div role="alert">
172
+ <p className="grid place-items-center text-center size-full" role="alert">
167
173
  {error}
168
- </div>
174
+ </p>
169
175
  )}
170
- <div className="space-y-2">
171
- <Label htmlFor="new-password">New Password</Label>
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
- </div>
181
- <div className="space-y-2">
182
- <Label htmlFor="confirm-password">Confirm Password</Label>
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
- </div>
198
+ </Label>
192
199
  <Button
193
200
  type="submit"
194
201
  className="w-full"