@jmruthers/pace-core 0.6.5 → 0.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (473) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/README.md +5 -403
  3. package/audit-tool/00-dependencies.cjs +394 -0
  4. package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
  5. package/audit-tool/audits/02-project-structure.cjs +255 -0
  6. package/audit-tool/audits/03-architecture.cjs +196 -0
  7. package/audit-tool/audits/04-code-quality.cjs +149 -0
  8. package/audit-tool/audits/05-styling.cjs +224 -0
  9. package/audit-tool/audits/06-security-rbac.cjs +544 -0
  10. package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
  11. package/audit-tool/audits/08-testing-documentation.cjs +202 -0
  12. package/audit-tool/audits/09-operations.cjs +208 -0
  13. package/audit-tool/index.cjs +291 -0
  14. package/audit-tool/utils/code-utils.cjs +218 -0
  15. package/audit-tool/utils/file-utils.cjs +230 -0
  16. package/audit-tool/utils/report-utils.cjs +241 -0
  17. package/core-usage-manifest.json +93 -0
  18. package/cursor-rules/00-standards-overview.mdc +156 -0
  19. package/cursor-rules/01-pace-core-compliance.mdc +586 -0
  20. package/cursor-rules/02-project-structure.mdc +42 -4
  21. package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
  22. package/cursor-rules/04-code-quality.mdc +419 -0
  23. package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
  24. package/cursor-rules/06-security-rbac.mdc +518 -0
  25. package/cursor-rules/07-api-tech-stack.mdc +377 -0
  26. package/cursor-rules/08-testing-documentation.mdc +324 -0
  27. package/cursor-rules/09-operations.mdc +365 -0
  28. package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
  29. package/dist/DataTable-7PMH7XN7.js +15 -0
  30. package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
  31. package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
  32. package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
  33. package/dist/api-Y4MQWOFW.js +4 -0
  34. package/dist/audit-MYQXYZFU.js +3 -0
  35. package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
  36. package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
  37. package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
  38. package/dist/chunk-6F3IILHI.js +62 -0
  39. package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
  40. package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
  41. package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
  42. package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
  43. package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
  44. package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
  45. package/dist/chunk-C7NSAPTL.js +1 -0
  46. package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
  47. package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
  48. package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
  49. package/dist/chunk-GHYHJTYV.js +994 -0
  50. package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
  51. package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
  52. package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
  53. package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
  54. package/dist/chunk-MBADTM7L.js +64 -0
  55. package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
  56. package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
  57. package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
  58. package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
  59. package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
  60. package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
  61. package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
  62. package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
  63. package/dist/components.d.ts +7 -5
  64. package/dist/components.js +46 -257
  65. package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
  66. package/dist/eslint-rules/index.cjs +35 -0
  67. package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
  68. package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
  69. package/dist/eslint-rules/rules/05-styling.cjs +61 -0
  70. package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
  71. package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
  72. package/dist/eslint-rules/rules/08-testing.cjs +94 -0
  73. package/dist/eslint-rules/utils/helpers.cjs +42 -0
  74. package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
  75. package/dist/hooks.d.ts +6 -6
  76. package/dist/hooks.js +62 -172
  77. package/dist/icons/index.d.ts +1 -0
  78. package/dist/icons/index.js +1 -0
  79. package/dist/index.d.ts +12 -11
  80. package/dist/index.js +67 -660
  81. package/dist/providers.d.ts +2 -2
  82. package/dist/providers.js +8 -35
  83. package/dist/rbac/eslint-rules.d.ts +46 -44
  84. package/dist/rbac/eslint-rules.js +7 -4
  85. package/dist/rbac/index.d.ts +109 -586
  86. package/dist/rbac/index.js +14 -207
  87. package/dist/styles/index.js +2 -12
  88. package/dist/theming/runtime.d.ts +14 -1
  89. package/dist/theming/runtime.js +3 -19
  90. package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
  91. package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
  92. package/dist/types-t9H8qKRw.d.ts +55 -0
  93. package/dist/types.d.ts +1 -1
  94. package/dist/types.js +7 -94
  95. package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
  96. package/dist/utils.d.ts +24 -117
  97. package/dist/utils.js +54 -392
  98. package/docs/README.md +17 -7
  99. package/docs/api/README.md +4 -402
  100. package/docs/api/modules.md +301 -871
  101. package/docs/api-reference/components.md +21 -21
  102. package/docs/api-reference/deprecated.md +31 -6
  103. package/docs/api-reference/hooks.md +80 -80
  104. package/docs/api-reference/rpc-functions.md +78 -3
  105. package/docs/api-reference/types.md +1 -1
  106. package/docs/api-reference/utilities.md +1 -1
  107. package/docs/architecture/README.md +1 -1
  108. package/docs/core-concepts/events.md +3 -3
  109. package/docs/core-concepts/organisations.md +6 -6
  110. package/docs/core-concepts/permissions.md +6 -6
  111. package/docs/documentation-index.md +12 -18
  112. package/docs/getting-started/cursor-rules.md +3 -23
  113. package/docs/getting-started/dependencies.md +650 -0
  114. package/docs/getting-started/documentation-index.md +1 -1
  115. package/docs/getting-started/examples/README.md +4 -4
  116. package/docs/getting-started/examples/full-featured-app.md +1 -1
  117. package/docs/getting-started/faq.md +2 -2
  118. package/docs/getting-started/installation-guide.md +20 -7
  119. package/docs/getting-started/quick-reference.md +4 -4
  120. package/docs/getting-started/quick-start.md +23 -12
  121. package/docs/implementation-guides/authentication.md +15 -15
  122. package/docs/implementation-guides/component-styling.md +1 -1
  123. package/docs/implementation-guides/data-tables.md +126 -33
  124. package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
  125. package/docs/implementation-guides/dynamic-colors.md +3 -3
  126. package/docs/implementation-guides/file-upload-storage.md +2 -2
  127. package/docs/implementation-guides/hierarchical-datatable.md +40 -60
  128. package/docs/implementation-guides/inactivity-tracking.md +3 -3
  129. package/docs/implementation-guides/large-datasets.md +3 -2
  130. package/docs/implementation-guides/organisation-security.md +2 -2
  131. package/docs/implementation-guides/performance.md +2 -2
  132. package/docs/implementation-guides/permission-enforcement.md +5 -1
  133. package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
  134. package/docs/migration/V0.4.0_rbac-migration.md +6 -6
  135. package/docs/rbac/MIGRATION_GUIDE.md +819 -0
  136. package/docs/rbac/RBAC_CONTRACT.md +724 -0
  137. package/docs/rbac/README.md +17 -8
  138. package/docs/rbac/advanced-patterns.md +6 -6
  139. package/docs/rbac/api-reference.md +20 -20
  140. package/docs/rbac/edge-functions-guide.md +376 -0
  141. package/docs/rbac/event-based-apps.md +3 -3
  142. package/docs/rbac/examples.md +41 -41
  143. package/docs/rbac/getting-started.md +37 -37
  144. package/docs/rbac/performance.md +1 -1
  145. package/docs/rbac/quick-start.md +52 -52
  146. package/docs/rbac/secure-client-protection.md +1 -35
  147. package/docs/rbac/troubleshooting.md +1 -1
  148. package/docs/security/README.md +5 -5
  149. package/docs/standards/0-standards-overview.md +220 -0
  150. package/docs/standards/1-pace-core-compliance-standards.md +986 -0
  151. package/docs/standards/2-project-structure-standards.md +949 -0
  152. package/docs/standards/3-architecture-standards.md +606 -0
  153. package/docs/standards/4-code-quality-standards.md +728 -0
  154. package/docs/standards/5-styling-standards.md +348 -0
  155. package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
  156. package/docs/standards/7-api-tech-stack-standards.md +662 -0
  157. package/docs/standards/8-testing-documentation-standards.md +401 -0
  158. package/docs/standards/9-operations-standards.md +1102 -0
  159. package/docs/standards/README.md +185 -57
  160. package/docs/troubleshooting/README.md +4 -4
  161. package/docs/troubleshooting/common-issues.md +2 -2
  162. package/docs/troubleshooting/debugging.md +9 -9
  163. package/docs/troubleshooting/migration.md +4 -4
  164. package/docs/troubleshooting/organisation-context-setup.md +42 -19
  165. package/eslint-config-pace-core.cjs +33 -6
  166. package/package.json +35 -23
  167. package/scripts/install-cursor-rules.cjs +25 -6
  168. package/scripts/install-eslint-config.cjs +284 -0
  169. package/src/__tests__/fixtures/supabase.ts +1 -1
  170. package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
  171. package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
  172. package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
  173. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
  174. package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
  175. package/src/__tests__/helpers/component-test-utils.tsx +1 -1
  176. package/src/__tests__/helpers/supabaseMock.ts +2 -2
  177. package/src/__tests__/integration/UserProfile.test.tsx +14 -14
  178. package/src/__tests__/public-recipe-view.test.ts +38 -9
  179. package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
  180. package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
  181. package/src/__tests__/templates/component.test.template.tsx +18 -15
  182. package/src/components/Button/Button.tsx +5 -1
  183. package/src/components/Calendar/Calendar.tsx +201 -47
  184. package/src/components/ContextSelector/ContextSelector.tsx +106 -119
  185. package/src/components/DataTable/AUDIT_REPORT.md +293 -0
  186. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
  187. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
  188. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
  189. package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
  190. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
  191. package/src/components/DataTable/components/DataTableCore.tsx +186 -13
  192. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
  193. package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
  194. package/src/components/DataTable/components/EditFields.tsx +23 -3
  195. package/src/components/DataTable/components/EditableRow.tsx +12 -9
  196. package/src/components/DataTable/components/EmptyState.tsx +10 -9
  197. package/src/components/DataTable/components/FilterRow.tsx +2 -4
  198. package/src/components/DataTable/components/ImportModal.tsx +124 -126
  199. package/src/components/DataTable/components/LoadingState.tsx +5 -6
  200. package/src/components/DataTable/components/RowComponent.tsx +12 -0
  201. package/src/components/DataTable/components/SortIndicator.tsx +50 -0
  202. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
  203. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
  204. package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
  205. package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
  206. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
  207. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
  208. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
  209. package/src/components/DataTable/components/index.ts +2 -1
  210. package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
  211. package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
  212. package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
  213. package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
  214. package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
  215. package/src/components/DataTable/types.ts +5 -18
  216. package/src/components/DataTable/utils/a11yUtils.ts +17 -0
  217. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
  218. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
  219. package/src/components/DateTimeField/DateTimeField.tsx +10 -9
  220. package/src/components/Dialog/Dialog.test.tsx +128 -104
  221. package/src/components/Dialog/Dialog.tsx +742 -24
  222. package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
  223. package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
  224. package/src/components/FileDisplay/FileDisplay.tsx +23 -17
  225. package/src/components/FileUpload/FileUpload.test.tsx +52 -14
  226. package/src/components/FileUpload/FileUpload.tsx +112 -130
  227. package/src/components/Form/Form.test.tsx +6 -8
  228. package/src/components/Form/Form.tsx +365 -4
  229. package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
  230. package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
  231. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
  232. package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
  233. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
  234. package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
  235. package/src/components/Progress/Progress.tsx +2 -4
  236. package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
  237. package/src/components/Select/Select.tsx +109 -98
  238. package/src/components/Select/types.ts +4 -1
  239. package/src/components/UserMenu/UserMenu.tsx +9 -6
  240. package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
  241. package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
  242. package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
  243. package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
  244. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
  245. package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
  246. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
  247. package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
  248. package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
  249. package/src/hooks/public/usePublicEvent.ts +67 -195
  250. package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
  251. package/src/hooks/public/usePublicEventLogo.ts +24 -14
  252. package/src/hooks/public/usePublicFileDisplay.ts +2 -2
  253. package/src/hooks/public/usePublicRouteParams.ts +5 -5
  254. package/src/hooks/useAppConfig.ts +28 -26
  255. package/src/hooks/useEventTheme.test.ts +217 -239
  256. package/src/hooks/useEventTheme.ts +16 -28
  257. package/src/hooks/useFileDisplay.ts +2 -2
  258. package/src/hooks/useOrganisationPermissions.ts +5 -7
  259. package/src/hooks/useQueryCache.ts +0 -1
  260. package/src/hooks/useSessionDraft.ts +380 -0
  261. package/src/hooks/useSessionRestoration.ts +3 -1
  262. package/src/icons/index.ts +27 -0
  263. package/src/index.ts +5 -0
  264. package/src/providers/OrganisationProvider.tsx +23 -14
  265. package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
  266. package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
  267. package/src/providers/__tests__/EventProvider.test.tsx +61 -61
  268. package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
  269. package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
  270. package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
  271. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
  272. package/src/providers/services/EventServiceProvider.tsx +1 -24
  273. package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
  274. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
  275. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
  276. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
  277. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
  278. package/src/rbac/adapters.tsx +7 -295
  279. package/src/rbac/api.test.ts +44 -56
  280. package/src/rbac/api.ts +10 -17
  281. package/src/rbac/cache-invalidation.ts +0 -1
  282. package/src/rbac/compliance/index.ts +10 -0
  283. package/src/rbac/compliance/pattern-detector.ts +553 -0
  284. package/src/rbac/compliance/runtime-compliance.ts +22 -0
  285. package/src/rbac/components/AccessDenied.tsx +150 -0
  286. package/src/rbac/components/NavigationGuard.tsx +12 -20
  287. package/src/rbac/components/PagePermissionGuard.tsx +4 -24
  288. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
  289. package/src/rbac/components/index.ts +3 -41
  290. package/src/rbac/eslint-rules.js +1 -1
  291. package/src/rbac/hooks/index.ts +0 -3
  292. package/src/rbac/hooks/permissions/index.ts +0 -3
  293. package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
  294. package/src/rbac/hooks/usePermissions.ts +0 -3
  295. package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
  296. package/src/rbac/hooks/useResolvedScope.ts +58 -140
  297. package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
  298. package/src/rbac/hooks/useResourcePermissions.ts +139 -48
  299. package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
  300. package/src/rbac/hooks/useRoleManagement.ts +147 -19
  301. package/src/rbac/hooks/useSecureSupabase.ts +4 -8
  302. package/src/rbac/index.ts +7 -9
  303. package/src/rbac/utils/contextValidator.ts +9 -7
  304. package/src/services/AuthService.ts +130 -18
  305. package/src/services/EventService.ts +4 -97
  306. package/src/services/InactivityService.ts +16 -0
  307. package/src/services/OrganisationService.ts +7 -44
  308. package/src/services/__tests__/OrganisationService.test.ts +26 -8
  309. package/src/services/base/BaseService.ts +0 -3
  310. package/src/styles/core.css +7 -0
  311. package/src/theming/__tests__/parseEventColours.test.ts +9 -3
  312. package/src/theming/parseEventColours.ts +22 -10
  313. package/src/types/database.generated.ts +4733 -3809
  314. package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
  315. package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
  316. package/src/utils/context/organisationContext.test.ts +13 -28
  317. package/src/utils/context/organisationContext.ts +21 -52
  318. package/src/utils/dynamic/dynamicUtils.ts +1 -1
  319. package/src/utils/file-reference/index.ts +39 -15
  320. package/src/utils/formatting/formatDateTime.test.ts +3 -2
  321. package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
  322. package/src/utils/index.ts +4 -1
  323. package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
  324. package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
  325. package/src/utils/persistence/keyDerivation.ts +304 -0
  326. package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
  327. package/src/utils/security/secureStorage.ts +5 -5
  328. package/src/utils/storage/README.md +1 -1
  329. package/src/utils/storage/helpers.ts +3 -3
  330. package/src/utils/supabase/createBaseClient.ts +147 -0
  331. package/src/utils/timezone/timezone.test.ts +1 -2
  332. package/src/utils/timezone/timezone.ts +1 -1
  333. package/src/utils/validation/csrf.ts +4 -4
  334. package/cursor-rules/00-pace-core-compliance.mdc +0 -331
  335. package/cursor-rules/01-standards-compliance.mdc +0 -244
  336. package/cursor-rules/04-testing-standards.mdc +0 -268
  337. package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
  338. package/cursor-rules/06-code-quality.mdc +0 -309
  339. package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
  340. package/cursor-rules/CHANGELOG.md +0 -119
  341. package/cursor-rules/README.md +0 -192
  342. package/dist/DataTable-AOVNCPTX.js +0 -175
  343. package/dist/DataTable-AOVNCPTX.js.map +0 -1
  344. package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
  345. package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
  346. package/dist/api-O6HTBX5Y.js +0 -52
  347. package/dist/api-O6HTBX5Y.js.map +0 -1
  348. package/dist/audit-V53FV5AG.js +0 -17
  349. package/dist/audit-V53FV5AG.js.map +0 -1
  350. package/dist/chunk-5DRSZLL2.js.map +0 -1
  351. package/dist/chunk-63FOKYGO.js.map +0 -1
  352. package/dist/chunk-6COVEUS7.js.map +0 -1
  353. package/dist/chunk-AFVQODI2.js +0 -263
  354. package/dist/chunk-AFVQODI2.js.map +0 -1
  355. package/dist/chunk-DGUM43GV.js.map +0 -1
  356. package/dist/chunk-E66EQZE6.js.map +0 -1
  357. package/dist/chunk-EFN2EIMK.js.map +0 -1
  358. package/dist/chunk-FFQEQTNW.js.map +0 -1
  359. package/dist/chunk-FMUCXFII.js.map +0 -1
  360. package/dist/chunk-G37KK66H.js.map +0 -1
  361. package/dist/chunk-G7QEZTYQ.js +0 -2053
  362. package/dist/chunk-G7QEZTYQ.js.map +0 -1
  363. package/dist/chunk-HU2C6SSC.js.map +0 -1
  364. package/dist/chunk-IHB5DR3H.js.map +0 -1
  365. package/dist/chunk-IVOFDYWT.js.map +0 -1
  366. package/dist/chunk-J36DSWQK.js.map +0 -1
  367. package/dist/chunk-JGRYX5UX.js.map +0 -1
  368. package/dist/chunk-KQCRWDSA.js +0 -1
  369. package/dist/chunk-KQCRWDSA.js.map +0 -1
  370. package/dist/chunk-L4OXEN46.js.map +0 -1
  371. package/dist/chunk-LMC26NLJ.js +0 -84
  372. package/dist/chunk-LMC26NLJ.js.map +0 -1
  373. package/dist/chunk-M43Y4SSO.js.map +0 -1
  374. package/dist/chunk-M7MPQISP.js.map +0 -1
  375. package/dist/chunk-NTM7ZSB6.js.map +0 -1
  376. package/dist/chunk-PWLANIRT.js.map +0 -1
  377. package/dist/chunk-QXHPKYJV.js.map +0 -1
  378. package/dist/chunk-RGAWHO7N.js.map +0 -1
  379. package/dist/chunk-UPPMRMYG.js.map +0 -1
  380. package/dist/chunk-VBXEHIUJ.js.map +0 -1
  381. package/dist/chunk-ZSAAAMVR.js.map +0 -1
  382. package/dist/components.js.map +0 -1
  383. package/dist/contextValidator-5OGXSPKS.js +0 -9
  384. package/dist/contextValidator-5OGXSPKS.js.map +0 -1
  385. package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
  386. package/dist/hooks.js.map +0 -1
  387. package/dist/index.js.map +0 -1
  388. package/dist/providers.js.map +0 -1
  389. package/dist/rbac/eslint-rules.js.map +0 -1
  390. package/dist/rbac/index.js.map +0 -1
  391. package/dist/styles/index.js.map +0 -1
  392. package/dist/theming/runtime.js.map +0 -1
  393. package/dist/types.js.map +0 -1
  394. package/dist/utils.js.map +0 -1
  395. package/docs/best-practices/README.md +0 -472
  396. package/docs/best-practices/accessibility.md +0 -601
  397. package/docs/best-practices/common-patterns.md +0 -516
  398. package/docs/best-practices/deployment.md +0 -1103
  399. package/docs/best-practices/performance.md +0 -1328
  400. package/docs/best-practices/security.md +0 -940
  401. package/docs/best-practices/testing.md +0 -1034
  402. package/docs/rbac/compliance/compliance-guide.md +0 -544
  403. package/docs/standards/01-architecture-standard.md +0 -44
  404. package/docs/standards/02-api-and-rpc-standard.md +0 -39
  405. package/docs/standards/03-component-standard.md +0 -32
  406. package/docs/standards/04-code-style-standard.md +0 -32
  407. package/docs/standards/05-security-standard.md +0 -44
  408. package/docs/standards/06-testing-and-docs-standard.md +0 -29
  409. package/docs/standards/pace-core-compliance.md +0 -432
  410. package/scripts/audit/core/checks/accessibility.cjs +0 -197
  411. package/scripts/audit/core/checks/api-usage.cjs +0 -191
  412. package/scripts/audit/core/checks/bundle.cjs +0 -142
  413. package/scripts/audit/core/checks/compliance.cjs +0 -2706
  414. package/scripts/audit/core/checks/config.cjs +0 -54
  415. package/scripts/audit/core/checks/coverage.cjs +0 -84
  416. package/scripts/audit/core/checks/dependencies.cjs +0 -994
  417. package/scripts/audit/core/checks/documentation.cjs +0 -268
  418. package/scripts/audit/core/checks/environment.cjs +0 -116
  419. package/scripts/audit/core/checks/error-handling.cjs +0 -340
  420. package/scripts/audit/core/checks/forms.cjs +0 -172
  421. package/scripts/audit/core/checks/heuristics.cjs +0 -68
  422. package/scripts/audit/core/checks/hooks.cjs +0 -334
  423. package/scripts/audit/core/checks/imports.cjs +0 -244
  424. package/scripts/audit/core/checks/performance.cjs +0 -325
  425. package/scripts/audit/core/checks/routes.cjs +0 -117
  426. package/scripts/audit/core/checks/state.cjs +0 -130
  427. package/scripts/audit/core/checks/structure.cjs +0 -65
  428. package/scripts/audit/core/checks/style.cjs +0 -584
  429. package/scripts/audit/core/checks/testing.cjs +0 -122
  430. package/scripts/audit/core/checks/typescript.cjs +0 -61
  431. package/scripts/audit/core/scanner.cjs +0 -199
  432. package/scripts/audit/core/utils.cjs +0 -137
  433. package/scripts/audit/index.cjs +0 -223
  434. package/scripts/audit/reporters/console.cjs +0 -151
  435. package/scripts/audit/reporters/json.cjs +0 -54
  436. package/scripts/audit/reporters/markdown.cjs +0 -124
  437. package/scripts/audit-consuming-app.cjs +0 -86
  438. package/src/components/DataTable/components/DataTableBody.tsx +0 -454
  439. package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
  440. package/src/components/DataTable/components/ExpandButton.tsx +0 -113
  441. package/src/components/DataTable/components/GroupHeader.tsx +0 -54
  442. package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
  443. package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
  444. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
  445. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
  446. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
  447. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
  448. package/src/components/DataTable/core/DataTableContext.tsx +0 -216
  449. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
  450. package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
  451. package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
  452. package/src/components/DataTable/utils/debugTools.ts +0 -514
  453. package/src/eslint-rules/pace-core-compliance.js +0 -638
  454. package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
  455. package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
  456. package/src/rbac/components/NavigationProvider.test.tsx +0 -481
  457. package/src/rbac/components/NavigationProvider.tsx +0 -345
  458. package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
  459. package/src/rbac/components/PagePermissionProvider.tsx +0 -279
  460. package/src/rbac/components/PermissionEnforcer.tsx +0 -312
  461. package/src/rbac/components/RoleBasedRouter.tsx +0 -440
  462. package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
  463. package/src/rbac/components/SecureDataProvider.tsx +0 -339
  464. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
  465. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
  466. package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
  467. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
  468. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
  469. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
  470. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
  471. package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
  472. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
  473. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
