@jmruthers/pace-core 0.6.2 → 0.6.4

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-E7YQZD7D.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-QPXO24B4.js} +5 -4
  8. package/dist/{api-MVVQZLJI.js → api-6LVZTHDS.js} +10 -10
  9. package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
  10. package/dist/chunk-36LVWXB2.js +227 -0
  11. package/dist/chunk-36LVWXB2.js.map +1 -0
  12. package/dist/{chunk-24UVZUZG.js → chunk-3LPHPB62.js} +129 -387
  13. package/dist/chunk-3LPHPB62.js.map +1 -0
  14. package/dist/{chunk-2UOI2FG5.js → chunk-5EC5MEWX.js} +4 -4
  15. package/dist/{chunk-3XC4CPTD.js → chunk-7JPAB3T5.js} +244 -5727
  16. package/dist/chunk-7JPAB3T5.js.map +1 -0
  17. package/dist/{chunk-6J4GEEJR.js → chunk-ATKZM7RX.js} +53 -27
  18. package/dist/chunk-ATKZM7RX.js.map +1 -0
  19. package/dist/{chunk-EHMR7VYL.js → chunk-AVMLPIM7.js} +443 -189
  20. package/dist/chunk-AVMLPIM7.js.map +1 -0
  21. package/dist/chunk-DGUM43GV.js +11 -0
  22. package/dist/{chunk-NECFR5MM.js → chunk-I6DAQMWX.js} +575 -647
  23. package/dist/chunk-I6DAQMWX.js.map +1 -0
  24. package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
  25. package/dist/{chunk-XWQCNGTQ.js → chunk-NN6WWZ5U.js} +173 -79
  26. package/dist/chunk-NN6WWZ5U.js.map +1 -0
  27. package/dist/{chunk-MMZ7JXPU.js → chunk-OEWDTMG7.js} +13 -21
  28. package/dist/{chunk-MMZ7JXPU.js.map → chunk-OEWDTMG7.js.map} +1 -1
  29. package/dist/{chunk-SFZUDBL5.js → chunk-YKRAFF5K.js} +70 -56
  30. package/dist/chunk-YKRAFF5K.js.map +1 -0
  31. package/dist/components.d.ts +2 -2
  32. package/dist/components.js +12 -13
  33. package/dist/contextValidator-OOPCLPZW.js +9 -0
  34. package/dist/contextValidator-OOPCLPZW.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 +10 -5
  60. package/scripts/audit/core/checks/compliance.cjs +72 -0
  61. package/scripts/audit/core/checks/dependencies.cjs +568 -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 +157 -36
  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 +285 -56
  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-E7YQZD7D.js.map} +0 -0
  294. /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-QPXO24B4.js.map} +0 -0
  295. /package/dist/{api-MVVQZLJI.js.map → api-6LVZTHDS.js.map} +0 -0
  296. /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
  297. /package/dist/{chunk-2UOI2FG5.js.map → chunk-5EC5MEWX.js.map} +0 -0
  298. /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
  299. /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
