@jmruthers/pace-core 0.5.184 → 0.5.185
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 +38 -0
- package/README.md +60 -1
- package/core-usage-manifest.json +312 -0
- package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
- package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
- package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-BABf6JCh.d.ts} +21 -10
- package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
- package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
- package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
- package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
- package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
- package/dist/{chunk-CSOFYHAG.js → chunk-AISXLWGZ.js} +374 -60
- package/dist/chunk-AISXLWGZ.js.map +1 -0
- package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
- package/dist/chunk-FXFJRTKI.js.map +1 -0
- package/dist/{chunk-NQPMQGS2.js → chunk-HC67NW5K.js} +379 -359
- package/dist/chunk-HC67NW5K.js.map +1 -0
- package/dist/chunk-HESYZWZW.js +388 -0
- package/dist/chunk-HESYZWZW.js.map +1 -0
- package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
- package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
- package/dist/{chunk-UHNYIBXL.js → chunk-IXSNYUCT.js} +1 -1
- package/dist/chunk-IXSNYUCT.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-MX3EIJGQ.js} +4 -3
- package/dist/{chunk-MI7HBHN3.js.map → chunk-MX3EIJGQ.js.map} +1 -1
- package/dist/{chunk-PWAHJW4G.js → chunk-OKI34GZD.js} +86 -33
- package/dist/chunk-OKI34GZD.js.map +1 -0
- package/dist/{chunk-W22JP75J.js → chunk-STTZQK2I.js} +3 -3
- package/dist/chunk-THRPYOFK.js +215 -0
- package/dist/chunk-THRPYOFK.js.map +1 -0
- package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
- package/dist/chunk-U6WNSFX5.js.map +1 -0
- package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
- package/dist/components.d.ts +182 -6
- package/dist/components.js +157 -11
- package/dist/components.js.map +1 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-BjR39ktt.d.ts} +7 -1
- package/dist/hooks.d.ts +7 -14
- package/dist/hooks.js +10 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +79 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +205 -14
- package/dist/rbac/index.js +28 -6
- package/dist/timezone-_pgH8qrY.d.ts +530 -0
- package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-CvnC3d-e.d.ts} +113 -2
- package/dist/utils.d.ts +109 -151
- package/dist/utils.js +128 -138
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +60 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +178 -0
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +54 -0
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +18 -2
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +30 -0
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
- package/docs/api/interfaces/DatabaseIssue.md +41 -0
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +6 -6
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +24 -8
- package/docs/api/interfaces/FileUploadProps.md +24 -13
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +62 -0
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +52 -0
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +4 -4
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +5 -5
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +41 -0
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
- package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +738 -42
- package/docs/api-reference/hooks.md +111 -0
- package/docs/api-reference/rpc-functions.md +1 -1
- package/docs/api-reference/utilities.md +184 -0
- package/docs/getting-started/installation-guide.md +75 -16
- package/docs/getting-started/quick-start.md +61 -11
- package/docs/implementation-guides/authentication.md +88 -12
- package/docs/implementation-guides/file-reference-system.md +2 -1
- package/docs/implementation-guides/file-upload-storage.md +21 -0
- package/docs/rbac/README.md +1 -0
- package/docs/rbac/compliance/compliance-guide.md +544 -0
- package/docs/rbac/getting-started.md +158 -33
- package/docs/standards/pace-core-compliance.md +432 -0
- package/eslint-config-pace-core.cjs +93 -0
- package/package.json +15 -3
- package/scripts/analyze-bundle.js +232 -0
- package/scripts/build-css.js +56 -0
- package/scripts/build-docs-incremental.js +1015 -0
- package/scripts/check-pace-core-compliance.cjs +2353 -0
- package/scripts/generate-docs.js +157 -0
- package/scripts/setup-build-cache.js +73 -0
- package/scripts/utils/command-runner.js +131 -0
- package/scripts/utils/env.js +33 -0
- package/scripts/utils/index.js +10 -0
- package/scripts/utils/logger.js +88 -0
- package/scripts/utils/path-helpers.js +37 -0
- package/scripts/validate-formats.js +133 -0
- package/scripts/validate-master.js +155 -0
- package/scripts/validate-pre-publish.js +140 -0
- package/scripts/validate-theme.js +142 -0
- package/src/components/Calendar/Calendar.tsx +8 -1
- package/src/components/Card/Card.tsx +47 -8
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
- package/src/components/DatePickerWithTimezone/README.md +135 -0
- package/src/components/DatePickerWithTimezone/index.ts +10 -0
- package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
- package/src/components/DateTimeField/DateTimeField.tsx +232 -0
- package/src/components/DateTimeField/README.md +148 -0
- package/src/components/DateTimeField/index.ts +10 -0
- package/src/components/FileUpload/FileUpload.tsx +3 -0
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +24 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
- package/src/components/PaceAppLayout/README.md +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +12 -4
- package/src/components/index.ts +8 -0
- package/src/eslint-rules/pace-core-compliance.cjs +406 -0
- package/src/eslint-rules/pace-core-compliance.js +640 -0
- package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useFileReference.test.ts +1 -0
- package/src/hooks/useFormDialog.ts +147 -0
- package/src/index.ts +27 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
- package/src/rbac/compliance/database-validator.ts +165 -0
- package/src/rbac/compliance/index.ts +38 -0
- package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
- package/src/rbac/compliance/runtime-compliance.ts +77 -0
- package/src/rbac/compliance/setup-validator.ts +131 -0
- package/src/rbac/components/PagePermissionGuard.tsx +8 -64
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
- package/src/rbac/docs/event-based-apps.md +285 -0
- package/src/rbac/errors.ts +11 -0
- package/src/rbac/hooks/useRoleManagement.ts +292 -12
- package/src/rbac/index.ts +30 -0
- package/src/services/OrganisationService.ts +4 -0
- package/src/types/file-reference.ts +6 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +2 -0
- package/src/utils/file-reference/index.ts +1 -0
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
- package/src/utils/formatting/formatting.ts +179 -0
- package/src/utils/index.ts +27 -1
- package/src/utils/location/index.ts +16 -0
- package/src/utils/location/location.test.ts +286 -0
- package/src/utils/location/location.ts +175 -0
- package/src/utils/timezone/index.ts +17 -0
- package/src/utils/timezone/timezone.test.ts +349 -0
- package/src/utils/timezone/timezone.ts +281 -0
- package/dist/chunk-CSOFYHAG.js.map +0 -1
- package/dist/chunk-FUEYYMX5.js.map +0 -1
- package/dist/chunk-HKIT6O7W.js +0 -198
- package/dist/chunk-HKIT6O7W.js.map +0 -1
- package/dist/chunk-KUEN3HFB.js +0 -94
- package/dist/chunk-KUEN3HFB.js.map +0 -1
- package/dist/chunk-M7W4CP3M.js.map +0 -1
- package/dist/chunk-NQPMQGS2.js.map +0 -1
- package/dist/chunk-PWAHJW4G.js.map +0 -1
- package/dist/chunk-UHNYIBXL.js.map +0 -1
- package/dist/formatting-5wETwiGF.d.ts +0 -162
- /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
- /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
- /package/dist/{chunk-W22JP75J.js.map → chunk-STTZQK2I.js.map} +0 -0
- /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# DateTimeField Component
|
|
2
|
+
|
|
3
|
+
Form input component for datetime values with automatic UTC ↔ timezone conversion.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `DateTimeField` component provides a datetime input field that automatically handles conversion between UTC (for storage) and a specified timezone (for display). It prevents unwanted conversions during user editing and displays timezone information when not using UTC.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Automatic UTC ↔ timezone conversion
|
|
12
|
+
- Prevents unwanted conversions during user editing
|
|
13
|
+
- Shows timezone information when not UTC
|
|
14
|
+
- Supports both ISO string and Date object values
|
|
15
|
+
- Uses native `datetime-local` input type
|
|
16
|
+
- Accessible form field with proper labels
|
|
17
|
+
- Error state support
|
|
18
|
+
- Helper text support
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Example
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { DateTimeField } from '@jmruthers/pace-core/components';
|
|
26
|
+
import { useState } from 'react';
|
|
27
|
+
|
|
28
|
+
function EventForm() {
|
|
29
|
+
const [startTime, setStartTime] = useState<string>();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<DateTimeField
|
|
33
|
+
label="Start Time"
|
|
34
|
+
value={startTime}
|
|
35
|
+
onChange={setStartTime}
|
|
36
|
+
timezone="America/New_York"
|
|
37
|
+
required
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### With Date Object
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { DateTimeField } from '@jmruthers/pace-core/components';
|
|
47
|
+
import { useState } from 'react';
|
|
48
|
+
|
|
49
|
+
function EventForm() {
|
|
50
|
+
const [startTime, setStartTime] = useState<Date>();
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<DateTimeField
|
|
54
|
+
label="Start Time"
|
|
55
|
+
value={startTime}
|
|
56
|
+
onChange={setStartTime}
|
|
57
|
+
timezone="America/New_York"
|
|
58
|
+
returnAsDate
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### With Error State
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
<DateTimeField
|
|
68
|
+
label="Start Time"
|
|
69
|
+
value={startTime}
|
|
70
|
+
onChange={setStartTime}
|
|
71
|
+
timezone="America/New_York"
|
|
72
|
+
error="Please select a valid start time"
|
|
73
|
+
required
|
|
74
|
+
/>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### With Helper Text
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<DateTimeField
|
|
81
|
+
label="Start Time"
|
|
82
|
+
value={startTime}
|
|
83
|
+
onChange={setStartTime}
|
|
84
|
+
timezone="America/New_York"
|
|
85
|
+
helperText="Select the event start time in your local timezone"
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
### Props
|
|
92
|
+
|
|
93
|
+
| Prop | Type | Default | Description |
|
|
94
|
+
|------|------|---------|-------------|
|
|
95
|
+
| `label` | `string` | **required** | Field label |
|
|
96
|
+
| `value` | `string \| Date \| undefined` | - | UTC date value (ISO string, Date object, or undefined) |
|
|
97
|
+
| `onChange` | `(value: string \| Date \| undefined) => void` | **required** | Change handler that receives UTC value |
|
|
98
|
+
| `timezone` | `string` | `'UTC'` | Target timezone for display (IANA timezone string) |
|
|
99
|
+
| `required` | `boolean` | `false` | Whether the field is required |
|
|
100
|
+
| `className` | `string` | - | Additional CSS classes |
|
|
101
|
+
| `returnAsDate` | `boolean` | `false` | If true, onChange returns Date object instead of ISO string |
|
|
102
|
+
| `id` | `string` | - | Input id (auto-generated if not provided) |
|
|
103
|
+
| `helperText` | `string` | - | Helper text to display below the label |
|
|
104
|
+
| `error` | `string` | - | Error message to display |
|
|
105
|
+
|
|
106
|
+
## Behavior
|
|
107
|
+
|
|
108
|
+
### Timezone Conversion
|
|
109
|
+
|
|
110
|
+
The component automatically converts between UTC (for storage) and the specified timezone (for display):
|
|
111
|
+
|
|
112
|
+
- **Display**: UTC values are converted to the specified timezone for display in the input
|
|
113
|
+
- **Input**: User-entered values (in the timezone) are converted back to UTC before calling `onChange`
|
|
114
|
+
|
|
115
|
+
### Editing Prevention
|
|
116
|
+
|
|
117
|
+
The component tracks editing state to prevent unwanted conversions while the user is typing. Conversions only occur when:
|
|
118
|
+
- The component receives a new `value` prop (controlled component)
|
|
119
|
+
- The user finishes editing (blur event)
|
|
120
|
+
|
|
121
|
+
### Timezone Display
|
|
122
|
+
|
|
123
|
+
- When `timezone` is `'UTC'`: No timezone indicator is shown
|
|
124
|
+
- When `timezone` matches the user's browser timezone: Shows "Local"
|
|
125
|
+
- When `timezone` is different: Shows the timezone name (e.g., "America/New_York")
|
|
126
|
+
|
|
127
|
+
## Accessibility
|
|
128
|
+
|
|
129
|
+
- Proper label association with `htmlFor`
|
|
130
|
+
- Required field indicators for screen readers
|
|
131
|
+
- Error messages with `role="alert"`
|
|
132
|
+
- Keyboard navigation support
|
|
133
|
+
- Focus management
|
|
134
|
+
|
|
135
|
+
## Edge Cases
|
|
136
|
+
|
|
137
|
+
- **Invalid dates**: Returns empty string in input, calls `onChange(undefined)`
|
|
138
|
+
- **Null/undefined values**: Displays empty input
|
|
139
|
+
- **Invalid timezone**: Falls back to UTC behavior
|
|
140
|
+
|
|
141
|
+
## Dependencies
|
|
142
|
+
|
|
143
|
+
- `date-fns` - Date formatting and parsing
|
|
144
|
+
- `date-fns-tz` - Timezone conversion
|
|
145
|
+
- `@jmruthers/pace-core/components/Label` - Label component
|
|
146
|
+
- `@jmruthers/pace-core/components/Input` - Input component
|
|
147
|
+
- `@jmruthers/pace-core/utils/timezone` - Timezone utilities
|
|
148
|
+
|
|
@@ -22,6 +22,7 @@ export interface FileUploadProps {
|
|
|
22
22
|
organisation_id: string;
|
|
23
23
|
app_id?: string; // Optional - will be resolved from app name if not provided
|
|
24
24
|
category: FileCategory;
|
|
25
|
+
pageContext: string; // The page context where the file upload occurs (e.g., 'configuration', 'forms', 'applications')
|
|
25
26
|
accept?: string;
|
|
26
27
|
maxSize?: number;
|
|
27
28
|
multiple?: boolean;
|
|
@@ -50,6 +51,7 @@ export function FileUpload({
|
|
|
50
51
|
organisation_id,
|
|
51
52
|
app_id,
|
|
52
53
|
category,
|
|
54
|
+
pageContext,
|
|
53
55
|
accept = '*/*',
|
|
54
56
|
maxSize = 10 * 1024 * 1024, // 10MB default
|
|
55
57
|
multiple = false,
|
|
@@ -284,6 +286,7 @@ export function FileUpload({
|
|
|
284
286
|
organisation_id,
|
|
285
287
|
app_id: resolvedAppId ? assertAppId(resolvedAppId) : assertAppId(''),
|
|
286
288
|
category,
|
|
289
|
+
pageContext,
|
|
287
290
|
is_public: isPublic
|
|
288
291
|
}, file);
|
|
289
292
|
|
|
@@ -60,6 +60,14 @@ vi.mock('../EventSelector', () => ({
|
|
|
60
60
|
),
|
|
61
61
|
}));
|
|
62
62
|
|
|
63
|
+
vi.mock('../OrganisationSelector', () => ({
|
|
64
|
+
OrganisationSelector: ({ placeholder, className, 'data-testid': testId }: any) => (
|
|
65
|
+
<div data-testid={testId || 'org-selector'} className={className}>
|
|
66
|
+
<button>{placeholder}</button>
|
|
67
|
+
</div>
|
|
68
|
+
),
|
|
69
|
+
}));
|
|
70
|
+
|
|
63
71
|
// Test data
|
|
64
72
|
const mockUser: User = {
|
|
65
73
|
id: '123',
|
|
@@ -312,16 +320,8 @@ describe('Header Component', () => {
|
|
|
312
320
|
expect(screen.getByRole('button', { name: 'Select event' })).toBeInTheDocument();
|
|
313
321
|
});
|
|
314
322
|
|
|
315
|
-
it('
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const placeholder = container.querySelector('del.invisible');
|
|
319
|
-
expect(placeholder).toBeInTheDocument();
|
|
320
|
-
expect(placeholder).toHaveTextContent('Event Selector N/A');
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
it('preserves grid layout when event selector is hidden', () => {
|
|
324
|
-
const { container } = renderWithProviders(
|
|
323
|
+
it('preserves layout when event selector is hidden', () => {
|
|
324
|
+
renderWithProviders(
|
|
325
325
|
<Header
|
|
326
326
|
showEventSelector={false}
|
|
327
327
|
user={mockUser}
|
|
@@ -333,15 +333,47 @@ describe('Header Component', () => {
|
|
|
333
333
|
expect(nav).toBeInTheDocument();
|
|
334
334
|
expect(nav).toBeVisible();
|
|
335
335
|
|
|
336
|
-
//
|
|
337
|
-
|
|
338
|
-
expect(placeholder).toBeInTheDocument();
|
|
336
|
+
// Event selector should not be rendered
|
|
337
|
+
expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
|
|
339
338
|
|
|
340
339
|
// User menu should still be present and positioned correctly
|
|
341
340
|
expect(screen.getByTestId('user-menu')).toBeInTheDocument();
|
|
342
341
|
});
|
|
343
342
|
});
|
|
344
343
|
|
|
344
|
+
// Organisation Selector tests
|
|
345
|
+
describe('Organisation Selector', () => {
|
|
346
|
+
it('does not render organisation selector by default', () => {
|
|
347
|
+
renderWithProviders(<Header />);
|
|
348
|
+
|
|
349
|
+
expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('renders organisation selector when showOrgSelector is true', () => {
|
|
353
|
+
renderWithProviders(<Header showOrgSelector={true} />);
|
|
354
|
+
|
|
355
|
+
expect(screen.getByTestId('org-selector')).toBeInTheDocument();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('does not render organisation selector when showOrgSelector is false', () => {
|
|
359
|
+
renderWithProviders(<Header showOrgSelector={false} />);
|
|
360
|
+
|
|
361
|
+
expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('can render both organisation and event selectors together', () => {
|
|
365
|
+
renderWithProviders(
|
|
366
|
+
<Header
|
|
367
|
+
showOrgSelector={true}
|
|
368
|
+
showEventSelector={true}
|
|
369
|
+
/>
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
expect(screen.getByTestId('org-selector')).toBeInTheDocument();
|
|
373
|
+
expect(screen.getByTestId('event-selector')).toBeInTheDocument();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
345
377
|
// Custom actions tests
|
|
346
378
|
describe('Custom Actions', () => {
|
|
347
379
|
it('renders custom actions when provided', () => {
|
|
@@ -560,7 +592,7 @@ describe('Header Component', () => {
|
|
|
560
592
|
});
|
|
561
593
|
|
|
562
594
|
it('renders minimal configuration', () => {
|
|
563
|
-
|
|
595
|
+
renderWithProviders(
|
|
564
596
|
<Header
|
|
565
597
|
showEventSelector={false}
|
|
566
598
|
showUserMenu={false}
|
|
@@ -573,10 +605,7 @@ describe('Header Component', () => {
|
|
|
573
605
|
expect(screen.queryByTestId('navigation-menu')).not.toBeInTheDocument();
|
|
574
606
|
expect(screen.queryByTestId('event-selector')).not.toBeInTheDocument();
|
|
575
607
|
expect(screen.queryByTestId('user-menu')).not.toBeInTheDocument();
|
|
576
|
-
|
|
577
|
-
// Should have placeholder for event selector
|
|
578
|
-
const placeholder = container.querySelector('del.invisible');
|
|
579
|
-
expect(placeholder).toBeInTheDocument();
|
|
608
|
+
expect(screen.queryByTestId('org-selector')).not.toBeInTheDocument();
|
|
580
609
|
});
|
|
581
610
|
});
|
|
582
611
|
|
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
* @since 0.1.0
|
|
6
6
|
*
|
|
7
7
|
* A comprehensive header component for application layouts with navigation,
|
|
8
|
-
* user menu, event selector, and customizable branding.
|
|
8
|
+
* user menu, organisation selector, event selector, and customizable branding.
|
|
9
9
|
*
|
|
10
10
|
* Features:
|
|
11
11
|
* - Customizable logo (URL or component)
|
|
12
12
|
* - Clickable logo that routes to dashboard (configurable)
|
|
13
13
|
* - Navigation menu integration
|
|
14
14
|
* - User menu with authentication
|
|
15
|
+
* - Organisation selector for multi-organisation applications
|
|
15
16
|
* - Event selector for multi-tenant applications
|
|
16
17
|
* - Custom actions support
|
|
17
18
|
* - Responsive design
|
|
@@ -82,6 +83,7 @@
|
|
|
82
83
|
* - Tailwind CSS - Styling
|
|
83
84
|
* - NavigationMenu component
|
|
84
85
|
* - UserMenu component
|
|
86
|
+
* - OrganisationSelector component
|
|
85
87
|
* - EventSelector component
|
|
86
88
|
*/
|
|
87
89
|
|
|
@@ -90,6 +92,7 @@ import { Link } from 'react-router-dom';
|
|
|
90
92
|
import { User } from '@supabase/supabase-js';
|
|
91
93
|
import { cn } from '../../utils/core/cn';
|
|
92
94
|
import { EventSelector } from '../EventSelector';
|
|
95
|
+
import { OrganisationSelector } from '../OrganisationSelector';
|
|
93
96
|
import { UserMenu } from '../UserMenu';
|
|
94
97
|
import { NavigationMenu } from '../NavigationMenu';
|
|
95
98
|
import type { NavigationItem } from '../NavigationMenu';
|
|
@@ -121,6 +124,8 @@ export interface HeaderProps {
|
|
|
121
124
|
className?: string;
|
|
122
125
|
/** Show/hide event selector */
|
|
123
126
|
showEventSelector?: boolean;
|
|
127
|
+
/** Show/hide organisation selector */
|
|
128
|
+
showOrgSelector?: boolean;
|
|
124
129
|
/** Show/hide user menu */
|
|
125
130
|
showUserMenu?: boolean;
|
|
126
131
|
/** Current path for navigation highlighting */
|
|
@@ -244,6 +249,7 @@ export function Header({
|
|
|
244
249
|
userMenu,
|
|
245
250
|
className,
|
|
246
251
|
showEventSelector = true,
|
|
252
|
+
showOrgSelector = false,
|
|
247
253
|
showUserMenu = true,
|
|
248
254
|
currentPath,
|
|
249
255
|
onNavigate,
|
|
@@ -311,19 +317,30 @@ export function Header({
|
|
|
311
317
|
)}
|
|
312
318
|
|
|
313
319
|
|
|
314
|
-
{/* Right side: Event Selector, Actions, and User Menu */}
|
|
320
|
+
{/* Right side: Organisation Selector, Event Selector, Actions, and User Menu */}
|
|
321
|
+
<div className="flex items-center gap-4 justify-end">
|
|
322
|
+
{/* Organisation Selector */}
|
|
323
|
+
{showOrgSelector ? (
|
|
324
|
+
<OrganisationSelector
|
|
325
|
+
placeholder="Select organisation"
|
|
326
|
+
className="w-64"
|
|
327
|
+
data-testid="org-selector"
|
|
328
|
+
compact={true}
|
|
329
|
+
/>
|
|
330
|
+
) : null}
|
|
315
331
|
|
|
316
332
|
{/* Event Selector */}
|
|
317
333
|
{showEventSelector ? (
|
|
318
334
|
<EventSelector
|
|
319
335
|
placeholder="Select event"
|
|
320
|
-
className="
|
|
336
|
+
className="w-96"
|
|
321
337
|
data-testid="event-selector"
|
|
322
338
|
/>
|
|
323
|
-
) :
|
|
324
|
-
|
|
325
|
-
)}
|
|
339
|
+
) : null}
|
|
340
|
+
</div>
|
|
326
341
|
|
|
342
|
+
{/* Custom Actions and User Menu */}
|
|
343
|
+
<div className="flex items-center gap-4">
|
|
327
344
|
{/* Custom Actions */}
|
|
328
345
|
{actions}
|
|
329
346
|
|
|
@@ -340,6 +357,7 @@ export function Header({
|
|
|
340
357
|
/>
|
|
341
358
|
)
|
|
342
359
|
)}
|
|
360
|
+
</div>
|
|
343
361
|
|
|
344
362
|
</nav>
|
|
345
363
|
</header>
|
|
@@ -128,6 +128,8 @@ export interface PaceAppLayoutProps {
|
|
|
128
128
|
navItems?: NavigationItem[];
|
|
129
129
|
/** Show/hide event selector in the header */
|
|
130
130
|
showEventSelector?: boolean;
|
|
131
|
+
/** Show/hide organisation selector in the header */
|
|
132
|
+
showOrgSelector?: boolean;
|
|
131
133
|
/** Custom actions to display in the header (between event selector and user menu) */
|
|
132
134
|
headerActions?: React.ReactNode;
|
|
133
135
|
/** Custom logo component (overrides default logo) */
|
|
@@ -344,6 +346,7 @@ export function PaceAppLayout({
|
|
|
344
346
|
appName,
|
|
345
347
|
navItems,
|
|
346
348
|
showEventSelector,
|
|
349
|
+
showOrgSelector,
|
|
347
350
|
headerActions,
|
|
348
351
|
customLogo,
|
|
349
352
|
logoHref = '/dashboard',
|
|
@@ -573,16 +576,25 @@ export function PaceAppLayout({
|
|
|
573
576
|
const hasOrganisationContext = currentScope.organisationId;
|
|
574
577
|
const hasUser = !!user?.id;
|
|
575
578
|
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
//
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
579
|
+
// BUG FIX: When showOrgSelector is enabled, show navigation optimistically while waiting
|
|
580
|
+
// for organisation selection. Only hide navigation if user is not loaded.
|
|
581
|
+
// This prevents navigation from disappearing after initial render while waiting for org selection.
|
|
582
|
+
if (!hasUser) {
|
|
583
|
+
// User not loaded yet - show all items until user is ready
|
|
584
|
+
if (isMounted) {
|
|
585
|
+
setFilteredMenuItems(baseMenuItems);
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// If no organisation context yet, show items optimistically if org selector is enabled
|
|
591
|
+
// This allows users to see navigation while they select an organisation
|
|
592
|
+
// Only proceed with permission filtering once organisation is selected
|
|
593
|
+
if (!hasOrganisationContext) {
|
|
584
594
|
if (isMounted) {
|
|
585
|
-
|
|
595
|
+
// Show items optimistically when org selector is enabled, otherwise show all items
|
|
596
|
+
// This prevents navigation from disappearing while waiting for organisation selection
|
|
597
|
+
setFilteredMenuItems(baseMenuItems);
|
|
586
598
|
}
|
|
587
599
|
return;
|
|
588
600
|
}
|
|
@@ -612,15 +624,6 @@ export function PaceAppLayout({
|
|
|
612
624
|
}
|
|
613
625
|
}
|
|
614
626
|
|
|
615
|
-
// If no organisation context yet, show all items until context is ready
|
|
616
|
-
// This prevents navigation from being empty while context loads
|
|
617
|
-
if (!currentScope.organisationId) {
|
|
618
|
-
if (isMounted) {
|
|
619
|
-
setFilteredMenuItems(baseMenuItems);
|
|
620
|
-
}
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
627
|
// Organisation context is ready - now filter items based on permissions
|
|
625
628
|
// OPTIMIZATION: Use batch permission map instead of individual checks to avoid rate limits
|
|
626
629
|
// This makes 1 call instead of N calls (where N = number of navigation items)
|
|
@@ -645,7 +648,9 @@ export function PaceAppLayout({
|
|
|
645
648
|
const filtered = baseMenuItems.map((item) => {
|
|
646
649
|
if (!item.href) return { item, hasAccess: true };
|
|
647
650
|
|
|
648
|
-
|
|
651
|
+
// Extract page ID from href: remove leading slash, fallback to 'dashboard' for root
|
|
652
|
+
// This matches database page names in rbac_app_pages
|
|
653
|
+
const pageId = pageIdMapping[item.href] || (item.href === '/' ? 'dashboard' : item.href.slice(1)) || 'dashboard';
|
|
649
654
|
const permission = routePermissions[item.href] || defaultPermission;
|
|
650
655
|
const fullPermission: Permission = permission.includes(':')
|
|
651
656
|
? (permission as Permission)
|
|
@@ -663,6 +668,9 @@ export function PaceAppLayout({
|
|
|
663
668
|
.filter(({ hasAccess }) => hasAccess)
|
|
664
669
|
.map(({ item }) => item);
|
|
665
670
|
|
|
671
|
+
// SECURITY: Never show all items if permission check fails - this would be a security risk
|
|
672
|
+
// If all items are filtered out, it means the user doesn't have permission to see any navigation
|
|
673
|
+
// This is the correct behavior - better to show nothing than show unauthorized items
|
|
666
674
|
setFilteredMenuItems(accessibleItems);
|
|
667
675
|
} catch (error) {
|
|
668
676
|
// On error, fall back to showing all items (graceful degradation)
|
|
@@ -679,7 +687,7 @@ export function PaceAppLayout({
|
|
|
679
687
|
return () => {
|
|
680
688
|
isMounted = false;
|
|
681
689
|
};
|
|
682
|
-
}, [baseMenuItems, pageIdMapping, routePermissions, defaultPermission, can, user?.id, scope, scopeLoading, contextAppId, resolvedScope?.appId]);
|
|
690
|
+
}, [baseMenuItems, pageIdMapping, routePermissions, defaultPermission, can, user?.id, scope, scopeLoading, contextAppId, resolvedScope?.appId, selectedOrganisation?.id]);
|
|
683
691
|
|
|
684
692
|
// NEW: Phase 2 - Enhanced Routing Features
|
|
685
693
|
// Check route access for role-based routing
|
|
@@ -883,6 +891,7 @@ export function PaceAppLayout({
|
|
|
883
891
|
}
|
|
884
892
|
}}
|
|
885
893
|
showEventSelector={showEventSelector}
|
|
894
|
+
showOrgSelector={showOrgSelector}
|
|
886
895
|
showUserMenu={showUserMenu}
|
|
887
896
|
className={headerClassName || "sticky top-0 z-[40] w-full"}
|
|
888
897
|
/>
|
|
@@ -17,6 +17,7 @@ A comprehensive application layout component that provides a consistent structur
|
|
|
17
17
|
- Flexible content area
|
|
18
18
|
- Branding support
|
|
19
19
|
- **Event selector control** - can be hidden for non-event applications
|
|
20
|
+
- **Organisation selector control** - can be shown for multi-organisation applications
|
|
20
21
|
- **Header customization** - custom logo, actions, user menu, and styling
|
|
21
22
|
- **Clickable logo** - logo automatically routes to dashboard page (configurable)
|
|
22
23
|
|
|
@@ -27,6 +28,7 @@ A comprehensive application layout component that provides a consistent structur
|
|
|
27
28
|
| `appName` | `string` | required | The name of the application to be displayed in the header |
|
|
28
29
|
| `navItems` | `NavigationItem[]` | optional | Navigation items for the header menu. If not provided, uses default navigation |
|
|
29
30
|
| `showEventSelector` | `boolean` | `true` | Show/hide event selector in the header |
|
|
31
|
+
| `showOrgSelector` | `boolean` | `false` | Show/hide organisation selector in the header |
|
|
30
32
|
| `headerActions` | `React.ReactNode` | optional | Custom actions to display in the header (between event selector and user menu) |
|
|
31
33
|
| `customLogo` | `React.ReactNode` | optional | Custom logo component (overrides default logo) |
|
|
32
34
|
| `logoHref` | `string` | `'/dashboard'` | URL to navigate to when logo is clicked (e.g., '/dashboard', '/home') |
|
|
@@ -243,6 +245,13 @@ function App() {
|
|
|
243
245
|
- Single-tenant applications
|
|
244
246
|
- Administrative dashboards that don't depend on events
|
|
245
247
|
|
|
248
|
+
### Show Organisation Selector (`showOrgSelector={true}`)
|
|
249
|
+
- Multi-organisation applications
|
|
250
|
+
- Apps where users need to switch between organisations
|
|
251
|
+
- Organisation management tools
|
|
252
|
+
- Multi-tenant applications with organisation context
|
|
253
|
+
- Apps requiring organisation-scoped data access
|
|
254
|
+
|
|
246
255
|
### Custom Header Components
|
|
247
256
|
- Applications requiring custom branding
|
|
248
257
|
- Apps with specific header actions (search, notifications, etc.)
|