@jmruthers/pace-core 0.5.193 → 0.6.2

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 (577) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +299 -0
  4. package/cursor-rules/01-standards-compliance.mdc +244 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +222 -0
  7. package/cursor-rules/04-testing-standards.mdc +268 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +309 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
  11. package/cursor-rules/08-markup-quality.mdc +452 -0
  12. package/cursor-rules/CHANGELOG.md +119 -0
  13. package/cursor-rules/README.md +192 -0
  14. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
  15. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
  16. package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
  17. package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
  18. package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
  19. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
  20. package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
  21. package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
  22. package/dist/chunk-24UVZUZG.js.map +1 -0
  23. package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
  24. package/dist/chunk-2UOI2FG5.js.map +1 -0
  25. package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
  26. package/dist/chunk-3XC4CPTD.js.map +1 -0
  27. package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
  28. package/dist/chunk-6J4GEEJR.js.map +1 -0
  29. package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
  30. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  31. package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
  32. package/dist/chunk-EHMR7VYL.js.map +1 -0
  33. package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
  34. package/dist/chunk-F2IMUDXZ.js.map +1 -0
  35. package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
  36. package/dist/chunk-FFQEQTNW.js.map +1 -0
  37. package/dist/chunk-FMUCXFII.js +76 -0
  38. package/dist/chunk-FMUCXFII.js.map +1 -0
  39. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  40. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  41. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  42. package/dist/chunk-L4OXEN46.js.map +1 -0
  43. package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
  44. package/dist/chunk-M43Y4SSO.js.map +1 -0
  45. package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
  46. package/dist/chunk-MMZ7JXPU.js.map +1 -0
  47. package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
  48. package/dist/chunk-NECFR5MM.js.map +1 -0
  49. package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
  50. package/dist/chunk-SFZUDBL5.js.map +1 -0
  51. package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
  52. package/dist/chunk-XWQCNGTQ.js.map +1 -0
  53. package/dist/components.d.ts +6 -6
  54. package/dist/components.js +15 -12
  55. package/dist/components.js.map +1 -1
  56. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  57. package/dist/hooks.d.ts +59 -126
  58. package/dist/hooks.js +19 -28
  59. package/dist/hooks.js.map +1 -1
  60. package/dist/index.d.ts +63 -16
  61. package/dist/index.js +23 -24
  62. package/dist/index.js.map +1 -1
  63. package/dist/providers.d.ts +21 -3
  64. package/dist/providers.js +2 -2
  65. package/dist/rbac/index.d.ts +146 -115
  66. package/dist/rbac/index.js +8 -11
  67. package/dist/theming/runtime.d.ts +1 -13
  68. package/dist/theming/runtime.js +1 -1
  69. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  70. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  71. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  72. package/dist/types.d.ts +2 -2
  73. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
  74. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  75. package/dist/utils.d.ts +4 -5
  76. package/dist/utils.js +15 -15
  77. package/dist/utils.js.map +1 -1
  78. package/docs/api/README.md +7 -1
  79. package/docs/api/classes/ColumnFactory.md +8 -8
  80. package/docs/api/classes/InvalidScopeError.md +4 -4
  81. package/docs/api/classes/Logger.md +1 -1
  82. package/docs/api/classes/MissingUserContextError.md +4 -4
  83. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  84. package/docs/api/classes/PermissionDeniedError.md +4 -4
  85. package/docs/api/classes/RBACAuditManager.md +1 -1
  86. package/docs/api/classes/RBACCache.md +1 -1
  87. package/docs/api/classes/RBACEngine.md +1 -1
  88. package/docs/api/classes/RBACError.md +4 -4
  89. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  90. package/docs/api/classes/SecureSupabaseClient.md +18 -15
  91. package/docs/api/classes/StorageUtils.md +1 -1
  92. package/docs/api/enums/FileCategory.md +1 -1
  93. package/docs/api/enums/LogLevel.md +1 -1
  94. package/docs/api/enums/RBACErrorCode.md +1 -1
  95. package/docs/api/enums/RPCFunction.md +1 -1
  96. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  97. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  98. package/docs/api/interfaces/AggregateConfig.md +4 -4
  99. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  100. package/docs/api/interfaces/AvatarProps.md +1 -1
  101. package/docs/api/interfaces/BadgeProps.md +9 -2
  102. package/docs/api/interfaces/ButtonProps.md +7 -4
  103. package/docs/api/interfaces/CalendarProps.md +8 -5
  104. package/docs/api/interfaces/CardProps.md +8 -5
  105. package/docs/api/interfaces/ColorPalette.md +1 -1
  106. package/docs/api/interfaces/ColorShade.md +1 -1
  107. package/docs/api/interfaces/ComplianceResult.md +1 -1
  108. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  109. package/docs/api/interfaces/DataRecord.md +1 -1
  110. package/docs/api/interfaces/DataTableAction.md +24 -21
  111. package/docs/api/interfaces/DataTableColumn.md +31 -31
  112. package/docs/api/interfaces/DataTableProps.md +1 -1
  113. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  114. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  115. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  116. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  117. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  118. package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
  119. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
  120. package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
  121. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  122. package/docs/api/interfaces/ExportColumn.md +1 -1
  123. package/docs/api/interfaces/ExportOptions.md +8 -8
  124. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  125. package/docs/api/interfaces/FileMetadata.md +1 -1
  126. package/docs/api/interfaces/FileReference.md +1 -1
  127. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  128. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  129. package/docs/api/interfaces/FileUploadProps.md +26 -23
  130. package/docs/api/interfaces/FooterProps.md +10 -8
  131. package/docs/api/interfaces/FormFieldProps.md +10 -10
  132. package/docs/api/interfaces/FormProps.md +1 -1
  133. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  134. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  135. package/docs/api/interfaces/InputProps.md +7 -4
  136. package/docs/api/interfaces/LabelProps.md +1 -1
  137. package/docs/api/interfaces/LoggerConfig.md +1 -1
  138. package/docs/api/interfaces/LoginFormProps.md +14 -11
  139. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  140. package/docs/api/interfaces/NavigationContextType.md +1 -1
  141. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  142. package/docs/api/interfaces/NavigationItem.md +11 -11
  143. package/docs/api/interfaces/NavigationMenuProps.md +15 -15
  144. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  145. package/docs/api/interfaces/Organisation.md +1 -1
  146. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  147. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  148. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  149. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  150. package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
  151. package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
  152. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  153. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  154. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  155. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  156. package/docs/api/interfaces/PaletteData.md +1 -1
  157. package/docs/api/interfaces/ParsedAddress.md +1 -1
  158. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  159. package/docs/api/interfaces/ProgressProps.md +1 -1
  160. package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
  161. package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
  162. package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
  163. package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
  164. package/docs/api/interfaces/QuickFix.md +1 -1
  165. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  166. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  167. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  168. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  169. package/docs/api/interfaces/RBACConfig.md +1 -1
  170. package/docs/api/interfaces/RBACContext.md +1 -1
  171. package/docs/api/interfaces/RBACLogger.md +1 -1
  172. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  173. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  174. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  175. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  176. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  177. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  178. package/docs/api/interfaces/RBACResult.md +1 -1
  179. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  180. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  181. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  182. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  183. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  184. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  185. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  186. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  187. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  188. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  189. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  190. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  191. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  192. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  193. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  194. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  195. package/docs/api/interfaces/RouteConfig.md +1 -1
  196. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  197. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  198. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  199. package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
  200. package/docs/api/interfaces/SetupIssue.md +1 -1
  201. package/docs/api/interfaces/StorageConfig.md +1 -1
  202. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  203. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  204. package/docs/api/interfaces/StorageListOptions.md +1 -1
  205. package/docs/api/interfaces/StorageListResult.md +1 -1
  206. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  207. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  208. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  209. package/docs/api/interfaces/StyleImport.md +1 -1
  210. package/docs/api/interfaces/SwitchProps.md +1 -1
  211. package/docs/api/interfaces/TabsContentProps.md +1 -1
  212. package/docs/api/interfaces/TabsListProps.md +1 -1
  213. package/docs/api/interfaces/TabsProps.md +1 -1
  214. package/docs/api/interfaces/TabsTriggerProps.md +3 -3
  215. package/docs/api/interfaces/TextareaProps.md +1 -1
  216. package/docs/api/interfaces/ToastActionElement.md +4 -1
  217. package/docs/api/interfaces/ToastProps.md +1 -1
  218. package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
  219. package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
  220. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  221. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  222. package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
  223. package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
  224. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  225. package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
  226. package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
  227. package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
  228. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
  229. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
  230. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
  231. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  232. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  233. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  234. package/docs/api/interfaces/UserEventAccess.md +14 -11
  235. package/docs/api/interfaces/UserMenuProps.md +8 -6
  236. package/docs/api/interfaces/UserProfile.md +1 -1
  237. package/docs/api/modules.md +575 -634
  238. package/docs/architecture/database-schema-requirements.md +161 -0
  239. package/docs/core-concepts/rbac-system.md +3 -3
  240. package/docs/documentation-index.md +2 -4
  241. package/docs/getting-started/cursor-rules.md +263 -0
  242. package/docs/getting-started/installation-guide.md +6 -1
  243. package/docs/getting-started/quick-start.md +6 -1
  244. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  245. package/docs/migration/MIGRATION_GUIDE.md +6 -28
  246. package/docs/migration/README.md +52 -6
  247. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  248. package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
  249. package/docs/migration/database-changes-december-2025.md +3 -3
  250. package/docs/rbac/event-based-apps.md +1 -1
  251. package/docs/rbac/getting-started.md +1 -1
  252. package/docs/rbac/quick-start.md +1 -1
  253. package/docs/standards/README.md +40 -0
  254. package/docs/troubleshooting/migration.md +4 -4
  255. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  256. package/package.json +12 -6
  257. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  258. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  259. package/scripts/audit/core/checks/bundle.cjs +142 -0
  260. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
  261. package/scripts/audit/core/checks/config.cjs +54 -0
  262. package/scripts/audit/core/checks/coverage.cjs +84 -0
  263. package/scripts/audit/core/checks/dependencies.cjs +454 -0
  264. package/scripts/audit/core/checks/documentation.cjs +203 -0
  265. package/scripts/audit/core/checks/environment.cjs +128 -0
  266. package/scripts/audit/core/checks/error-handling.cjs +299 -0
  267. package/scripts/audit/core/checks/forms.cjs +172 -0
  268. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  269. package/scripts/audit/core/checks/hooks.cjs +334 -0
  270. package/scripts/audit/core/checks/imports.cjs +244 -0
  271. package/scripts/audit/core/checks/performance.cjs +325 -0
  272. package/scripts/audit/core/checks/routes.cjs +117 -0
  273. package/scripts/audit/core/checks/state.cjs +130 -0
  274. package/scripts/audit/core/checks/structure.cjs +65 -0
  275. package/scripts/audit/core/checks/style.cjs +584 -0
  276. package/scripts/audit/core/checks/testing.cjs +122 -0
  277. package/scripts/audit/core/checks/typescript.cjs +61 -0
  278. package/scripts/audit/core/scanner.cjs +199 -0
  279. package/scripts/audit/core/utils.cjs +137 -0
  280. package/scripts/audit/index.cjs +223 -0
  281. package/scripts/audit/reporters/console.cjs +151 -0
  282. package/scripts/audit/reporters/json.cjs +54 -0
  283. package/scripts/audit/reporters/markdown.cjs +124 -0
  284. package/scripts/audit-consuming-app.cjs +86 -0
  285. package/scripts/build-docs/build-decision.js +240 -0
  286. package/scripts/build-docs/cache-utils.js +105 -0
  287. package/scripts/build-docs/content-normalization.js +150 -0
  288. package/scripts/build-docs/file-utils.js +105 -0
  289. package/scripts/build-docs/git-utils.js +86 -0
  290. package/scripts/build-docs/hash-utils.js +116 -0
  291. package/scripts/build-docs/typedoc-runner.js +220 -0
  292. package/scripts/build-docs-incremental.js +77 -913
  293. package/scripts/install-cursor-rules.cjs +236 -0
  294. package/scripts/utils/command-runner.js +16 -11
  295. package/scripts/validate-formats.js +61 -56
  296. package/scripts/validate-master.js +74 -69
  297. package/scripts/validate-pre-publish.js +70 -65
  298. package/src/__tests__/helpers/test-providers.tsx +1 -1
  299. package/src/__tests__/helpers/test-utils.tsx +1 -1
  300. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  301. package/src/components/Alert/Alert.test.tsx +12 -18
  302. package/src/components/Alert/Alert.tsx +5 -7
  303. package/src/components/Avatar/Avatar.test.tsx +4 -4
  304. package/src/components/Badge/Badge.tsx +16 -4
  305. package/src/components/Button/Button.tsx +27 -4
  306. package/src/components/Calendar/Calendar.tsx +9 -3
  307. package/src/components/Card/Card.tsx +4 -0
  308. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  309. package/src/components/Checkbox/Checkbox.tsx +2 -2
  310. package/src/components/DataTable/DataTable.test.tsx +57 -93
  311. package/src/components/DataTable/DataTable.tsx +40 -6
  312. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  313. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
  314. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  315. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  316. package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
  317. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  318. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  319. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  320. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  321. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  322. package/src/components/DataTable/components/DataTableCore.tsx +200 -561
  323. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  324. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  325. package/src/components/DataTable/components/DataTableModals.tsx +9 -1
  326. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  327. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  328. package/src/components/DataTable/components/EditFields.tsx +307 -0
  329. package/src/components/DataTable/components/EditableRow.tsx +9 -1
  330. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  331. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  332. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  333. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  334. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  335. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  336. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  337. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  338. package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
  339. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  340. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  341. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  342. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  343. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  344. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  345. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  346. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  347. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  348. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  349. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  350. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  351. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  352. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  353. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  354. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  355. package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
  356. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  357. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  358. package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
  359. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  360. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  361. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  362. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  363. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  364. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  365. package/src/components/DataTable/styles.ts +6 -6
  366. package/src/components/DataTable/types.ts +6 -10
  367. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  368. package/src/components/DataTable/utils/debugTools.ts +18 -113
  369. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  370. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  371. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  372. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  373. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  374. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  375. package/src/components/Dialog/Dialog.tsx +8 -7
  376. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  377. package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
  378. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  379. package/src/components/ErrorBoundary/index.ts +27 -2
  380. package/src/components/EventSelector/EventSelector.tsx +4 -1
  381. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
  382. package/src/components/FileDisplay/FileDisplay.tsx +32 -18
  383. package/src/components/FileUpload/FileUpload.tsx +22 -2
  384. package/src/components/Footer/Footer.test.tsx +16 -16
  385. package/src/components/Footer/Footer.tsx +15 -12
  386. package/src/components/Form/Form.test.tsx +36 -15
  387. package/src/components/Form/Form.tsx +31 -26
  388. package/src/components/Header/Header.tsx +22 -11
  389. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  390. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  391. package/src/components/Input/Input.test.tsx +2 -2
  392. package/src/components/Input/Input.tsx +36 -34
  393. package/src/components/Label/Label.tsx +1 -1
  394. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  395. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  396. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  397. package/src/components/LoginForm/LoginForm.tsx +12 -8
  398. package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
  399. package/src/components/NavigationMenu/types.ts +56 -0
  400. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  401. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
  402. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  403. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
  404. package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
  405. package/src/components/PaceAppLayout/README.md +1 -1
  406. package/src/components/PaceAppLayout/test-setup.tsx +1 -2
  407. package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
  408. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  409. package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
  410. package/src/components/Progress/Progress.tsx +1 -1
  411. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  412. package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
  413. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  414. package/src/components/Select/Select.tsx +95 -438
  415. package/src/components/Select/context.ts +23 -0
  416. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  417. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  418. package/src/components/Select/hooks/useSelectState.ts +104 -0
  419. package/src/components/Select/index.ts +9 -1
  420. package/src/components/Select/types.ts +123 -0
  421. package/src/components/Select/utils/text.ts +26 -0
  422. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
  423. package/src/components/Switch/Switch.tsx +4 -4
  424. package/src/components/Table/Table.tsx +1 -1
  425. package/src/components/Tabs/Tabs.tsx +1 -1
  426. package/src/components/Textarea/Textarea.tsx +27 -29
  427. package/src/components/Toast/Toast.tsx +5 -1
  428. package/src/components/Tooltip/Tooltip.tsx +3 -3
  429. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  430. package/src/components/UserMenu/UserMenu.tsx +22 -19
  431. package/src/components/index.ts +2 -2
  432. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  433. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  434. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  435. package/src/hooks/index.ts +1 -2
  436. package/src/hooks/public/usePublicEvent.ts +5 -1
  437. package/src/hooks/public/usePublicEventLogo.ts +5 -1
  438. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  439. package/src/hooks/public/usePublicRouteParams.ts +5 -1
  440. package/src/hooks/services/useAuth.ts +32 -0
  441. package/src/hooks/services/useCurrentEvent.ts +6 -0
  442. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  443. package/src/hooks/useDataTableState.ts +8 -18
  444. package/src/hooks/useDebounce.ts +9 -0
  445. package/src/hooks/useEventTheme.ts +6 -0
  446. package/src/hooks/useFileDisplay.ts +4 -0
  447. package/src/hooks/useFileReference.ts +25 -7
  448. package/src/hooks/useFileUrl.ts +11 -1
  449. package/src/hooks/useFocusManagement.ts +16 -2
  450. package/src/hooks/useFocusTrap.ts +7 -4
  451. package/src/hooks/useFormDialog.ts +8 -7
  452. package/src/hooks/useInactivityTracker.ts +4 -1
  453. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  454. package/src/hooks/useOrganisationPermissions.ts +4 -0
  455. package/src/hooks/useOrganisationSecurity.ts +4 -0
  456. package/src/hooks/usePerformanceMonitor.ts +4 -0
  457. package/src/hooks/usePermissionCache.ts +8 -1
  458. package/src/hooks/useQueryCache.ts +12 -1
  459. package/src/hooks/useSessionRestoration.ts +4 -0
  460. package/src/hooks/useStorage.ts +4 -0
  461. package/src/hooks/useToast.ts +3 -3
  462. package/src/index.ts +2 -1
  463. package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
  464. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  465. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  466. package/src/providers/services/AuthServiceProvider.tsx +18 -0
  467. package/src/providers/services/EventServiceProvider.tsx +18 -0
  468. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  469. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  470. package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
  471. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
  472. package/src/rbac/README.md +1 -1
  473. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
  474. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  475. package/src/rbac/adapters.tsx +14 -5
  476. package/src/rbac/api.ts +100 -67
  477. package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
  478. package/src/rbac/components/NavigationGuard.tsx +1 -1
  479. package/src/rbac/components/NavigationProvider.tsx +5 -2
  480. package/src/rbac/components/PagePermissionGuard.tsx +158 -18
  481. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  482. package/src/rbac/components/PermissionEnforcer.tsx +1 -1
  483. package/src/rbac/components/RoleBasedRouter.tsx +6 -2
  484. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  485. package/src/rbac/components/SecureDataProvider.tsx +21 -6
  486. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  487. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  488. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  489. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  490. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  491. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  492. package/src/rbac/engine.ts +38 -14
  493. package/src/rbac/hooks/permissions/index.ts +7 -0
  494. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  495. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  496. package/src/rbac/hooks/permissions/useCan.ts +347 -0
  497. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  498. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  499. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  500. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  501. package/src/rbac/hooks/useCan.test.ts +71 -64
  502. package/src/rbac/hooks/usePermissions.ts +14 -995
  503. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  504. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  505. package/src/rbac/hooks/useSecureSupabase.ts +33 -13
  506. package/src/rbac/permissions.ts +0 -30
  507. package/src/rbac/secureClient.ts +212 -61
  508. package/src/rbac/types.ts +8 -0
  509. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  510. package/src/theming/parseEventColours.ts +5 -19
  511. package/src/types/vitest-globals.d.ts +51 -26
  512. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  513. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  514. package/src/utils/__tests__/index.unit.test.ts +2 -2
  515. package/src/utils/audit/audit.ts +0 -3
  516. package/src/utils/core/cn.ts +1 -1
  517. package/src/utils/file-reference/index.ts +53 -1
  518. package/src/utils/formatting/formatting.ts +8 -18
  519. package/src/utils/index.ts +0 -1
  520. package/src/utils/security/secureDataAccess.test.ts +31 -20
  521. package/src/utils/security/secureDataAccess.ts +4 -3
  522. package/dist/chunk-6C4YBBJM.js +0 -628
  523. package/dist/chunk-6C4YBBJM.js.map +0 -1
  524. package/dist/chunk-7D4SUZUM.js 2.map +0 -1
  525. package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
  526. package/dist/chunk-7EQTDTTJ.js.map +0 -1
  527. package/dist/chunk-7FLMSG37.js 2.map +0 -1
  528. package/dist/chunk-7FLMSG37.js.map +0 -1
  529. package/dist/chunk-BC4IJKSL.js.map +0 -1
  530. package/dist/chunk-E3SPN4VZ.js +0 -12917
  531. package/dist/chunk-E3SPN4VZ.js.map +0 -1
  532. package/dist/chunk-E66EQZE6 5.js +0 -37
  533. package/dist/chunk-E66EQZE6.js 2.map +0 -1
  534. package/dist/chunk-HWIIPPNI.js.map +0 -1
  535. package/dist/chunk-I7PSE6JW 5.js +0 -191
  536. package/dist/chunk-I7PSE6JW.js 2.map +0 -1
  537. package/dist/chunk-I7PSE6JW.js.map +0 -1
  538. package/dist/chunk-IIELH4DL.js.map +0 -1
  539. package/dist/chunk-KNC55RTG.js 5.map +0 -1
  540. package/dist/chunk-KNC55RTG.js.map +0 -1
  541. package/dist/chunk-KQCRWDSA.js 5.map +0 -1
  542. package/dist/chunk-LFNCN2SP.js +0 -412
  543. package/dist/chunk-LFNCN2SP.js 2.map +0 -1
  544. package/dist/chunk-LFNCN2SP.js.map +0 -1
  545. package/dist/chunk-LMC26NLJ 2.js +0 -84
  546. package/dist/chunk-NOAYCWCX.js +0 -4993
  547. package/dist/chunk-NOAYCWCX.js.map +0 -1
  548. package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
  549. package/dist/chunk-QWWZ5CAQ.js.map +0 -1
  550. package/dist/chunk-QXHPKYJV 3.js +0 -113
  551. package/dist/chunk-R77UEZ4E.js +0 -68
  552. package/dist/chunk-R77UEZ4E.js.map +0 -1
  553. package/dist/chunk-SQGMNID3.js.map +0 -1
  554. package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
  555. package/dist/chunk-XNXXZ43G.js.map +0 -1
  556. package/dist/chunk-ZSAAAMVR 6.js +0 -25
  557. package/dist/components.js 5.map +0 -1
  558. package/dist/styles/index 2.js +0 -12
  559. package/dist/styles/index.js 5.map +0 -1
  560. package/dist/theming/runtime 5.js +0 -19
  561. package/dist/theming/runtime.js 5.map +0 -1
  562. package/docs/api/classes/ErrorBoundary.md +0 -144
  563. package/docs/migration/quick-migration-guide.md +0 -356
  564. package/docs/migration/service-architecture.md +0 -281
  565. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  566. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  567. package/src/hooks/useSecureDataAccess.ts +0 -666
  568. /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
  569. /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
  570. /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
  571. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  572. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  573. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  574. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  575. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  576. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  577. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -184,7 +184,7 @@
