@jmruthers/pace-core 0.6.2 → 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 (299) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/cursor-rules/00-pace-core-compliance.mdc +34 -2
  3. package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
  4. package/dist/{DataTable-TPTKCX4D.js → DataTable-THFPBKTP.js} +9 -8
  5. package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
  6. package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
  7. package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
  8. package/dist/{api-MVVQZLJI.js → api-IAGWF3ZG.js} +10 -10
  9. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  10. package/dist/{chunk-SFZUDBL5.js → chunk-2T2IG7T7.js} +70 -56
  11. package/dist/chunk-2T2IG7T7.js.map +1 -0
  12. package/dist/{chunk-MMZ7JXPU.js → chunk-6Z7LTB3D.js} +13 -21
  13. package/dist/{chunk-MMZ7JXPU.js.map → chunk-6Z7LTB3D.js.map} +1 -1
  14. package/dist/{chunk-6J4GEEJR.js → chunk-CNCQDFLN.js} +53 -27
  15. package/dist/chunk-CNCQDFLN.js.map +1 -0
  16. package/dist/chunk-DGUM43GV.js +11 -0
  17. package/dist/{chunk-EHMR7VYL.js → chunk-DWUBLJJM.js} +361 -187
  18. package/dist/chunk-DWUBLJJM.js.map +1 -0
  19. package/dist/{chunk-2UOI2FG5.js → chunk-HFZBI76P.js} +4 -4
  20. package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
  21. package/dist/{chunk-3XC4CPTD.js → chunk-PQBSKX33.js} +244 -5727
  22. package/dist/chunk-PQBSKX33.js.map +1 -0
  23. package/dist/chunk-QRPVRXYT.js +226 -0
  24. package/dist/chunk-QRPVRXYT.js.map +1 -0
  25. package/dist/{chunk-24UVZUZG.js → chunk-RWEBCB47.js} +129 -387
  26. package/dist/chunk-RWEBCB47.js.map +1 -0
  27. package/dist/{chunk-XWQCNGTQ.js → chunk-YDQHOZNA.js} +173 -79
  28. package/dist/chunk-YDQHOZNA.js.map +1 -0
  29. package/dist/{chunk-NECFR5MM.js → chunk-ZNIWI3UC.js} +562 -644
  30. package/dist/chunk-ZNIWI3UC.js.map +1 -0
  31. package/dist/components.d.ts +2 -2
  32. package/dist/components.js +12 -13
  33. package/dist/contextValidator-3JNZKUTX.js +9 -0
  34. package/dist/contextValidator-3JNZKUTX.js.map +1 -0
  35. package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
  36. package/dist/hooks.d.ts +2 -2
  37. package/dist/hooks.js +7 -6
  38. package/dist/hooks.js.map +1 -1
  39. package/dist/index.d.ts +7 -7
  40. package/dist/index.js +21 -16
  41. package/dist/index.js.map +1 -1
  42. package/dist/providers.d.ts +3 -3
  43. package/dist/providers.js +4 -3
  44. package/dist/rbac/index.d.ts +67 -27
  45. package/dist/rbac/index.js +15 -8
  46. package/dist/styles/index.js +1 -1
  47. package/dist/theming/runtime.js +1 -1
  48. package/dist/types.js +1 -1
  49. package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
  50. package/dist/utils.js +5 -7
  51. package/dist/utils.js.map +1 -1
  52. package/docs/api/README.md +14 -16
  53. package/docs/api/modules.md +3796 -2513
  54. package/docs/components/context-selector.md +126 -0
  55. package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
  56. package/docs/pace-mint-fix-auto-selection.md +218 -0
  57. package/docs/pace-mint-rbac-setup.md +391 -0
  58. package/docs/rbac/secure-client-protection.md +330 -0
  59. package/package.json +3 -3
  60. package/scripts/audit/core/checks/compliance.cjs +72 -0
  61. package/scripts/audit/core/checks/dependencies.cjs +559 -28
  62. package/scripts/audit/core/checks/documentation.cjs +68 -3
  63. package/scripts/audit/core/checks/environment.cjs +2 -14
  64. package/scripts/audit/core/checks/error-handling.cjs +47 -6
  65. package/src/components/ContextSelector/ContextSelector.tsx +384 -0
  66. package/src/components/ContextSelector/index.ts +3 -0
  67. package/src/components/DataTable/components/RowComponent.tsx +19 -19
  68. package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
  69. package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
  70. package/src/components/Dialog/Dialog.tsx +29 -1
  71. package/src/components/FileDisplay/FileDisplay.tsx +42 -10
  72. package/src/components/Header/Header.test.tsx +43 -73
  73. package/src/components/Header/Header.tsx +44 -45
  74. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
  75. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
  76. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
  77. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
  78. package/src/components/PaceAppLayout/PaceAppLayout.tsx +135 -33
  79. package/src/components/PaceAppLayout/README.md +14 -17
  80. package/src/components/PaceAppLayout/test-setup.tsx +2 -2
  81. package/src/components/index.ts +5 -5
  82. package/src/eslint-rules/pace-core-compliance.cjs +106 -0
  83. package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
  84. package/src/hooks/useAppConfig.ts +15 -30
  85. package/src/hooks/useFileDisplay.ts +77 -50
  86. package/src/index.ts +4 -5
  87. package/src/providers/services/AuthServiceProvider.tsx +17 -7
  88. package/src/providers/services/EventServiceProvider.tsx +33 -5
  89. package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
  90. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
  91. package/src/rbac/adapters.tsx +2 -2
  92. package/src/rbac/api.test.ts +59 -51
  93. package/src/rbac/api.ts +178 -132
  94. package/src/rbac/components/PagePermissionGuard.tsx +38 -10
  95. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
  96. package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
  97. package/src/rbac/hooks/permissions/useCan.ts +41 -11
  98. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
  99. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
  100. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
  101. package/src/rbac/hooks/useCan.test.ts +0 -9
  102. package/src/rbac/hooks/useRBAC.test.ts +1 -5
  103. package/src/rbac/hooks/useRBAC.ts +36 -37
  104. package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
  105. package/src/rbac/hooks/useResolvedScope.ts +35 -40
  106. package/src/rbac/hooks/useSecureSupabase.ts +7 -7
  107. package/src/rbac/index.ts +7 -0
  108. package/src/rbac/secureClient.test.ts +22 -18
  109. package/src/rbac/secureClient.ts +103 -16
  110. package/src/rbac/security.ts +0 -17
  111. package/src/rbac/types.ts +1 -0
  112. package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
  113. package/src/rbac/utils/clientSecurity.ts +93 -0
  114. package/src/rbac/utils/contextValidator.ts +77 -168
  115. package/src/services/AuthService.ts +39 -7
  116. package/src/services/EventService.ts +186 -54
  117. package/src/services/OrganisationService.ts +81 -14
  118. package/src/services/__tests__/EventService.test.ts +1 -2
  119. package/src/services/base/BaseService.ts +3 -0
  120. package/src/utils/dynamic/dynamicUtils.ts +7 -4
  121. package/dist/chunk-24UVZUZG.js.map +0 -1
  122. package/dist/chunk-3XC4CPTD.js.map +0 -1
  123. package/dist/chunk-6J4GEEJR.js.map +0 -1
  124. package/dist/chunk-7D4SUZUM.js +0 -38
  125. package/dist/chunk-EHMR7VYL.js.map +0 -1
  126. package/dist/chunk-NECFR5MM.js.map +0 -1
  127. package/dist/chunk-SFZUDBL5.js.map +0 -1
  128. package/dist/chunk-XWQCNGTQ.js.map +0 -1
  129. package/docs/api/classes/ColumnFactory.md +0 -243
  130. package/docs/api/classes/InvalidScopeError.md +0 -73
  131. package/docs/api/classes/Logger.md +0 -178
  132. package/docs/api/classes/MissingUserContextError.md +0 -66
  133. package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
  134. package/docs/api/classes/PermissionDeniedError.md +0 -73
  135. package/docs/api/classes/RBACAuditManager.md +0 -297
  136. package/docs/api/classes/RBACCache.md +0 -322
  137. package/docs/api/classes/RBACEngine.md +0 -171
  138. package/docs/api/classes/RBACError.md +0 -76
  139. package/docs/api/classes/RBACNotInitializedError.md +0 -66
  140. package/docs/api/classes/SecureSupabaseClient.md +0 -163
  141. package/docs/api/classes/StorageUtils.md +0 -328
  142. package/docs/api/enums/FileCategory.md +0 -184
  143. package/docs/api/enums/LogLevel.md +0 -54
  144. package/docs/api/enums/RBACErrorCode.md +0 -228
  145. package/docs/api/enums/RPCFunction.md +0 -118
  146. package/docs/api/interfaces/AddressFieldProps.md +0 -241
  147. package/docs/api/interfaces/AddressFieldRef.md +0 -94
  148. package/docs/api/interfaces/AggregateConfig.md +0 -43
  149. package/docs/api/interfaces/AutocompleteOptions.md +0 -75
  150. package/docs/api/interfaces/AvatarProps.md +0 -128
  151. package/docs/api/interfaces/BadgeProps.md +0 -34
  152. package/docs/api/interfaces/ButtonProps.md +0 -56
  153. package/docs/api/interfaces/CalendarProps.md +0 -73
  154. package/docs/api/interfaces/CardProps.md +0 -69
  155. package/docs/api/interfaces/ColorPalette.md +0 -7
  156. package/docs/api/interfaces/ColorShade.md +0 -66
  157. package/docs/api/interfaces/ComplianceResult.md +0 -30
  158. package/docs/api/interfaces/DataAccessRecord.md +0 -96
  159. package/docs/api/interfaces/DataRecord.md +0 -11
  160. package/docs/api/interfaces/DataTableAction.md +0 -252
  161. package/docs/api/interfaces/DataTableColumn.md +0 -504
  162. package/docs/api/interfaces/DataTableProps.md +0 -625
  163. package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
  164. package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
  165. package/docs/api/interfaces/DatabaseIssue.md +0 -41
  166. package/docs/api/interfaces/EmptyStateConfig.md +0 -61
  167. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
  168. package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
  169. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
  170. package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
  171. package/docs/api/interfaces/EventAppRoleData.md +0 -71
  172. package/docs/api/interfaces/ExportColumn.md +0 -90
  173. package/docs/api/interfaces/ExportOptions.md +0 -126
  174. package/docs/api/interfaces/FileDisplayProps.md +0 -249
  175. package/docs/api/interfaces/FileMetadata.md +0 -129
  176. package/docs/api/interfaces/FileReference.md +0 -118
  177. package/docs/api/interfaces/FileSizeLimits.md +0 -7
  178. package/docs/api/interfaces/FileUploadOptions.md +0 -139
  179. package/docs/api/interfaces/FileUploadProps.md +0 -296
  180. package/docs/api/interfaces/FooterProps.md +0 -107
  181. package/docs/api/interfaces/FormFieldProps.md +0 -166
  182. package/docs/api/interfaces/FormProps.md +0 -113
  183. package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
  184. package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
  185. package/docs/api/interfaces/InputProps.md +0 -56
  186. package/docs/api/interfaces/LabelProps.md +0 -107
  187. package/docs/api/interfaces/LoggerConfig.md +0 -62
  188. package/docs/api/interfaces/LoginFormProps.md +0 -187
  189. package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
  190. package/docs/api/interfaces/NavigationContextType.md +0 -164
  191. package/docs/api/interfaces/NavigationGuardProps.md +0 -139
  192. package/docs/api/interfaces/NavigationItem.md +0 -120
  193. package/docs/api/interfaces/NavigationMenuProps.md +0 -221
  194. package/docs/api/interfaces/NavigationProviderProps.md +0 -117
  195. package/docs/api/interfaces/Organisation.md +0 -140
  196. package/docs/api/interfaces/OrganisationContextType.md +0 -388
  197. package/docs/api/interfaces/OrganisationMembership.md +0 -140
  198. package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
  199. package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
  200. package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
  201. package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
  202. package/docs/api/interfaces/PageAccessRecord.md +0 -85
  203. package/docs/api/interfaces/PagePermissionContextType.md +0 -140
  204. package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
  205. package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
  206. package/docs/api/interfaces/PaletteData.md +0 -41
  207. package/docs/api/interfaces/ParsedAddress.md +0 -120
  208. package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
  209. package/docs/api/interfaces/ProgressProps.md +0 -42
  210. package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
  211. package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
  212. package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
  213. package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
  214. package/docs/api/interfaces/QuickFix.md +0 -52
  215. package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
  216. package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
  217. package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
  218. package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
  219. package/docs/api/interfaces/RBACConfig.md +0 -133
  220. package/docs/api/interfaces/RBACContext.md +0 -52
  221. package/docs/api/interfaces/RBACLogger.md +0 -112
  222. package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
  223. package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
  224. package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
  225. package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
  226. package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
  227. package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
  228. package/docs/api/interfaces/RBACResult.md +0 -58
  229. package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
  230. package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
  231. package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
  232. package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
  233. package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
  234. package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
  235. package/docs/api/interfaces/RBACRolesListParams.md +0 -52
  236. package/docs/api/interfaces/RBACRolesListResult.md +0 -74
  237. package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
  238. package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
  239. package/docs/api/interfaces/ResourcePermissions.md +0 -155
  240. package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
  241. package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
  242. package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
  243. package/docs/api/interfaces/RoleManagementResult.md +0 -52
  244. package/docs/api/interfaces/RouteAccessRecord.md +0 -107
  245. package/docs/api/interfaces/RouteConfig.md +0 -134
  246. package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
  247. package/docs/api/interfaces/SecureDataContextType.md +0 -168
  248. package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
  249. package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
  250. package/docs/api/interfaces/SetupIssue.md +0 -41
  251. package/docs/api/interfaces/StorageConfig.md +0 -41
  252. package/docs/api/interfaces/StorageFileInfo.md +0 -74
  253. package/docs/api/interfaces/StorageFileMetadata.md +0 -151
  254. package/docs/api/interfaces/StorageListOptions.md +0 -99
  255. package/docs/api/interfaces/StorageListResult.md +0 -41
  256. package/docs/api/interfaces/StorageUploadOptions.md +0 -101
  257. package/docs/api/interfaces/StorageUploadResult.md +0 -63
  258. package/docs/api/interfaces/StorageUrlOptions.md +0 -60
  259. package/docs/api/interfaces/StyleImport.md +0 -19
  260. package/docs/api/interfaces/SwitchProps.md +0 -34
  261. package/docs/api/interfaces/TabsContentProps.md +0 -9
  262. package/docs/api/interfaces/TabsListProps.md +0 -9
  263. package/docs/api/interfaces/TabsProps.md +0 -9
  264. package/docs/api/interfaces/TabsTriggerProps.md +0 -50
  265. package/docs/api/interfaces/TextareaProps.md +0 -53
  266. package/docs/api/interfaces/ToastActionElement.md +0 -12
  267. package/docs/api/interfaces/ToastProps.md +0 -9
  268. package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
  269. package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
  270. package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
  271. package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
  272. package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
  273. package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
  274. package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
  275. package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
  276. package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
  277. package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
  278. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
  279. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
  280. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
  281. package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
  282. package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
  283. package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
  284. package/docs/api/interfaces/UserEventAccess.md +0 -121
  285. package/docs/api/interfaces/UserMenuProps.md +0 -88
  286. package/docs/api/interfaces/UserProfile.md +0 -63
  287. package/src/components/EventSelector/EventSelector.test.tsx +0 -720
  288. package/src/components/EventSelector/EventSelector.tsx +0 -423
  289. package/src/components/EventSelector/index.ts +0 -3
  290. package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
  291. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
  292. package/src/components/OrganisationSelector/index.ts +0 -9
  293. /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-THFPBKTP.js.map} +0 -0
  294. /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
  295. /package/dist/{api-MVVQZLJI.js.map → api-IAGWF3ZG.js.map} +0 -0
  296. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  297. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  298. /package/dist/{chunk-2UOI2FG5.js.map → chunk-HFZBI76P.js.map} +0 -0
  299. /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
