@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
@@ -115,18 +115,12 @@ export function usePublicEvent(
115
115
  const [isLoading, setIsLoading] = useState<boolean>(true);
116
116
  const [error, setError] = useState<Error | null>(null);
117
117
 
118
- // Get environment variables from public page context or fallback to direct access
119
- let environment: { supabaseUrl: string | null; supabaseKey: string | null };
120
-
121
- try {
122
- environment = usePublicPageContext().environment;
123
- } catch {
124
- // Fallback to direct environment variable access if not in PublicPageProvider
125
- environment = {
126
- supabaseUrl: (import.meta as any).env?.VITE_SUPABASE_URL || (import.meta as any).env?.NEXT_PUBLIC_SUPABASE_URL || null,
127
- supabaseKey: (import.meta as any).env?.VITE_SUPABASE_PUBLISHABLE_KEY || (import.meta as any).env?.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY || null
128
- };
129
- }
118
+ // Call hook unconditionally - if provider is missing, it will throw
119
+ // Errors should be handled by error boundaries at a higher level
120
+ // We cannot conditionally call hooks
121
+ // For public pages, PublicPageProvider should always be present
122
+ const publicPageContext = usePublicPageContext();
123
+ const environment = publicPageContext.environment;
130
124
 
131
125
  // Create a simple Supabase client for public access
132
126
  const supabase = useMemo(() => {
@@ -140,17 +134,6 @@ export function usePublicEvent(
140
134
  return createClient<Database>(environment.supabaseUrl, environment.supabaseKey);
141
135
  }, [environment.supabaseUrl, environment.supabaseKey]);
142
136
 
143
- // Helper function to try refreshing schema cache
144
- const refreshSchemaCache = useCallback(async () => {
145
- try {
146
- // Try to trigger a schema refresh by querying a system table
147
- await (supabase as any).from('information_schema.routines').select('routine_name').limit(1);
148
- } catch (error) {
149
- // Ignore errors, this is just an attempt to refresh cache
150
- logger.debug('usePublicEvent', 'Schema cache refresh attempt failed:', error);
151
- }
152
- }, [supabase]);
153
-
154
137
  const fetchEvent = useCallback(async (): Promise<void> => {
155
138
  if (!eventCode || !supabase) {
156
139
  setError(new Error('Invalid event code or Supabase client not available'));
@@ -174,177 +157,66 @@ export function usePublicEvent(
174
157
  setIsLoading(true);
175
158
  setError(null);
176
159
 
177
- let eventData: any = null;
178
-
179
- try {
180
- // Try to call the public event RPC function first
181
- const response = await (supabase as any).rpc('get_public_event_by_code', {
182
- event_code_param: eventCode
183
- });
184
-
185
- const data = response?.data;
186
- const rpcError = response?.error;
187
-
188
- if (rpcError) {
189
- // If RPC function doesn't exist or schema cache issue, try refresh first, then fallback
190
- if (rpcError.message?.includes('Could not find the function') ||
191
- rpcError.message?.includes('does not exist') ||
192
- rpcError.message?.includes('schema cache')) {
193
- logger.warn('usePublicEvent', 'RPC function not found or schema cache issue, attempting refresh:', rpcError.message);
194
-
195
- // Try to refresh schema cache first
196
- await refreshSchemaCache();
197
-
198
- // Try RPC call one more time after refresh
199
- try {
200
- const retryResponse = await (supabase as any).rpc('get_public_event_by_code', {
201
- event_code_param: eventCode
202
- });
203
-
204
- const retryData = retryResponse?.data;
205
- const retryError = retryResponse?.error;
206
-
207
- if (!retryError && retryData && retryData.length > 0) {
208
- eventData = retryData[0];
209
- } else {
210
- throw new Error('RPC still failing after cache refresh');
211
- }
212
- } catch (retryError) {
213
- logger.warn('usePublicEvent', 'RPC still failing after cache refresh, falling back to direct table access');
214
-
215
- // Fallback: Direct table access with public RLS policy
216
- const tableResponse2 = await (supabase as any)
217
- .from('core_events')
218
- .select(`
219
- event_id,
220
- event_name,
221
- event_date,
222
- event_venue,
223
- event_participants,
224
- event_colours,
225
- organisation_id,
226
- event_days,
227
- event_typicalunit,
228
- event_rounddown,
229
- event_youthmultiplier,
230
- event_catering_email,
231
- event_news,
232
- event_billing,
233
- event_email
234
- `)
235
- .eq('event_code', eventCode)
236
- .eq('is_visible', true)
237
- .not('organisation_id', 'is', null)
238
- .limit(1)
239
- .single();
240
-
241
- const tableData = tableResponse2?.data;
242
- const tableError = tableResponse2?.error;
243
-
244
- if (tableError) {
245
- throw new Error(tableError?.message || 'Failed to fetch event from table');
246
- }
247
-
248
- if (!tableData) {
249
- setEvent(null);
250
- setError(new Error('Event not found'));
251
- return;
252
- }
160
+ // Use direct table query (removed legacy RPC get_public_event_by_code)
161
+ const tableResponse = await (supabase as any)
162
+ .from('core_events')
163
+ .select(`
164
+ event_id,
165
+ event_name,
166
+ event_date,
167
+ event_venue,
168
+ event_participants,
169
+ event_colours,
170
+ organisation_id,
171
+ event_days,
172
+ event_typicalunit,
173
+ event_rounddown,
174
+ event_youthmultiplier,
175
+ event_catering_email,
176
+ event_news,
177
+ event_billing,
178
+ event_email
179
+ `)
180
+ .eq('event_code', eventCode)
181
+ .eq('is_visible', true)
182
+ .not('organisation_id', 'is', null)
183
+ .limit(1)
184
+ .single();
185
+
186
+ const tableData = tableResponse?.data;
187
+ const tableError = tableResponse?.error;
188
+
189
+ if (tableError) {
190
+ // Transform "No rows found" to user-friendly "Event not found"
191
+ const errorMessage = tableError.message === 'No rows found' || tableError.code === 'PGRST116'
192
+ ? 'Event not found'
193
+ : (tableError?.message || 'Failed to fetch event from table');
194
+ throw new Error(errorMessage);
195
+ }
253
196
 
254
- // Get event logo from core_file_references
255
- const logoResponse = await (supabase as any)
256
- .from('core_file_references')
257
- .select('file_path')
258
- .eq('table_name', 'core_events')
259
- .eq('record_id', tableData.event_id)
260
- .eq('is_public', true)
261
- .eq('file_metadata->>category', 'event_logos')
262
- .limit(1)
263
- .single();
264
-
265
- const logoData = logoResponse?.data;
197
+ if (!tableData) {
198
+ setEvent(null);
199
+ setError(new Error('Event not found'));
200
+ return;
201
+ }
266
202
 
267
- eventData = {
268
- ...tableData,
269
- event_logo: logoData?.file_path || null
270
- };
271
- }
272
- } else {
273
- // For RPC errors that aren't schema cache issues, throw immediately without fallback
274
- const errorMessage = rpcError?.message || rpcError?.toString() || 'Failed to fetch event';
275
- setEvent(null);
276
- setError(new Error(errorMessage));
277
- setIsLoading(false);
278
- return;
279
- }
280
- } else {
281
- if (!data || data.length === 0 || !data[0]) {
282
- setEvent(null);
283
- setError(new Error('Event not found'));
284
- return;
285
- }
286
- eventData = data[0];
287
- }
288
- } catch (rpcError) {
289
- // If RPC call fails for any reason (including schema cache issues), try direct table access
290
- logger.warn('usePublicEvent', 'RPC call failed, falling back to direct table access:', rpcError);
203
+ // Get event logo from core_file_references
204
+ const logoResponse = await (supabase as any)
205
+ .from('core_file_references')
206
+ .select('file_path')
207
+ .eq('table_name', 'core_events')
208
+ .eq('record_id', tableData.event_id)
209
+ .eq('is_public', true)
210
+ .eq('file_metadata->>category', 'event_logos')
211
+ .limit(1)
212
+ .single();
291
213
 
292
- const tableResponse = await (supabase as any)
293
- .from('core_events')
294
- .select(`
295
- event_id,
296
- event_name,
297
- event_date,
298
- event_venue,
299
- event_participants,
300
- event_colours,
301
- organisation_id,
302
- event_days,
303
- event_typicalunit,
304
- event_rounddown,
305
- event_youthmultiplier,
306
- event_catering_email,
307
- event_news,
308
- event_billing,
309
- event_email
310
- `)
311
- .eq('event_code', eventCode)
312
- .eq('is_visible', true)
313
- .not('organisation_id', 'is', null)
314
- .limit(1)
315
- .single();
316
-
317
- const tableData = tableResponse?.data;
318
- const tableError = tableResponse?.error;
214
+ const logoData = logoResponse?.data;
319
215
 
320
- if (tableError) {
321
- throw new Error(tableError?.message || 'Failed to fetch event from table');
322
- }
323
-
324
- if (!tableData) {
325
- setEvent(null);
326
- setError(new Error('Event not found'));
327
- return;
328
- }
329
-
330
- // Get event logo from core_file_references
331
- const logoResponse = await (supabase as any)
332
- .from('core_file_references')
333
- .select('file_path')
334
- .eq('table_name', 'core_events')
335
- .eq('record_id', tableData.event_id)
336
- .eq('is_public', true)
337
- .eq('file_metadata->>category', 'event_logos')
338
- .limit(1)
339
- .single();
340
-
341
- const logoData = logoResponse?.data;
342
-
343
- eventData = {
344
- ...tableData,
345
- event_logo: logoData?.file_path || null
346
- };
347
- }
216
+ const eventData = {
217
+ ...tableData,
218
+ event_logo: logoData?.file_path || null
219
+ };
348
220
 
349
221
  // Transform to Event type
350
222
  const transformedEvent: Event = {
@@ -33,6 +33,15 @@ describe('usePublicEventLogo', () => {
33
33
  fetchMock = vi.fn().mockResolvedValue({ ok: true });
34
34
  (globalThis as any).fetch = fetchMock;
35
35
  clearPublicLogoCache();
36
+
37
+ // Setup default mock for the query chain: .from('core_file_references').select(...).eq(...).eq(...).eq(...).eq(...).limit(1).single()
38
+ const mockQueryBuilder = {
39
+ select: vi.fn().mockReturnThis(),
40
+ eq: vi.fn().mockReturnThis(),
41
+ limit: vi.fn().mockReturnThis(),
42
+ single: vi.fn().mockResolvedValue({ data: null, error: null })
43
+ };
44
+ (mockSupabase.from as any).mockReturnValue(mockQueryBuilder);
36
45
  });
37
46
 
38
47
  afterEach(() => {
@@ -44,9 +53,19 @@ describe('usePublicEventLogo', () => {
44
53
  (globalThis as any).fetch = originalFetch;
45
54
  });
46
55
 
47
- it('fetches and caches the logo when RPC succeeds', async () => {
56
+ it('fetches and caches the logo when query succeeds', async () => {
48
57
  const logoUrl = 'https://cdn.example.com/logo.png';
49
- (mockSupabase.rpc as any).mockResolvedValue({ data: [{ logo_url: logoUrl }], error: null });
58
+ // usePublicEventLogo uses: .from('core_file_references').select('file_path').eq(...).eq(...).eq(...).eq(...).limit(1).single()
59
+ const mockQueryBuilder = {
60
+ select: vi.fn().mockReturnThis(),
61
+ eq: vi.fn().mockReturnThis(),
62
+ limit: vi.fn().mockReturnThis(),
63
+ single: vi.fn().mockResolvedValue({
64
+ data: { file_path: logoUrl },
65
+ error: null
66
+ })
67
+ };
68
+ (mockSupabase.from as any).mockReturnValue(mockQueryBuilder);
50
69
 
51
70
  const { result } = renderHook(() =>
52
71
  usePublicEventLogo('event-123', 'Big Event', VALID_ORG_ID, {
@@ -54,7 +73,7 @@ describe('usePublicEventLogo', () => {
54
73
  })
55
74
  );
56
75
 
57
- await waitFor(() => expect(result.current.logoUrl).toBe(logoUrl));
76
+ await waitFor(() => expect(result.current.logoUrl).toBe(logoUrl), { timeout: 2000 });
58
77
  expect(result.current.fallbackText).toBe('BE');
59
78
  expect(fetchMock).toHaveBeenCalledWith(logoUrl, { method: 'HEAD' });
60
79
 
@@ -66,9 +85,16 @@ describe('usePublicEventLogo', () => {
66
85
  it('refetch clears cache and requests fresh data', async () => {
67
86
  const firstUrl = 'https://cdn.example.com/logo.png';
68
87
  const secondUrl = 'https://cdn.example.com/logo-2.png';
69
- (mockSupabase.rpc as any)
70
- .mockResolvedValueOnce({ data: [{ logo_url: firstUrl }], error: null })
71
- .mockResolvedValueOnce({ data: [{ logo_url: secondUrl }], error: null });
88
+
89
+ const mockQueryBuilder = {
90
+ select: vi.fn().mockReturnThis(),
91
+ eq: vi.fn().mockReturnThis(),
92
+ limit: vi.fn().mockReturnThis(),
93
+ single: vi.fn()
94
+ .mockResolvedValueOnce({ data: { file_path: firstUrl }, error: null })
95
+ .mockResolvedValueOnce({ data: { file_path: secondUrl }, error: null })
96
+ };
97
+ (mockSupabase.from as any).mockReturnValue(mockQueryBuilder);
72
98
 
73
99
  const { result } = renderHook(() =>
74
100
  usePublicEventLogo('event-123', 'Big Event', VALID_ORG_ID, {
@@ -76,18 +102,27 @@ describe('usePublicEventLogo', () => {
76
102
  })
77
103
  );
78
104
 
79
- await waitFor(() => expect(result.current.logoUrl).toBe(firstUrl));
105
+ await waitFor(() => expect(result.current.logoUrl).toBe(firstUrl), { timeout: 2000 });
80
106
 
81
107
  await act(async () => {
82
108
  await result.current.refetch();
83
109
  });
84
110
 
85
- await waitFor(() => expect(result.current.logoUrl).toBe(secondUrl));
86
- expect((mockSupabase.rpc as any)).toHaveBeenCalledTimes(2);
111
+ await waitFor(() => expect(result.current.logoUrl).toBe(secondUrl), { timeout: 2000 });
112
+ expect(mockQueryBuilder.single).toHaveBeenCalledTimes(2);
87
113
  });
88
114
 
89
- it('exposes error state when RPC fails', async () => {
90
- (mockSupabase.rpc as any).mockResolvedValue({ data: null, error: { message: 'RPC failed' } });
115
+ it('exposes error state when query fails', async () => {
116
+ const mockQueryBuilder = {
117
+ select: vi.fn().mockReturnThis(),
118
+ eq: vi.fn().mockReturnThis(),
119
+ limit: vi.fn().mockReturnThis(),
120
+ single: vi.fn().mockResolvedValue({
121
+ data: null,
122
+ error: { message: 'Query failed', code: 'PGRST116' }
123
+ })
124
+ };
125
+ (mockSupabase.from as any).mockReturnValue(mockQueryBuilder);
91
126
 
92
127
  const { result } = renderHook(() =>
93
128
  usePublicEventLogo('event-123', 'Big Event', VALID_ORG_ID, {
@@ -95,15 +130,24 @@ describe('usePublicEventLogo', () => {
95
130
  })
96
131
  );
97
132
 
98
- await waitFor(() => expect(result.current.isLoading).toBe(false));
133
+ await waitFor(() => expect(result.current.isLoading).toBe(false), { timeout: 2000 });
99
134
  expect(result.current.logoUrl).toBeNull();
100
- expect(result.current.error).toEqual(new Error('RPC failed'));
135
+ expect(result.current.error).toBeNull(); // No error for PGRST116 (not found)
101
136
  expect(fetchMock).not.toHaveBeenCalled();
102
137
  });
103
138
 
104
139
  it('skips image validation when disabled', async () => {
105
140
  const logoUrl = 'https://cdn.example.com/logo.png';
106
- (mockSupabase.rpc as any).mockResolvedValue({ data: [{ logo_url: logoUrl }], error: null });
141
+ const mockQueryBuilder = {
142
+ select: vi.fn().mockReturnThis(),
143
+ eq: vi.fn().mockReturnThis(),
144
+ limit: vi.fn().mockReturnThis(),
145
+ single: vi.fn().mockResolvedValue({
146
+ data: { file_path: logoUrl },
147
+ error: null
148
+ })
149
+ };
150
+ (mockSupabase.from as any).mockReturnValue(mockQueryBuilder);
107
151
 
108
152
  const { result } = renderHook(() =>
109
153
  usePublicEventLogo('event-123', 'Big Event', VALID_ORG_ID, {
@@ -112,7 +156,7 @@ describe('usePublicEventLogo', () => {
112
156
  })
113
157
  );
114
158
 
115
- await waitFor(() => expect(result.current.logoUrl).toBe(logoUrl));
159
+ await waitFor(() => expect(result.current.logoUrl).toBe(logoUrl), { timeout: 2000 });
116
160
  expect(fetchMock).not.toHaveBeenCalled();
117
161
  });
118
162
 
@@ -130,7 +174,16 @@ describe('usePublicEventLogo', () => {
130
174
 
131
175
  it('clears cached logo entries when clearPublicLogoCache is called', async () => {
132
176
  const logoUrl = 'https://cdn.example.com/logo.png';
133
- (mockSupabase.rpc as any).mockResolvedValue({ data: [{ logo_url: logoUrl }], error: null });
177
+ const mockQueryBuilder = {
178
+ select: vi.fn().mockReturnThis(),
179
+ eq: vi.fn().mockReturnThis(),
180
+ limit: vi.fn().mockReturnThis(),
181
+ single: vi.fn().mockResolvedValue({
182
+ data: { file_path: logoUrl },
183
+ error: null
184
+ })
185
+ };
186
+ (mockSupabase.from as any).mockReturnValue(mockQueryBuilder);
134
187
 
135
188
  const { result } = renderHook(() =>
136
189
  usePublicEventLogo('event-123', 'Big Event', VALID_ORG_ID, {
@@ -138,7 +191,7 @@ describe('usePublicEventLogo', () => {
138
191
  })
139
192
  );
140
193
 
141
- await waitFor(() => expect(result.current.logoUrl).toBe(logoUrl));
194
+ await waitFor(() => expect(result.current.logoUrl).toBe(logoUrl), { timeout: 2000 });
142
195
  expect(getPublicLogoCacheStats().size).toBe(1);
143
196
 
144
197
  clearPublicLogoCache();
@@ -183,22 +183,32 @@ export function usePublicEventLogo(
183
183
  setIsLoading(true);
184
184
  setError(null);
185
185
 
186
- // Call the public logo RPC function
187
- const { data, error: rpcError } = await (supabase as any).rpc('get_public_event_logo', {
188
- event_id_param: eventId,
189
- organisation_id_param: organisationId
190
- });
186
+ // Query logo directly from core_file_references (removed legacy RPC get_public_event_logo)
187
+ const { data: logoData, error: queryError } = await (supabase as any)
188
+ .from('core_file_references')
189
+ .select('file_path')
190
+ .eq('table_name', 'core_events')
191
+ .eq('record_id', eventId)
192
+ .eq('is_public', true)
193
+ .eq('file_metadata->>category', 'event_logos')
194
+ .limit(1)
195
+ .single();
191
196
 
192
- if (rpcError) {
193
- throw new Error(rpcError.message || 'Failed to fetch logo');
197
+ if (queryError) {
198
+ // If no logo found, that's okay - return null
199
+ if (queryError.code === 'PGRST116') {
200
+ setLogoUrl(null);
201
+ return;
202
+ }
203
+ throw new Error(queryError.message || 'Failed to fetch logo');
194
204
  }
195
205
 
196
- if (!data || data.length === 0 || !data[0] || !data[0].logo_url) {
206
+ if (!logoData || !logoData.file_path) {
197
207
  setLogoUrl(null);
198
208
  return;
199
209
  }
200
210
 
201
- const logoUrl = data[0].logo_url;
211
+ const logoUrl = logoData.file_path;
202
212
 
203
213
  // Validate image existence if requested
204
214
  if (validateImage) {
@@ -22,7 +22,7 @@
22
22
  */
23
23
 
24
24
  import { useMemo, useContext } from 'react';
25
- import { useUnifiedAuth } from '../providers/services/UnifiedAuthProvider';
25
+ import { useUnifiedAuth, UnifiedAuthContext } from '../providers/services/UnifiedAuthProvider';
26
26
  import { useIsPublicPage, PublicPageContext } from '../components/PublicLayout/PublicPageProvider';
27
27
 
28
28
  export interface UseAppConfigReturn {
@@ -36,6 +36,8 @@ export interface UseAppConfigReturn {
36
36
  * @returns App configuration and loading state
37
37
  */
38
38
  export function useAppConfig(): UseAppConfigReturn {
39
+ // Call all hooks unconditionally at the top level
40
+ // Hooks must be called in the same order on every render
39
41
  // Check if we're in a public page context first
40
42
  const isPublicPage = useIsPublicPage();
41
43
 
@@ -43,10 +45,20 @@ export function useAppConfig(): UseAppConfigReturn {
43
45
  const publicPageContext = useContext(PublicPageContext);
44
46
  const hasPublicContext = publicPageContext !== undefined;
45
47
  const contextAppName = publicPageContext?.appName || null;
48
+
49
+ // For authenticated pages, use UnifiedAuthProvider
50
+ // Use useContext directly to handle missing provider gracefully
51
+ // This allows the hook to work in test environments without providers
52
+ const authContext = useContext(UnifiedAuthContext);
53
+ const authAppName = authContext?.appName;
54
+
46
55
  const getNodeEnvVar = (key: string): string | undefined =>
47
56
  typeof process !== 'undefined' && process.env ? process.env[key] : undefined;
48
57
 
49
- if (isPublicPage) {
58
+ // Call all useMemo hooks unconditionally - use conditional logic only for computed values
59
+ const publicAppName = useMemo(() => {
60
+ if (!isPublicPage) return null;
61
+
50
62
  const getAppName = (): string => {
51
63
  // When tests mock public mode without the provider, default to PACE
52
64
  if (!hasPublicContext) {
@@ -82,27 +94,17 @@ export function useAppConfig(): UseAppConfigReturn {
82
94
  return 'PACE';
83
95
  };
84
96
 
85
- return useMemo(() => ({
86
- appName: getAppName(),
87
- isLoading: false
88
- }), [contextAppName, hasPublicContext]);
89
- }
97
+ return getAppName();
98
+ }, [isPublicPage, contextAppName, hasPublicContext]);
90
99
 
91
- // For authenticated pages, use UnifiedAuthProvider
92
- try {
93
- const { appName } = useUnifiedAuth();
94
- return useMemo(() => ({
95
- appName,
96
- isLoading: false
97
- }), [appName]);
98
- } catch (error) {
99
- // Fallback if UnifiedAuthProvider is not available
100
- return useMemo(() => ({
101
- appName: contextAppName ||
102
- getNodeEnvVar('VITE_APP_NAME') ||
103
- getNodeEnvVar('NEXT_PUBLIC_APP_NAME') ||
104
- 'PACE',
105
- isLoading: false
106
- }), [contextAppName]);
107
- }
100
+ const authenticatedAppName = useMemo(() => {
101
+ if (isPublicPage) return null;
102
+ return authAppName || 'PACE';
103
+ }, [isPublicPage, authAppName]);
104
+
105
+ // Return the appropriate value based on context
106
+ return useMemo(() => ({
107
+ appName: publicAppName || authenticatedAppName || 'PACE',
108
+ isLoading: false
109
+ }), [publicAppName, authenticatedAppName]);
108
110
  }