@@ -22,16 +22,16 @@
22
22
  * const { eventCode } = usePublicRouteParams();
23
23
  * const { event, isLoading, error, refetch } = usePublicEvent(eventCode);
24
24
  *
25
- * if (isLoading) return <div>Loading event...</div>;
26
- * if (error) return <div>Error: {error.message}</div>;
27
- * if (!event) return <div>Event not found</div>;
25
+ * if (isLoading) return <p>Loading event...</p>;
26
+ * if (error) return <p>Error: {error.message}</p>;
27
+ * if (!event) return <p>Event not found</p>;
28
28
  *
29
29
  * return (
30
- * <div>
30
+ * <section>
31
31
  * <h1>{event.event_name}</h1>
32
32
  * <p>Date: {event.event_date}</p>
33
33
  * <p>Venue: {event.event_venue}</p>
34
- * </div>
34
+ * </section>
35
35
  * );
36
36
  * }
37
37
  * ```
@@ -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();
@@ -26,17 +26,17 @@
26
26
  * organisationId
27
27
  * );
28
28
  *
29
- * if (isLoading) return <div>Loading logo...</div>;
30
- * if (error) return <div>Error: {error.message}</div>;
29
+ * if (isLoading) return <p>Loading logo...</p>;
30
+ * if (error) return <p>Error: {error.message}</p>;
31
31
  *
32
32
  * return (
33
- * <div>
33
+ * <section>
34
34
  * {logoUrl ? (
35
35
  * <img src={logoUrl} alt={`${eventName} logo`} />
36
36
  * ) : (
37
- * <div className="logo-fallback">{fallbackText}</div>
37
+ * <p className="logo-fallback">{fallbackText}</p>
38
38
  * )}
39
- * </div>
39
+ * </section>
40
40
  * );
41
41
  * }
42
42
  * ```
