@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
|
@@ -486,6 +486,117 @@ function UserForm() {
|
|
|
486
486
|
}
|
|
487
487
|
```
|
|
488
488
|
|
|
489
|
+
### useFormDialog
|
|
490
|
+
|
|
491
|
+
Generic React hook for managing form dialog state (open/close, form data, reset behavior).
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
interface UseFormDialogOptions<T = unknown> {
|
|
495
|
+
onOpenChange?: (open: boolean) => void;
|
|
496
|
+
resetOnClose?: boolean; // Default: true
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
interface UseFormDialogReturn<T = unknown> {
|
|
500
|
+
isOpen: boolean;
|
|
501
|
+
formData: T | null;
|
|
502
|
+
openDialog: (data?: T | null) => void;
|
|
503
|
+
closeDialog: () => void;
|
|
504
|
+
handleOpenChange: (open: boolean) => void;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function useFormDialog<T = unknown>(
|
|
508
|
+
options?: UseFormDialogOptions<T>
|
|
509
|
+
): UseFormDialogReturn<T>;
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
#### Usage
|
|
513
|
+
|
|
514
|
+
```tsx
|
|
515
|
+
import { useFormDialog } from '@jmruthers/pace-core';
|
|
516
|
+
import { Dialog, DialogContent, DialogTrigger } from '@jmruthers/pace-core';
|
|
517
|
+
|
|
518
|
+
interface ContactFormData {
|
|
519
|
+
id: string;
|
|
520
|
+
name: string;
|
|
521
|
+
email: string;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function ContactDialog() {
|
|
525
|
+
const { isOpen, formData, openDialog, closeDialog, handleOpenChange } =
|
|
526
|
+
useFormDialog<ContactFormData>();
|
|
527
|
+
|
|
528
|
+
return (
|
|
529
|
+
<>
|
|
530
|
+
<button onClick={() => openDialog({ id: '1', name: 'John', email: 'john@example.com' })}>
|
|
531
|
+
Edit Contact
|
|
532
|
+
</button>
|
|
533
|
+
<Dialog open={isOpen} onOpenChange={handleOpenChange}>
|
|
534
|
+
<DialogContent>
|
|
535
|
+
{formData && <p>Editing: {formData.name}</p>}
|
|
536
|
+
{/* Form content */}
|
|
537
|
+
</DialogContent>
|
|
538
|
+
</Dialog>
|
|
539
|
+
</>
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### Basic Usage
|
|
545
|
+
|
|
546
|
+
```tsx
|
|
547
|
+
// Simple dialog without form data
|
|
548
|
+
const { isOpen, openDialog, closeDialog } = useFormDialog();
|
|
549
|
+
|
|
550
|
+
// Open dialog
|
|
551
|
+
openDialog();
|
|
552
|
+
|
|
553
|
+
// Close dialog
|
|
554
|
+
closeDialog();
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
#### With Form Data
|
|
558
|
+
|
|
559
|
+
```tsx
|
|
560
|
+
interface UserData {
|
|
561
|
+
id: string;
|
|
562
|
+
name: string;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const { isOpen, formData, openDialog } = useFormDialog<UserData>();
|
|
566
|
+
|
|
567
|
+
// Open with data
|
|
568
|
+
openDialog({ id: '1', name: 'John' });
|
|
569
|
+
|
|
570
|
+
// Access form data
|
|
571
|
+
if (formData) {
|
|
572
|
+
console.log(formData.name);
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
#### Controlled Usage
|
|
577
|
+
|
|
578
|
+
```tsx
|
|
579
|
+
const { handleOpenChange, isOpen } = useFormDialog({
|
|
580
|
+
onOpenChange: (open) => {
|
|
581
|
+
console.log('Dialog state:', open);
|
|
582
|
+
},
|
|
583
|
+
resetOnClose: false // Keep form data after closing
|
|
584
|
+
});
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
#### Options
|
|
588
|
+
|
|
589
|
+
- `onOpenChange?: (open: boolean) => void` - Callback invoked when dialog state changes
|
|
590
|
+
- `resetOnClose?: boolean` - Whether to reset form data when dialog closes (default: `true`)
|
|
591
|
+
|
|
592
|
+
#### Return Values
|
|
593
|
+
|
|
594
|
+
- `isOpen: boolean` - Current open state of the dialog
|
|
595
|
+
- `formData: T | null` - Current form data (null when closed or no data provided)
|
|
596
|
+
- `openDialog: (data?: T | null) => void` - Open dialog with optional form data
|
|
597
|
+
- `closeDialog: () => void` - Close dialog
|
|
598
|
+
- `handleOpenChange: (open: boolean) => void` - Handler for controlled dialog components
|
|
599
|
+
|
|
489
600
|
## Utility Hooks
|
|
490
601
|
|
|
491
602
|
### useDebounce
|
|
@@ -120,7 +120,7 @@ Documented in [File Reference System Guide](../implementation-guides/file-refere
|
|
|
120
120
|
- `data_file_reference_count_get` - Get count of file references for a record
|
|
121
121
|
|
|
122
122
|
**Write Operations (exception to naming standard):**
|
|
123
|
-
- `data_file_reference_create` - Create a file reference (
|
|
123
|
+
- `data_file_reference_create` - Create a file reference with context-aware permission checks. Requires `p_page_context` parameter to check appropriate page-level permissions (e.g., 'create:page.configuration' or 'update:page.configuration'). Uses `data_*` prefix for consistency within file reference system.
|
|
124
124
|
- `data_file_reference_delete` - Delete a file reference (uses `data_*` prefix for consistency within file reference system)
|
|
125
125
|
|
|
126
126
|
### RBAC System
|
|
@@ -369,6 +369,190 @@ import { getDateRange } from '@jmruthers/pace-core';
|
|
|
369
369
|
const dateRange = getDateRange(startDate, endDate, 'day');
|
|
370
370
|
```
|
|
371
371
|
|
|
372
|
+
## Timezone Utilities
|
|
373
|
+
|
|
374
|
+
Comprehensive timezone conversion and formatting utilities using `date-fns-tz` and native `Intl` APIs. All functions handle DST transitions and return safe error values ('Invalid date' strings) rather than throwing exceptions.
|
|
375
|
+
|
|
376
|
+
### formatInTimeZone
|
|
377
|
+
|
|
378
|
+
Format a date in a specific timezone using date-fns format strings.
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
function formatInTimeZone(
|
|
382
|
+
date: Date | string | number,
|
|
383
|
+
timeZone: string,
|
|
384
|
+
formatStr: string
|
|
385
|
+
): string;
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### Usage
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { formatInTimeZone } from '@jmruthers/pace-core';
|
|
392
|
+
|
|
393
|
+
// Format UTC date in specific timezone
|
|
394
|
+
formatInTimeZone(new Date('2024-01-15T10:00:00Z'), 'America/New_York', 'MMM dd, yyyy HH:mm');
|
|
395
|
+
// "Jan 15, 2024 05:00"
|
|
396
|
+
|
|
397
|
+
// With ISO string
|
|
398
|
+
formatInTimeZone('2024-01-15T10:00:00Z', 'Europe/London', 'yyyy-MM-dd');
|
|
399
|
+
// "2024-01-15"
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### getTimezoneAbbreviation
|
|
403
|
+
|
|
404
|
+
Get the timezone abbreviation (EST, PST, etc.) for a date in a specific timezone.
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
function getTimezoneAbbreviation(date: Date, timeZone: string): string;
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### Usage
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
import { getTimezoneAbbreviation } from '@jmruthers/pace-core';
|
|
414
|
+
|
|
415
|
+
getTimezoneAbbreviation(new Date('2024-01-15T10:00:00Z'), 'America/New_York');
|
|
416
|
+
// "EST" or "EDT" depending on DST
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### formatTimeInTimeZone
|
|
420
|
+
|
|
421
|
+
Format time only in a specific timezone (HH:mm format).
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
function formatTimeInTimeZone(date: Date | string, timeZone: string): string;
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### Usage
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
import { formatTimeInTimeZone } from '@jmruthers/pace-core';
|
|
431
|
+
|
|
432
|
+
formatTimeInTimeZone(new Date('2024-01-15T10:00:00Z'), 'America/New_York');
|
|
433
|
+
// "05:00"
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### getUserTimeZone
|
|
437
|
+
|
|
438
|
+
Get the user's browser timezone.
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
function getUserTimeZone(): string;
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
#### Usage
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
import { getUserTimeZone } from '@jmruthers/pace-core';
|
|
448
|
+
|
|
449
|
+
const userTz = getUserTimeZone();
|
|
450
|
+
// "America/New_York" (or user's actual timezone)
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### toZonedTime
|
|
454
|
+
|
|
455
|
+
Convert a UTC date to a specific timezone's local time representation.
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
function toZonedTime(date: Date, timezone: string): Date;
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### Usage
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
import { toZonedTime } from '@jmruthers/pace-core';
|
|
465
|
+
|
|
466
|
+
const utcDate = new Date('2024-01-15T10:00:00Z');
|
|
467
|
+
const localDate = toZonedTime(utcDate, 'America/New_York');
|
|
468
|
+
// Returns Date object representing Jan 15, 2024 05:00 in local time
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
### fromZonedTime
|
|
472
|
+
|
|
473
|
+
Convert a local time in a specific timezone to UTC.
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
function fromZonedTime(localDate: Date, timezone: string): Date;
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### Usage
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
import { fromZonedTime } from '@jmruthers/pace-core';
|
|
483
|
+
|
|
484
|
+
const localInput = new Date(2024, 0, 15, 10, 0); // Jan 15, 2024 10:00 AM (local)
|
|
485
|
+
const utcDate = fromZonedTime(localInput, 'America/New_York');
|
|
486
|
+
// Returns Date object representing Jan 15, 2024 15:00 UTC
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### roundToNearestMinutes
|
|
490
|
+
|
|
491
|
+
Round a date to the nearest X minutes.
|
|
492
|
+
|
|
493
|
+
```typescript
|
|
494
|
+
function roundToNearestMinutes(date: Date, minutesStep?: number): Date;
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
#### Usage
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
import { roundToNearestMinutes } from '@jmruthers/pace-core';
|
|
501
|
+
|
|
502
|
+
const date = new Date('2024-01-15T10:23:00Z');
|
|
503
|
+
roundToNearestMinutes(date, 5);
|
|
504
|
+
// Returns Date object for 10:25:00
|
|
505
|
+
|
|
506
|
+
roundToNearestMinutes(date, 15);
|
|
507
|
+
// Returns Date object for 10:30:00
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Note:** `minutesStep` must be one of: 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, or 30. Other values will be rounded to the nearest valid step.
|
|
511
|
+
|
|
512
|
+
### getTimeZoneDifference
|
|
513
|
+
|
|
514
|
+
Calculate the time difference between two timezones in hours.
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
function getTimeZoneDifference(fromTimeZone: string, toTimeZone: string): number;
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
#### Usage
|
|
521
|
+
|
|
522
|
+
```typescript
|
|
523
|
+
import { getTimeZoneDifference } from '@jmruthers/pace-core';
|
|
524
|
+
|
|
525
|
+
getTimeZoneDifference('America/New_York', 'America/Los_Angeles');
|
|
526
|
+
// Returns -3 (EST is 3 hours ahead of PST)
|
|
527
|
+
|
|
528
|
+
getTimeZoneDifference('UTC', 'America/New_York');
|
|
529
|
+
// Returns -5 (or -4 during DST)
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### Complete Example
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
import {
|
|
536
|
+
toZonedTime,
|
|
537
|
+
fromZonedTime,
|
|
538
|
+
formatInTimeZone,
|
|
539
|
+
getUserTimeZone
|
|
540
|
+
} from '@jmruthers/pace-core';
|
|
541
|
+
|
|
542
|
+
// Convert UTC to local timezone
|
|
543
|
+
const utcDate = new Date('2024-01-15T10:00:00Z');
|
|
544
|
+
const userTz = getUserTimeZone();
|
|
545
|
+
const localDate = toZonedTime(utcDate, userTz);
|
|
546
|
+
|
|
547
|
+
// Format with timezone
|
|
548
|
+
const formatted = formatInTimeZone(utcDate, userTz, 'MMM dd, yyyy HH:mm (zzz)');
|
|
549
|
+
// "Jan 15, 2024 05:00 (EST)"
|
|
550
|
+
|
|
551
|
+
// Convert local time to UTC
|
|
552
|
+
const localInput = new Date(2024, 0, 15, 10, 0);
|
|
553
|
+
const utcResult = fromZonedTime(localInput, userTz);
|
|
554
|
+
```
|
|
555
|
+
|
|
372
556
|
## String Utilities
|
|
373
557
|
|
|
374
558
|
### truncateText
|
|
@@ -56,7 +56,10 @@ npm uninstall tailwindcss@^3.0.0 postcss autoprefixer
|
|
|
56
56
|
|
|
57
57
|
### Step 3: Configure Vite
|
|
58
58
|
|
|
59
|
-
**⚠️ CRITICAL**:
|
|
59
|
+
**⚠️ CRITICAL**: This configuration is required for:
|
|
60
|
+
1. **Tailwind CSS scanning** - Ensures PACE Core components are styled
|
|
61
|
+
2. **React context consistency** - Prevents "useUnifiedAuth must be used within a UnifiedAuthProvider" errors
|
|
62
|
+
3. **Router context availability** - Prevents "useNavigate() may be used only in the context of a <Router>" errors
|
|
60
63
|
|
|
61
64
|
Create or update `vite.config.ts`:
|
|
62
65
|
|
|
@@ -64,6 +67,7 @@ Create or update `vite.config.ts`:
|
|
|
64
67
|
import { defineConfig } from 'vite'
|
|
65
68
|
import react from '@vitejs/plugin-react'
|
|
66
69
|
import tailwindcss from '@tailwindcss/vite'
|
|
70
|
+
import path from 'path'
|
|
67
71
|
|
|
68
72
|
export default defineConfig({
|
|
69
73
|
plugins: [
|
|
@@ -76,6 +80,27 @@ export default defineConfig({
|
|
|
76
80
|
]
|
|
77
81
|
})
|
|
78
82
|
],
|
|
83
|
+
resolve: {
|
|
84
|
+
alias: {
|
|
85
|
+
"@": path.resolve(__dirname, "./src"),
|
|
86
|
+
},
|
|
87
|
+
// CRITICAL: Dedupe React and React Router to ensure single instances
|
|
88
|
+
// This prevents Router context issues when pace-core uses react-router-dom
|
|
89
|
+
dedupe: ['react', 'react-dom', 'react-router-dom']
|
|
90
|
+
},
|
|
91
|
+
// CRITICAL: Exclude pace-core from pre-bundling to prevent React context mismatches
|
|
92
|
+
// Pre-bundling creates separate React instances, causing context errors
|
|
93
|
+
optimizeDeps: {
|
|
94
|
+
include: [
|
|
95
|
+
'react',
|
|
96
|
+
'react-dom',
|
|
97
|
+
'react/jsx-runtime',
|
|
98
|
+
'@radix-ui/react-slot'
|
|
99
|
+
],
|
|
100
|
+
// CRITICAL: Exclude pace-core and react-router-dom from pre-bundling
|
|
101
|
+
// This ensures pace-core uses the same React instance as your app
|
|
102
|
+
exclude: ['@jmruthers/pace-core', 'react-router-dom']
|
|
103
|
+
},
|
|
79
104
|
server: {
|
|
80
105
|
port: 3000,
|
|
81
106
|
open: true,
|
|
@@ -279,27 +304,61 @@ VITE_SUPABASE_URL=https://your-project.supabase.co
|
|
|
279
304
|
VITE_SUPABASE_ANON_KEY=your-publishable-key-or-anon-key-here
|
|
280
305
|
```
|
|
281
306
|
|
|
282
|
-
Wrap your app with providers:
|
|
307
|
+
Wrap your app with providers in the correct nesting order:
|
|
283
308
|
|
|
284
309
|
```tsx
|
|
285
|
-
|
|
310
|
+
// src/main.tsx - CRITICAL: Correct provider nesting order
|
|
311
|
+
import React from 'react';
|
|
312
|
+
import ReactDOM from 'react-dom/client';
|
|
313
|
+
import { BrowserRouter } from 'react-router-dom';
|
|
314
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
315
|
+
import { UnifiedAuthProvider, OrganisationProvider, setupRBAC } from '@jmruthers/pace-core';
|
|
286
316
|
import { supabase } from './lib/supabase';
|
|
317
|
+
import App from './App';
|
|
287
318
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
319
|
+
// Import CSS
|
|
320
|
+
import '@jmruthers/pace-core/src/styles/core.css';
|
|
321
|
+
|
|
322
|
+
// ⚠️ REQUIRED: Setup RBAC before rendering
|
|
323
|
+
setupRBAC(supabase);
|
|
324
|
+
|
|
325
|
+
const queryClient = new QueryClient();
|
|
326
|
+
|
|
327
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
328
|
+
<React.StrictMode>
|
|
329
|
+
{/* CRITICAL: Correct nesting order */}
|
|
330
|
+
<QueryClientProvider client={queryClient}>
|
|
331
|
+
<BrowserRouter>
|
|
332
|
+
<UnifiedAuthProvider
|
|
333
|
+
supabaseClient={supabase}
|
|
334
|
+
appName="my-app"
|
|
335
|
+
idleTimeoutMs={30 * 60 * 1000} // 30 minutes
|
|
336
|
+
warnBeforeMs={5 * 60 * 1000} // 5 minutes warning
|
|
337
|
+
onIdleLogout={() => window.location.href = '/login'}
|
|
338
|
+
>
|
|
339
|
+
<OrganisationProvider>
|
|
340
|
+
<App />
|
|
341
|
+
</OrganisationProvider>
|
|
342
|
+
</UnifiedAuthProvider>
|
|
343
|
+
</BrowserRouter>
|
|
344
|
+
</QueryClientProvider>
|
|
345
|
+
</React.StrictMode>
|
|
346
|
+
);
|
|
301
347
|
```
|
|
302
348
|
|
|
349
|
+
**⚠️ CRITICAL Provider Nesting Order:**
|
|
350
|
+
1. `QueryClientProvider` (outermost)
|
|
351
|
+
2. `BrowserRouter`
|
|
352
|
+
3. `UnifiedAuthProvider`
|
|
353
|
+
4. `OrganisationProvider`
|
|
354
|
+
5. `App` (innermost)
|
|
355
|
+
|
|
356
|
+
**Common mistakes to avoid:**
|
|
357
|
+
- ❌ `BrowserRouter` inside `UnifiedAuthProvider` (causes Router context errors)
|
|
358
|
+
- ❌ `UnifiedAuthProvider` wrapping `BrowserRouter` (causes context errors)
|
|
359
|
+
- ❌ Missing `BrowserRouter` (causes `useNavigate` errors)
|
|
360
|
+
- ❌ Missing `QueryClientProvider` (required for React Query features)
|
|
361
|
+
|
|
303
362
|
## 🚨 Breaking Changes (v0.5.65+)
|
|
304
363
|
|
|
305
364
|
**CRITICAL**: As of v0.5.65+, inactivity timeouts are **mandatory** in `UnifiedAuthProvider`.
|
|
@@ -45,12 +45,15 @@ npm install -D tailwindcss@^4.0.0 @tailwindcss/vite
|
|
|
45
45
|
|
|
46
46
|
## Step 3: Configure Vite
|
|
47
47
|
|
|
48
|
+
**⚠️ CRITICAL**: This configuration prevents React context and Router context errors.
|
|
49
|
+
|
|
48
50
|
Create or update `vite.config.ts`:
|
|
49
51
|
|
|
50
52
|
```typescript
|
|
51
53
|
import { defineConfig } from 'vite';
|
|
52
54
|
import react from '@vitejs/plugin-react';
|
|
53
55
|
import tailwindcss from '@tailwindcss/vite';
|
|
56
|
+
import path from 'path';
|
|
54
57
|
|
|
55
58
|
export default defineConfig({
|
|
56
59
|
plugins: [
|
|
@@ -62,6 +65,23 @@ export default defineConfig({
|
|
|
62
65
|
]
|
|
63
66
|
})
|
|
64
67
|
],
|
|
68
|
+
resolve: {
|
|
69
|
+
alias: {
|
|
70
|
+
"@": path.resolve(__dirname, "./src"),
|
|
71
|
+
},
|
|
72
|
+
// CRITICAL: Dedupe React and React Router to ensure single instances
|
|
73
|
+
dedupe: ['react', 'react-dom', 'react-router-dom']
|
|
74
|
+
},
|
|
75
|
+
// CRITICAL: Exclude pace-core from pre-bundling to prevent React context mismatches
|
|
76
|
+
optimizeDeps: {
|
|
77
|
+
include: [
|
|
78
|
+
'react',
|
|
79
|
+
'react-dom',
|
|
80
|
+
'react/jsx-runtime'
|
|
81
|
+
],
|
|
82
|
+
// CRITICAL: Exclude pace-core and react-router-dom from pre-bundling
|
|
83
|
+
exclude: ['@jmruthers/pace-core', 'react-router-dom']
|
|
84
|
+
},
|
|
65
85
|
server: {
|
|
66
86
|
port: 3000,
|
|
67
87
|
open: true,
|
|
@@ -135,39 +155,66 @@ CREATE POLICY "Users can delete their own tasks" ON tasks
|
|
|
135
155
|
|
|
136
156
|
## Step 7: Create Your App
|
|
137
157
|
|
|
158
|
+
**⚠️ CRITICAL**: Provider nesting order matters! Follow this exact structure.
|
|
159
|
+
|
|
138
160
|
Update `src/main.tsx`:
|
|
139
161
|
|
|
140
162
|
```tsx
|
|
141
163
|
import React from 'react';
|
|
142
164
|
import ReactDOM from 'react-dom/client';
|
|
143
165
|
import { BrowserRouter } from 'react-router-dom';
|
|
144
|
-
import {
|
|
166
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
167
|
+
import { UnifiedAuthProvider, OrganisationProvider, setupRBAC } from '@jmruthers/pace-core';
|
|
145
168
|
import { supabase } from './lib/supabase';
|
|
146
169
|
import App from './App';
|
|
147
170
|
import './app.css';
|
|
148
171
|
|
|
172
|
+
// ⚠️ REQUIRED: Setup RBAC before rendering
|
|
173
|
+
setupRBAC(supabase);
|
|
174
|
+
|
|
175
|
+
const queryClient = new QueryClient();
|
|
176
|
+
|
|
149
177
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
150
178
|
<React.StrictMode>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
appName="My Task App"
|
|
154
|
-
idleTimeoutMs={30 * 60 * 1000} // 30 minutes
|
|
155
|
-
warnBeforeMs={60 * 1000} // 1 minute warning
|
|
156
|
-
onIdleLogout={() => window.location.assign('/login')}
|
|
157
|
-
>
|
|
179
|
+
{/* CRITICAL: Correct nesting order */}
|
|
180
|
+
<QueryClientProvider client={queryClient}>
|
|
158
181
|
<BrowserRouter>
|
|
159
|
-
<
|
|
182
|
+
<UnifiedAuthProvider
|
|
183
|
+
supabaseClient={supabase}
|
|
184
|
+
appName="My Task App"
|
|
185
|
+
idleTimeoutMs={30 * 60 * 1000} // 30 minutes
|
|
186
|
+
warnBeforeMs={60 * 1000} // 1 minute warning
|
|
187
|
+
onIdleLogout={() => window.location.assign('/login')}
|
|
188
|
+
>
|
|
189
|
+
<OrganisationProvider>
|
|
190
|
+
<App />
|
|
191
|
+
</OrganisationProvider>
|
|
192
|
+
</UnifiedAuthProvider>
|
|
160
193
|
</BrowserRouter>
|
|
161
|
-
</
|
|
194
|
+
</QueryClientProvider>
|
|
162
195
|
</React.StrictMode>
|
|
163
196
|
);
|
|
164
197
|
```
|
|
165
198
|
|
|
199
|
+
**Provider Nesting Order (CRITICAL):**
|
|
200
|
+
1. `React.StrictMode` (outermost)
|
|
201
|
+
2. `QueryClientProvider`
|
|
202
|
+
3. `BrowserRouter`
|
|
203
|
+
4. `UnifiedAuthProvider`
|
|
204
|
+
5. `OrganisationProvider`
|
|
205
|
+
6. `App` (innermost)
|
|
206
|
+
|
|
207
|
+
**Why this order matters:**
|
|
208
|
+
- `BrowserRouter` must wrap `UnifiedAuthProvider` to provide Router context
|
|
209
|
+
- `UnifiedAuthProvider` must be inside `BrowserRouter` to use Router hooks
|
|
210
|
+
- `QueryClientProvider` wraps everything for React Query support
|
|
211
|
+
- Wrong nesting causes "useNavigate() may be used only in the context of a <Router>" errors
|
|
212
|
+
|
|
166
213
|
Create `src/App.tsx`:
|
|
167
214
|
|
|
168
215
|
```tsx
|
|
169
216
|
import { Routes, Route, Navigate } from 'react-router-dom';
|
|
170
|
-
import {
|
|
217
|
+
import { PaceLoginPage, PaceAppLayout, ProtectedRoute, useUnifiedAuth } from '@jmruthers/pace-core';
|
|
171
218
|
import { TasksPage } from './pages/TasksPage';
|
|
172
219
|
import { DashboardPage } from './pages/DashboardPage';
|
|
173
220
|
|
|
@@ -180,6 +227,7 @@ function App() {
|
|
|
180
227
|
path="/login"
|
|
181
228
|
element={isAuthenticated ? <Navigate to="/" replace /> : <PaceLoginPage appName="My Task App" />}
|
|
182
229
|
/>
|
|
230
|
+
{/* ProtectedRoute from pace-core package - handles auth and event selection */}
|
|
183
231
|
<Route element={<ProtectedRoute />}>
|
|
184
232
|
<Route element={<PaceAppLayout appName="My Task App" />}>
|
|
185
233
|
<Route path="/" element={<DashboardPage />} />
|
|
@@ -193,6 +241,8 @@ function App() {
|
|
|
193
241
|
export default App;
|
|
194
242
|
```
|
|
195
243
|
|
|
244
|
+
**Note:** `ProtectedRoute` is imported from `@jmruthers/pace-core` - do not create a local version. The package version handles authentication, session restoration, and optional event selection.
|
|
245
|
+
|
|
196
246
|
## Step 8: Create the Dashboard Page
|
|
197
247
|
|
|
198
248
|
Create `src/pages/DashboardPage.tsx`:
|
|
@@ -69,17 +69,64 @@ if (!supabaseUrl || !supabaseAnonKey) {
|
|
|
69
69
|
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
### 4.
|
|
72
|
+
### 4. Configure Vite (CRITICAL)
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
**⚠️ CRITICAL**: This configuration prevents React context and Router context errors.
|
|
75
|
+
|
|
76
|
+
Add to your `vite.config.ts`:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { defineConfig } from 'vite';
|
|
80
|
+
import react from '@vitejs/plugin-react';
|
|
81
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
82
|
+
import path from 'path';
|
|
83
|
+
|
|
84
|
+
export default defineConfig({
|
|
85
|
+
plugins: [
|
|
86
|
+
react(),
|
|
87
|
+
tailwindcss({
|
|
88
|
+
content: [
|
|
89
|
+
'./src/**/*.{js,ts,jsx,tsx}',
|
|
90
|
+
'./node_modules/@jmruthers/pace-core/src/**/*.{js,ts,jsx,tsx}'
|
|
91
|
+
]
|
|
92
|
+
})
|
|
93
|
+
],
|
|
94
|
+
resolve: {
|
|
95
|
+
alias: {
|
|
96
|
+
"@": path.resolve(__dirname, "./src"),
|
|
97
|
+
},
|
|
98
|
+
// CRITICAL: Dedupe React and React Router to ensure single instances
|
|
99
|
+
dedupe: ['react', 'react-dom', 'react-router-dom']
|
|
100
|
+
},
|
|
101
|
+
// CRITICAL: Exclude pace-core from pre-bundling to prevent React context mismatches
|
|
102
|
+
optimizeDeps: {
|
|
103
|
+
include: [
|
|
104
|
+
'react',
|
|
105
|
+
'react-dom',
|
|
106
|
+
'react/jsx-runtime'
|
|
107
|
+
],
|
|
108
|
+
exclude: ['@jmruthers/pace-core', 'react-router-dom']
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 5. App Setup with Authentication
|
|
114
|
+
|
|
115
|
+
**⚠️ CRITICAL**:
|
|
116
|
+
1. Provider nesting order matters - follow the exact structure below
|
|
117
|
+
2. UnifiedAuthProvider requires inactivity timeout configuration
|
|
118
|
+
3. setupRBAC() must be called before rendering
|
|
75
119
|
|
|
76
120
|
```tsx
|
|
77
121
|
// src/main.tsx
|
|
78
122
|
import React from 'react'
|
|
79
123
|
import ReactDOM from 'react-dom/client'
|
|
80
124
|
import { BrowserRouter } from 'react-router-dom'
|
|
125
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
81
126
|
import {
|
|
82
|
-
UnifiedAuthProvider
|
|
127
|
+
UnifiedAuthProvider,
|
|
128
|
+
OrganisationProvider,
|
|
129
|
+
setupRBAC
|
|
83
130
|
} from '@jmruthers/pace-core'
|
|
84
131
|
import { supabase } from './lib/supabase'
|
|
85
132
|
import App from './App.tsx'
|
|
@@ -87,17 +134,46 @@ import App from './App.tsx'
|
|
|
87
134
|
// CRITICAL: Import the CSS system - includes everything you need
|
|
88
135
|
import '@jmruthers/pace-core/src/styles/core.css'
|
|
89
136
|
|
|
137
|
+
// ⚠️ REQUIRED: Setup RBAC before rendering
|
|
138
|
+
setupRBAC(supabase);
|
|
139
|
+
|
|
140
|
+
const queryClient = new QueryClient();
|
|
141
|
+
|
|
90
142
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
91
143
|
<React.StrictMode>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
144
|
+
{/* CRITICAL: Correct nesting order */}
|
|
145
|
+
<QueryClientProvider client={queryClient}>
|
|
146
|
+
<BrowserRouter>
|
|
147
|
+
<UnifiedAuthProvider
|
|
148
|
+
supabaseClient={supabase}
|
|
149
|
+
appName="my-app"
|
|
150
|
+
requireOrganisationContext={true}
|
|
151
|
+
idleTimeoutMs={30 * 60 * 1000} // 30 minutes - REQUIRED
|
|
152
|
+
warnBeforeMs={60 * 1000} // 60 seconds - REQUIRED
|
|
153
|
+
onIdleLogout={() => window.location.href = '/login'} // REQUIRED
|
|
154
|
+
>
|
|
155
|
+
<OrganisationProvider>
|
|
156
|
+
<App />
|
|
157
|
+
</OrganisationProvider>
|
|
158
|
+
</UnifiedAuthProvider>
|
|
159
|
+
</BrowserRouter>
|
|
160
|
+
</QueryClientProvider>
|
|
161
|
+
</React.StrictMode>
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Provider Nesting Order (CRITICAL):**
|
|
166
|
+
1. `React.StrictMode` (outermost)
|
|
167
|
+
2. `QueryClientProvider`
|
|
168
|
+
3. `BrowserRouter`
|
|
169
|
+
4. `UnifiedAuthProvider`
|
|
170
|
+
5. `OrganisationProvider`
|
|
171
|
+
6. `App` (innermost)
|
|
172
|
+
|
|
173
|
+
**Why this order matters:**
|
|
174
|
+
- `BrowserRouter` must wrap `UnifiedAuthProvider` to provide Router context
|
|
175
|
+
- `UnifiedAuthProvider` must be inside `BrowserRouter` to use Router hooks
|
|
176
|
+
- Wrong nesting causes "useNavigate() may be used only in the context of a <Router>" errors
|
|
101
177
|
<App />
|
|
102
178
|
</UnifiedAuthProvider>
|
|
103
179
|
</BrowserRouter>
|