@jmruthers/pace-core 0.6.1 → 0.6.3

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 (549) hide show
  1. package/CHANGELOG.md +88 -10
  2. package/cursor-rules/00-pace-core-compliance.mdc +46 -87
  3. package/cursor-rules/01-standards-compliance.mdc +16 -47
  4. package/cursor-rules/02-project-structure.mdc +4 -4
  5. package/cursor-rules/03-solid-principles.mdc +45 -164
  6. package/cursor-rules/04-testing-standards.mdc +22 -69
  7. package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
  8. package/cursor-rules/06-code-quality.mdc +42 -125
  9. package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
  10. package/cursor-rules/08-markup-quality.mdc +452 -0
  11. package/cursor-rules/CHANGELOG.md +18 -0
  12. package/cursor-rules/README.md +2 -1
  13. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
  14. package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
  15. package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
  16. package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
  17. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
  18. package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  19. package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
  20. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  21. package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
  22. package/dist/chunk-2T2IG7T7.js.map +1 -0
  23. package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
  24. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  25. package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
  26. package/dist/chunk-6Z7LTB3D.js.map +1 -0
  27. package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
  28. package/dist/chunk-CNCQDFLN.js.map +1 -0
  29. package/dist/chunk-DGUM43GV.js +11 -0
  30. package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
  31. package/dist/chunk-DWUBLJJM.js.map +1 -0
  32. package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
  33. package/dist/chunk-FFQEQTNW.js.map +1 -0
  34. package/dist/chunk-FMUCXFII.js +76 -0
  35. package/dist/chunk-FMUCXFII.js.map +1 -0
  36. package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
  37. package/dist/chunk-HFZBI76P.js.map +1 -0
  38. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  39. package/dist/chunk-L4OXEN46.js.map +1 -0
  40. package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
  41. package/dist/chunk-M43Y4SSO.js.map +1 -0
  42. package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
  43. package/dist/chunk-M7MPQISP.js.map +1 -0
  44. package/dist/chunk-PQBSKX33.js +7793 -0
  45. package/dist/chunk-PQBSKX33.js.map +1 -0
  46. package/dist/chunk-QRPVRXYT.js +226 -0
  47. package/dist/chunk-QRPVRXYT.js.map +1 -0
  48. package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
  49. package/dist/chunk-RWEBCB47.js.map +1 -0
  50. package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
  51. package/dist/chunk-YDQHOZNA.js.map +1 -0
  52. package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
  53. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  54. package/dist/components.d.ts +5 -5
  55. package/dist/components.js +18 -16
  56. package/dist/components.js.map +1 -1
  57. package/dist/contextValidator-3JNZKUTX.js +9 -0
  58. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  59. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  60. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  61. package/dist/hooks.d.ts +55 -122
  62. package/dist/hooks.js +10 -13
  63. package/dist/hooks.js.map +1 -1
  64. package/dist/index.d.ts +60 -13
  65. package/dist/index.js +30 -25
  66. package/dist/index.js.map +1 -1
  67. package/dist/providers.d.ts +21 -3
  68. package/dist/providers.js +4 -3
  69. package/dist/rbac/index.d.ts +210 -139
  70. package/dist/rbac/index.js +17 -13
  71. package/dist/styles/index.js +1 -1
  72. package/dist/theming/runtime.d.ts +1 -13
  73. package/dist/theming/runtime.js +2 -2
  74. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  75. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  76. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  77. package/dist/types.d.ts +2 -2
  78. package/dist/types.js +1 -1
  79. package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
  80. package/dist/utils.d.ts +4 -5
  81. package/dist/utils.js +17 -19
  82. package/dist/utils.js.map +1 -1
  83. package/docs/api/README.md +21 -17
  84. package/docs/api/modules.md +4191 -2967
  85. package/docs/architecture/database-schema-requirements.md +161 -0
  86. package/docs/components/context-selector.md +126 -0
  87. package/docs/core-concepts/rbac-system.md +3 -3
  88. package/docs/documentation-index.md +2 -4
  89. package/docs/getting-started/cursor-rules.md +2 -1
  90. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  91. package/docs/migration/MIGRATION_GUIDE.md +2 -24
  92. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  93. package/docs/migration/README.md +52 -6
  94. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  95. package/docs/migration/database-changes-december-2025.md +3 -3
  96. package/docs/pace-mint-fix-auto-selection.md +218 -0
  97. package/docs/pace-mint-rbac-setup.md +391 -0
  98. package/docs/rbac/event-based-apps.md +1 -1
  99. package/docs/rbac/getting-started.md +1 -1
  100. package/docs/rbac/quick-start.md +1 -1
  101. package/docs/rbac/secure-client-protection.md +330 -0
  102. package/docs/standards/README.md +1 -0
  103. package/package.json +4 -3
  104. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  105. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  106. package/scripts/audit/core/checks/bundle.cjs +142 -0
  107. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
  108. package/scripts/audit/core/checks/config.cjs +54 -0
  109. package/scripts/audit/core/checks/coverage.cjs +84 -0
  110. package/scripts/audit/core/checks/dependencies.cjs +985 -0
  111. package/scripts/audit/core/checks/documentation.cjs +268 -0
  112. package/scripts/audit/core/checks/environment.cjs +116 -0
  113. package/scripts/audit/core/checks/error-handling.cjs +340 -0
  114. package/scripts/audit/core/checks/forms.cjs +172 -0
  115. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  116. package/scripts/audit/core/checks/hooks.cjs +334 -0
  117. package/scripts/audit/core/checks/imports.cjs +244 -0
  118. package/scripts/audit/core/checks/performance.cjs +325 -0
  119. package/scripts/audit/core/checks/routes.cjs +117 -0
  120. package/scripts/audit/core/checks/state.cjs +130 -0
  121. package/scripts/audit/core/checks/structure.cjs +65 -0
  122. package/scripts/audit/core/checks/style.cjs +584 -0
  123. package/scripts/audit/core/checks/testing.cjs +122 -0
  124. package/scripts/audit/core/checks/typescript.cjs +61 -0
  125. package/scripts/audit/core/scanner.cjs +199 -0
  126. package/scripts/audit/core/utils.cjs +137 -0
  127. package/scripts/audit/index.cjs +223 -0
  128. package/scripts/audit/reporters/console.cjs +151 -0
  129. package/scripts/audit/reporters/json.cjs +54 -0
  130. package/scripts/audit/reporters/markdown.cjs +124 -0
  131. package/scripts/audit-consuming-app.cjs +61 -936
  132. package/scripts/build-docs/build-decision.js +240 -0
  133. package/scripts/build-docs/cache-utils.js +105 -0
  134. package/scripts/build-docs/content-normalization.js +150 -0
  135. package/scripts/build-docs/file-utils.js +105 -0
  136. package/scripts/build-docs/git-utils.js +86 -0
  137. package/scripts/build-docs/hash-utils.js +116 -0
  138. package/scripts/build-docs/typedoc-runner.js +220 -0
  139. package/scripts/build-docs-incremental.js +77 -913
  140. package/scripts/utils/command-runner.js +16 -11
  141. package/scripts/validate-formats.js +61 -56
  142. package/scripts/validate-master.js +74 -69
  143. package/scripts/validate-pre-publish.js +70 -65
  144. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  145. package/src/components/Alert/Alert.test.tsx +12 -18
  146. package/src/components/Alert/Alert.tsx +5 -7
  147. package/src/components/Avatar/Avatar.test.tsx +4 -4
  148. package/src/components/Badge/Badge.tsx +14 -0
  149. package/src/components/Button/Button.tsx +22 -0
  150. package/src/components/Calendar/Calendar.tsx +8 -2
  151. package/src/components/Card/Card.tsx +4 -0
  152. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  153. package/src/components/Checkbox/Checkbox.tsx +2 -2
  154. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  155. package/src/components/ContextSelector/index.ts +3 -0
  156. package/src/components/DataTable/DataTable.tsx +38 -4
  157. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  158. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
  159. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  160. package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
  161. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  162. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  163. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  164. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  165. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  166. package/src/components/DataTable/components/DataTableCore.tsx +196 -554
  167. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  168. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  169. package/src/components/DataTable/components/DataTableModals.tsx +8 -0
  170. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  171. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  172. package/src/components/DataTable/components/EditFields.tsx +307 -0
  173. package/src/components/DataTable/components/EditableRow.tsx +8 -0
  174. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  175. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  176. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  177. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  178. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  179. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  180. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  181. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  182. package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
  183. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  184. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  185. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  186. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  187. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  188. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  189. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  190. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  191. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  192. package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
  193. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  194. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  195. package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
  196. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  197. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  198. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  199. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  200. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  201. package/src/components/DataTable/styles.ts +6 -6
  202. package/src/components/DataTable/types.ts +6 -10
  203. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  204. package/src/components/DataTable/utils/debugTools.ts +18 -113
  205. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  206. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  207. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  208. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  209. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  210. package/src/components/Dialog/Dialog.tsx +31 -3
  211. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  212. package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
  213. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  214. package/src/components/ErrorBoundary/index.ts +27 -2
  215. package/src/components/FileDisplay/FileDisplay.tsx +74 -28
  216. package/src/components/FileUpload/FileUpload.tsx +22 -2
  217. package/src/components/Footer/Footer.test.tsx +16 -16
  218. package/src/components/Footer/Footer.tsx +14 -11
  219. package/src/components/Form/Form.tsx +1 -0
  220. package/src/components/Header/Header.test.tsx +43 -73
  221. package/src/components/Header/Header.tsx +59 -49
  222. package/src/components/Input/Input.test.tsx +2 -2
  223. package/src/components/Input/Input.tsx +8 -4
  224. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  225. package/src/components/LoginForm/LoginForm.tsx +4 -0
  226. package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
  227. package/src/components/NavigationMenu/types.ts +56 -0
  228. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  229. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  230. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  231. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  232. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
  233. package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
  234. package/src/components/PaceAppLayout/README.md +14 -17
  235. package/src/components/PaceAppLayout/test-setup.tsx +3 -4
  236. package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
  237. package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
  238. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  239. package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
  240. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  241. package/src/components/Select/Select.tsx +80 -434
  242. package/src/components/Select/context.ts +23 -0
  243. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  244. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  245. package/src/components/Select/hooks/useSelectState.ts +104 -0
  246. package/src/components/Select/index.ts +9 -1
  247. package/src/components/Select/types.ts +123 -0
  248. package/src/components/Select/utils/text.ts +26 -0
  249. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
  250. package/src/components/Switch/Switch.tsx +4 -4
  251. package/src/components/Tabs/Tabs.tsx +1 -1
  252. package/src/components/Toast/Toast.tsx +4 -0
  253. package/src/components/Tooltip/Tooltip.tsx +2 -2
  254. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  255. package/src/components/UserMenu/UserMenu.tsx +21 -18
  256. package/src/components/index.ts +7 -7
  257. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  258. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  259. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  260. package/src/hooks/index.ts +1 -2
  261. package/src/hooks/public/usePublicEvent.ts +4 -0
  262. package/src/hooks/public/usePublicEventLogo.ts +4 -0
  263. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  264. package/src/hooks/public/usePublicRouteParams.ts +4 -0
  265. package/src/hooks/services/useAuth.ts +32 -0
  266. package/src/hooks/services/useCurrentEvent.ts +6 -0
  267. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  268. package/src/hooks/useAppConfig.ts +15 -30
  269. package/src/hooks/useDebounce.ts +9 -0
  270. package/src/hooks/useEventTheme.ts +6 -0
  271. package/src/hooks/useFileDisplay.ts +81 -50
  272. package/src/hooks/useFileReference.ts +25 -7
  273. package/src/hooks/useFileUrl.ts +11 -1
  274. package/src/hooks/useFocusManagement.ts +14 -0
  275. package/src/hooks/useFocusTrap.ts +3 -0
  276. package/src/hooks/useInactivityTracker.ts +3 -0
  277. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  278. package/src/hooks/useOrganisationPermissions.ts +4 -0
  279. package/src/hooks/useOrganisationSecurity.ts +4 -0
  280. package/src/hooks/usePerformanceMonitor.ts +4 -0
  281. package/src/hooks/usePermissionCache.ts +7 -0
  282. package/src/hooks/useQueryCache.ts +12 -1
  283. package/src/hooks/useSessionRestoration.ts +4 -0
  284. package/src/hooks/useStorage.ts +4 -0
  285. package/src/hooks/useToast.ts +1 -1
  286. package/src/index.ts +6 -6
  287. package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
  288. package/src/providers/services/AuthServiceProvider.tsx +35 -7
  289. package/src/providers/services/EventServiceProvider.tsx +51 -5
  290. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  291. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  292. package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
  293. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
  294. package/src/rbac/README.md +1 -1
  295. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  296. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  297. package/src/rbac/adapters.tsx +12 -3
  298. package/src/rbac/api.test.ts +59 -51
  299. package/src/rbac/api.ts +246 -167
  300. package/src/rbac/components/NavigationProvider.tsx +4 -1
  301. package/src/rbac/components/PagePermissionGuard.tsx +185 -17
  302. package/src/rbac/components/RoleBasedRouter.tsx +5 -1
  303. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  304. package/src/rbac/components/SecureDataProvider.tsx +20 -5
  305. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  306. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  307. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  308. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  309. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  310. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  311. package/src/rbac/engine.ts +38 -14
  312. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  313. package/src/rbac/hooks/permissions/index.ts +7 -0
  314. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  315. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  316. package/src/rbac/hooks/permissions/useCan.ts +377 -0
  317. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  318. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  319. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  320. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  321. package/src/rbac/hooks/useCan.test.ts +64 -66
  322. package/src/rbac/hooks/usePermissions.ts +14 -995
  323. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  324. package/src/rbac/hooks/useRBAC.ts +36 -37
  325. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  326. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  327. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  328. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  329. package/src/rbac/hooks/useSecureSupabase.ts +27 -7
  330. package/src/rbac/index.ts +7 -0
  331. package/src/rbac/permissions.ts +0 -30
  332. package/src/rbac/secureClient.test.ts +22 -18
  333. package/src/rbac/secureClient.ts +294 -68
  334. package/src/rbac/security.ts +0 -17
  335. package/src/rbac/types.ts +9 -0
  336. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  337. package/src/rbac/utils/clientSecurity.ts +93 -0
  338. package/src/rbac/utils/contextValidator.ts +77 -168
  339. package/src/services/AuthService.ts +39 -7
  340. package/src/services/EventService.ts +186 -54
  341. package/src/services/OrganisationService.ts +81 -14
  342. package/src/services/__tests__/EventService.test.ts +1 -2
  343. package/src/services/base/BaseService.ts +3 -0
  344. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  345. package/src/theming/parseEventColours.ts +5 -19
  346. package/src/types/vitest-globals.d.ts +51 -26
  347. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  348. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  349. package/src/utils/__tests__/index.unit.test.ts +2 -2
  350. package/src/utils/audit/audit.ts +0 -3
  351. package/src/utils/core/cn.ts +1 -1
  352. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  353. package/src/utils/file-reference/index.ts +53 -1
  354. package/src/utils/formatting/formatting.ts +8 -18
  355. package/src/utils/index.ts +0 -1
  356. package/dist/chunk-3QRJFVBR.js.map +0 -1
  357. package/dist/chunk-3XTALGJF.js.map +0 -1
  358. package/dist/chunk-4N5C5XZU.js.map +0 -1
  359. package/dist/chunk-4ZC4GX36.js.map +0 -1
  360. package/dist/chunk-7D4SUZUM.js +0 -38
  361. package/dist/chunk-BYFSK72L.js.map +0 -1
  362. package/dist/chunk-EXUD6RNJ.js +0 -451
  363. package/dist/chunk-EXUD6RNJ.js.map +0 -1
  364. package/dist/chunk-GLK6VM3F.js.map +0 -1
  365. package/dist/chunk-I7PSE6JW.js.map +0 -1
  366. package/dist/chunk-JBKQ3SAO.js.map +0 -1
  367. package/dist/chunk-KNC55RTG.js.map +0 -1
  368. package/dist/chunk-LXQLPRQ2.js.map +0 -1
  369. package/dist/chunk-R77UEZ4E.js.map +0 -1
  370. package/dist/chunk-SQGMNID3.js.map +0 -1
  371. package/dist/chunk-T33XF5ZC.js +0 -12922
  372. package/dist/chunk-T33XF5ZC.js.map +0 -1
  373. package/dist/chunk-XM25TVIE.js.map +0 -1
  374. package/docs/api/classes/ColumnFactory.md +0 -243
  375. package/docs/api/classes/ErrorBoundary.md +0 -144
  376. package/docs/api/classes/InvalidScopeError.md +0 -73
  377. package/docs/api/classes/Logger.md +0 -178
  378. package/docs/api/classes/MissingUserContextError.md +0 -66
  379. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  380. package/docs/api/classes/PermissionDeniedError.md +0 -73
  381. package/docs/api/classes/RBACAuditManager.md +0 -297
  382. package/docs/api/classes/RBACCache.md +0 -322
  383. package/docs/api/classes/RBACEngine.md +0 -171
  384. package/docs/api/classes/RBACError.md +0 -76
  385. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  386. package/docs/api/classes/SecureSupabaseClient.md +0 -160
  387. package/docs/api/classes/StorageUtils.md +0 -328
  388. package/docs/api/enums/FileCategory.md +0 -184
  389. package/docs/api/enums/LogLevel.md +0 -54
  390. package/docs/api/enums/RBACErrorCode.md +0 -228
  391. package/docs/api/enums/RPCFunction.md +0 -118
  392. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  393. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  394. package/docs/api/interfaces/AggregateConfig.md +0 -43
  395. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  396. package/docs/api/interfaces/AvatarProps.md +0 -128
  397. package/docs/api/interfaces/BadgeProps.md +0 -27
  398. package/docs/api/interfaces/ButtonProps.md +0 -53
  399. package/docs/api/interfaces/CalendarProps.md +0 -70
  400. package/docs/api/interfaces/CardProps.md +0 -66
  401. package/docs/api/interfaces/ColorPalette.md +0 -7
  402. package/docs/api/interfaces/ColorShade.md +0 -66
  403. package/docs/api/interfaces/ComplianceResult.md +0 -30
  404. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  405. package/docs/api/interfaces/DataRecord.md +0 -11
  406. package/docs/api/interfaces/DataTableAction.md +0 -249
  407. package/docs/api/interfaces/DataTableColumn.md +0 -504
  408. package/docs/api/interfaces/DataTableProps.md +0 -625
  409. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  410. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  411. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  412. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  413. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  414. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  415. package/docs/api/interfaces/ExportColumn.md +0 -90
  416. package/docs/api/interfaces/ExportOptions.md +0 -126
  417. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  418. package/docs/api/interfaces/FileMetadata.md +0 -129
  419. package/docs/api/interfaces/FileReference.md +0 -118
  420. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  421. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  422. package/docs/api/interfaces/FileUploadProps.md +0 -293
  423. package/docs/api/interfaces/FooterProps.md +0 -105
  424. package/docs/api/interfaces/FormFieldProps.md +0 -166
  425. package/docs/api/interfaces/FormProps.md +0 -113
  426. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  427. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  428. package/docs/api/interfaces/InputProps.md +0 -53
  429. package/docs/api/interfaces/LabelProps.md +0 -107
  430. package/docs/api/interfaces/LoggerConfig.md +0 -62
  431. package/docs/api/interfaces/LoginFormProps.md +0 -184
  432. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  433. package/docs/api/interfaces/NavigationContextType.md +0 -164
  434. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  435. package/docs/api/interfaces/NavigationItem.md +0 -120
  436. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  437. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  438. package/docs/api/interfaces/Organisation.md +0 -140
  439. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  440. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  441. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  442. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  443. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
  444. package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
  445. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  446. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  447. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  448. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  449. package/docs/api/interfaces/PaletteData.md +0 -41
  450. package/docs/api/interfaces/ParsedAddress.md +0 -120
  451. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  452. package/docs/api/interfaces/ProgressProps.md +0 -42
  453. package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
  454. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  455. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  456. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
  457. package/docs/api/interfaces/QuickFix.md +0 -52
  458. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  459. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  460. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  461. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  462. package/docs/api/interfaces/RBACConfig.md +0 -133
  463. package/docs/api/interfaces/RBACContext.md +0 -52
  464. package/docs/api/interfaces/RBACLogger.md +0 -112
  465. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  466. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  467. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  468. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  469. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  470. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  471. package/docs/api/interfaces/RBACResult.md +0 -58
  472. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  473. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  474. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  475. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  476. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  477. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  478. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  479. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  480. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  481. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  482. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  483. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  484. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  485. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  486. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  487. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  488. package/docs/api/interfaces/RouteConfig.md +0 -134
  489. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  490. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  491. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  492. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  493. package/docs/api/interfaces/SetupIssue.md +0 -41
  494. package/docs/api/interfaces/StorageConfig.md +0 -41
  495. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  496. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  497. package/docs/api/interfaces/StorageListOptions.md +0 -99
  498. package/docs/api/interfaces/StorageListResult.md +0 -41
  499. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  500. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  501. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  502. package/docs/api/interfaces/StyleImport.md +0 -19
  503. package/docs/api/interfaces/SwitchProps.md +0 -34
  504. package/docs/api/interfaces/TabsContentProps.md +0 -9
  505. package/docs/api/interfaces/TabsListProps.md +0 -9
  506. package/docs/api/interfaces/TabsProps.md +0 -9
  507. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  508. package/docs/api/interfaces/TextareaProps.md +0 -53
  509. package/docs/api/interfaces/ToastActionElement.md +0 -9
  510. package/docs/api/interfaces/ToastProps.md +0 -9
  511. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
  512. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
  513. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  514. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  515. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
  516. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  517. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  518. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
  519. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  520. package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
  521. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  522. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
  523. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
  524. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  525. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  526. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  527. package/docs/api/interfaces/UserEventAccess.md +0 -118
  528. package/docs/api/interfaces/UserMenuProps.md +0 -86
  529. package/docs/api/interfaces/UserProfile.md +0 -63
  530. package/docs/migration/quick-migration-guide.md +0 -356
  531. package/docs/migration/service-architecture.md +0 -281
  532. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  533. package/src/components/EventSelector/EventSelector.tsx +0 -420
  534. package/src/components/EventSelector/index.ts +0 -3
  535. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  536. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
  537. package/src/components/OrganisationSelector/index.ts +0 -9
  538. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  539. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  540. package/src/hooks/useSecureDataAccess.ts +0 -681
  541. /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
  542. /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  543. /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
  544. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  545. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  546. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  547. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  548. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  549. /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
