@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
@@ -284,13 +284,15 @@ export function useDataTablePermissions<TData extends DataRecord>(
284
284
  // Otherwise, use the normal permission check results
285
285
  const createSuperAdminAwarePermission = (result: ReturnType<typeof useCan>) => ({
286
286
  // If super admin check completed and user is super admin, grant all permissions
287
- // Otherwise use the normal permission check result
288
- // Use === true to distinguish between null (not checked) and false (checked, not super admin)
287
+ // and mark loading as complete for DataTable gating purposes.
288
+ // This is not a "hack": super admins *semantically* bypass permission checks, so the
289
+ // table must not remain blocked behind background permission queries.
289
290
  can: isSuperAdminUser === true ? true : result.can,
290
- // Show loading if super admin check is in progress (null and checking) OR if the underlying permission check is loading
291
- // React 19 fix: Read isLoading directly from the result object to ensure we get the latest state
292
- isLoading: (isSuperAdminUser === null && isCheckingSuperAdmin) || result.isLoading,
293
- error: result.error,
291
+ isLoading:
292
+ isSuperAdminUser === true
293
+ ? false
294
+ : (isSuperAdminUser === null && isCheckingSuperAdmin) || result.isLoading,
295
+ error: isSuperAdminUser === true ? null : result.error,
294
296
  refetch: result.refetch,
295
297
  });
296
298
 
@@ -159,7 +159,35 @@ import { X } from 'lucide-react';
159
159
  import { cn } from '../../utils/core/cn';
160
160
  import { renderSafeHtml } from '../../utils/validation/htmlSanitization';
161
161
  import { useState, useEffect } from 'react';
162
- import { debounce } from 'lodash';
162
+
163
+ /**
164
+ * Simple debounce function that matches lodash debounce API
165
+ * Returns a debounced function with a cancel method
166
+ */
167
+ function debounce<T extends (...args: any[]) => void>(
168
+ func: T,
169
+ wait: number
170
+ ): T & { cancel: () => void } {
171
+ let timeoutId: ReturnType<typeof setTimeout> | null = null;
172
+
173
+ const debounced = ((...args: Parameters<T>) => {
174
+ if (timeoutId !== null) {
175
+ clearTimeout(timeoutId);
176
+ }
177
+ timeoutId = setTimeout(() => {
178
+ func(...args);
179
+ }, wait);
180
+ }) as T & { cancel: () => void };
181
+
182
+ debounced.cancel = () => {
183
+ if (timeoutId !== null) {
184
+ clearTimeout(timeoutId);
185
+ timeoutId = null;
186
+ }
187
+ };
188
+
189
+ return debounced;
190
+ }
163
191
 
164
192
  /**
165
193
  * Dialog size variants
@@ -125,7 +125,7 @@ interface FileDisplayContentProps {
125
125
  showMetadata?: boolean;
126
126
  }
127
127
 
128
- function FileDisplayContent({
128
+ const FileDisplayContent = React.memo(function FileDisplayContent({
129
129
  isLoading,
130
130
  error,
131
131
  fileUrl,
@@ -156,6 +156,29 @@ function FileDisplayContent({
156
156
  const [internalFileUrls, setInternalFileUrls] = useState<Map<string, string>>(new Map(fileUrls));
157
157
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
158
158
  const fileReferencesRef = useRef<FileReference[]>([]);
159
+ const imgRef = useRef<HTMLImageElement | null>(null);
160
+ const currentSrcRef = useRef<string | null>(null);
161
+ const isImageLoadingRef = useRef(false);
162
+
163
+ // Stabilize fileUrl to prevent unnecessary image reloads
164
+ // This prevents NS_BINDING_ABORTED errors when the component re-renders
165
+ const stableFileUrl = useMemo(() => fileUrl, [fileUrl]);
166
+
167
+ // Track when image starts loading to prevent cancellation
168
+ const handleImageLoadStart = useCallback(() => {
169
+ isImageLoadingRef.current = true;
170
+ if (stableFileUrl) {
171
+ currentSrcRef.current = stableFileUrl;
172
+ }
173
+ }, [stableFileUrl]);
174
+
175
+ // Track when image finishes loading
176
+ const handleImageLoad = useCallback(() => {
177
+ isImageLoadingRef.current = false;
178
+ if (stableFileUrl) {
179
+ currentSrcRef.current = stableFileUrl;
180
+ }
181
+ }, [stableFileUrl]);
159
182
 
160
183
  // Compute fallback text
161
184
  const computedFallbackText = useMemo(() => {
@@ -328,7 +351,7 @@ function FileDisplayContent({
328
351
  }
329
352
 
330
353
  // Show loading skeleton if URL is not available yet
331
- if (!fileUrl) {
354
+ if (!stableFileUrl) {
332
355
  return (
333
356
  <figure className={className || "max-w-full h-48"} title="Loading">
334
357
  <p className={fallbackClasses}>
@@ -341,10 +364,15 @@ function FileDisplayContent({
341
364
  return (
342
365
  <figure className={className || ""}>
343
366
  <img
344
- src={fileUrl}
367
+ ref={imgRef}
368
+ key={fileReference.id}
369
+ src={stableFileUrl || undefined}
345
370
  alt={fileReference.file_metadata.fileName || 'File'}
346
371
  className={imgClassName || "object-cover size-full"}
347
372
  onError={handleImageError}
373
+ onLoadStart={handleImageLoadStart}
374
+ onLoad={handleImageLoad}
375
+ loading="lazy"
348
376
  />
349
377
  </figure>
350
378
  );
@@ -352,14 +380,14 @@ function FileDisplayContent({
352
380
 
353
381
  // Document link display when displayOnly is true and file is not an image
354
382
  // Render non-image files as clickable links that open in a new tab
355
- if (displayOnly && !isImage && fileUrl && fileReference && !showDelete) {
383
+ if (displayOnly && !isImage && stableFileUrl && fileReference && !showDelete) {
356
384
  const fileName = fileReference.file_metadata?.fileName || 'Document';
357
385
  const ariaLabel = `Open ${fileName} in new tab`;
358
386
 
359
387
  return (
360
388
  <figure className={className}>
361
389
  <a
362
- href={fileUrl}
390
+ href={stableFileUrl || undefined}
363
391
  target="_blank"
364
392
  rel="noopener noreferrer"
365
393
  aria-label={ariaLabel}
@@ -385,7 +413,7 @@ function FileDisplayContent({
385
413
 
386
414
  // Standard single file display with wrapper
387
415
  // For displayOnly mode, if fallback is enabled and there's no URL or image error, show fallback instead of folder icon
388
- if (displayOnly && showFallback && (!fileUrl || imageError || !isImage)) {
416
+ if (displayOnly && showFallback && (!stableFileUrl || imageError || !isImage)) {
389
417
  return (
390
418
  <figure className={className} title={fileReference.file_metadata.fileName || 'File'}>
391
419
  <p className={fallbackClasses}>
@@ -397,13 +425,15 @@ function FileDisplayContent({
397
425
 
398
426
  return (
399
427
  <figure className={`relative ${className}`}>
400
- {isImage && fileUrl && !imageError ? (
428
+ {isImage && stableFileUrl && !imageError ? (
401
429
  <>
402
430
  <img
403
- src={fileUrl}
431
+ key={fileReference.id}
432
+ src={stableFileUrl}
404
433
  alt={fileReference.file_metadata.fileName || 'File'}
405
434
  className={imgClassName || "object-cover size-full"}
406
435
  onError={handleImageError}
436
+ loading="lazy"
407
437
  />
408
438
  {showDelete && (
409
439
  <>
@@ -541,10 +571,12 @@ function FileDisplayContent({
541
571
  <figure key={fileRef.id} className="flex items-center space-x-3 p-3 bg-sec-50 rounded-lg border border-sec-200">
542
572
  {isImage && fileUrl ? (
543
573
  <img
544
- src={fileUrl}
574
+ key={fileRef.id}
575
+ src={fileUrl || undefined}
545
576
  alt={fileRef.file_metadata.fileName || 'File'}
546
577
  className={imgClassName || "object-cover size-full"}
547
578
  onError={handleImageError}
579
+ loading="lazy"
548
580
  />
549
581
  ) : (
550
582
  <span className="text-2xl">
@@ -596,7 +628,7 @@ function FileDisplayContent({
596
628
  {children}
597
629
  </figure>
598
630
  );
599
- }
631
+ });
600
632
 
601
633
  /**
602
634
  * Internal component for public page context
@@ -52,20 +52,24 @@ vi.mock('../UserMenu', () => ({
52
52
  ),
53
53
  }));
54
54
 
55
- vi.mock('../EventSelector', () => ({
56
- EventSelector: ({ placeholder, className, 'data-testid': testId }: any) => (
57
- <div data-testid={testId || 'event-selector'} className={className}>
58
- <button>{placeholder}</button>
55
+ vi.mock('../ContextSelector', () => ({
56
+ ContextSelector: ({ placeholder, className, 'data-testid': testId }: any) => (
57
+ <div data-testid={testId || 'context-selector'} className={className}>
58
+ <button>{placeholder || 'Select organisation or event'}</button>
59
59
  </div>
60
60
  ),
61
61
  }));
62
62
 
63
- vi.mock('../OrganisationSelector', () => ({
64
- OrganisationSelector: ({ placeholder, className, 'data-testid': testId }: any) => (
65
- <div data-testid={testId || 'org-selector'} className={className}>
66
- <button>{placeholder}</button>
67
- </div>
68
- ),
63
+ // Mock useEventService to prevent provider requirement
64
+ vi.mock('../../hooks/services/useEventService', () => ({
65
+ useEventService: vi.fn(() => ({
66
+ getEvents: vi.fn(() => []),
67
+ getSelectedEvent: vi.fn(() => null),
68
+ isLoading: vi.fn(() => false),
69
+ getError: vi.fn(() => null),
70
+ setSelectedEvent: vi.fn(),
71
+ refreshEvents: vi.fn()
72
+ }))
69
73
  }));
70
74
 
71
75
  // Mock useOrganisations hook
@@ -90,7 +94,7 @@ vi.mock('../../hooks/useOrganisations', () => ({
90
94
  isContextReady: true,
91
95
  isLoading: false,
92
96
  error: null,
93
- selectOrganisation: vi.fn(),
97
+ switchOrganisation: vi.fn(),
94
98
  refreshOrganisations: vi.fn(),
95
99
  userMemberships: []
96
100
  }))
@@ -314,44 +318,44 @@ describe('Header Component', () => {
314
318
  });
315
319
  });
316
320
 
317
- // Event selector tests
318
- describe('Event Selector', () => {
319
- it('renders event selector when showEventSelector is true', () => {
320
- renderWithProviders(<Header showEventSelector={true} />);
321
+ // Context selector tests
322
+ describe('Context Selector', () => {
323
+ it('renders context selector by default', () => {
324
+ renderWithProviders(<Header />);
321
325
 
322
- expect(screen.getByTestId('event-selector')).toBeInTheDocument();
326
+ expect(screen.getByTestId('context-selector')).toBeInTheDocument();
323
327
  });
324
328
 
325
- it('does not render event selector when showEventSelector is false', () => {
326
- renderWithProviders(<Header showEventSelector={false} />);
329
+ it('does not render context selector when showContextSelector is false', () => {
330
+ renderWithProviders(<Header showContextSelector={false} />);
327
331
 
328
- expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
332
+ expect(screen.queryByTestId('context-selector')).not.toBeInTheDocument();
329
333
  });
330
334
 
331
- it('renders event selector by default', () => {
332
- renderWithProviders(<Header />);
335
+ it('renders context selector when showContextSelector is explicitly true', () => {
336
+ renderWithProviders(<Header showContextSelector={true} />);
333
337
 
334
- expect(screen.getByTestId('event-selector')).toBeInTheDocument();
338
+ expect(screen.getByTestId('context-selector')).toBeInTheDocument();
335
339
  });
336
340
 
337
- it('applies correct className to event selector', () => {
338
- renderWithProviders(<Header showEventSelector={true} />);
341
+ it('applies correct className to context selector', () => {
342
+ renderWithProviders(<Header showContextSelector={true} />);
339
343
 
340
- const eventSelector = screen.getByTestId('event-selector');
341
- expect(eventSelector).toBeInTheDocument();
342
- expect(eventSelector).toBeVisible();
344
+ const contextSelector = screen.getByTestId('context-selector');
345
+ expect(contextSelector).toBeInTheDocument();
346
+ expect(contextSelector).toBeVisible();
343
347
  });
344
348
 
345
349
  it('shows correct placeholder text', () => {
346
- renderWithProviders(<Header showEventSelector={true} />);
350
+ renderWithProviders(<Header showContextSelector={true} />);
347
351
 
348
- expect(screen.getByRole('button', { name: 'Select event' })).toBeInTheDocument();
352
+ expect(screen.getByRole('button', { name: 'Select organisation or event' })).toBeInTheDocument();
349
353
  });
350
354
 
351
- it('preserves layout when event selector is hidden', () => {
355
+ it('preserves layout when context selector is hidden', () => {
352
356
  renderWithProviders(
353
357
  <Header
354
- showEventSelector={false}
358
+ showContextSelector={false}
355
359
  user={mockUser}
356
360
  showUserMenu={true}
357
361
  />
@@ -361,47 +365,14 @@ describe('Header Component', () => {
361
365
  expect(nav).toBeInTheDocument();
362
366
  expect(nav).toBeVisible();
363
367
 
364
- // Event selector should not be rendered
365
- expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
368
+ // Context selector should not be rendered
369
+ expect(screen.queryByTestId('context-selector')).not.toBeInTheDocument();
366
370
 
367
371
  // User menu should still be present and positioned correctly
368
372
  expect(screen.getByTestId('user-menu')).toBeInTheDocument();
369
373
  });
370
374
  });
371
375
 
372
- // Organisation Selector tests
373
- describe('Organisation Selector', () => {
374
- it('does not render organisation selector by default', () => {
375
- renderWithProviders(<Header />);
376
-
377
- expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
378
- });
379
-
380
- it('renders organisation selector when showOrgSelector is true', () => {
381
- renderWithProviders(<Header showOrgSelector={true} />);
382
-
383
- expect(screen.getByTestId('org-selector')).toBeInTheDocument();
384
- });
385
-
386
- it('does not render organisation selector when showOrgSelector is false', () => {
387
- renderWithProviders(<Header showOrgSelector={false} />);
388
-
389
- expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
390
- });
391
-
392
- it('can render both organisation and event selectors together', () => {
393
- renderWithProviders(
394
- <Header
395
- showOrgSelector={true}
396
- showEventSelector={true}
397
- />
398
- );
399
-
400
- expect(screen.getByTestId('org-selector')).toBeInTheDocument();
401
- expect(screen.getByTestId('event-selector')).toBeInTheDocument();
402
- });
403
- });
404
-
405
376
  // Custom actions tests
406
377
  describe('Custom Actions', () => {
407
378
  it('renders custom actions when provided', () => {
@@ -598,7 +569,7 @@ describe('Header Component', () => {
598
569
  navItems={mockNavItems}
599
570
  user={mockUser}
600
571
  actions={customActions}
601
- showEventSelector={true}
572
+ showContextSelector={true}
602
573
  showUserMenu={true}
603
574
  />
604
575
  );
@@ -609,8 +580,8 @@ describe('Header Component', () => {
609
580
  // Navigation
610
581
  expect(screen.getByTestId('navigation-menu')).toBeInTheDocument();
611
582
 
612
- // Event Selector
613
- expect(screen.getByTestId('event-selector')).toBeInTheDocument();
583
+ // Context Selector
584
+ expect(screen.getByTestId('context-selector')).toBeInTheDocument();
614
585
 
615
586
  // Custom Actions
616
587
  expect(screen.getByTestId('custom-actions')).toBeInTheDocument();
@@ -622,7 +593,7 @@ describe('Header Component', () => {
622
593
  it('renders minimal configuration', () => {
623
594
  renderWithProviders(
624
595
  <Header
625
- showEventSelector={false}
596
+ showContextSelector={false}
626
597
  showUserMenu={false}
627
598
  />
628
599
  );
@@ -631,9 +602,8 @@ describe('Header Component', () => {
631
602
  expect(screen.getByRole('banner')).toBeInTheDocument();
632
603
  expect(screen.getByRole('img', { name: 'Logo' })).toBeInTheDocument();
633
604
  expect(screen.queryByTestId('navigation-menu')).not.toBeInTheDocument();
634
- expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
605
+ expect(screen.queryByTestId('context-selector')).not.toBeInTheDocument();
635
606
  expect(screen.queryByTestId('user-menu')).not.toBeInTheDocument();
636
- expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
637
607
  });
638
608
  });
639
609
 
@@ -649,7 +619,7 @@ describe('Header Component', () => {
649
619
  navItems={mockNavItems}
650
620
  user={mockUser}
651
621
  actions={<div>Actions</div>}
652
- showEventSelector={true}
622
+ showContextSelector={true}
653
623
  showUserMenu={true}
654
624
  />
655
625
  );
@@ -51,11 +51,11 @@
51
51
  * onSignOut={handleSignOut}
52
52
  * />
53
53
  *
54
- * // Header without event selector
54
+ * // Header without context selector
55
55
  * <Header
56
56
  * logoUrl="/logo.svg"
57
57
  * logoHref="/dashboard"
58
- * showEventSelector={false}
58
+ * showContextSelector={false}
59
59
  * user={currentUser}
60
60
  * onSignOut={handleSignOut}
61
61
  * />
@@ -83,21 +83,21 @@
83
83
  * - Tailwind CSS - Styling
84
84
  * - NavigationMenu component
85
85
  * - UserMenu component
86
- * - OrganisationSelector component
87
- * - EventSelector component
86
+ * - ContextSelector component (unified org/event selector)
88
87
  */
89
88
 
90
89
  import React from 'react';
91
90
  import { Link } from 'react-router-dom';
92
91
  import { User } from '@supabase/supabase-js';
93
92
  import { cn } from '../../utils/core/cn';
94
- import { EventSelector } from '../EventSelector';
95
- import { OrganisationSelector } from '../OrganisationSelector';
93
+ import { ContextSelector } from '../ContextSelector';
96
94
  import { UserMenu } from '../UserMenu';
97
95
  import { NavigationMenu } from '../NavigationMenu';
98
96
  import type { NavigationItem } from '../NavigationMenu';
99
97
  import type { PasswordChangeFormError } from '../PasswordChange/PasswordChangeForm';
100
98
  import { useOrganisations } from '../../hooks/useOrganisations';
99
+ import { useEvents } from '../../hooks/useEvents';
100
+ import type { Event } from '../../types/event';
101
101
 
102
102
  /**
103
103
  * Props for the Header component
@@ -123,10 +123,12 @@ export interface HeaderProps {
123
123
  userMenu?: React.ReactNode;
124
124
  /** Custom className */
125
125
  className?: string;
126
- /** Show/hide event selector */
127
- showEventSelector?: boolean;
128
- /** Show/hide organisation selector */
129
- showOrgSelector?: boolean;
126
+ /** Show/hide context selector (unified org/event selector) - default: true */
127
+ showContextSelector?: boolean;
128
+ /** Show organisations in context selector - default: true */
129
+ showOrganisations?: boolean;
130
+ /** Show events in context selector - default: true */
131
+ showEvents?: boolean;
130
132
  /** Show/hide user menu */
131
133
  showUserMenu?: boolean;
132
134
  /** Current path for navigation highlighting */
@@ -225,7 +227,7 @@ export interface HeaderProps {
225
227
  * <Header
226
228
  * logoUrl="/simple-logo.svg"
227
229
  * logoAlt="Simple App"
228
- * showEventSelector={false}
230
+ * showContextSelector={false}
229
231
  * user={currentUser}
230
232
  * onSignOut={handleSignOut}
231
233
  * />
@@ -256,29 +258,20 @@ export function Header({
256
258
  actions,
257
259
  userMenu,
258
260
  className,
259
- showEventSelector = true,
260
- showOrgSelector = false,
261
+ showContextSelector = true,
262
+ showOrganisations = true,
263
+ showEvents = true,
261
264
  showUserMenu = true,
262
265
  currentPath,
263
266
  onNavigate,
264
267
  logoHref
265
268
  }: HeaderProps) {
266
- // Conditional wrapper for organisation selector - only show if user has organisations
267
- const OrganisationSelectorConditional = () => {
268
- const { organisations, isContextReady } = useOrganisations();
269
- // Only show selector if user has organisations and context is ready
270
- if (!isContextReady || !organisations || organisations.length === 0) {
271
- return null;
272
- }
273
- return (
274
- <OrganisationSelector
275
- placeholder="Select organisation"
276
- className="w-64"
277
- data-testid="org-selector"
278
- compact={true}
279
- />
280
- );
281
- };
269
+ // Determine if context selector should be shown
270
+ const shouldShowContextSelector = showContextSelector !== false;
271
+
272
+ // Get hooks for context selector
273
+ const { switchOrganisation } = useOrganisations();
274
+ const { events, setSelectedEvent } = useEvents();
282
275
 
283
276
  return (
284
277
  <header className={cn(
@@ -341,25 +334,31 @@ export function Header({
341
334
  />
342
335
  )}
343
336
 
344
- {/* Organisation Selector - Only show if user has organisations */}
345
- {showOrgSelector ? (
346
- <OrganisationSelectorConditional />
347
- ) : null}
348
-
349
- {/* Event Selector */}
350
- {showEventSelector ? (
351
- <EventSelector
352
- placeholder="Select event"
337
+ {/* Unified Context Selector - Shows all accessible orgs and events */}
338
+ {shouldShowContextSelector ? (
339
+ <ContextSelector
340
+ placeholder="Select organisation or event"
353
341
  className={cn(
354
342
  "w-96",
355
- // If both org selector and actions exist, EventSelector uses 1 column
356
- // If only one exists, EventSelector spans 2 columns
357
- // If neither exists, EventSelector spans 3 columns
358
- showOrgSelector && actions ? "col-span-1" :
359
- showOrgSelector || actions ? "col-span-2" :
360
- "col-span-3"
343
+ // Adjust width based on whether actions exist
344
+ actions ? "col-span-1" : "col-span-2"
361
345
  )}
362
- data-testid="event-selector"
346
+ showOrganisations={showOrganisations}
347
+ showEvents={showEvents}
348
+ onOrganisationSelect={async (org) => {
349
+ // When switching to an organisation, clear event selection FIRST
350
+ // This ensures the userClearedEventRef flag is set before any event refresh
351
+ // This prevents auto-selection from re-selecting the event
352
+ setSelectedEvent(null);
353
+ // Then switch organisation (this may trigger event refresh, but flag is already set)
354
+ await switchOrganisation(org.id);
355
+ }}
356
+ onEventSelect={(event) => {
357
+ // Find the full event object from the events list
358
+ const fullEvent = events.find((e: Event) => (e.event_id || e.id) === (event.event_id || event.id));
359
+ setSelectedEvent(fullEvent || event);
360
+ }}
361
+ compact={true}
363
362
  />
364
363
  ) : null}
365
364
 
@@ -162,7 +162,7 @@ vi.mock('../../hooks/services/useEventService', () => ({
162
162
  useEventService: mockUseEventService,
163
163
  }));
164
164
 
165
- // Mock useEvents hook (used by useEventTheme and EventSelector)
165
+ // Mock useEvents hook (used by useEventTheme and ContextSelector)
166
166
  // Use the hoisted mockUseEventService so useEvents can work properly
167
167
  // But also provide a direct mock for components that import useEvents directly
168
168
  const mockUseEvents = vi.hoisted(() => vi.fn(() => ({
@@ -302,20 +302,11 @@ vi.mock('../../rbac/hooks', () => ({
302
302
  })),
303
303
  }));
304
304
 
305
- // Mock EventSelector to avoid useEventService requirement
306
- // Mock both the component file and the index file
307
- vi.mock('../../EventSelector/EventSelector', () => ({
308
- EventSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
309
- <div data-testid={testId || 'event-selector'} className={className}>
310
- {placeholder || 'Select event'}
311
- </div>
312
- ))
313
- }));
314
-
315
- vi.mock('../../EventSelector', () => ({
316
- EventSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
317
- <div data-testid={testId || 'event-selector'} className={className}>
318
- {placeholder || 'Select event'}
305
+ // Mock ContextSelector to avoid useEventService requirement
306
+ vi.mock('../ContextSelector', () => ({
307
+ ContextSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
308
+ <div data-testid={testId || 'context-selector'} className={className}>
309
+ {placeholder || 'Select organisation or event'}
319
310
  </div>
320
311
  ))
321
312
  }));
@@ -334,7 +325,7 @@ vi.mock('../Header', () => ({
334
325
  userMenu,
335
326
  logo,
336
327
  logoUrl,
337
- showEventSelector,
328
+ showContextSelector,
338
329
  showUserMenu,
339
330
  className
340
331
  }) => (
@@ -407,7 +398,7 @@ vi.mock('../Header', () => ({
407
398
  Change Password
408
399
  </button>
409
400
  <div data-testid="current-path">{currentPath}</div>
410
- <div data-testid="show-event-selector">{showEventSelector !== false ? 'true' : 'false'}</div>
401
+ <div data-testid="show-context-selector">{showContextSelector !== false ? 'true' : 'false'}</div>
411
402
  </header>
412
403
  ))
413
404
  }));
@@ -1054,7 +1045,7 @@ describe('PaceAppLayout Integration', () => {
1054
1045
  headerActions={<HeaderActions />}
1055
1046
  customUserMenu={<CustomUserMenu />}
1056
1047
  customLogo={<CustomLogo />}
1057
- showEventSelector={false}
1048
+ showContextSelector={false}
1058
1049
  showUserMenu={false}
1059
1050
  headerClassName="complex-header-class"
1060
1051
  enforcePermissions={false}
@@ -1073,7 +1064,7 @@ describe('PaceAppLayout Integration', () => {
1073
1064
  // Verify configuration is applied
1074
1065
  expect(screen.getByTestId('app-name')).toHaveTextContent('Complex App');
1075
1066
  expect(screen.getByTestId('nav-items-count')).toHaveTextContent('2');
1076
- expect(screen.getByTestId('show-event-selector')).toHaveTextContent('false');
1067
+ expect(screen.getByTestId('show-context-selector')).toHaveTextContent('false');
1077
1068
  expect(screen.getByTestId('show-user-menu')).toHaveTextContent('false');
1078
1069
  expect(screen.getByTestId('mock-header')).toHaveClass('complex-header-class');
1079
1070
  });
@@ -626,7 +626,7 @@ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
626
626
  <TestWrapper>
627
627
  <PaceAppLayout
628
628
  appName={`Test App ${i}`}
629
- showEventSelector={i % 2 === 0}
629
+ showContextSelector={i % 2 === 0}
630
630
  showUserMenu={i % 2 === 1}
631
631
  />
632
632
  </TestWrapper>
@@ -916,7 +916,7 @@ const TestWrapper = ({ children }: { children: React.ReactNode }) => (
916
916
  navItems={customNavItems}
917
917
  headerActions={<HeaderActions />}
918
918
  customUserMenu={<CustomUserMenu />}
919
- showEventSelector={false}
919
+ showContextSelector={false}
920
920
  showUserMenu={true}
921
921
  headerClassName="complex-header-class"
922
922
  enforcePermissions={false}
@@ -296,11 +296,11 @@ vi.mock('../../rbac/hooks', () => ({
296
296
  })),
297
297
  }));
298
298
 
299
- // Mock EventSelector to avoid useEventService requirement
300
- vi.mock('../EventSelector', () => ({
301
- EventSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
302
- <div data-testid={testId || 'event-selector'} className={className}>
303
- {placeholder || 'Select event'}
299
+ // Mock ContextSelector to avoid useEventService requirement
300
+ vi.mock('../ContextSelector', () => ({
301
+ ContextSelector: vi.fn(({ placeholder, className, 'data-testid': testId }: any) => (
302
+ <div data-testid={testId || 'context-selector'} className={className}>
303
+ {placeholder || 'Select organisation or event'}
304
304
  </div>
305
305
  ))
306
306
  }));