@@ -1,423 +0,0 @@
1
- /**
2
- * @file EventSelector Component
3
- * @package @jmruthers/pace-core
4
- * @module Components/EventSelector
5
- * @since 0.1.0
6
- *
7
- * A secure event selector component that allows users to choose from events they have
8
- * access to based on their role-based permissions. Integrates with the RBAC system
9
- * to ensure users only see events they're authorized to access.
10
- *
11
- * Features:
12
- * - Role-based event access control
13
- * - Automatic next event detection and selection
14
- * - Cross-device synchronization via Supabase
15
- * - Offline support with localStorage fallback
16
- * - Comprehensive error handling and retry functionality
17
- * - Event details display in dropdown
18
- * - Next/upcoming event indicators
19
- * - Loading states and user feedback
20
- * - Accessible interface design
21
- * - Integration with EventProvider
22
- *
23
- * @example
24
- * ```tsx
25
- * // Basic event selector
26
- * <EventSelector
27
- * onEventChange={(event) => console.log('Selected event:', event)}
28
- * />
29
- *
30
- * // With custom configuration
31
- * <EventSelector
32
- * placeholder="Choose an event..."
33
- * showEventDetails={true}
34
- * showNextEventIndicator={true}
35
- * showRetryButton={true}
36
- * onEventChange={handleEventChange}
37
- * />
38
- *
39
- * // In a header component
40
- * <Header>
41
- * <EventSelector
42
- * className="w-64"
43
- * showEventDetails={false}
44
- * onEventChange={setCurrentEvent}
45
- * />
46
- * </Header>
47
- *
48
- * // With error handling
49
- * <EventSelector
50
- * showNoEventsMessage={true}
51
- * showRetryButton={true}
52
- * onEventChange={(event) => {
53
- * if (event) {
54
- * setCurrentEvent(event);
55
- * navigate(`/events/${event.id}`);
56
- * }
57
- * }}
58
- * />
59
- * ```
60
- *
61
- * @accessibility
62
- * - WCAG 2.1 AA compliant
63
- * - Proper ARIA labels and descriptions
64
- * - Keyboard navigation support
65
- * - Screen reader friendly
66
- * - Focus management
67
- * - High contrast support
68
- * - Clear error identification
69
- *
70
- * @security
71
- * - Role-based access control integration
72
- * - Secure event data handling
73
- * - User permission validation
74
- * - No unauthorized event exposure
75
- * - Secure session management
76
- *
77
- * @dependencies
78
- * - EventProvider - Event context and state
79
- * - Select components - Dropdown interface
80
- * - Button component - Retry functionality
81
- * - Alert component - Error display
82
- * - LoadingSpinner - Loading states
83
- * - React 19+ - Hooks and effects
84
- * - Tailwind CSS - Styling
85
- */
86
-
87
- import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../Select';
88
- import { Alert, AlertDescription } from '../Alert/Alert';
89
- import { Button } from '../Button/Button';
90
- import { LoadingSpinner } from '../LoadingSpinner/LoadingSpinner';
91
- import { RefreshCw, AlertCircle, Lock, Calendar, Star } from 'lucide-react';
92
- import { useEvents } from '../../hooks/useEvents';
93
- import { Event } from '../../types/event';
94
- import { useEffect, useMemo, useRef } from 'react';
95
- import { cn } from '../../utils/core/cn';
96
- import { logger } from '../../utils/core/logger';
97
-
98
- /**
99
- * Props for the EventSelector component.
100
- */
101
- export interface EventSelectorProps {
102
- /** Placeholder text for the dropdown */
103
- placeholder?: string;
104
- /** Additional CSS classes */
105
- className?: string;
106
- /** Callback fired when an event changes, providing full event object */
107
- onEventChange?: (event: Event | null) => void;
108
- /** Show friendly message when no events available */
109
- showNoEventsMessage?: boolean;
110
- /** Show retry button on errors */
111
- showRetryButton?: boolean;
112
- /** Show event details in dropdown */
113
- showEventDetails?: boolean;
114
- /** Show indicator for next/upcoming events */
115
- showNextEventIndicator?: boolean;
116
- }
117
-
118
- /**
119
- * EventSelector component for selecting events with built-in access control
120
- *
121
- * This component provides secure event selection with:
122
- * - Database integration via rbac_event_app_roles table
123
- * - Auto-selection of next upcoming event by date
124
- * - Cross-device sync via Supabase user session metadata
125
- * - localStorage fallback for offline scenarios
126
- * - Comprehensive error handling and user feedback
127
- *
128
- * @component
129
- * @example
130
- * <UnifiedAuthProvider supabaseClient={supabase} appName="PACE">
131
- * <EventSelector onEventChange={(event) => console.log(event)} />
132
- * </UnifiedAuthProvider>
133
- */
134
- export function EventSelector({
135
- placeholder = "Select an event",
136
- className,
137
- onEventChange,
138
- showNoEventsMessage = true,
139
- showRetryButton = true,
140
- showEventDetails = true,
141
- showNextEventIndicator = true
142
- }: EventSelectorProps) {
143
- const {
144
- events,
145
- selectedEvent,
146
- isLoading,
147
- error,
148
- setSelectedEvent,
149
- refreshEvents,
150
- } = useEvents();
151
-
152
- // Removed excessive debug logging - only log on significant state changes, not every render
153
-
154
-
155
- const handleValueChange = (eventId: string) => {
156
- const event = events.find((e) => (e.event_id || e.id) === eventId);
157
-
158
- if (event) {
159
- setSelectedEvent(event);
160
- if (onEventChange) {
161
- onEventChange(event);
162
- }
163
- }
164
- };
165
-
166
- const handleRetry = () => {
167
- refreshEvents();
168
- };
169
-
170
- // Helper function to check if an event is the next upcoming event
171
- const isNextEvent = (event: Event): boolean => {
172
- if (!event.event_date) return false;
173
-
174
- const now = new Date();
175
- const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
176
- const eventDate = new Date(event.event_date);
177
-
178
- return eventDate >= today;
179
- };
180
-
181
- // Helper function to format event date
182
- const formatEventDate = (dateString: string): string => {
183
- const date = new Date(dateString);
184
- const today = new Date();
185
- const tomorrow = new Date(today);
186
- tomorrow.setDate(tomorrow.getDate() + 1);
187
-
188
- // Normalize dates to compare only the date part (ignore time)
189
- const normalizeDate = (d: Date) => {
190
- const normalized = new Date(d);
191
- normalized.setHours(0, 0, 0, 0);
192
- return normalized;
193
- };
194
-
195
- const normalizedDate = normalizeDate(date);
196
- const normalizedToday = normalizeDate(today);
197
- const normalizedTomorrow = normalizeDate(tomorrow);
198
-
199
-
200
- if (normalizedDate.getTime() === normalizedToday.getTime()) {
201
- return 'Today';
202
- } else if (normalizedDate.getTime() === normalizedTomorrow.getTime()) {
203
- return 'Tomorrow';
204
- } else {
205
- return date.toLocaleDateString();
206
- }
207
- };
208
-
209
- // Compute sorted list: descending by event_date (newest first); events without date go last
210
- const sortedEvents = useMemo(() => {
211
- const getTime = (e: Event) => (e.event_date ? new Date(e.event_date).getTime() : Number.NEGATIVE_INFINITY);
212
- return [...events].sort((a, b) => getTime(b) - getTime(a));
213
- }, [events]);
214
-
215
- // Default to the next upcoming event if none selected, fallback to most recent past event
216
- // IMPORTANT: Only auto-select if there's no persisted event being restored
217
- // EventService handles all persistence internally - we don't check storage directly
218
- // Use refs to track previous values and prevent unnecessary effect runs
219
- const prevEventsLengthRef = useRef(events.length);
220
- const prevSelectedEventIdRef = useRef(selectedEvent?.event_id);
221
- const hasAutoSelectedRef = useRef(false);
222
-
223
- useEffect(() => {
224
- // Track changes to detect when events are actually loaded
225
- const eventsLengthChanged = events.length !== prevEventsLengthRef.current;
226
- const selectedEventChanged = selectedEvent?.event_id !== prevSelectedEventIdRef.current;
227
-
228
- if (eventsLengthChanged) {
229
- prevEventsLengthRef.current = events.length;
230
- // Reset auto-select flag when new events are loaded
231
- if (events.length > 0) {
232
- hasAutoSelectedRef.current = false;
233
- }
234
- }
235
- if (selectedEventChanged) {
236
- prevSelectedEventIdRef.current = selectedEvent?.event_id;
237
- // Reset auto-select flag when selected event changes externally
238
- if (selectedEvent) {
239
- hasAutoSelectedRef.current = false;
240
- }
241
- }
242
-
243
- // Only auto-select if:
244
- // 1. Events are loaded (length > 0)
245
- // 2. No event is selected
246
- // 3. Not currently loading
247
- // 4. Events actually changed (new events loaded)
248
- // 5. Haven't already auto-selected (prevent loops)
249
- if (!selectedEvent && events.length > 0 && !isLoading && eventsLengthChanged && !hasAutoSelectedRef.current) {
250
- // No event selected - safe to auto-select
251
- // EventService has already checked for persisted events during initialization
252
- hasAutoSelectedRef.current = true;
253
- autoSelectEvent();
254
- }
255
-
256
- function autoSelectEvent() {
257
- const today = new Date();
258
- const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate()).getTime();
259
-
260
- // Try to find next future event
261
- const next = [...events]
262
- .filter(e => e.event_date && new Date(e.event_date).getTime() >= startOfToday)
263
- .sort((a, b) => new Date(a.event_date as string).getTime() - new Date(b.event_date as string).getTime())[0];
264
-
265
- if (next) {
266
- setSelectedEvent(next);
267
- if (onEventChange) onEventChange(next);
268
- } else {
269
- // Fallback to most recent past event if no future events found
270
- const mostRecentPast = [...events]
271
- .filter(e => {
272
- if (!e.event_date) return false;
273
- const eventDate = new Date(e.event_date);
274
- const startOfEventDate = new Date(eventDate.getFullYear(), eventDate.getMonth(), eventDate.getDate()).getTime();
275
- return startOfEventDate < startOfToday;
276
- })
277
- .sort((a, b) => new Date(b.event_date as string).getTime() - new Date(a.event_date as string).getTime())[0];
278
-
279
- if (mostRecentPast) {
280
- setSelectedEvent(mostRecentPast);
281
- if (onEventChange) onEventChange(mostRecentPast);
282
- }
283
- }
284
- }
285
- }, [events.length, selectedEvent?.event_id, isLoading, setSelectedEvent, onEventChange]);
286
-
287
- // Loading state
288
- if (isLoading) {
289
- return (
290
- <div className={`flex items-center gap-2 ${className}`}>
291
- <LoadingSpinner size="sm" />
292
- <span className="text-sm text-muted-foreground">Loading events...</span>
293
- </div>
294
- );
295
- }
296
-
297
- // Access error state
298
- if (error) {
299
- return (
300
- <div className={className}>
301
- <Alert variant="destructive">
302
- <Lock className="size-4" />
303
- <AlertDescription className="flex items-center justify-between">
304
- <span>{error.message}</span>
305
- {showRetryButton && (
306
- <Button
307
- variant="outline"
308
- size="sm"
309
- onClick={handleRetry}
310
- className="ml-2"
311
- >
312
- <RefreshCw className="size-3 mr-1" />
313
- Retry
314
- </Button>
315
- )}
316
- </AlertDescription>
317
- </Alert>
318
- </div>
319
- );
320
- }
321
-
322
- // No events available state
323
- if (events.length === 0) {
324
- if (showNoEventsMessage) {
325
- return (
326
- <div className={className}>
327
- <Alert variant="inline">
328
- <AlertCircle className="size-4 text-acc-700" />
329
- <AlertDescription className="flex items-center justify-between">
330
- <span>No events available.</span>
331
- {showRetryButton && (
332
- <Button
333
- variant="outline"
334
- size="sm"
335
- onClick={handleRetry}
336
- className="ml-2"
337
- >
338
- <RefreshCw className="size-3 mr-1" />
339
- Refresh
340
- </Button>
341
- )}
342
- </AlertDescription>
343
- </Alert>
344
- </div>
345
- );
346
- }
347
- return null;
348
- }
349
-
350
- // Normal selector state
351
- return (
352
- <Select
353
- value={selectedEvent ? (selectedEvent.event_id || selectedEvent.id) : ''}
354
- onValueChange={handleValueChange}
355
- className={className}
356
- >
357
- <SelectTrigger className="text-left" variant="outline">
358
- <SelectValue placeholder={placeholder}>
359
- {selectedEvent && (
360
- <div className="flex items-center gap-2">
361
- <Calendar className="size-4 flex-shrink-0" />
362
- <span className="truncate">{selectedEvent.event_name}</span>
363
- {selectedEvent.event_date && (
364
- <span className="text-xs text-muted-foreground flex-shrink-0">
365
- ({formatEventDate(selectedEvent.event_date)})
366
- </span>
367
- )}
368
- </div>
369
- )}
370
- </SelectValue>
371
- </SelectTrigger>
372
- <SelectContent>
373
- {sortedEvents
374
- .map((event) => {
375
- const isNext = isNextEvent(event);
376
- const isSelected = selectedEvent && (selectedEvent.event_id === event.event_id || selectedEvent.id === event.id);
377
-
378
- return (
379
- <SelectItem
380
- key={event.event_id || event.id}
381
- value={event.event_id || event.id}
382
- className="flex items-center justify-between"
383
- >
384
- <div className="flex items-center gap-2 w-full">
385
- {showNextEventIndicator && isNext && (
386
- <Star className="size-3 text-acc-500" />
387
- )}
388
- <div className="flex-1">
389
- <div className="flex items-center gap-2">
390
- <span className={isSelected ? "font-semibold" : ""}>
391
- {event.event_name}
392
- </span>
393
- {isSelected && (
394
- <span className="text-xs bg-primary text-primary-foreground px-1 rounded">
395
- Current
396
- </span>
397
- )}
398
- </div>
399
- {showEventDetails && event.event_date && (
400
- <div className="flex items-center gap-1 text-xs text-muted-foreground">
401
- <Calendar className="size-3" />
402
- <span>{formatEventDate(event.event_date)}</span>
403
- {showNextEventIndicator && isNext && (
404
- <span className="text-acc-600 font-medium">
405
- (Next)
406
- </span>
407
- )}
408
- </div>
409
- )}
410
- {showEventDetails && event.event_venue && (
411
- <div className="text-xs text-muted-foreground">
412
- 📍 {event.event_venue}
413
- </div>
414
- )}
415
- </div>
416
- </div>
417
- </SelectItem>
418
- );
419
- })}
420
- </SelectContent>
421
- </Select>
422
- );
423
- }
@@ -1,3 +0,0 @@
1
- export { EventSelector } from './EventSelector';
2
- export type { EventSelectorProps } from './EventSelector';
3
- export type { Event, EventTheme, EventContextType } from '../../types/event';