@@ -23,6 +23,8 @@ type AuthStateSubscription = {
23
23
  };
24
24
 
25
25
  export class AuthService extends BaseService implements IAuthService {
26
+ private static instanceCount = 0;
27
+ private instanceId: number;
26
28
  private user: User | null = null;
27
29
  private session: Session | null = null;
28
30
  private authLoading = false;
@@ -43,12 +45,23 @@ export class AuthService extends BaseService implements IAuthService {
43
45
 
44
46
  constructor(supabaseClient: SupabaseClient, appName?: string) {
45
47
  super();
48
+ this.instanceId = ++AuthService.instanceCount;
46
49
  this.supabaseClient = supabaseClient;
47
50
  this.appName = appName;
51
+ logger.debug('AuthService', `Instance created [ID:${this.instanceId}]`, { appName });
52
+ }
53
+
54
+ getInstanceId(): number {
55
+ return this.instanceId;
48
56
  }
49
57
 
50
58
  // Auth state getters
51
59
  getUser(): User | null {
60
+ if (this.user) {
61
+ logger.debug('AuthService', `getUser() [ID:${this.instanceId}] returning user: ${this.user.id}`);
62
+ } else {
63
+ logger.debug('AuthService', `getUser() [ID:${this.instanceId}] returning null`);
64
+ }
52
65
  return this.user;
53
66
  }
54
67
 
@@ -398,6 +411,11 @@ export class AuthService extends BaseService implements IAuthService {
398
411
  const subscription = this.supabaseClient.auth.onAuthStateChange(
399
412
  (event: AuthChangeEvent, session: SupabaseSession | null) => {
400
413
  try {
414
+ logger.debug('AuthService', `Auth state change [ID:${this.instanceId}]`, {
415
+ event,
416
+ hasSession: !!session,
417
+ userId: session?.user?.id
418
+ });
401
419
  // Handle different auth events
402
420
  if (event === 'SIGNED_OUT') {
403
421
  this.session = null;
@@ -407,15 +425,15 @@ export class AuthService extends BaseService implements IAuthService {
407
425
  // Automatic session tracking (non-blocking)
408
426
  if (session?.user) {
409
427
  this.trackSession('logout', session).catch(err => {
410
- logger.warn('AuthService', 'Failed to track logout session:', err);
428
+ logger.warn('AuthService', `Failed to track logout session [ID:${this.instanceId}]:`, err);
411
429
  });
412
430
  }
413
431
  } else if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
414
432
  this.session = session;
415
433
  this.user = session?.user ?? null;
416
434
 
417
- // Only clear auth error if we have a valid session
418
- if (session) {
435
+ // Ensure state is set before non-blocking tracking calls
436
+ if (session?.user) {
419
437
  this.authError = null;
420
438
  }
421
439
 
@@ -423,7 +441,7 @@ export class AuthService extends BaseService implements IAuthService {
423
441
  // Only track on SIGNED_IN, not TOKEN_REFRESHED (to avoid duplicate login records)
424
442
  if (event === 'SIGNED_IN' && session?.user) {
425
443
  this.trackSession('login', session).catch(err => {
426
- logger.warn('AuthService', 'Failed to track login session:', err);
444
+ logger.warn('AuthService', `Failed to track login session [ID:${this.instanceId}]:`, err);
427
445
  });
428
446
  }
429
447
  } else if (event === 'INITIAL_SESSION') {
@@ -452,16 +470,30 @@ export class AuthService extends BaseService implements IAuthService {
452
470
  // This ensures ProtectedRoute waits for session restoration to complete
453
471
  // before checking authentication state
454
472
  this.authLoading = false;
473
+
474
+ // Final check: Ensure state matches what we just logged
475
+ logger.debug('AuthService', `State synchronized after INITIAL_SESSION [ID:${this.instanceId}]`, {
476
+ hasUser: !!this.user,
477
+ userId: this.user?.id
478
+ });
479
+
455
480
  this.notify();
456
481
  return; // Return early to avoid setting loading to false again below
457
482
  }
458
483
 
459
- // For other events (SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED), set loading to false
460
- // INITIAL_SESSION is handled above and returns early
484
+ // For other events (SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED), set loading to false and notify
461
485
  this.authLoading = false;
486
+
487
+ // Final check: Ensure state matches what we just logged
488
+ logger.debug('AuthService', `State synchronized after event [ID:${this.instanceId}]`, {
489
+ event,
490
+ hasUser: !!this.user,
491
+ userId: this.user?.id
492
+ });
493
+
462
494
  this.notify();
463
495
  } catch (error) {
464
- logger.warn('AuthService', 'Error in auth state change handler:', error);
496
+ logger.warn('AuthService', `Error in auth state change handler [ID:${this.instanceId}]:`, error);
465
497
  this.authLoading = false;
466
498
  this.notify();
467
499
  }
@@ -16,11 +16,12 @@ import { Organisation } from '../types/organisation';
16
16
  import { assertOrganisationId } from '../types/core';
17
17
  import { logger } from '../utils/core/logger';
18
18
  import { secureStorage } from '../utils/security/secureStorage';
19
- import { isSuperAdmin, getAppConfigByName } from '../rbac/api';
19
+ import { isSuperAdmin } from '../rbac/api';
20
20
  import type { UUID } from '../rbac/types';
21
- import type { AppConfig } from '../rbac/utils/contextValidator';
22
21
 
23
22
  export class EventService extends BaseService implements IEventService {
23
+ private static instanceCount = 0;
24
+ private instanceId: number;
24
25
  private events: Event[] = [];
25
26
  private selectedEvent: Event | null = null;
26
27
  private _isLoading = false; // Start as false to avoid blocking UI
@@ -34,7 +35,7 @@ export class EventService extends BaseService implements IEventService {
34
35
  private selectedOrganisation: Organisation | null = null;
35
36
  private setSelectedEventId: ((eventId: string | null) => void) | null = null;
36
37
  private isSuperAdmin: boolean = false; // Track super admin status for conditional validation
37
- private appConfig: AppConfig | null = null; // Cache app config to avoid repeated lookups
38
+ // App config removed - scope is now page-level only (rbac_app_pages.scope_type)
38
39
 
39
40
  // Internal state management
40
41
  private isInitializedRef = false;
@@ -51,12 +52,22 @@ export class EventService extends BaseService implements IEventService {
51
52
  setSelectedEventId: (eventId: string | null) => void
52
53
  ) {
53
54
  super();
55
+ this.instanceId = ++EventService.instanceCount;
54
56
  this.supabaseClient = supabaseClient;
55
57
  this.user = user;
56
58
  this.session = session;
57
59
  this.appName = appName;
58
60
  this.selectedOrganisation = selectedOrganisation;
59
61
  this.setSelectedEventId = setSelectedEventId;
62
+ logger.debug('EventService', `Instance created [ID:${this.instanceId}]`, {
63
+ appName,
64
+ hasUser: !!user,
65
+ userId: user?.id
66
+ });
67
+ }
68
+
69
+ getInstanceId(): number {
70
+ return this.instanceId;
60
71
  }
61
72
 
62
73
  // Helper method to get user-scoped storage key
@@ -97,6 +108,12 @@ export class EventService extends BaseService implements IEventService {
97
108
  this.resetInitialization();
98
109
  this.isInitializedRef = false;
99
110
  this.isFetchingRef = false;
111
+
112
+ logger.debug('EventService', `User changed [ID:${this.instanceId}]`, {
113
+ previousUserId,
114
+ newUserId,
115
+ willInitialize: newUserId !== null
116
+ });
100
117
  }
101
118
 
102
119
  this.supabaseClient = supabaseClient;
@@ -106,19 +123,43 @@ export class EventService extends BaseService implements IEventService {
106
123
  this.selectedOrganisation = selectedOrganisation;
107
124
  this.setSelectedEventId = setSelectedEventId;
108
125
 
109
- // Clear app config cache when app name changes
110
- if (previousAppName !== appName) {
111
- this.appConfig = null;
112
- }
126
+ // App name changed - state will be reset by updateDependencies
113
127
 
114
128
  // Update super admin status when user changes
115
129
  // This allows super admins to select events from any organisation
130
+ // RBAC should be initialized synchronously by UnifiedAuthProvider, so this should always work
116
131
  if (user?.id) {
117
132
  try {
118
- this.isSuperAdmin = await isSuperAdmin(user.id as UUID);
133
+ const { isRBACInitialized, isSuperAdmin: checkSuperAdmin, setupRBAC } = await import('../rbac/api');
134
+
135
+ // Ensure RBAC is initialized if possible (defensive check for edge cases)
136
+ if (!isRBACInitialized() && this.supabaseClient) {
137
+ setupRBAC(this.supabaseClient);
138
+ }
139
+
140
+ if (isRBACInitialized()) {
141
+ this.isSuperAdmin = await checkSuperAdmin(user.id as UUID);
142
+ logger.debug('EventService', 'Super admin status updated in updateDependencies', {
143
+ userId: user.id,
144
+ isSuperAdmin: this.isSuperAdmin
145
+ });
146
+ } else {
147
+ // RBAC not initialized - this should be rare since UnifiedAuthProvider initializes it synchronously
148
+ // Keep existing value (don't reset to false) to avoid clearing a previously determined super admin status
149
+ logger.warn('EventService', 'RBAC not initialized in updateDependencies, keeping existing super admin status', {
150
+ userId: user.id,
151
+ existingIsSuperAdmin: this.isSuperAdmin,
152
+ note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
153
+ });
154
+ }
119
155
  } catch (error) {
120
- logger.warn('EventService', 'Failed to check super admin status', { error });
121
- this.isSuperAdmin = false; // Default to false on error
156
+ logger.warn('EventService', 'Failed to check super admin status in updateDependencies', {
157
+ error,
158
+ userId: user.id,
159
+ existingIsSuperAdmin: this.isSuperAdmin
160
+ });
161
+ // Don't reset to false on error - keep existing value to avoid blocking super admins
162
+ // The error might be transient, and we don't want to clear a valid super admin status
122
163
  }
123
164
  } else {
124
165
  this.isSuperAdmin = false;
@@ -131,14 +172,26 @@ export class EventService extends BaseService implements IEventService {
131
172
  this.resetInitialization(); // Reset BaseService's isInitialized flag
132
173
  this.isInitializedRef = false;
133
174
  this.isFetchingRef = false;
175
+
176
+ // SECURITY: Super admins can see events regardless of organisation context
177
+ // Do not clear events for super admins when organisation context is removed
178
+ const shouldClearEvents = !this.isSuperAdmin;
179
+
134
180
  // Clear events ONLY when switching between different organisations (not when org first becomes available)
135
181
  if (previousOrgId !== null && newOrgId !== null && previousOrgId !== newOrgId) {
136
- this.events = [];
137
- this.selectedEvent = null;
182
+ if (shouldClearEvents) {
183
+ this.events = [];
184
+ // Use setSelectedEvent(null) to preserve userClearedEventRef flag if user explicitly cleared
185
+ // This prevents auto-selection from re-selecting the event after org switch
186
+ this.setSelectedEvent(null);
187
+ }
138
188
  } else if (previousOrgId !== null && newOrgId === null) {
139
- // Organisation was removed - clear events
140
- this.events = [];
141
- this.selectedEvent = null;
189
+ // Organisation was removed - clear events if not super admin
190
+ if (shouldClearEvents) {
191
+ this.events = [];
192
+ // Use setSelectedEvent(null) to preserve userClearedEventRef flag if user explicitly cleared
193
+ this.setSelectedEvent(null);
194
+ }
142
195
  }
143
196
  }
144
197
 
@@ -220,7 +273,7 @@ export class EventService extends BaseService implements IEventService {
220
273
  const persistedEvent = events.find(event => event.event_id === persistedEventId);
221
274
 
222
275
  if (persistedEvent) {
223
- // Use setSelectedEvent() to go through same path as EventSelector
276
+ // Use setSelectedEvent() to ensure consistent behavior
224
277
  // This ensures consistent behavior and proper notification
225
278
  // Theme will be applied by useEventTheme hook once user navigates away from login
226
279
  this.setSelectedEvent(persistedEvent);
@@ -315,11 +368,13 @@ export class EventService extends BaseService implements IEventService {
315
368
  protected async doInitialize(): Promise<void> {
316
369
  // Skip if already initialized
317
370
  if (this.isInitializedRef) {
371
+ logger.debug('EventService', 'Skipping initialization - already initialized');
318
372
  return;
319
373
  }
320
374
 
321
375
  // Skip if already fetching
322
376
  if (this.isFetchingRef) {
377
+ logger.debug('EventService', 'Skipping initialization - already fetching');
323
378
  return;
324
379
  }
325
380
 
@@ -336,14 +391,27 @@ export class EventService extends BaseService implements IEventService {
336
391
  // For event-required apps, selectedOrganisation may be null (org derived from event)
337
392
  // For org-required apps, selectedOrganisation is required
338
393
  if (!this.user) {
394
+ logger.debug('EventService', 'Skipping initialization - no user');
339
395
  return;
340
396
  }
341
397
 
398
+ logger.debug('EventService', 'Initializing', {
399
+ userId: this.user.id,
400
+ appName: this.appName,
401
+ hasSelectedOrganisation: !!this.selectedOrganisation,
402
+ hasSupabaseClient: !!this.supabaseClient,
403
+ hasSession: !!this.session
404
+ });
405
+
342
406
  // Initial setup - fetch events on initialization
343
407
  await this.fetchEvents(false);
344
408
 
345
409
  // Mark as initialized after successful fetch
346
410
  this.isInitializedRef = true;
411
+ logger.debug('EventService', 'Initialization complete', {
412
+ eventsCount: this.events.length,
413
+ hasError: !!this.error
414
+ });
347
415
  }
348
416
 
349
417
  protected doCleanup(): void {
@@ -372,18 +440,7 @@ export class EventService extends BaseService implements IEventService {
372
440
  let isMounted = true;
373
441
 
374
442
  try {
375
- // Load app config if not already cached (only once per app)
376
- if (!this.appConfig && this.appName) {
377
- try {
378
- this.appConfig = await getAppConfigByName(this.appName);
379
- } catch (configError) {
380
- logger.warn('EventService', 'Failed to load app config, defaulting to event-required', {
381
- error: configError
382
- });
383
- // Default to event-required for safety
384
- this.appConfig = { requires_event: true };
385
- }
386
- }
443
+ // Scope is now page-level only - no app-level config needed
387
444
 
388
445
  // Determine organisationId for RPC call
389
446
  // For event-required apps: org is derived from selectedEvent (if available), or null to get all accessible events
@@ -392,58 +449,119 @@ export class EventService extends BaseService implements IEventService {
392
449
  let organisationIdForRpc: string | null = null;
393
450
 
394
451
  // Check if user is super admin first
395
- let userIsSuperAdmin = false;
452
+ // RBAC should be initialized synchronously by UnifiedAuthProvider before EventService is used
453
+ // Use cached value as fallback for edge cases (e.g., EventService used outside UnifiedAuthProvider)
454
+ let userIsSuperAdmin = this.isSuperAdmin; // Start with cached value from updateDependencies
396
455
  try {
397
- userIsSuperAdmin = await isSuperAdmin(this.user.id as UUID);
456
+ const { isRBACInitialized, isSuperAdmin: checkSuperAdmin, setupRBAC } = await import('../rbac/api');
457
+
458
+ // Ensure RBAC is initialized if possible (defensive check for edge cases)
459
+ if (!isRBACInitialized() && this.supabaseClient) {
460
+ setupRBAC(this.supabaseClient);
461
+ }
462
+
463
+ // Check super admin status if RBAC is ready
464
+ // RBAC should always be initialized by UnifiedAuthProvider, but we check defensively
465
+ if (isRBACInitialized()) {
466
+ userIsSuperAdmin = await checkSuperAdmin(this.user.id as UUID);
467
+ // Update cached value for future use
468
+ this.isSuperAdmin = userIsSuperAdmin;
469
+ logger.debug('EventService', 'Super admin check completed', {
470
+ userId: this.user.id,
471
+ isSuperAdmin: userIsSuperAdmin
472
+ });
473
+ } else {
474
+ // RBAC not initialized - this should be rare since UnifiedAuthProvider initializes it synchronously
475
+ // Use cached value from updateDependencies as fallback
476
+ // If cached value is true, trust it to avoid blocking super admins
477
+ if (this.isSuperAdmin) {
478
+ userIsSuperAdmin = true;
479
+ logger.warn('EventService', 'RBAC not initialized, using cached super admin status', {
480
+ userId: this.user.id,
481
+ cachedIsSuperAdmin: this.isSuperAdmin,
482
+ note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
483
+ });
484
+ } else {
485
+ logger.warn('EventService', 'RBAC not initialized, using cached non-super-admin status', {
486
+ userId: this.user.id,
487
+ cachedIsSuperAdmin: this.isSuperAdmin,
488
+ note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
489
+ });
490
+ }
491
+ }
492
+
398
493
  if (userIsSuperAdmin) {
399
494
  // Super admin: Pass null to see all events across all organisations
400
495
  organisationIdForRpc = null;
401
496
  } else {
402
- // Not super admin: determine org from context based on app type
497
+ // Not super admin: determine org from available context
498
+ // Scope is now page-level only - use whatever context is available
403
499
  if (this.selectedEvent) {
404
500
  // If event is already selected, use its organisation
405
501
  organisationIdForRpc = this.selectedEvent.organisation_id;
406
- } else if (this.appConfig?.requires_event === true) {
407
- // Event-required app with no selected event yet: pass null to get all accessible events
408
- // The RPC will filter by event app roles, returning all events the user has access to
409
- organisationIdForRpc = null;
410
502
  } else if (this.selectedOrganisation) {
411
- // Org-required app: use selected organisation
503
+ // Use selected organisation
412
504
  organisationIdForRpc = this.selectedOrganisation.id;
413
505
  } else {
414
- // No context available - this shouldn't happen for authenticated users
415
- logger.warn('EventService', 'No organisation context available for event fetch', {
506
+ // No context available - pass null to get all accessible events via event-app roles
507
+ // This allows users with event-app roles to see their events even without org context
508
+ logger.debug('EventService', 'No organisation context available, fetching all accessible events', {
416
509
  hasSelectedEvent: !!this.selectedEvent,
417
- hasSelectedOrganisation: !!this.selectedOrganisation,
418
- appRequiresEvent: this.appConfig?.requires_event
510
+ hasSelectedOrganisation: !!this.selectedOrganisation
419
511
  });
420
- organisationIdForRpc = null; // Will return empty list
512
+ organisationIdForRpc = null; // Will return events user has access to via event-app roles
421
513
  }
422
514
  }
423
515
  } catch (superAdminCheckError) {
424
- // If super admin check fails, fall back to organisation-scoped query
425
- logger.warn('EventService', 'Failed to check super admin status, using organisation-scoped query', {
426
- error: superAdminCheckError
427
- });
428
- // Fallback: use available context
429
- if (this.selectedEvent) {
430
- organisationIdForRpc = this.selectedEvent.organisation_id;
431
- } else if (this.appConfig?.requires_event === true) {
432
- // Event-required app: pass null to get all accessible events
433
- organisationIdForRpc = null;
434
- } else if (this.selectedOrganisation) {
435
- organisationIdForRpc = this.selectedOrganisation.id;
516
+ // If super admin check fails, use cached value as fallback
517
+ // If cached value is true, trust it to avoid blocking super admins
518
+ if (this.isSuperAdmin) {
519
+ userIsSuperAdmin = true;
520
+ organisationIdForRpc = null; // Super admin gets all events
521
+ logger.warn('EventService', 'Super admin check failed, using cached super admin status', {
522
+ error: superAdminCheckError,
523
+ cachedIsSuperAdmin: this.isSuperAdmin
524
+ });
525
+ } else {
526
+ // Fallback: use available context
527
+ logger.warn('EventService', 'Failed to check super admin status, using organisation-scoped query', {
528
+ error: superAdminCheckError,
529
+ cachedIsSuperAdmin: this.isSuperAdmin
530
+ });
531
+ if (this.selectedEvent) {
532
+ organisationIdForRpc = this.selectedEvent.organisation_id;
533
+ } else if (this.selectedOrganisation) {
534
+ organisationIdForRpc = this.selectedOrganisation.id;
535
+ } else {
536
+ // No context - pass null to get all accessible events via event-app roles
537
+ organisationIdForRpc = null;
538
+ }
436
539
  }
437
540
  }
438
541
 
439
542
  // Call the RPC function following the established pattern
440
543
  // For super admins, pass null for p_organisation_id to see all events
544
+ logger.debug('EventService', 'Fetching events', {
545
+ userId: this.user.id,
546
+ organisationIdForRpc,
547
+ appName: this.appName,
548
+ hasSelectedEvent: !!this.selectedEvent,
549
+ hasSelectedOrganisation: !!this.selectedOrganisation,
550
+ isSuperAdmin: userIsSuperAdmin
551
+ });
552
+
441
553
  let { data, error: rpcError } = await this.supabaseClient.rpc('data_user_events_get', {
442
554
  p_user_id: this.user.id,
443
555
  p_organisation_id: organisationIdForRpc,
444
556
  p_app_name: this.appName
445
557
  });
446
558
 
559
+ logger.debug('EventService', 'RPC response', {
560
+ dataLength: data?.length || 0,
561
+ hasError: !!rpcError,
562
+ error: rpcError
563
+ });
564
+
447
565
  if (rpcError) {
448
566
  logger.error('EventService', 'RPC error fetching events:', rpcError);
449
567
  throw new Error(rpcError.message || 'Failed to fetch events');
@@ -482,7 +600,21 @@ export class EventService extends BaseService implements IEventService {
482
600
  updated_at: new Date().toISOString()
483
601
  }));
484
602
 
485
- this.events = transformedEvents;
603
+ // Sort events by event_date descending (newest first)
604
+ // Handle null dates by putting them at the end
605
+ const sortedEvents = [...transformedEvents].sort((a, b) => {
606
+ // If both have dates, sort descending (newest first)
607
+ if (a.event_date && b.event_date) {
608
+ return new Date(b.event_date).getTime() - new Date(a.event_date).getTime();
609
+ }
610
+ // If only one has a date, prioritize the one with a date
611
+ if (a.event_date && !b.event_date) return -1;
612
+ if (!a.event_date && b.event_date) return 1;
613
+ // If neither has a date, maintain original order
614
+ return 0;
615
+ });
616
+
617
+ this.events = sortedEvents;
486
618
  this.error = null;
487
619
 
488
620
  // Reset auto-selection ref for new events
@@ -20,7 +20,6 @@ import type {
20
20
  import { setOrganisationContext } from '../utils/context/organisationContext';
21
21
  import { logger } from '../utils/core/logger';
22
22
  import { assertUserId, assertOrganisationId } from '../types/core';
23
- import { isSuperAdmin } from '../rbac/api';
24
23
  import type { UUID } from '../rbac/types';
25
24
 
26
25
  // Type for RPC response from data_user_organisation_roles_get
@@ -42,6 +41,8 @@ interface OrganisationRoleRpcResponse {
42
41
  }
43
42
 
44
43
  export class OrganisationService extends BaseService implements IOrganisationService {
44
+ private static instanceCount = 0;
45
+ private instanceId: number;
45
46
  private _selectedOrganisation: Organisation | null = null;
46
47
  private _organisations: Organisation[] = [];
47
48
  private _userMemberships: OrganisationMembership[] = [];
@@ -65,9 +66,18 @@ export class OrganisationService extends BaseService implements IOrganisationSer
65
66
 
66
67
  constructor(supabaseClient: SupabaseClient, user: User | null, session: Session | null) {
67
68
  super();
69
+ this.instanceId = ++OrganisationService.instanceCount;
68
70
  this.supabaseClient = supabaseClient;
69
71
  this.user = user;
70
72
  this.session = session;
73
+ logger.debug('OrganisationService', `Instance created [ID:${this.instanceId}]`, {
74
+ hasUser: !!user,
75
+ userId: user?.id
76
+ });
77
+ }
78
+
79
+ getInstanceId(): number {
80
+ return this.instanceId;
71
81
  }
72
82
 
73
83
  // Interface implementation
@@ -144,23 +154,43 @@ export class OrganisationService extends BaseService implements IOrganisationSer
144
154
 
145
155
  // Update dependencies
146
156
  updateDependencies(user: User | null, session: Session | null): void {
147
- const wasAuthenticated = !!(this.user && this.session);
148
- const isAuthenticated = !!(user && session);
157
+ const previousUserId = this.user?.id || null;
158
+ const newUserId = user?.id || null;
149
159
 
150
- // Reset super admin cache when user changes
151
- if (this.user?.id !== user?.id) {
160
+ // Only reset if the User ID has actually changed (null -> ID or ID -> different ID)
161
+ const userChanged = previousUserId !== newUserId;
162
+ const needsRetry = this._error !== null && !this.isLoadingRef;
163
+ const isEmpty = newUserId !== null && this._organisations.length === 0 && !this.isLoadingRef;
164
+
165
+ if (userChanged || needsRetry || isEmpty) {
166
+ if (userChanged) {
167
+ logger.debug('OrganisationService', `User changed [ID:${this.instanceId}], resetting initialization`, {
168
+ previousUserId,
169
+ newUserId
170
+ });
171
+ } else if (needsRetry) {
172
+ logger.debug('OrganisationService', `Previous error detected [ID:${this.instanceId}], retrying initialization`);
173
+ } else if (isEmpty) {
174
+ logger.debug('OrganisationService', `No organisations found [ID:${this.instanceId}], retrying initialization`);
175
+ }
176
+
152
177
  this._isSuperAdmin = false;
178
+ this.resetInitialization();
179
+
180
+ // Only clear all state if the user actually changed
181
+ if (userChanged) {
182
+ this._organisations = [];
183
+ this._userMemberships = [];
184
+ this._roleMapState = new Map();
185
+ this._selectedOrganisation = null;
186
+ this._isContextReady = false;
187
+ }
188
+ this.lastLoadTimeRef = 0; // Allow immediate reload
153
189
  }
154
190
 
155
191
  this.user = user;
156
192
  this.session = session;
157
193
 
158
- // If user logs out, allow re-initialization when they log back in
159
- if (wasAuthenticated && !isAuthenticated) {
160
- // Reset BaseService initialization state to allow re-initialization
161
- this.resetInitialization();
162
- }
163
-
164
194
  this.notify();
165
195
  }
166
196
 
@@ -260,6 +290,13 @@ export class OrganisationService extends BaseService implements IOrganisationSer
260
290
 
261
291
  // Lifecycle methods
262
292
  async initialize(): Promise<void> {
293
+ // SECURITY: Only initialize if we have an authenticated user
294
+ // This prevents premature initialization during early auth states
295
+ if (!this.user) {
296
+ logger.debug('OrganisationService', 'Skipping initialization - no user');
297
+ return;
298
+ }
299
+
263
300
  await super.initialize();
264
301
 
265
302
  // Don't load if already loading (prevents duplicate loads during rapid auth events)
@@ -351,8 +388,9 @@ export class OrganisationService extends BaseService implements IOrganisationSer
351
388
  }
352
389
 
353
390
  // Prevent rapid retries - minimum 2 seconds between attempts
391
+ // Skip this check if we don't have any organisations yet
354
392
  const now = Date.now();
355
- if (now - this.lastLoadTimeRef < 2000) {
393
+ if (this._organisations.length > 0 && now - this.lastLoadTimeRef < 2000) {
356
394
  // Ensure loading state is correct
357
395
  if (this._organisations.length > 0 || this._selectedOrganisation) {
358
396
  this._isLoading = false;
@@ -377,6 +415,10 @@ export class OrganisationService extends BaseService implements IOrganisationSer
377
415
  this._isLoading = true;
378
416
  this._error = null;
379
417
  this.notify();
418
+
419
+ logger.debug('OrganisationService', 'Loading organisations for user', {
420
+ userId: this.user.id
421
+ });
380
422
 
381
423
  try {
382
424
  // Get user's organisation roles directly from rbac_organisation_roles table
@@ -458,6 +500,11 @@ export class OrganisationService extends BaseService implements IOrganisationSer
458
500
 
459
501
  organisations = Array.from(organisationsMap.values());
460
502
 
503
+ logger.debug('OrganisationService', 'Query results', {
504
+ membershipsCount: memberships.length,
505
+ organisationsCount: organisations.length
506
+ });
507
+
461
508
  // Extract organisations from join results
462
509
  } catch (queryError) {
463
510
  // Extract error message properly from Supabase error objects
@@ -476,7 +523,20 @@ export class OrganisationService extends BaseService implements IOrganisationSer
476
523
  let userIsSuperAdmin = false;
477
524
  if (this.user?.id) {
478
525
  try {
479
- userIsSuperAdmin = await isSuperAdmin(this.user.id as UUID);
526
+ // Dynamic import to avoid circular dependencies and ensure RBAC is initialized
527
+ const { isSuperAdmin: checkSuperAdmin, isRBACInitialized, setupRBAC } = await import('../rbac/api');
528
+
529
+ // Ensure RBAC is initialized if possible
530
+ if (!isRBACInitialized() && this.supabaseClient) {
531
+ setupRBAC(this.supabaseClient);
532
+ }
533
+
534
+ if (isRBACInitialized()) {
535
+ userIsSuperAdmin = await checkSuperAdmin(this.user.id as UUID);
536
+ } else {
537
+ userIsSuperAdmin = false;
538
+ }
539
+
480
540
  this._isSuperAdmin = userIsSuperAdmin; // Cache the result
481
541
  } catch (error) {
482
542
  logger.warn('OrganisationService', 'Failed to check super admin status', { error });
@@ -542,7 +602,14 @@ export class OrganisationService extends BaseService implements IOrganisationSer
542
602
  throw new Error('User has no access to active organisations') as OrganisationSecurityError;
543
603
  }
544
604
 
545
- this._organisations = activeOrgs;
605
+ // Sort organisations alphabetically by display_name
606
+ const sortedOrgs = [...activeOrgs].sort((a, b) => {
607
+ const nameA = (a.display_name || a.name || '').toLowerCase();
608
+ const nameB = (b.display_name || b.name || '').toLowerCase();
609
+ return nameA.localeCompare(nameB);
610
+ });
611
+
612
+ this._organisations = sortedOrgs;
546
613
  // Memberships already have branded types from earlier mapping
547
614
  this._userMemberships = memberships as OrganisationMembership[];
548
615