@jmruthers/pace-core 0.5.184 → 0.5.186
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-DIzEzwKl.d.ts} +23 -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-W22JP75J.js → chunk-DAGICKHT.js} +9 -7
- package/dist/chunk-DAGICKHT.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-CSOFYHAG.js → chunk-GRIQLQ52.js} +374 -60
- package/dist/chunk-GRIQLQ52.js.map +1 -0
- package/dist/{chunk-NQPMQGS2.js → chunk-HDCUMOOI.js} +497 -399
- package/dist/chunk-HDCUMOOI.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-PWAHJW4G.js → chunk-OALXJH4Y.js} +86 -33
- package/dist/chunk-OALXJH4Y.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-TC7D3CR3.js} +89 -9
- package/dist/chunk-TC7D3CR3.js.map +1 -0
- 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-UHNYIBXL.js → chunk-UQWSHFVX.js} +1 -1
- package/dist/chunk-UQWSHFVX.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/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-PRTSLxKx.d.ts} +10 -1
- package/dist/hooks.d.ts +52 -15
- package/dist/hooks.js +12 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +82 -18
- 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 +206 -15
- 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 +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-D71QLlg4.d.ts} +114 -3
- package/dist/utils.d.ts +110 -152
- 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 +48 -8
- package/docs/api/interfaces/FileUploadProps.md +46 -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 +6 -6
- 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 +746 -50
- package/docs/api-reference/components.md +26 -12
- 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 +26 -3
- package/docs/implementation-guides/file-upload-storage.md +30 -1
- 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/check-pace-core-compliance.js +512 -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.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +10 -1
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +22 -7
- 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 +146 -5
- 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 +5 -0
- package/src/hooks/useFileReference.test.ts +2 -0
- package/src/hooks/useFormDialog.ts +147 -0
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- 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__/rbac-role-isolation.test.ts +456 -0
- 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/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +9 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +60 -4
- package/src/utils/file-reference/index.ts +13 -2
- 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/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +68 -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-MI7HBHN3.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/chunk-W22JP75J.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-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
|
@@ -1829,37 +1829,51 @@ A comprehensive file upload component with drag-and-drop support, validation, an
|
|
|
1829
1829
|
```typescript
|
|
1830
1830
|
interface FileUploadProps {
|
|
1831
1831
|
supabase: SupabaseClient;
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1832
|
+
table_name: string;
|
|
1833
|
+
record_id: string;
|
|
1834
|
+
organisation_id: string;
|
|
1835
|
+
app_id?: string; // Optional - will be resolved from app name if not provided
|
|
1836
|
+
category: FileCategory; // File category for metadata (stored in file_metadata JSONB field)
|
|
1837
|
+
folder: string; // Folder name in storage bucket (e.g., 'profile_photos', 'documents')
|
|
1838
|
+
pageContext: string; // The page context where the file upload occurs (e.g., 'configuration', 'forms', 'applications')
|
|
1836
1839
|
accept?: string;
|
|
1837
1840
|
maxSize?: number;
|
|
1838
1841
|
multiple?: boolean;
|
|
1839
1842
|
disabled?: boolean;
|
|
1843
|
+
isPublic?: boolean; // Whether files should be uploaded to public-files bucket
|
|
1840
1844
|
className?: string;
|
|
1845
|
+
showPreview?: boolean; // Show image preview for accepted files
|
|
1846
|
+
showProgress?: boolean; // Show upload progress bar
|
|
1847
|
+
onUploadSuccess?: (result: FileUploadResult) => void;
|
|
1848
|
+
onUploadError?: (error: string, file?: File) => void;
|
|
1849
|
+
onProgress?: (progress: UploadProgress) => void;
|
|
1841
1850
|
children?: React.ReactNode;
|
|
1842
1851
|
}
|
|
1843
1852
|
```
|
|
1844
1853
|
|
|
1854
|
+
**Note:** The `category` prop is used for metadata purposes (stored in the `file_metadata` JSONB field), while the `folder` prop determines the actual storage path: `{orgId}/{folder}/{timestamp-uuid-filename}`. You can use the same value for both (e.g., `category={FileCategory.PROFILE_PHOTOS}` and `folder="profile_photos"`), or use different values if needed.
|
|
1855
|
+
|
|
1845
1856
|
#### Usage
|
|
1846
1857
|
|
|
1847
1858
|
```tsx
|
|
1848
|
-
import { FileUpload, FileCategory
|
|
1859
|
+
import { FileUpload, FileCategory } from '@jmruthers/pace-core';
|
|
1849
1860
|
|
|
1850
1861
|
function MyFileUpload() {
|
|
1851
|
-
const handleUpload = (fileRef: FileReference) => {
|
|
1852
|
-
logger.debug('FileUpload', 'File uploaded:', { fileId: fileRef.id, fileName: fileRef.name });
|
|
1853
|
-
};
|
|
1854
|
-
|
|
1855
1862
|
return (
|
|
1856
1863
|
<FileUpload
|
|
1857
1864
|
supabase={supabase}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1865
|
+
table_name="pace_person"
|
|
1866
|
+
record_id={personId}
|
|
1867
|
+
organisation_id={organisationId}
|
|
1868
|
+
category={FileCategory.PROFILE_PHOTOS}
|
|
1869
|
+
folder="profile_photos"
|
|
1870
|
+
pageContext="configuration"
|
|
1861
1871
|
accept="image/*"
|
|
1862
1872
|
maxSize={2 * 1024 * 1024} // 2MB
|
|
1873
|
+
onUploadSuccess={(result) => {
|
|
1874
|
+
console.log('Uploaded:', result.file_reference);
|
|
1875
|
+
console.log('URL:', result.file_url);
|
|
1876
|
+
}}
|
|
1863
1877
|
>
|
|
1864
1878
|
<div className="border-2 border-dashed border-main-300 rounded-lg p-8 text-center">
|
|
1865
1879
|
<p>Drag and drop files here or click to browse</p>
|
|
@@ -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`:
|