184
184
  * - Responsive design considerations
185
185
  *
186
186
  * @dependencies
187
- * - React 18+ - Component framework and hooks
187
+ * - React 19+ - Component framework and hooks
188
188
  * - Lucide React - Icon components
189
189
  * - Radix UI - Dropdown menu primitives
190
190
  * - React Router (optional) - For navigation handling
@@ -192,70 +192,12 @@
192
192
  */
193
193
 
194
194
  import * as React from "react";
195
- import { Menu, ChevronDown } from "lucide-react";
196
- import { cn } from "../../utils/core/cn";
197
- import { Button } from "../Button";
195
+ import { ChevronDown } from "lucide-react";
198
196
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../Select";
199
- import { useUnifiedAuth } from "../../providers/services/UnifiedAuthProvider";
200
- import { useRBAC } from "../../rbac/hooks/useRBAC";
201
- import { useResolvedScope } from "../../rbac/hooks";
202
- import { usePermissions } from "../../rbac/hooks/usePermissions";
203
- import type { Permission, AccessLevel as RBACAccessLevel, UUID } from "../../rbac/types";
204
197
  import { logger } from "../../utils/core/logger";
205
-
206
- /**
207
- * Navigation mode type
208
- */
209
- export type NavigationMode = 'dropdown' | 'hierarchical';
210
-
211
- /**
212
- * Navigation item metadata
213
- */
214
- export interface NavigationItemMeta {
215
- hidden?: boolean;
216
- [key: string]: unknown;
217
- }
218
-
219
- /**
220
- * Navigation item interface
221
- */
222
- export interface NavigationItem {
223
- id: string;
224
- label: string;
225
- href?: string;
226
- icon?: string;
227
- children?: NavigationItem[];
228
- permissions?: (Permission | string)[];
229
- roles?: string[];
230
- accessLevel?: RBACAccessLevel | string;
231
- meta?: NavigationItemMeta;
232
- pageId?: string;
233
- }
234
-
235
- /**
236
- * Navigation menu component props
237
- */
238
- export interface NavigationMenuProps extends React.HTMLAttributes<HTMLDivElement> {
239
- items: NavigationItem[];
240
- mode?: NavigationMode;
241
- currentPath?: string;
242
- onNavigate?: (item: NavigationItem) => void;
243
- className?: string;
244
- disabled?: boolean;
245
- buttonText?: string;
246
- showIcons?: boolean;
247
- navigationLabel?: string;
248
- strictMode?: boolean;
249
- auditLog?: boolean;
250
- onNavigationAccessDenied?: (item: NavigationItem) => void;
251
- onStrictModeViolation?: (item: NavigationItem, reason: string) => void;
252
- /**
253
- * If true, indicates that items have already been filtered by the parent component (e.g., PaceAppLayout).
254
- * When true, NavigationMenu will skip expensive permission checks and trust the provided items.
255
- * This significantly improves performance when items are pre-filtered.
256
- */
257
- itemsPreFiltered?: boolean;
258
- }
198
+ import { useNavigationFiltering } from "./useNavigationFiltering";
199
+ import type { NavigationItem, NavigationMenuProps } from "./types";
200
+ import type { Permission } from "../../rbac/types";
259
201
 
