@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.
- package/CHANGELOG.md +45 -0
- package/cursor-rules/00-pace-core-compliance.mdc +34 -2
- package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
- package/dist/{DataTable-TPTKCX4D.js → DataTable-THFPBKTP.js} +9 -8
- package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
- package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
- package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
- package/dist/{api-MVVQZLJI.js → api-IAGWF3ZG.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/{chunk-SFZUDBL5.js → chunk-2T2IG7T7.js} +70 -56
- package/dist/chunk-2T2IG7T7.js.map +1 -0
- package/dist/{chunk-MMZ7JXPU.js → chunk-6Z7LTB3D.js} +13 -21
- package/dist/{chunk-MMZ7JXPU.js.map → chunk-6Z7LTB3D.js.map} +1 -1
- package/dist/{chunk-6J4GEEJR.js → chunk-CNCQDFLN.js} +53 -27
- package/dist/chunk-CNCQDFLN.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-EHMR7VYL.js → chunk-DWUBLJJM.js} +361 -187
- package/dist/chunk-DWUBLJJM.js.map +1 -0
- package/dist/{chunk-2UOI2FG5.js → chunk-HFZBI76P.js} +4 -4
- package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
- package/dist/{chunk-3XC4CPTD.js → chunk-PQBSKX33.js} +244 -5727
- package/dist/chunk-PQBSKX33.js.map +1 -0
- package/dist/chunk-QRPVRXYT.js +226 -0
- package/dist/chunk-QRPVRXYT.js.map +1 -0
- package/dist/{chunk-24UVZUZG.js → chunk-RWEBCB47.js} +129 -387
- package/dist/chunk-RWEBCB47.js.map +1 -0
- package/dist/{chunk-XWQCNGTQ.js → chunk-YDQHOZNA.js} +173 -79
- package/dist/chunk-YDQHOZNA.js.map +1 -0
- package/dist/{chunk-NECFR5MM.js → chunk-ZNIWI3UC.js} +562 -644
- package/dist/chunk-ZNIWI3UC.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +12 -13
- package/dist/contextValidator-3JNZKUTX.js +9 -0
- package/dist/contextValidator-3JNZKUTX.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -6
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +21 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +67 -27
- package/dist/rbac/index.js +15 -8
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
- package/dist/utils.js +5 -7
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +14 -16
- package/docs/api/modules.md +3796 -2513
- package/docs/components/context-selector.md +126 -0
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/secure-client-protection.md +330 -0
- package/package.json +3 -3
- package/scripts/audit/core/checks/compliance.cjs +72 -0
- package/scripts/audit/core/checks/dependencies.cjs +559 -28
- package/scripts/audit/core/checks/documentation.cjs +68 -3
- package/scripts/audit/core/checks/environment.cjs +2 -14
- package/scripts/audit/core/checks/error-handling.cjs +47 -6
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/components/RowComponent.tsx +19 -19
- package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
- package/src/components/Dialog/Dialog.tsx +29 -1
- package/src/components/FileDisplay/FileDisplay.tsx +42 -10
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +44 -45
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +135 -33
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +2 -2
- package/src/components/index.ts +5 -5
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useFileDisplay.ts +77 -50
- package/src/index.ts +4 -5
- package/src/providers/services/AuthServiceProvider.tsx +17 -7
- package/src/providers/services/EventServiceProvider.tsx +33 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/adapters.tsx +2 -2
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +178 -132
- package/src/rbac/components/PagePermissionGuard.tsx +38 -10
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
- package/src/rbac/hooks/permissions/useCan.ts +41 -11
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +0 -9
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useSecureSupabase.ts +7 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +103 -16
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +1 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +186 -54
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -0
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/dist/chunk-24UVZUZG.js.map +0 -1
- package/dist/chunk-3XC4CPTD.js.map +0 -1
- package/dist/chunk-6J4GEEJR.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-EHMR7VYL.js.map +0 -1
- package/dist/chunk-NECFR5MM.js.map +0 -1
- package/dist/chunk-SFZUDBL5.js.map +0 -1
- package/dist/chunk-XWQCNGTQ.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -163
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -34
- package/docs/api/interfaces/ButtonProps.md +0 -56
- package/docs/api/interfaces/CalendarProps.md +0 -73
- package/docs/api/interfaces/CardProps.md +0 -69
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -252
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
- package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -296
- package/docs/api/interfaces/FooterProps.md +0 -107
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -56
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -187
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -12
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -121
- package/docs/api/interfaces/UserMenuProps.md +0 -88
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -423
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
- package/src/components/OrganisationSelector/index.ts +0 -9
- /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-THFPBKTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
- /package/dist/{api-MVVQZLJI.js.map → api-IAGWF3ZG.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/dist/{chunk-2UOI2FG5.js.map → chunk-HFZBI76P.js.map} +0 -0
- /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
|
-
}
|