@@ -205,14 +205,14 @@ vi.mock('../Header', () => ({
205
205
  onChangePassword,
206
206
  currentPath,
207
207
  onNavigate,
208
- showEventSelector,
208
+ showContextSelector,
209
209
  showUserMenu,
210
210
  className
211
211
  }: any) => (
212
212
  <header
213
213
  data-testid="header"
214
214
  className={className}
215
- data-show-event-selector={showEventSelector !== false ? 'true' : 'false'}
215
+ data-show-context-selector={showContextSelector !== false ? 'true' : 'false'}
216
216
  data-show-user-menu={showUserMenu !== false ? 'true' : 'false'}
217
217
  >
218
218
  <div data-testid="header-logo" data-logo-url={logoUrl} data-logo-alt={logoAlt}>
@@ -421,29 +421,29 @@ describe('PaceAppLayout Component', () => {
421
421
  { withRouter: false }
422
422
  );
423
423
 
424
- expect(screen.getByTestId('header')).toHaveAttribute('data-show-event-selector', 'true');
424
+ expect(screen.getByTestId('header')).toHaveAttribute('data-show-context-selector', 'true');
425
425
  });
426
426
 
427
- it('hides event selector when showEventSelector is false', () => {
427
+ it('hides context selector when showContextSelector is false', () => {
428
428
  renderWithProviders(
429
429
  <TestWrapper>
430
- <PaceAppLayout {...baseProps} showEventSelector={false} />
430
+ <PaceAppLayout {...baseProps} showContextSelector={false} />
431
431
  </TestWrapper>,
432
432
  { withRouter: false }
433
433
  );
434
434
 
435
- expect(screen.getByTestId('header')).toHaveAttribute('data-show-event-selector', 'false');
435
+ expect(screen.getByTestId('header')).toHaveAttribute('data-show-context-selector', 'false');
436
436
  });
437
437
 
438
- it('shows event selector when showEventSelector is explicitly true', () => {
438
+ it('shows context selector when showContextSelector is explicitly true', () => {
439
439
  renderWithProviders(
440
440
  <TestWrapper>
441
- <PaceAppLayout {...baseProps} showEventSelector={true} />
441
+ <PaceAppLayout {...baseProps} showContextSelector={true} />
442
442
  </TestWrapper>,
443
443
  { withRouter: false }
444
444
  );
445
445
 
446
- expect(screen.getByTestId('header')).toHaveAttribute('data-show-event-selector', 'true');
446
+ expect(screen.getByTestId('header')).toHaveAttribute('data-show-context-selector', 'true');
447
447
  });
448
448
  });
449
449
 
@@ -130,10 +130,12 @@ export interface PaceAppLayoutProps {
130
130
  appName: string;
131
131
  /** Optional navigation items for the header menu. If not provided, uses default navigation. */
132
132
  navItems?: NavigationItem[];
133
- /** Show/hide event selector in the header */
134
- showEventSelector?: boolean;
135
- /** Show/hide organisation selector in the header */
136
- showOrgSelector?: boolean;
133
+ /** Show/hide unified context selector (shows all accessible orgs and events) - default: true */
134
+ showContextSelector?: boolean;
135
+ /** Show organisations in context selector - default: true */
136
+ showOrganisations?: boolean;
137
+ /** Show events in context selector - default: true */
138
+ showEvents?: boolean;
137
139
  /** Custom actions to display in the header (between event selector and user menu) */
138
140
  headerActions?: React.ReactNode;
139
141
  /** Custom logo component (overrides default logo) */
@@ -349,8 +351,9 @@ export interface PaceAppLayoutProps {
349
351
  export function PaceAppLayout({
350
352
  appName,
351
353
  navItems,
352
- showEventSelector,
353
- showOrgSelector,
354
+ showContextSelector = true,
355
+ showOrganisations = true,
356
+ showEvents = true,
354
357
  headerActions,
355
358
  customLogo,
356
359
  logoHref = '/dashboard',
@@ -614,9 +617,9 @@ export function PaceAppLayout({
614
617
  const hasOrganisationContext = currentScope.organisationId;
615
618
  const hasUser = !!user?.id;
616
619
 
617
- // BUG FIX: When showOrgSelector is enabled, show navigation optimistically while waiting
618
- // for organisation selection. Only hide navigation if user is not loaded.
619
- // This prevents navigation from disappearing after initial render while waiting for org selection.
620
+ // Show navigation optimistically while waiting for context selection.
621
+ // Only hide navigation if user is not loaded.
622
+ // This prevents navigation from disappearing after initial render while waiting for context.
620
623
  if (!hasUser) {
621
624
  // User not loaded yet - show all items until user is ready
622
625
  if (isMounted) {
@@ -625,9 +628,9 @@ export function PaceAppLayout({
625
628
  return;
626
629
  }
627
630
 
628
- // If no organisation context yet, show items optimistically if org selector is enabled
629
- // This allows users to see navigation while they select an organisation
630
- // Only proceed with permission filtering once organisation is selected
631
+ // If no organisation context yet, show items optimistically
632
+ // This allows users to see navigation while they select a context
633
+ // Only proceed with permission filtering once context is selected
631
634
  if (!hasOrganisationContext) {
632
635
  if (isMounted) {
633
636
  // Show items optimistically when org selector is enabled, otherwise show all items
@@ -682,32 +685,130 @@ export function PaceAppLayout({
682
685
  scope: permissionScope,
683
686
  });
684
687
 
685
- // Filter items using the permission map (synchronous, no rate limit issues)
686
- const filtered = baseMenuItems.map((item) => {
687
- if (!item.href) return { item, hasAccess: true };
688
-
689
- // Extract page ID from href: remove leading slash, fallback to 'dashboard' for root
690
- // This matches database page names in rbac_app_pages
691
- const pageId = pageIdMapping[item.href] || (item.href === '/' ? 'dashboard' : item.href.slice(1)) || 'dashboard';
692
- const permission = routePermissions[item.href] || defaultPermission;
693
- const fullPermission: Permission = permission.includes(':')
694
- ? (permission as Permission)
695
- : (pageId ? `${permission}:page.${pageId}` : permission) as Permission;
696
-
697
- // Check permission map (super admin check already handled in getPermissionMap)
698
- const hasAccess = permissionMap['*'] === true || permissionMap[fullPermission] === true;
699
-
700
- return { item, hasAccess };
701
- });
688
+ // Filter items using the permission map and scope type
689
+ // First, get scope types for all pages (batch if possible, or individual)
690
+ const { getPageScopeType } = await import('../../rbac/api');
691
+ const effectiveAppId = currentScope.appId || resolvedAppId;
692
+ const effectiveAppName = appName;
693
+
694
+ // Determine current context type for scope filtering
695
+ const hasEventContext = !!currentScope.eventId;
696
+ const hasOrgContext = !!currentScope.organisationId;
697
+
698
+ // Filter items using both permission map and scope type
699
+ const filtered = await Promise.all(
700
+ baseMenuItems.map(async (item) => {
701
+ if (!item.href) return { item, hasAccess: true, matchesScope: true, scopeCheckError: false };
702
+
703
+ // Extract page ID from href: remove leading slash, fallback to 'dashboard' for root
704
+ // This matches database page names in rbac_app_pages
705
+ const pageId = pageIdMapping[item.href] || (item.href === '/' ? 'dashboard' : item.href.slice(1)) || 'dashboard';
706
+ const permission = routePermissions[item.href] || defaultPermission;
707
+ const fullPermission: Permission = permission.includes(':')
708
+ ? (permission as Permission)
709
+ : (pageId ? `${permission}:page.${pageId}` : permission) as Permission;
710
+
711
+ // Check permission map (super admin check already handled in getPermissionMap)
712
+ const hasAccess = permissionMap['*'] === true || permissionMap[fullPermission] === true;
713
+
714
+ // Check scope type compatibility
715
+ let matchesScope = true;
716
+ let scopeCheckError = false;
717
+ if (effectiveAppId || effectiveAppName) {
718
+ try {
719
+ const pageScopeType = await getPageScopeType(
720
+ pageId,
721
+ effectiveAppId,
722
+ effectiveAppName
723
+ );
724
+
725
+ // Filter based on current context:
726
+ // - If event is selected: show only 'event' or 'both' pages
727
+ // - If only org is selected: show only 'organisation' or 'both' pages
728
+ if (hasEventContext) {
729
+ // Event context: show 'event' or 'both' pages only
730
+ matchesScope = pageScopeType === 'event' || pageScopeType === 'both';
731
+ } else if (hasOrgContext) {
732
+ // Organisation context only: show 'organisation' or 'both' pages only
733
+ matchesScope = pageScopeType === 'organisation' || pageScopeType === 'both';
734
+ } else {
735
+ // No context: show all pages (shouldn't happen, but safe fallback)
736
+ matchesScope = true;
737
+ }
738
+ } catch (error) {
739
+ // If we can't get scope type, allow the page (graceful degradation)
740
+ // This prevents navigation from disappearing if there's a database issue
741
+ scopeCheckError = true;
742
+ logger.warn('PaceAppLayout', 'Failed to get page scope type for navigation filtering', {
743
+ pageId,
744
+ href: item.href,
745
+ contextType: hasEventContext ? 'event' : (hasOrgContext ? 'organisation' : 'none'),
746
+ error: error instanceof Error ? error.message : String(error),
747
+ note: 'Allowing page to prevent navigation from disappearing - this may indicate missing scope_type in database'
748
+ });
749
+ matchesScope = true; // Default to allowing if we can't check
750
+ }
751
+ } else {
752
+ // No appId/appName available - can't check scope, allow the page
753
+ // This happens during initial load before app context is resolved
754
+ matchesScope = true;
755
+ }
756
+
757
+ return { item, hasAccess, matchesScope, scopeCheckError };
758
+ })
759
+ );
702
760
 
703
761
  if (!isMounted) return;
704
762
 
705
763
  const accessibleItems = filtered
706
- .filter(({ hasAccess }) => hasAccess)
764
+ .filter(({ hasAccess, matchesScope }) => hasAccess && matchesScope)
707
765
  .map(({ item }) => item);
708
766
 
767
+ // If all items were filtered out, check if it's due to scope filtering
768
+ // This can happen if:
769
+ // 1. All pages are scoped for the other context type (expected behavior)
770
+ // 2. Scope type lookup failed for all pages (should fallback to showing items with permissions)
771
+ if (accessibleItems.length === 0 && filtered.length > 0) {
772
+ const itemsWithPermissions = filtered.filter(({ hasAccess }) => hasAccess);
773
+ const itemsFilteredByScope = filtered.filter(({ hasAccess, matchesScope }) => hasAccess && !matchesScope);
774
+ const itemsWithScopeErrors = filtered.filter(({ hasAccess, scopeCheckError }) => hasAccess && scopeCheckError);
775
+
776
+ // If scope checking failed for ALL items with permissions, fall back to showing them
777
+ // This prevents navigation from disappearing due to database/API issues
778
+ if (itemsWithPermissions.length > 0 &&
779
+ itemsWithScopeErrors.length === itemsWithPermissions.length) {
780
+ logger.warn('PaceAppLayout', 'Scope checking failed for all items - falling back to permission-only filtering', {
781
+ contextType: hasEventContext ? 'event' : (hasOrgContext ? 'organisation' : 'none'),
782
+ totalItems: baseMenuItems.length,
783
+ itemsWithPermissions: itemsWithPermissions.length,
784
+ scopeCheckErrorCount: itemsWithScopeErrors.length,
785
+ note: 'Showing items based on permissions only - scope filtering disabled due to errors. This may indicate database issues or missing scope_type values.'
786
+ });
787
+
788
+ // Fall back to showing items that have permissions (ignore scope check)
789
+ const fallbackItems = filtered
790
+ .filter(({ hasAccess }) => hasAccess)
791
+ .map(({ item }) => item);
792
+
793
+ if (isMounted && fallbackItems.length > 0) {
794
+ setFilteredMenuItems(fallbackItems);
795
+ return;
796
+ }
797
+ } else if (itemsWithPermissions.length > 0 && itemsFilteredByScope.length === itemsWithPermissions.length) {
798
+ // All items were filtered by scope (not errors) - this is expected if all pages are scoped for other context
799
+ logger.info('PaceAppLayout', 'All navigation items filtered out by scope type', {
800
+ contextType: hasEventContext ? 'event' : (hasOrgContext ? 'organisation' : 'none'),
801
+ totalItems: baseMenuItems.length,
802
+ itemsWithPermissions: itemsWithPermissions.length,
803
+ itemsFilteredByScope: itemsFilteredByScope.length,
804
+ note: 'All pages appear to be scoped for a different context type - this is expected behavior. Navigation will be empty until user selects the appropriate context.'
805
+ });
806
+ }
807
+ }
808
+
709
809
  // SECURITY: Never show all items if permission check fails - this would be a security risk
710
810
  // If all items are filtered out, it means the user doesn't have permission to see any navigation
811
+ // OR all pages are scoped for a different context type
711
812
  // This is the correct behavior - better to show nothing than show unauthorized items
712
813
  setFilteredMenuItems(accessibleItems);
713
814
  } catch (error) {
@@ -725,7 +826,7 @@ export function PaceAppLayout({
725
826
  return () => {
726
827
  isMounted = false;
727
828
  };
728
- }, [baseMenuItems, pageIdMapping, routePermissions, defaultPermission, can, user?.id, scope, scopeLoading, contextAppId, resolvedScope?.appId, selectedOrganisation?.id]);
829
+ }, [baseMenuItems, pageIdMapping, routePermissions, defaultPermission, can, user?.id, scope, scopeLoading, contextAppId, resolvedScope?.appId, selectedOrganisation?.id, selectedEvent?.event_id, appName]);
729
830
 
730
831
  // NEW: Phase 2 - Enhanced Routing Features
731
832
  // Check route access for role-based routing
@@ -974,8 +1075,9 @@ export function PaceAppLayout({
974
1075
  navigate(item.href);
975
1076
  }
976
1077
  }}
977
- showEventSelector={showEventSelector}
978
- showOrgSelector={showOrgSelector}
1078
+ showContextSelector={showContextSelector}
1079
+ showOrganisations={showOrganisations}
1080
+ showEvents={showEvents}
979
1081
  showUserMenu={showUserMenu}
980
1082
  className={headerClassName || "sticky top-0 z-[40] w-full"}
981
1083
  />
@@ -27,8 +27,7 @@ A comprehensive application layout component that provides a consistent structur
27
27
  |------|------|---------|-------------|
28
28
  | `appName` | `string` | required | The name of the application to be displayed in the header |
29
29
  | `navItems` | `NavigationItem[]` | optional | Navigation items for the header menu. If not provided, uses default navigation |
30
- | `showEventSelector` | `boolean` | `true` | Show/hide event selector in the header |
31
- | `showOrgSelector` | `boolean` | `false` | Show/hide organisation selector in the header |
30
+ | `showContextSelector` | `boolean` | `true` | Show/hide unified context selector (organisations and events) in the header |
32
31
  | `headerActions` | `React.ReactNode` | optional | Custom actions to display in the header (between event selector and user menu) |
33
32
  | `customLogo` | `React.ReactNode` | optional | Custom logo component (overrides default logo) |
34
33
  | `logoHref` | `string` | `'/dashboard'` | URL to navigate to when logo is clicked (e.g., '/dashboard', '/home') |
@@ -162,7 +161,7 @@ function TeamApp() {
162
161
  <PaceAppLayout
163
162
  appName="TEAM"
164
163
  navItems={teamNavItems}
165
- showEventSelector={false} // Hide event selector
164
+ showContextSelector={false} // Hide context selector
166
165
  />
167
166
  }>
168
167
  <Route index element={<ProfilePage />} />
@@ -231,26 +230,24 @@ function App() {
231
230
 
232
231
  ## When to Use Each Feature
233
232
 
234
- ### Show Event Selector (`showEventSelector={true}`)
233
+ ### Show Context Selector (`showContextSelector={true}`) - Default
235
234
  - Event management applications
236
235
  - Apps with multi-tenant event contexts
237
- - Applications where users need to switch between events
236
+ - Multi-organisation applications
237
+ - Applications where users need to switch between events or organisations
238
238
  - Event planning tools
239
239
  - Event registration systems
240
+ - Hybrid apps (like pace-mint) that support both event and organisation contexts
241
+ - Organisation management tools
242
+ - Multi-tenant applications with organisation context
243
+ - Apps requiring organisation-scoped data access
240
244
 
241
- ### Hide Event Selector (`showEventSelector={false}`)
245
+ ### Hide Context Selector (`showContextSelector={false}`)
242
246
  - Personal profile management apps (like TEAM)
243
247
  - User account settings applications
244
- - Non-event specific tools
248
+ - Apps that don't require event or organisation context
245
249
  - Single-tenant applications
246
- - Administrative dashboards that don't depend on events
247
-
248
- ### Show Organisation Selector (`showOrgSelector={true}`)
249
- - Multi-organisation applications
250
- - Apps where users need to switch between organisations
251
- - Organisation management tools
252
- - Multi-tenant applications with organisation context
253
- - Apps requiring organisation-scoped data access
250
+ - Administrative dashboards that don't depend on events or organisations
254
251
 
255
252
  ### Custom Header Components
256
253
  - Applications requiring custom branding
@@ -267,7 +264,7 @@ function App() {
267
264
 
268
265
  ## Integration with UnifiedAuthProvider
269
266
 
270
- The PaceAppLayout works seamlessly with the UnifiedAuthProvider. When `showEventSelector={false}`, the provider will:
267
+ The PaceAppLayout works seamlessly with the UnifiedAuthProvider. When `showContextSelector={false}`, the provider will:
271
268
 
272
269
  - Still handle user authentication
273
270
  - Provide user profile access
@@ -285,7 +282,7 @@ function App() {
285
282
  <Route path="/" element={
286
283
  <PaceAppLayout
287
284
  appName="TEAM"
288
- showEventSelector={false} // No events needed
285
+ showContextSelector={false} // No context selection needed
289
286
  />
290
287
  }>
291
288
  <Route index element={<ProfilePage />} />
@@ -117,7 +117,7 @@ export const createMockHeader = () => ({
117
117
  onChangePassword,
118
118
  currentPath,
119
119
  onNavigate,
120
- showEventSelector,
120
+ showContextSelector,
121
121
  showUserMenu,
122
122
  className
123
123
  }: any) => (
@@ -156,7 +156,7 @@ export const createMockHeader = () => ({
156
156
  Navigate
157
157
  </button>
158
158
  <div data-testid="current-path">{currentPath}</div>
159
- <div data-testid="show-event-selector">{showEventSelector !== false ? 'true' : 'false'}</div>
159
+ <div data-testid="show-context-selector">{showContextSelector !== false ? 'true' : 'false'}</div>
160
160
  </header>
161
161
  )
162
162
  });
@@ -215,8 +215,9 @@ export {
215
215
  } from './NavigationMenu';
216
216
  export type { NavigationMenuProps, NavigationItem } from './NavigationMenu';
217
217
 
218
- export { OrganisationSelector } from './OrganisationSelector';
219
- export type { OrganisationSelectorProps } from './OrganisationSelector';
218
+ // Unified context selector (replaces separate org/event selectors)
219
+ export { ContextSelector } from './ContextSelector';
220
+ export type { ContextSelectorProps } from './ContextSelector';
220
221
 
221
222
  export { UserMenu } from './UserMenu';
222
223
 
@@ -235,10 +236,9 @@ export { SessionRestorationLoader } from './SessionRestorationLoader';
235
236
  export type { SessionRestorationLoaderProps } from './SessionRestorationLoader';
236
237
 
237
238
  // ============================================================================
238
- // EVENT MANAGEMENT
239
+ // CONTEXT SELECTION
239
240
  // ============================================================================
240
-
241
- export { EventSelector } from './EventSelector';
241
+ // Note: EventSelector and OrganisationSelector removed - use ContextSelector instead
242
242
 
243
243
  // ============================================================================
244
244
  // AUTHENTICATION FORMS
@@ -378,6 +378,112 @@ module.exports = {
378
378
  }
379
379
  };
380
380
  }
381
+ },
382
+
383
+ /**
384
+ * Disallow direct Supabase client creation - must use useSecureSupabase
385
+ */
386
+ 'no-direct-supabase-client': {
387
+ meta: {
388
+ type: 'problem',
389
+ docs: {
390
+ description: 'Disallow direct createClient calls from @supabase/supabase-js. Use useSecureSupabase() from pace-core instead to ensure organisation context and RLS policies are enforced.',
391
+ category: 'Security',
392
+ recommended: true
393
+ },
394
+ messages: {
395
+ directClientCreation: "Direct Supabase client creation detected. You MUST use useSecureSupabase() from '@jmruthers/pace-core/rbac' instead to ensure organisation context and RLS policies are enforced. This prevents cross-organisation data access.",
396
+ directClientImport: "Direct import of createClient from @supabase/supabase-js is not allowed. Use useSecureSupabase() from '@jmruthers/pace-core/rbac' instead."
397
+ },
398
+ hasSuggestions: true
399
+ },
400
+ create(context) {
401
+ let hasSupabaseImport = false;
402
+ let createClientImported = false;
403
+ const filename = context.getFilename();
404
+
405
+ // Allow createClient in specific config files (supabaseClient.ts/js, etc.)
406
+ const isConfigFile = /(supabase|client)\.(ts|js|tsx|jsx)$/i.test(filename) &&
407
+ (filename.includes('supabase') || filename.includes('client'));
408
+
409
+ return {
410
+ ImportDeclaration(node) {
411
+ const importSource = node.source.value;
412
+
413
+ // Check for @supabase/supabase-js import
414
+ if (importSource === '@supabase/supabase-js') {
415
+ hasSupabaseImport = true;
416
+
417
+ // Check if createClient is imported
418
+ const hasCreateClient = node.specifiers.some(spec => {
419
+ if (spec.type === 'ImportSpecifier') {
420
+ return spec.imported.name === 'createClient';
421
+ }
422
+ if (spec.type === 'ImportNamespaceSpecifier') {
423
+ return true; // import * as supabase
424
+ }
425
+ return false;
426
+ });
427
+
428
+ if (hasCreateClient) {
429
+ createClientImported = true;
430
+
431
+ // Only report if not in a config file
432
+ if (!isConfigFile) {
433
+ context.report({
434
+ node: node.source,
435
+ messageId: 'directClientImport',
436
+ suggest: [{
437
+ desc: 'Replace with useSecureSupabase hook',
438
+ fix(fixer) {
439
+ // Remove the import
440
+ return fixer.remove(node);
441
+ }
442
+ }]
443
+ });
444
+ }
445
+ }
446
+ }
447
+ },
448
+
449
+ CallExpression(node) {
450
+ // Check for createClient() calls
451
+ if (node.callee.type === 'Identifier' && node.callee.name === 'createClient') {
452
+ // Only report if not in a config file
453
+ if (!isConfigFile) {
454
+ context.report({
455
+ node,
456
+ messageId: 'directClientCreation',
457
+ suggest: [{
458
+ desc: 'Use useSecureSupabase() hook instead',
459
+ fix(fixer) {
460
+ // This is complex to auto-fix, so we'll just report
461
+ return null;
462
+ }
463
+ }]
464
+ });
465
+ }
466
+ }
467
+
468
+ // Check for supabase.createClient() or similar patterns
469
+ if (node.callee.type === 'MemberExpression' &&
470
+ node.callee.property?.name === 'createClient') {
471
+ if (!isConfigFile) {
472
+ context.report({
473
+ node,
474
+ messageId: 'directClientCreation',
475
+ suggest: [{
476
+ desc: 'Use useSecureSupabase() hook instead',
477
+ fix(fixer) {
478
+ return null;
479
+ }
480
+ }]
481
+ });
482
+ }
483
+ }
484
+ }
485
+ };
486
+ }
381
487
  }
382
488
  }
383
489
  };