@@ -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) {
@@ -28,8 +28,8 @@
28
28
  * { supabase }
29
29
  * );
30
30
  *
31
- * if (isLoading) return <div>Loading...</div>;
32
- * if (error) return <div>Error: {error.message}</div>;
31
+ * if (isLoading) return <p>Loading...</p>;
32
+ * if (error) return <p>Error: {error.message}</p>;
33
33
  *
34
34
  * return fileUrl ? <img src={fileUrl} alt="File" /> : null;
35
35
  * }
@@ -21,15 +21,15 @@
21
21
  * function PublicEventPage() {
22
22
  * const { eventCode, eventId, event, error, isLoading } = usePublicRouteParams();
23
23
  *
24
- * if (isLoading) return <div>Loading...</div>;
25
- * if (error) return <div>Error: {error.message}</div>;
26
- * if (!event) return <div>Event not found</div>;
24
+ * if (isLoading) return <p>Loading...</p>;
25
+ * if (error) return <p>Error: {error.message}</p>;
26
+ * if (!event) return <p>Event not found</p>;
27
27
  *
28
28
  * return (
29
- * <div>
29
+ * <section>
30
30
  * <h1>{event.event_name}</h1>
31
31
  * <p>Event Code: {eventCode}</p>
32
- * </div>
32
+ * </section>
33
33
  * );
34
34
  * }
35
35
  * ```
@@ -14,15 +14,15 @@
14
14
  * function MyComponent() {
15
15
  * const { appName, isLoading } = useAppConfig();
16
16
  *
17
- * if (isLoading) return <div>Loading...</div>;
17
+ * if (isLoading) return <p>Loading...</p>;
18
18
  *
19
- * return <div>App: {appName}</div>;
19
+ * return <p>App: {appName}</p>;
20
20
  * }
21
21
  * ```
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
  }