260
202
  /**
261
203
  * Unified NavigationMenu component that supports both dropdown and hierarchical navigation modes.
@@ -472,456 +414,8 @@ export const NavigationMenu = React.forwardRef<
472
414
  const [expandedItems, setExpandedItems] = React.useState<Set<string>>(new Set());
473
415
  const buttonRef = React.useRef<HTMLButtonElement>(null);
474
416
 
475
- // Get authentication context
476
- let authContext = null;
477
- try {
478
- authContext = useUnifiedAuth();
479
- } catch (error) {
480
- // NavigationMenu is being used outside of UnifiedAuthProvider
481
- logger.warn('NavigationMenu', 'useUnifiedAuth not available, running in unauthenticated mode');
482
- }
483
-
484
- // Get RBAC context for permission and role checks
485
- let rbacContext = null;
486
- try {
487
- rbacContext = useRBAC();
488
- } catch (error) {
489
- // RBAC not available - permission filtering will be disabled
490
- logger.warn('NavigationMenu', 'useRBAC not available, permission filtering disabled');
491
- }
492
-
493
- // Get event context state for checking readiness
494
- // Store the raw value to check if it's undefined (tests without event provider)
495
- const eventLoadingRaw = authContext?.eventLoading;
496
- const eventLoading = eventLoadingRaw ?? false;
497
- const selectedEvent = authContext?.selectedEvent || null;
498
- // Check org context readiness: use isContextReady if available, otherwise fall back to checking selectedOrganisation
499
- // This handles both production (with isContextReady) and test scenarios (without it)
500
- const orgContextReady = authContext?.isContextReady ?? (authContext?.selectedOrganisation?.id ? true : false);
501
-
502
- // Get resolved scope for permission checks
503
- // Note: Always call useResolvedScope (hooks must be called unconditionally)
504
- // However, if itemsPreFiltered is true, we'll skip using the results to avoid blocking
505
- // Permission filtering is always enabled - scope is always needed (unless items are pre-filtered)
506
- const { supabase } = authContext || {};
507
- const { selectedOrganisation } = authContext || {};
508
- const { resolvedScope, isLoading: scopeLoading, error: scopeError } = useResolvedScope({
509
- supabase: itemsPreFiltered ? null : (supabase || null), // Skip expensive resolution if pre-filtered
510
- selectedOrganisationId: itemsPreFiltered ? null : (selectedOrganisation?.id || null),
511
- selectedEventId: itemsPreFiltered ? null : (selectedEvent?.event_id || null)
512
- });
513
-
514
- // Resolve appId when useResolvedScope fails but context is ready
515
- // This is needed because usePermissions requires appId to fetch permissions correctly
516
- const [resolvedAppId, setResolvedAppId] = React.useState<string | undefined>(undefined);
517
- React.useEffect(() => {
518
- // Only resolve appId if:
519
- // 1. useResolvedScope errored or returned null
520
- // 2. But we have organisation context ready
521
- // 3. And we have appName from authContext
522
- // 4. And we haven't resolved it yet
523
- if (
524
- !scopeLoading &&
525
- !resolvedScope?.appId &&
526
- selectedOrganisation?.id &&
527
- authContext?.appName &&
528
- authContext?.user?.id &&
529
- !resolvedAppId
530
- ) {
531
- // Resolve appId using the same method as useRBAC
532
- // Double-check user exists (TypeScript narrowing) and capture values
533
- if (!authContext.user || !authContext.appName) {
534
- return;
535
- }
536
- const userId = authContext.user.id;
537
- const appName = authContext.appName;
538
- import('../../rbac/api').then(({ resolveAppContext }) => {
539
- resolveAppContext({
540
- userId,
541
- appName
542
- }).then((result) => {
543
- if (result?.appId) {
544
- setResolvedAppId(result.appId);
545
- }
546
- }).catch((error) => {
547
- // Silently fail - usePermissions will handle it
548
- });
549
- });
550
- }
551
- }, [scopeLoading, resolvedScope?.appId, selectedOrganisation?.id, authContext?.appName, authContext?.user?.id, resolvedAppId]);
552
-
553
- // Build scope from resolvedScope if available, otherwise fall back to context values
554
- // This handles the case where useResolvedScope errored initially but context is now ready
555
- // IMPORTANT: We need appId for usePermissions to work correctly
556
- // CRITICAL: Use selectedOrganisation?.id immediately when available, even during loading
557
- // This prevents usePermissions from skipping fetch due to empty organisationId
558
- const effectiveScope = React.useMemo(() => {
559
- // Prefer resolvedScope if available (includes appId)
560
- if (resolvedScope?.organisationId) {
561
- return resolvedScope;
562
- }
563
-
564
- // Fall back to building scope from context if resolvedScope is not available yet
565
- // Use selectedOrganisation?.id immediately when available, even if scopeLoading is true
566
- // This prevents delay in permission fetching
567
- if (selectedOrganisation?.id) {
568
- const fallbackScope = {
569
- organisationId: selectedOrganisation.id,
570
- eventId: selectedEvent?.event_id || undefined,
571
- appId: resolvedAppId
572
- };
573
- return fallbackScope;
574
- }
575
-
576
- return null;
577
- }, [resolvedScope, selectedOrganisation?.id, selectedEvent?.event_id, resolvedAppId]);
578
-
579
- // Create a stable scope object that changes when effectiveScope changes
580
- // This ensures usePermissions detects scope changes and re-runs
581
- // We memoize it to prevent unnecessary re-renders while still triggering usePermissions when scope changes
582
- // IMPORTANT: Use a string key for dependencies to ensure React detects changes
583
- // This is more reliable than depending on the object itself
584
- const scopeKey = effectiveScope
585
- ? `${effectiveScope.organisationId || ''}-${effectiveScope.eventId || ''}-${effectiveScope.appId || ''}`
586
- : 'empty';
587
-
588
- const stableScope = React.useMemo(() => {
589
- if (effectiveScope?.organisationId) {
590
- return {
591
- organisationId: effectiveScope.organisationId,
592
- eventId: effectiveScope.eventId,
593
- appId: effectiveScope.appId
594
- };
595
- }
596
- // Return empty scope object (not null) so usePermissions can handle it
597
- return {
598
- organisationId: '',
599
- eventId: undefined,
600
- appId: undefined
601
- };
602
- }, [scopeKey, effectiveScope]);
603
-
604
- // Get permissions map for synchronous permission checks
605
- // Skip expensive permission fetching if items are pre-filtered
606
- const userId = (authContext?.user?.id || '') as UUID;
607
-
608
- // Call usePermissions with primitive parameters
609
- // If itemsPreFiltered is true, pass null/undefined to skip expensive permission fetching
610
- // CRITICAL: Pass null for userId and undefined for organisationId to skip the 3-second timeout
611
- // Empty string '' triggers the timeout, but null/undefined allows immediate early return
612
- const { permissions: permissionMap, hasAnyPermission, isLoading: permissionsLoading, error: permissionsError } = usePermissions(
613
- itemsPreFiltered ? (null as any) : userId, // Pass null to trigger early return (empty string would wait 3s)
614
- itemsPreFiltered ? undefined : stableScope.organisationId, // Pass undefined to skip timeout
615
- itemsPreFiltered ? undefined : stableScope.eventId, // Skip if pre-filtered
616
- itemsPreFiltered ? undefined : stableScope.appId // Skip if pre-filtered
617
- );
618
-
619
-
620
-
621
- // NEW: Phase 2 - Enhanced Security Features
622
- // Filter navigation items based on permissions using RBAC hooks
623
- // Use ref to preserve previous filtered items during permission refetches (e.g., when event changes)
624
- const previousFilteredItemsRef = React.useRef<NavigationItem[]>([]);
625
-
626
- const filteredItems = React.useMemo(() => {
627
- // PERFORMANCE OPTIMIZATION: If items are pre-filtered, skip all expensive permission checks
628
- // This significantly improves load time when PaceAppLayout has already filtered items
629
- if (itemsPreFiltered && items && items.length > 0) {
630
- // Items are pre-filtered by parent (e.g., PaceAppLayout) - trust them and only filter hidden items
631
- const visibleItems = (items || []).filter(item => !item.meta?.hidden);
632
- previousFilteredItemsRef.current = visibleItems;
633
- // Don't log here - logging happens in useEffect to avoid spam on re-renders
634
- return visibleItems;
635
- }
636
-
637
- // SECURITY: Permission filtering is ALWAYS enabled - users only see navigation items they have permission to access
638
- // Security: If we're missing required context or still loading, show NO items
639
- // This prevents security risk of showing items before permissions are verified
640
-
641
- // CRITICAL: Wait for BOTH organisation AND event context to be ready before checking permissions
642
- // This prevents the error "No organisation or event context available" when event context is still loading
643
- const isOrgContextReady = orgContextReady && selectedOrganisation?.id;
644
- // Event context is ready when not loading
645
- // If eventLoadingRaw is undefined (tests without event provider), consider it ready
646
- // Only wait for event context if we're actually loading events
647
- const isEventContextReady = eventLoadingRaw === undefined ? true : !eventLoading;
648
-
649
- // Check if we have valid context for permission checking
650
- // Use actual context values, not just resolvedScope, because resolvedScope might be null
651
- // if useResolvedScope errored initially but context is now ready
652
- const hasValidContext = isOrgContextReady && isEventContextReady;
653
-
654
- // If scope is still loading or we don't have valid context yet, show nothing
655
- // BUT: If scope errored but context is now ready, retry (don't block forever)
656
- const shouldWaitForScope = scopeLoading || (!hasValidContext);
657
- const shouldRetryAfterError = scopeError && hasValidContext && !scopeLoading;
658
-
659
- // During initial load or when scope/context is loading, check if we can trust PaceAppLayout's filtering
660
- // CRITICAL: If items are provided and we have valid context, trust PaceAppLayout's filtering
661
- // This prevents delay - PaceAppLayout already filtered items, so show them immediately
662
- // even if scope is still loading or rbacContext is not yet available
663
- if (items && items.length > 0 && selectedOrganisation?.id) {
664
- // Items are provided by PaceAppLayout - trust its filtering and show immediately
665
- // This eliminates delay while usePermissions/useResolvedScope are still loading
666
- // Items provided - trusting PaceAppLayout filtering (removed verbose logging)
667
- // Filter out hidden items only - PaceAppLayout already did permission filtering
668
- const visibleItems = (items || []).filter(item => !item.meta?.hidden);
669
- // Store in ref for use during permission refetches
670
- previousFilteredItemsRef.current = visibleItems;
671
- return visibleItems;
672
- }
673
-
674
- // If no items provided or no valid context, wait for context to be ready
675
- if (!authContext || !rbacContext || (shouldWaitForScope && !shouldRetryAfterError)) {
676
- // Still loading - show nothing to prevent security risk
677
- // Note: We check both org and event context readiness to prevent premature permission checks
678
- // Exception: If scope errored but context is now ready, we'll retry below
679
- return [];
680
- }
681
-
682
- // If scope errored but context is now ready, we can proceed with permission checks
683
- // The resolvedScope might be null, but we'll use the actual context values
684
- // usePermissions will handle the scope resolution internally
685
-
686
- // During permission refetch (after initial load), preserve previous items if we have them
687
- // This prevents navigation from disappearing when switching events
688
- if (permissionsLoading) {
689
- // If we have previous items, keep showing them during refetch
690
- // This prevents the navigation from disappearing when event changes
691
- if (previousFilteredItemsRef.current.length > 0) {
692
- return previousFilteredItemsRef.current;
693
- }
694
- // CRITICAL: If items are provided and we have valid scope, trust PaceAppLayout's filtering
695
- // This prevents delay - PaceAppLayout already filtered items, so show them immediately
696
- // even if usePermissions is still loading
697
- if (items && items.length > 0 && stableScope.organisationId) {
698
- // Permissions loading but items provided - trusting PaceAppLayout filtering (removed verbose logging)
699
- // Filter out hidden items only - PaceAppLayout already did permission filtering
700
- return (items || []).filter(item => !item.meta?.hidden);
701
- }
702
- // Otherwise, show nothing during initial load
703
- return [];
704
- }
705
-
706
- // If there's an error after loading, show nothing
707
- // Security: Better to show nothing than risk showing unauthorized items
708
- if (permissionsError) {
709
- logger.warn('NavigationMenu', 'Permission check error - showing no items for security', {
710
- permissionsError: permissionsError?.message
711
- });
712
- return [];
713
- }
714
-
715
- // If permission map is empty after loading, we need to handle this carefully:
716
- // 1. If items were already filtered by PaceAppLayout (which uses getPermissionMap directly),
717
- // we should trust those items and show them (they've already been filtered)
718
- // 2. If NavigationMenu is used standalone, we need permissions to filter - show nothing
719
- //
720
- // Since PaceAppLayout filters items before passing to NavigationMenu, and NavigationMenu
721
- // uses usePermissions hook which may return empty map while PaceAppLayout gets permissions,
722
- // we should trust items that are provided if permission map is empty but we have valid scope.
723
- // This is safe because PaceAppLayout already filtered them using getPermissionMap.
724
- if (!permissionMap || Object.keys(permissionMap).length === 0) {
725
- // If we have valid scope and items are provided, they were likely already filtered by PaceAppLayout
726
- // In this case, trust the items and show them (they've already been filtered for security)
727
- if (stableScope.organisationId && items && items.length > 0) {
728
- // Permission map empty but items provided - trusting PaceAppLayout filtering (removed verbose logging)
729
- // Items were already filtered by PaceAppLayout - trust them and only filter hidden items
730
- return (items || []).filter(item => !item.meta?.hidden);
731
- }
732
-
733
- // If no items provided or no valid scope, show nothing (can't verify permissions)
734
- if (stableScope.organisationId) {
735
- logger.warn('NavigationMenu', 'Permission map is empty and no items provided - showing nothing', {
736
- permissionMapSize: 0,
737
- organisationId: stableScope.organisationId,
738
- eventId: stableScope.eventId,
739
- appId: stableScope.appId
740
- });
741
- }
742
- return [];
743
- }
744
-
745
- // SECURITY: Permission filtering is always enabled - filtering cannot be disabled
746
- // Helper function to derive page ID from href
747
- const getPageIdFromHref = (href?: string): string | null => {
748
- if (!href) return null;
749
- // Remove leading slash and any query params/hash
750
- const path = href.split('?')[0].split('#')[0].replace(/^\//, '');
751
- return path || 'home';
752
- };
753
-
754
- // Helper function to check if item has permission to be shown
755
- const hasItemPermission = (item: NavigationItem): boolean => {
756
- // If item has href, we'll check page permissions later - skip explicit permission check
757
- // This allows items with href to be filtered by page permissions instead of explicit permissions
758
- // Check permissions if available AND item doesn't have href
759
- if (item.permissions && item.permissions.length > 0 && !item.href) {
760
- // Convert string permissions to Permission type and check
761
- const permissions = item.permissions
762
- .filter((p): p is string => typeof p === 'string')
763
- .map(p => p as Permission);
764
-
765
- if (permissions.length > 0) {
766
- const hasPermission = hasAnyPermission(permissions);
767
- if (!hasPermission) {
768
- return false;
769
- }
770
- }
771
- }
772
-
773
- // Check roles if available
774
- if (item.roles && item.roles.length > 0) {
775
- const hasRole = item.roles.some(role => {
776
- if (typeof role !== 'string') return true;
777
-
778
- // Map role strings to RBAC role checks
779
- switch (role.toLowerCase()) {
780
- case 'super_admin':
781
- case 'super admin':
782
- return rbacContext.isSuperAdmin;
783
- case 'org_admin':
784
- case 'org admin':
785
- case 'admin':
786
- return rbacContext.isOrgAdmin || rbacContext.isSuperAdmin;
787
- case 'event_admin':
788
- case 'event admin':
789
- return rbacContext.isEventAdmin || rbacContext.isSuperAdmin;
790
- default:
791
- // For other roles, check against organisationRole or eventAppRole
792
- return (
793
- rbacContext.organisationRole === role ||
794
- rbacContext.eventAppRole === role ||
795
- rbacContext.isSuperAdmin
796
- );
797
- }
798
- });
799
- if (!hasRole) {
800
- return false;
801
- }
802
- }
803
-
804
- // Check access level if available
805
- if (item.accessLevel) {
806
- if (typeof item.accessLevel === 'string') {
807
- // Map access level string to RBAC access level
808
- const accessLevel = item.accessLevel.toLowerCase() as RBACAccessLevel;
809
- const userEventRole = rbacContext.eventAppRole;
810
-
811
- // If user is super admin, they have all access levels
812
- if (rbacContext.isSuperAdmin) {
813
- // Super admin has access
814
- } else {
815
- // Map eventAppRole to access level for comparison
816
- // eventAppRole: 'viewer' | 'participant' | 'planner' | 'event_admin'
817
- const roleToAccessLevel: Record<string, RBACAccessLevel> = {
818
- 'viewer': 'viewer',
819
- 'participant': 'participant',
820
- 'planner': 'planner',
821
- 'event_admin': 'admin',
822
- };
823
- const userAccessLevel = userEventRole ? (roleToAccessLevel[userEventRole] || 'viewer') : null;
824
-
825
- // Check if user's access level meets the required access level
826
- const levelHierarchy: Record<RBACAccessLevel, number> = {
827
- viewer: 1,
828
- participant: 2,
829
- planner: 3,
830
- admin: 4,
831
- super: 5
832
- };
833
- const requiredLevel = levelHierarchy[accessLevel] || 0;
834
- const userLevel = userAccessLevel ? levelHierarchy[userAccessLevel] || 0 : 0;
835
-
836
- if (userLevel < requiredLevel) {
837
- return false;
838
- }
839
- }
840
- }
841
- }
842
-
843
- // NEW: Auto-check page permissions for items with href
844
- // Always check page permissions for items with href, even if they have explicit permissions/roles/accessLevel
845
- // This ensures that items are filtered out if the user doesn't have access to the page itself
846
- if (item.href) {
847
- const pageId = item.pageId || getPageIdFromHref(item.href);
848
- if (pageId) {
849
- // Check for read permission on the page
850
- const pagePermission: Permission = `read:page.${pageId}` as Permission;
851
-
852
- // Check permission map (super admin has access to everything via '*' key)
853
- // Only allow if permission is explicitly true (undefined/false means no access)
854
- const isSuperAdmin = permissionMap['*'] === true;
855
- const hasPagePermission = permissionMap[pagePermission] === true;
856
- const finalHasPermission = isSuperAdmin || hasPagePermission;
857
-
858
- if (!finalHasPermission) {
859
- return false;
860
- }
861
- }
862
- }
863
-
864
- return true;
865
- };
866
-
867
- // Helper function to filter items recursively (creates new objects to avoid mutations)
868
- const filterItem = (item: NavigationItem): NavigationItem | null => {
869
- // Check if item should be hidden
870
- if (item.meta?.hidden) return null;
871
-
872
- // Check if item has permission
873
- if (!hasItemPermission(item)) return null;
874
-
875
- // Recursively filter children if present
876
- let filteredChildren: NavigationItem[] | undefined;
877
- if (item.children && item.children.length > 0) {
878
- filteredChildren = item.children
879
- .map(child => filterItem(child))
880
- .filter((child): child is NavigationItem => child !== null);
881
-
882
- // If parent has no accessible children, hide the parent too (unless it has its own href)
883
- if (filteredChildren.length === 0 && !item.href) {
884
- return null;
885
- }
886
- }
887
-
888
- // Return filtered item (with filtered children if applicable)
889
- return {
890
- ...item,
891
- children: filteredChildren
892
- };
893
- };
894
-
895
- // Filter items based on permissions - only show items with explicit permission
896
- // Security: No fallback - if items don't have permission, they are hidden
897
- const filtered = (items || [])
898
- .map(item => filterItem(item))
899
- .filter((item): item is NavigationItem => item !== null);
900
-
901
- // Update the ref with the new filtered items so we can preserve them during refetches
902
- previousFilteredItemsRef.current = filtered;
903
-
904
- return filtered;
905
- }, [
906
- items,
907
- itemsPreFiltered, // Add itemsPreFiltered to dependencies
908
- authContext,
909
- rbacContext,
910
- permissionMap,
911
- hasAnyPermission,
912
- scopeLoading,
913
- scopeError,
914
- permissionsLoading,
915
- resolvedScope,
916
- effectiveScope,
917
- auditLog,
918
- // Add event context state to dependencies so we re-check permissions when event context becomes available
919
- eventLoadingRaw,
920
- eventLoading,
921
- selectedEvent,
922
- orgContextReady,
923
- selectedOrganisation?.id
924
- ]);
417
+ const { authContext, rbacContext, filteredItems, permissionMap, hasAnyPermission } =
418
+ useNavigationFiltering({ items, itemsPreFiltered, auditLog });
925
419
 
926
420
  // Note: Navigation access attempts are logged in handleItemClick, not here
927
421
  // This prevents excessive logging on every render
@@ -1209,4 +703,11 @@ export const NavigationMenu = React.forwardRef<
1209
703
  );
1210
704
  });
1211
705
 
1212
- NavigationMenu.displayName = "NavigationMenu";
706
+ NavigationMenu.displayName = "NavigationMenu";
707
+
708
+ export type {
709
+ NavigationMode,
710
+ NavigationItemMeta,
711
+ NavigationItem,
712
+ NavigationMenuProps,
713
+ } from "./types";
@@ -0,0 +1,56 @@
1
+ import * as React from "react";
2
+ import type { Permission, AccessLevel as RBACAccessLevel } from "../../rbac/types";
3
+
4
+ /**
5
+ * Navigation mode type
6
+ */
7
+ export type NavigationMode = "dropdown" | "hierarchical";
8
+
9
+ /**
10
+ * Navigation item metadata
11
+ */
12
+ export interface NavigationItemMeta {
13
+ hidden?: boolean;
14
+ [key: string]: unknown;
15
+ }
16
+
17
+ /**
18
+ * Navigation item interface
19
+ */
20
+ export interface NavigationItem {
21
+ id: string;
22
+ label: string;
23
+ href?: string;
24
+ icon?: string;
25
+ children?: NavigationItem[];
26
+ permissions?: (Permission | string)[];
27
+ roles?: string[];
28
+ accessLevel?: RBACAccessLevel | string;
29
+ meta?: NavigationItemMeta;
30
+ pageId?: string;
31
+ }
32
+
33
+ /**
34
+ * Navigation menu component props
35
+ */
36
+ export interface NavigationMenuProps extends React.HTMLAttributes<HTMLDivElement> {
37
+ items: NavigationItem[];
38
+ mode?: NavigationMode;
39
+ currentPath?: string;
40
+ onNavigate?: (item: NavigationItem) => void;
41
+ className?: string;
42
+ disabled?: boolean;
43
+ buttonText?: string;
44
+ showIcons?: boolean;
45
+ navigationLabel?: string;
46
+ strictMode?: boolean;
47
+ auditLog?: boolean;
48
+ onNavigationAccessDenied?: (item: NavigationItem) => void;
49
+ onStrictModeViolation?: (item: NavigationItem, reason: string) => void;
50
+ /**
51
+ * If true, indicates that items have already been filtered by the parent component (e.g., PaceAppLayout).
52
+ * When true, NavigationMenu will skip expensive permission checks and trust the provided items.
53
+ * This significantly improves performance when items are pre-filtered.
54
+ */
55
+ itemsPreFiltered?: boolean;
56
+ }