@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
|
@@ -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
|
-
//
|
|
288
|
-
//
|
|
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
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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 (!
|
|
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
|
-
|
|
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 &&
|
|
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={
|
|
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 && (!
|
|
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 &&
|
|
428
|
+
{isImage && stableFileUrl && !imageError ? (
|
|
401
429
|
<>
|
|
402
430
|
<img
|
|
403
|
-
|
|
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
|
-
|
|
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('../
|
|
56
|
-
|
|
57
|
-
<div data-testid={testId || '
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
318
|
-
describe('
|
|
319
|
-
it('renders
|
|
320
|
-
renderWithProviders(<Header
|
|
321
|
+
// Context selector tests
|
|
322
|
+
describe('Context Selector', () => {
|
|
323
|
+
it('renders context selector by default', () => {
|
|
324
|
+
renderWithProviders(<Header />);
|
|
321
325
|
|
|
322
|
-
expect(screen.getByTestId('
|
|
326
|
+
expect(screen.getByTestId('context-selector')).toBeInTheDocument();
|
|
323
327
|
});
|
|
324
328
|
|
|
325
|
-
it('does not render
|
|
326
|
-
renderWithProviders(<Header
|
|
329
|
+
it('does not render context selector when showContextSelector is false', () => {
|
|
330
|
+
renderWithProviders(<Header showContextSelector={false} />);
|
|
327
331
|
|
|
328
|
-
expect(screen.queryByTestId('
|
|
332
|
+
expect(screen.queryByTestId('context-selector')).not.toBeInTheDocument();
|
|
329
333
|
});
|
|
330
334
|
|
|
331
|
-
it('renders
|
|
332
|
-
renderWithProviders(<Header />);
|
|
335
|
+
it('renders context selector when showContextSelector is explicitly true', () => {
|
|
336
|
+
renderWithProviders(<Header showContextSelector={true} />);
|
|
333
337
|
|
|
334
|
-
expect(screen.getByTestId('
|
|
338
|
+
expect(screen.getByTestId('context-selector')).toBeInTheDocument();
|
|
335
339
|
});
|
|
336
340
|
|
|
337
|
-
it('applies correct className to
|
|
338
|
-
renderWithProviders(<Header
|
|
341
|
+
it('applies correct className to context selector', () => {
|
|
342
|
+
renderWithProviders(<Header showContextSelector={true} />);
|
|
339
343
|
|
|
340
|
-
const
|
|
341
|
-
expect(
|
|
342
|
-
expect(
|
|
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
|
|
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
|
|
355
|
+
it('preserves layout when context selector is hidden', () => {
|
|
352
356
|
renderWithProviders(
|
|
353
357
|
<Header
|
|
354
|
-
|
|
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
|
-
//
|
|
365
|
-
expect(screen.queryByTestId('
|
|
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
|
-
|
|
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
|
-
//
|
|
613
|
-
expect(screen.getByTestId('
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
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
|
|
54
|
+
* // Header without context selector
|
|
55
55
|
* <Header
|
|
56
56
|
* logoUrl="/logo.svg"
|
|
57
57
|
* logoHref="/dashboard"
|
|
58
|
-
*
|
|
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
|
-
* -
|
|
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 {
|
|
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
|
-
|
|
128
|
-
/** Show
|
|
129
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
//
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
{/*
|
|
345
|
-
{
|
|
346
|
-
<
|
|
347
|
-
|
|
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
|
-
//
|
|
356
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
306
|
-
|
|
307
|
-
vi.
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
300
|
-
vi.mock('../
|
|
301
|
-
|
|
302
|
-
<div data-testid={testId || '
|
|
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
|
}));
|