@jmruthers/pace-core 0.5.54 → 0.5.55
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/README.md +0 -4
- package/dist/{DataTable-7FMFXA7A.js → DataTable-4T627QFJ.js} +11 -11
- package/dist/{PublicLoadingSpinner-Bq_-BeK-.d.ts → PublicLoadingSpinner-SL8WaQN7.d.ts} +2 -21
- package/dist/{api-H5A3H4IR.js → api-LUNF5O6M.js} +3 -3
- package/dist/{appConfig-BVGyuvI7.d.ts → appConfig-DjpeG6P-.d.ts} +9 -1
- package/dist/{appNameResolver-7GHF5ED2.js → appNameResolver-UURKN7NF.js} +2 -2
- package/dist/{audit-BUW3LMJB.js → audit-6TOCAMKO.js} +2 -2
- package/dist/{chunk-MZBUOP4P.js → chunk-5BSLGBYI.js} +4 -3
- package/dist/chunk-5BSLGBYI.js.map +1 -0
- package/dist/{chunk-I5Z3QH5X.js → chunk-66C4BSAY.js} +2 -2
- package/dist/{chunk-I5Z3QH5X.js.map → chunk-66C4BSAY.js.map} +1 -1
- package/dist/{chunk-MYP2EGHX.js → chunk-AJ2KMES7.js} +21 -14
- package/dist/chunk-AJ2KMES7.js.map +1 -0
- package/dist/{chunk-EL2O4IUX.js → chunk-AQFRLC7K.js} +16 -24
- package/dist/{chunk-EL2O4IUX.js.map → chunk-AQFRLC7K.js.map} +1 -1
- package/dist/{chunk-7BNPOCLL.js → chunk-B2WTCLCV.js} +6 -2
- package/dist/chunk-B2WTCLCV.js.map +1 -0
- package/dist/{chunk-WJARTBCT.js → chunk-D7ARGIA3.js} +16 -7
- package/dist/chunk-D7ARGIA3.js.map +1 -0
- package/dist/{chunk-NRK4AIHQ.js → chunk-KBRACSJI.js} +3 -3
- package/dist/{chunk-NYUJ4FJR.js → chunk-KJDPSM64.js} +7 -7
- package/dist/chunk-KJDPSM64.js.map +1 -0
- package/dist/{chunk-GWSBHC4J.js → chunk-KLPVOPRI.js} +261 -38
- package/dist/chunk-KLPVOPRI.js.map +1 -0
- package/dist/{chunk-TRIZ7IB7.js → chunk-MPQDF75X.js} +148 -288
- package/dist/chunk-MPQDF75X.js.map +1 -0
- package/dist/{chunk-MSFACPQQ.js → chunk-PAEM3OWN.js} +11 -11
- package/dist/{chunk-MSFACPQQ.js.map → chunk-PAEM3OWN.js.map} +1 -1
- package/dist/{chunk-GIO7BFE7.js → chunk-RQD3D2CO.js} +66 -169
- package/dist/{chunk-GIO7BFE7.js.map → chunk-RQD3D2CO.js.map} +1 -1
- package/dist/{chunk-YDJW5XTN.js → chunk-STT7INZR.js} +25 -1
- package/dist/chunk-STT7INZR.js.map +1 -0
- package/dist/{chunk-6MTY77WU.js → chunk-TNMXZLDR.js} +3 -3
- package/dist/{chunk-BC3S53OZ.js → chunk-UQE2Y64H.js} +30 -14
- package/dist/chunk-UQE2Y64H.js.map +1 -0
- package/dist/{chunk-22KLBHPS.js → chunk-W66AZIOH.js} +2 -2
- package/dist/chunk-W66AZIOH.js.map +1 -0
- package/dist/{chunk-SS3E6QLB.js → chunk-YNUBMSMV.js} +2 -2
- package/dist/chunk-YNUBMSMV.js.map +1 -0
- package/dist/{chunk-NZ655MWE.js → chunk-ZOD2ZY6X.js} +5 -4
- package/dist/chunk-ZOD2ZY6X.js.map +1 -0
- package/dist/{chunk-74C6SNEC.js → chunk-ZPK5656W.js} +3 -3
- package/dist/{chunk-74C6SNEC.js.map → chunk-ZPK5656W.js.map} +1 -1
- package/dist/components.d.ts +22 -899
- package/dist/components.js +436 -3118
- package/dist/components.js.map +1 -1
- package/dist/file-reference-9xUOnwyt.d.ts +70 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +10 -10
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +49 -9
- package/dist/index.js +190 -25
- package/dist/index.js.map +1 -1
- package/dist/{organisation-CO3Sh3_D.d.ts → organisation-t-vvQC3g.d.ts} +1 -8
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +5 -5
- package/dist/rbac/index.d.ts +65 -46
- package/dist/rbac/index.js +10 -12
- package/dist/styles/core.css +0 -125
- package/dist/types.d.ts +2 -1
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -1
- package/dist/{usePublicRouteParams-B2OcAsur.d.ts → usePublicRouteParams-CdoFxnJK.d.ts} +1 -1
- package/dist/utils.d.ts +3 -4
- package/dist/utils.js +44 -13
- package/dist/utils.js.map +1 -1
- package/docs/FILE_REFERENCE_SYSTEM.md +440 -0
- package/docs/INDEX.md +7 -5
- package/docs/README.md +0 -1
- package/docs/api/README.md +0 -4
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +2 -2
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +12 -12
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +6 -6
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +281 -0
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- 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/DataAccessRecord.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/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- 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/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
- 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 +1 -1
- 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 +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.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/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- 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/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/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- 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/UsePublicRouteParamsReturn.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 +204 -200
- package/docs/api-reference/components.md +141 -163
- package/docs/api-reference/hooks.md +347 -0
- package/docs/core-concepts/rbac-system.md +69 -16
- package/docs/getting-started/examples/basic-auth-app.md +0 -1
- package/docs/implementation-guides/datatable-rbac-usage.md +12 -11
- package/docs/implementation-guides/file-upload-storage.md +733 -0
- package/docs/implementation-guides/inactivity-tracking.md +779 -0
- package/docs/implementation-guides/organisation-security.md +748 -0
- package/docs/implementation-guides/public-pages-advanced.md +1022 -0
- package/docs/migration/MIGRATION_GUIDE.md +684 -0
- package/docs/migration/README.md +13 -2
- package/docs/migration/rbac-migration.md +73 -0
- package/docs/rbac/examples/rbac-rls-integration-example.md +11 -13
- package/docs/style-guide.md +269 -1
- package/package.json +1 -1
- package/src/__tests__/TESTING_GUIDELINES.md +331 -18
- package/src/__tests__/helpers/supabaseMock.ts +99 -0
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +10 -7
- package/src/__tests__/shared.ts +6 -0
- package/src/components/DataTable/components/ActionButtons.tsx +2 -2
- package/src/components/DataTable/components/DataTableCore.tsx +2 -2
- package/src/components/DataTable/components/UnifiedTableBody.tsx +1 -1
- package/src/components/DataTable/utils/debugTools.ts +2 -2
- package/src/components/Dialog/Dialog.test.tsx +12 -2
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +6 -6
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +2 -2
- package/src/components/FileDisplay.tsx +233 -0
- package/src/components/FileUpload.tsx +176 -0
- package/src/components/Footer/Footer.test.tsx +7 -7
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +13 -6
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +30 -3
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +1 -1
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +558 -0
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +1 -1
- package/src/components/PublicLayout/PublicPageDebugger.tsx +2 -2
- package/src/components/PublicLayout/PublicPageDiagnostic.tsx +2 -2
- package/src/components/PublicLayout/PublicPageProvider.tsx +2 -2
- package/src/components/Select/Select.test.tsx +50 -15
- package/src/components/SuperAdminGuard.tsx +2 -2
- package/src/components/__tests__/SuperAdminGuard.test.tsx +559 -0
- package/src/components/index.ts +0 -183
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +2 -2
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +1 -1
- package/src/hooks/__tests__/useRBAC.unit.test.ts +191 -138
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/useAppConfig.ts +3 -3
- package/src/hooks/useComponentPerformance.ts +1 -1
- package/src/hooks/useDataTablePerformance.ts +1 -1
- package/src/hooks/useFileReference.ts +232 -0
- package/src/hooks/useOrganisationPermissions.test.ts +254 -344
- package/src/hooks/useOrganisationPermissions.ts +15 -7
- package/src/hooks/useOrganisationSecurity.test.ts +390 -402
- package/src/hooks/usePerformanceMonitor.ts +1 -1
- package/src/hooks/usePermissionCache.test.ts +264 -395
- package/src/hooks/usePermissionCache.ts +34 -4
- package/src/hooks/useSecureDataAccess.test.ts +486 -0
- package/src/hooks/useSecureDataAccess.ts +4 -1
- package/src/providers/InactivityProvider.tsx +2 -2
- package/src/providers/OrganisationProvider.test.simple.tsx +168 -0
- package/src/providers/OrganisationProvider.test.tsx +168 -0
- package/src/providers/OrganisationProvider.tsx +18 -31
- package/src/providers/UnifiedAuthProvider.test.simple.tsx +205 -0
- package/src/providers/UnifiedAuthProvider.test.tsx +128 -0
- package/src/providers/__tests__/InactivityProvider.test.tsx +3 -4
- package/src/providers/__tests__/OrganisationProvider.test.tsx +19 -14
- package/src/rbac/__tests__/integration.authflow.test.tsx +123 -0
- package/src/rbac/__tests__/integration.navigation.test.tsx +72 -0
- package/src/rbac/__tests__/integration.securedata.test.tsx +92 -0
- package/src/rbac/__tests__/integration.smoke.test.tsx +73 -0
- package/src/rbac/__tests__/rbac-core.test.tsx +26 -22
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +411 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +285 -0
- package/src/rbac/__tests__/rbac-functions.test.ts +655 -0
- package/src/rbac/__tests__/rbac-integration.test.ts +532 -0
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +196 -0
- package/src/rbac/api.test.ts +6 -6
- package/src/rbac/api.ts +2 -2
- package/src/rbac/audit.test.ts +485 -0
- package/src/rbac/audit.ts +7 -1
- package/src/rbac/cache-invalidation.ts +318 -0
- package/src/rbac/cache.test.ts +286 -0
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +559 -0
- package/src/rbac/components/EnhancedNavigationMenu.tsx +29 -23
- package/src/rbac/components/NavigationProvider.test.tsx +449 -0
- package/src/rbac/components/PagePermissionGuard.tsx +4 -4
- package/src/rbac/components/PagePermissionProvider.test.tsx +479 -0
- package/src/rbac/components/SecureDataProvider.test.tsx +511 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +159 -430
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +4 -5
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +112 -118
- package/src/rbac/config.test.ts +410 -0
- package/src/rbac/engine.test.simple.ts +237 -0
- package/src/rbac/engine.test.ts +233 -0
- package/src/rbac/engine.ts +37 -41
- package/src/rbac/examples/CompleteRBACExample.tsx +3 -3
- package/src/rbac/examples/EventBasedApp.tsx +4 -4
- package/src/rbac/hooks/useRBAC.simple.test.ts +16 -0
- package/src/rbac/hooks/useRBAC.test.ts +207 -455
- package/src/rbac/hooks/useRBAC.ts +30 -22
- package/src/rbac/permissions.test.ts +128 -0
- package/src/rbac/permissions.ts +56 -141
- package/src/rbac/providers/RBACProvider.tsx +1 -1
- package/src/rbac/secureClient.test.ts +444 -0
- package/src/rbac/security.test.ts +390 -0
- package/src/rbac/security.ts +1 -1
- package/src/rbac/types.test.ts +382 -0
- package/src/rbac/types.ts +2 -2
- package/src/styles/core.css +0 -125
- package/src/types/file-reference.ts +77 -0
- package/src/types/rbac-functions.ts +290 -0
- package/src/types/supabase.ts +10 -28
- package/src/types/unified.ts +4 -1
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +81 -55
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +21 -12
- package/src/utils/__tests__/organisationContext.unit.test.ts +13 -7
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +3 -3
- package/src/utils/__tests__/sessionTracking.unit.test.ts +32 -12
- package/src/utils/appConfig.ts +1 -1
- package/src/utils/appIdResolver.test.ts +503 -0
- package/src/utils/appIdResolver.ts +1 -1
- package/src/utils/appNameResolver.test.ts +494 -0
- package/src/utils/appNameResolver.ts +3 -2
- package/src/utils/bundleAnalysis.ts +3 -3
- package/src/utils/debugLogger.ts +1 -1
- package/src/utils/file-reference.ts +263 -0
- package/src/utils/formatDate.test.ts +2 -2
- package/src/utils/organisationContext.test.ts +340 -0
- package/src/utils/organisationContext.ts +19 -6
- package/src/utils/performanceBudgets.ts +2 -2
- package/src/utils/permissionUtils.test.ts +393 -0
- package/src/utils/permissionUtils.ts +5 -2
- package/src/utils/secureDataAccess.test.ts +715 -0
- package/src/utils/secureDataAccess.ts +21 -5
- package/src/utils/sessionTracking.ts +34 -4
- package/src/utils/storage/__tests__/helpers.unit.test.ts +328 -0
- package/src/utils/storage/__tests__/index.unit.test.ts +16 -0
- package/src/utils/storage/helpers.ts +20 -25
- package/src/utils/storage/index.ts +29 -1
- package/src/vite-env.d.ts +17 -0
- package/dist/chunk-22KLBHPS.js.map +0 -1
- package/dist/chunk-7BNPOCLL.js.map +0 -1
- package/dist/chunk-BC3S53OZ.js.map +0 -1
- package/dist/chunk-GWSBHC4J.js.map +0 -1
- package/dist/chunk-MYP2EGHX.js.map +0 -1
- package/dist/chunk-MZBUOP4P.js.map +0 -1
- package/dist/chunk-NYUJ4FJR.js.map +0 -1
- package/dist/chunk-NZ655MWE.js.map +0 -1
- package/dist/chunk-SS3E6QLB.js.map +0 -1
- package/dist/chunk-TRIZ7IB7.js.map +0 -1
- package/dist/chunk-WJARTBCT.js.map +0 -1
- package/dist/chunk-YDJW5XTN.js.map +0 -1
- package/docs/print-components/README.md +0 -258
- package/docs/print-components/api-reference.md +0 -636
- package/docs/print-components/examples/README.md +0 -204
- package/docs/print-components/examples/basic-report.tsx +0 -92
- package/docs/print-components/examples/card-catalog.tsx +0 -149
- package/docs/print-components/examples/cover-page-report.tsx +0 -163
- package/docs/print-components/quick-start.md +0 -363
- package/src/components/PrintButton/PrintButton.tsx +0 -321
- package/src/components/PrintButton/PrintButtonGroup.tsx +0 -84
- package/src/components/PrintButton/PrintToolbar.tsx +0 -94
- package/src/components/PrintButton/__tests__/PrintButton.test.tsx +0 -271
- package/src/components/PrintButton/examples/PrintButtonShowcase.tsx +0 -438
- package/src/components/PrintButton/index.ts +0 -33
- package/src/components/PrintButton/types.ts +0 -173
- package/src/components/PrintCard/PrintCard.tsx +0 -154
- package/src/components/PrintCard/PrintCardContent.tsx +0 -57
- package/src/components/PrintCard/PrintCardFooter.tsx +0 -60
- package/src/components/PrintCard/PrintCardGrid.tsx +0 -91
- package/src/components/PrintCard/PrintCardHeader.tsx +0 -78
- package/src/components/PrintCard/PrintCardImage.tsx +0 -81
- package/src/components/PrintCard/examples/PrintCardShowcase.tsx +0 -239
- package/src/components/PrintCard/index.ts +0 -34
- package/src/components/PrintCard/types.ts +0 -171
- package/src/components/PrintDataTable/PrintDataTable.tsx +0 -215
- package/src/components/PrintDataTable/PrintTableGroup.tsx +0 -90
- package/src/components/PrintDataTable/PrintTableRow.tsx +0 -76
- package/src/components/PrintDataTable/index.ts +0 -25
- package/src/components/PrintDataTable/types.ts +0 -67
- package/src/components/PrintFooter/PrintFooter.tsx +0 -183
- package/src/components/PrintFooter/PrintFooterContent.tsx +0 -71
- package/src/components/PrintFooter/PrintFooterInfo.tsx +0 -86
- package/src/components/PrintFooter/PrintPageNumber.tsx +0 -90
- package/src/components/PrintFooter/examples/PrintFooterShowcase.tsx +0 -390
- package/src/components/PrintFooter/index.ts +0 -30
- package/src/components/PrintFooter/types.ts +0 -149
- package/src/components/PrintGrid/PrintGrid.tsx +0 -180
- package/src/components/PrintGrid/PrintGridBreakpoint.tsx +0 -109
- package/src/components/PrintGrid/PrintGridContainer.tsx +0 -128
- package/src/components/PrintGrid/PrintGridItem.tsx +0 -220
- package/src/components/PrintGrid/examples/PrintGridShowcase.tsx +0 -359
- package/src/components/PrintGrid/index.ts +0 -31
- package/src/components/PrintGrid/types.ts +0 -159
- package/src/components/PrintHeader/PrintCoverHeader.tsx +0 -230
- package/src/components/PrintHeader/PrintHeader.tsx +0 -150
- package/src/components/PrintHeader/index.ts +0 -17
- package/src/components/PrintHeader/types.ts +0 -42
- package/src/components/PrintLayout/PrintLayout.tsx +0 -122
- package/src/components/PrintLayout/PrintLayoutContext.tsx +0 -66
- package/src/components/PrintLayout/PrintPageBreak.tsx +0 -52
- package/src/components/PrintLayout/examples/PrintShowcase.tsx +0 -230
- package/src/components/PrintLayout/index.ts +0 -19
- package/src/components/PrintLayout/types.ts +0 -37
- package/src/components/PrintPageBreak/PrintPageBreak.tsx +0 -120
- package/src/components/PrintPageBreak/PrintPageBreakGroup.tsx +0 -90
- package/src/components/PrintPageBreak/PrintPageBreakIndicator.tsx +0 -112
- package/src/components/PrintPageBreak/examples/PrintPageBreakShowcase.tsx +0 -279
- package/src/components/PrintPageBreak/index.ts +0 -23
- package/src/components/PrintPageBreak/types.ts +0 -94
- package/src/components/PrintSection/PrintColumn.tsx +0 -104
- package/src/components/PrintSection/PrintDivider.tsx +0 -101
- package/src/components/PrintSection/PrintSection.tsx +0 -129
- package/src/components/PrintSection/PrintSectionContent.tsx +0 -75
- package/src/components/PrintSection/PrintSectionHeader.tsx +0 -97
- package/src/components/PrintSection/examples/PrintSectionShowcase.tsx +0 -258
- package/src/components/PrintSection/index.ts +0 -33
- package/src/components/PrintSection/types.ts +0 -155
- package/src/components/PrintText/PrintText.tsx +0 -116
- package/src/components/PrintText/index.ts +0 -16
- package/src/components/PrintText/types.ts +0 -24
- package/src/rbac/__tests__/integration.test.tsx +0 -218
- package/src/utils/print/PrintDataProcessor.ts +0 -390
- package/src/utils/print/examples/PrintUtilitiesShowcase.tsx +0 -397
- package/src/utils/print/index.ts +0 -29
- package/src/utils/print/types.ts +0 -196
- package/src/utils/print/usePrintOptimization.ts +0 -272
- /package/dist/{DataTable-7FMFXA7A.js.map → DataTable-4T627QFJ.js.map} +0 -0
- /package/dist/{api-H5A3H4IR.js.map → api-LUNF5O6M.js.map} +0 -0
- /package/dist/{appNameResolver-7GHF5ED2.js.map → appNameResolver-UURKN7NF.js.map} +0 -0
- /package/dist/{audit-BUW3LMJB.js.map → audit-6TOCAMKO.js.map} +0 -0
- /package/dist/{chunk-NRK4AIHQ.js.map → chunk-KBRACSJI.js.map} +0 -0
- /package/dist/{chunk-6MTY77WU.js.map → chunk-TNMXZLDR.js.map} +0 -0
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
# File Upload & Storage System
|
|
2
|
+
|
|
3
|
+
> **📁 Complete File Management** | [← Back to Documentation](../README.md) | [↑ Table of Contents](#table-of-contents)
|
|
4
|
+
|
|
5
|
+
Comprehensive guide to implementing file upload, storage, and management using PACE Core's file reference system.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
PACE Core provides a complete file management system with:
|
|
10
|
+
- **File Upload Component** - Drag-and-drop file upload with validation
|
|
11
|
+
- **File Display Component** - Secure file viewing and management
|
|
12
|
+
- **File Reference System** - Database-backed file tracking with RLS
|
|
13
|
+
- **Storage Hooks** - React hooks for file operations
|
|
14
|
+
- **Security Integration** - Organisation-scoped access control
|
|
15
|
+
|
|
16
|
+
## Architecture
|
|
17
|
+
|
|
18
|
+
```mermaid
|
|
19
|
+
graph TD
|
|
20
|
+
A[FileUpload Component] --> B[useFileReference Hook]
|
|
21
|
+
B --> C[File Reference Database]
|
|
22
|
+
C --> D[Storage Bucket]
|
|
23
|
+
E[FileDisplay Component] --> F[useFileReferenceForRecord Hook]
|
|
24
|
+
F --> C
|
|
25
|
+
G[RLS Policies] --> C
|
|
26
|
+
H[Organisation Context] --> G
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### 1. Basic File Upload
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { FileUpload, FileCategory } from '@jmruthers/pace-core';
|
|
35
|
+
import { useFileReference } from '@jmruthers/pace-core';
|
|
36
|
+
|
|
37
|
+
function MyFileUpload() {
|
|
38
|
+
const { uploadFile, getFileUrl, deleteFile, isLoading, error } = useFileReference(supabase);
|
|
39
|
+
|
|
40
|
+
const handleUpload = async (file: File) => {
|
|
41
|
+
try {
|
|
42
|
+
const fileRef = await uploadFile({
|
|
43
|
+
file,
|
|
44
|
+
category: FileCategory.DOCUMENT,
|
|
45
|
+
description: 'User uploaded document'
|
|
46
|
+
});
|
|
47
|
+
console.log('File uploaded:', fileRef);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error('Upload failed:', err);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<FileUpload
|
|
55
|
+
supabase={supabase}
|
|
56
|
+
orgId="org-123"
|
|
57
|
+
onUploadComplete={handleUpload}
|
|
58
|
+
accept=".pdf,.doc,.docx"
|
|
59
|
+
maxSize={5 * 1024 * 1024} // 5MB
|
|
60
|
+
>
|
|
61
|
+
<div className="border-2 border-dashed border-main-300 rounded-lg p-8 text-center">
|
|
62
|
+
<p>Drag and drop files here or click to browse</p>
|
|
63
|
+
</div>
|
|
64
|
+
</FileUpload>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 2. File Display with Management
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { FileDisplay, FileCategory } from '@jmruthers/pace-core';
|
|
73
|
+
import { useFileReferenceForRecord } from '@jmruthers/pace-core';
|
|
74
|
+
|
|
75
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
76
|
+
const { fileUrl, uploadFile, deleteFile, isLoading } = useFileReferenceForRecord(
|
|
77
|
+
supabase,
|
|
78
|
+
'pace_person',
|
|
79
|
+
userId,
|
|
80
|
+
'org-123'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div>
|
|
85
|
+
<h2>Profile Documents</h2>
|
|
86
|
+
<FileDisplay
|
|
87
|
+
supabase={supabase}
|
|
88
|
+
orgId="org-123"
|
|
89
|
+
recordType="pace_person"
|
|
90
|
+
recordId={userId}
|
|
91
|
+
category={FileCategory.DOCUMENT}
|
|
92
|
+
onUpload={uploadFile}
|
|
93
|
+
onDelete={deleteFile}
|
|
94
|
+
showUpload={true}
|
|
95
|
+
showDelete={true}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Components
|
|
103
|
+
|
|
104
|
+
### FileUpload
|
|
105
|
+
|
|
106
|
+
A comprehensive file upload component with drag-and-drop support, validation, and progress tracking.
|
|
107
|
+
|
|
108
|
+
#### Props
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
interface FileUploadProps {
|
|
112
|
+
supabase: SupabaseClient;
|
|
113
|
+
appName: string;
|
|
114
|
+
orgId: string;
|
|
115
|
+
onUploadComplete?: (fileRef: FileReference) => void;
|
|
116
|
+
onUploadStart?: (file: File) => void;
|
|
117
|
+
accept?: string;
|
|
118
|
+
maxSize?: number;
|
|
119
|
+
multiple?: boolean;
|
|
120
|
+
disabled?: boolean;
|
|
121
|
+
className?: string;
|
|
122
|
+
children?: React.ReactNode;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Usage Examples
|
|
127
|
+
|
|
128
|
+
**Basic Upload:**
|
|
129
|
+
```tsx
|
|
130
|
+
<FileUpload
|
|
131
|
+
supabase={supabase}
|
|
132
|
+
appName="my-app"
|
|
133
|
+
orgId={organisationId}
|
|
134
|
+
onUploadComplete={(fileRef) => console.log('Uploaded:', fileRef)}
|
|
135
|
+
accept="image/*"
|
|
136
|
+
maxSize={2 * 1024 * 1024} // 2MB
|
|
137
|
+
>
|
|
138
|
+
<Button>Upload Image</Button>
|
|
139
|
+
</FileUpload>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Multiple Files:**
|
|
143
|
+
```tsx
|
|
144
|
+
<FileUpload
|
|
145
|
+
supabase={supabase}
|
|
146
|
+
appName="my-app"
|
|
147
|
+
orgId={organisationId}
|
|
148
|
+
multiple={true}
|
|
149
|
+
accept=".pdf,.doc,.docx"
|
|
150
|
+
onUploadComplete={(fileRef) => addToFileList(fileRef)}
|
|
151
|
+
>
|
|
152
|
+
<div className="upload-area">
|
|
153
|
+
<UploadIcon className="w-8 h-8" />
|
|
154
|
+
<p>Upload Documents</p>
|
|
155
|
+
</div>
|
|
156
|
+
</FileUpload>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Custom Validation:**
|
|
160
|
+
```tsx
|
|
161
|
+
<FileUpload
|
|
162
|
+
supabase={supabase}
|
|
163
|
+
appName="my-app"
|
|
164
|
+
orgId={organisationId}
|
|
165
|
+
onUploadStart={(file) => {
|
|
166
|
+
if (file.size > 10 * 1024 * 1024) {
|
|
167
|
+
throw new Error('File too large');
|
|
168
|
+
}
|
|
169
|
+
}}
|
|
170
|
+
onUploadComplete={(fileRef) => handleUpload(fileRef)}
|
|
171
|
+
>
|
|
172
|
+
<CustomUploadArea />
|
|
173
|
+
</FileUpload>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### FileDisplay
|
|
177
|
+
|
|
178
|
+
A component for displaying and managing files associated with database records.
|
|
179
|
+
|
|
180
|
+
#### Props
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
interface FileDisplayProps {
|
|
184
|
+
supabase: SupabaseClient;
|
|
185
|
+
appName: string;
|
|
186
|
+
orgId: string;
|
|
187
|
+
recordType: string;
|
|
188
|
+
recordId: string;
|
|
189
|
+
category?: FileCategory;
|
|
190
|
+
onUpload?: (file: File) => Promise<FileReference>;
|
|
191
|
+
onDelete?: (fileRef: FileReference) => Promise<void>;
|
|
192
|
+
showUpload?: boolean;
|
|
193
|
+
showDelete?: boolean;
|
|
194
|
+
showPreview?: boolean;
|
|
195
|
+
className?: string;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### Usage Examples
|
|
200
|
+
|
|
201
|
+
**Read-Only Display:**
|
|
202
|
+
```tsx
|
|
203
|
+
<FileDisplay
|
|
204
|
+
supabase={supabase}
|
|
205
|
+
appName="my-app"
|
|
206
|
+
orgId={organisationId}
|
|
207
|
+
recordType="pace_person"
|
|
208
|
+
recordId={personId}
|
|
209
|
+
category={FileCategory.DOCUMENT}
|
|
210
|
+
showUpload={false}
|
|
211
|
+
showDelete={false}
|
|
212
|
+
/>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Full Management:**
|
|
216
|
+
```tsx
|
|
217
|
+
<FileDisplay
|
|
218
|
+
supabase={supabase}
|
|
219
|
+
appName="my-app"
|
|
220
|
+
orgId={organisationId}
|
|
221
|
+
recordType="pace_person"
|
|
222
|
+
recordId={personId}
|
|
223
|
+
category={FileCategory.IMAGE}
|
|
224
|
+
onUpload={handleUpload}
|
|
225
|
+
onDelete={handleDelete}
|
|
226
|
+
showUpload={true}
|
|
227
|
+
showDelete={true}
|
|
228
|
+
showPreview={true}
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Hooks
|
|
233
|
+
|
|
234
|
+
### useFileReference
|
|
235
|
+
|
|
236
|
+
Primary hook for file operations with a Supabase client.
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
interface UseFileReferenceReturn {
|
|
240
|
+
uploadFile: (options: UploadFileOptions) => Promise<FileReference>;
|
|
241
|
+
getFileUrl: (fileRef: FileReference) => Promise<string>;
|
|
242
|
+
deleteFile: (fileRef: FileReference) => Promise<void>;
|
|
243
|
+
isLoading: boolean;
|
|
244
|
+
error: Error | null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
interface UploadFileOptions {
|
|
248
|
+
file: File;
|
|
249
|
+
category: FileCategory;
|
|
250
|
+
description?: string;
|
|
251
|
+
metadata?: Record<string, any>;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Usage
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
import { useFileReference } from '@jmruthers/pace-core';
|
|
259
|
+
|
|
260
|
+
function FileManager() {
|
|
261
|
+
const { uploadFile, getFileUrl, deleteFile, isLoading, error } = useFileReference(supabase);
|
|
262
|
+
|
|
263
|
+
const handleUpload = async (file: File) => {
|
|
264
|
+
try {
|
|
265
|
+
const fileRef = await uploadFile({
|
|
266
|
+
file,
|
|
267
|
+
category: FileCategory.DOCUMENT,
|
|
268
|
+
description: 'User document',
|
|
269
|
+
metadata: { uploadedBy: user.id }
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Get the public URL
|
|
273
|
+
const url = await getFileUrl(fileRef);
|
|
274
|
+
console.log('File available at:', url);
|
|
275
|
+
} catch (err) {
|
|
276
|
+
console.error('Upload failed:', err);
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const handleDelete = async (fileRef: FileReference) => {
|
|
281
|
+
try {
|
|
282
|
+
await deleteFile(fileRef);
|
|
283
|
+
console.log('File deleted');
|
|
284
|
+
} catch (err) {
|
|
285
|
+
console.error('Delete failed:', err);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div>
|
|
291
|
+
{/* Upload UI */}
|
|
292
|
+
{isLoading && <div>Uploading...</div>}
|
|
293
|
+
{error && <div>Error: {error.message}</div>}
|
|
294
|
+
</div>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### useFileReferenceForRecord
|
|
300
|
+
|
|
301
|
+
Hook for managing files associated with specific database records.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
interface UseFileReferenceForRecordReturn {
|
|
305
|
+
fileUrl: string | null;
|
|
306
|
+
uploadFile: (file: File) => Promise<FileReference>;
|
|
307
|
+
deleteFile: (fileRef: FileReference) => Promise<void>;
|
|
308
|
+
isLoading: boolean;
|
|
309
|
+
error: Error | null;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Usage
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
import { useFileReferenceForRecord } from '@jmruthers/pace-core';
|
|
317
|
+
|
|
318
|
+
function PersonProfile({ personId }: { personId: string }) {
|
|
319
|
+
const { fileUrl, uploadFile, deleteFile, isLoading } = useFileReferenceForRecord(
|
|
320
|
+
supabase,
|
|
321
|
+
'pace_person',
|
|
322
|
+
personId,
|
|
323
|
+
organisationId
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<div>
|
|
328
|
+
{fileUrl && (
|
|
329
|
+
<img
|
|
330
|
+
src={fileUrl}
|
|
331
|
+
alt="Profile"
|
|
332
|
+
className="w-32 h-32 rounded-full"
|
|
333
|
+
/>
|
|
334
|
+
)}
|
|
335
|
+
|
|
336
|
+
<FileUpload onUploadComplete={uploadFile}>
|
|
337
|
+
<Button>Change Photo</Button>
|
|
338
|
+
</FileUpload>
|
|
339
|
+
|
|
340
|
+
{fileUrl && (
|
|
341
|
+
<Button onClick={() => deleteFile(fileRef)} variant="destructive">
|
|
342
|
+
Remove Photo
|
|
343
|
+
</Button>
|
|
344
|
+
)}
|
|
345
|
+
</div>
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## File Categories
|
|
351
|
+
|
|
352
|
+
PACE Core supports predefined file categories for better organization:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
enum FileCategory {
|
|
356
|
+
DOCUMENT = 'document',
|
|
357
|
+
IMAGE = 'image',
|
|
358
|
+
AUDIO = 'audio',
|
|
359
|
+
VIDEO = 'video',
|
|
360
|
+
ARCHIVE = 'archive',
|
|
361
|
+
SPREADSHEET = 'spreadsheet',
|
|
362
|
+
PRESENTATION = 'presentation',
|
|
363
|
+
OTHER = 'other'
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Usage
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
import { FileCategory } from '@jmruthers/pace-core';
|
|
371
|
+
|
|
372
|
+
// Upload a document
|
|
373
|
+
await uploadFile({
|
|
374
|
+
file: pdfFile,
|
|
375
|
+
category: FileCategory.DOCUMENT,
|
|
376
|
+
description: 'Contract document'
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Upload an image
|
|
380
|
+
await uploadFile({
|
|
381
|
+
file: imageFile,
|
|
382
|
+
category: FileCategory.IMAGE,
|
|
383
|
+
description: 'Profile photo'
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Database Schema
|
|
388
|
+
|
|
389
|
+
The file reference system uses the following database structure:
|
|
390
|
+
|
|
391
|
+
### file_references Table
|
|
392
|
+
|
|
393
|
+
```sql
|
|
394
|
+
CREATE TABLE file_references (
|
|
395
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
396
|
+
organisation_id UUID NOT NULL REFERENCES organisations(id),
|
|
397
|
+
record_type TEXT NOT NULL,
|
|
398
|
+
record_id UUID NOT NULL,
|
|
399
|
+
file_name TEXT NOT NULL,
|
|
400
|
+
file_size BIGINT NOT NULL,
|
|
401
|
+
file_type TEXT NOT NULL,
|
|
402
|
+
file_category TEXT NOT NULL,
|
|
403
|
+
storage_path TEXT NOT NULL,
|
|
404
|
+
description TEXT,
|
|
405
|
+
metadata JSONB,
|
|
406
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
407
|
+
created_by UUID REFERENCES auth.users(id),
|
|
408
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
-- RLS Policies
|
|
412
|
+
ALTER TABLE file_references ENABLE ROW LEVEL SECURITY;
|
|
413
|
+
|
|
414
|
+
CREATE POLICY "Users can view files in their organisation" ON file_references
|
|
415
|
+
FOR SELECT USING (
|
|
416
|
+
organisation_id IN (
|
|
417
|
+
SELECT organisation_id FROM user_organisation_memberships
|
|
418
|
+
WHERE user_id = auth.uid()
|
|
419
|
+
)
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
CREATE POLICY "Users can insert files in their organisation" ON file_references
|
|
423
|
+
FOR INSERT WITH CHECK (
|
|
424
|
+
organisation_id IN (
|
|
425
|
+
SELECT organisation_id FROM user_organisation_memberships
|
|
426
|
+
WHERE user_id = auth.uid()
|
|
427
|
+
)
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
CREATE POLICY "Users can update files in their organisation" ON file_references
|
|
431
|
+
FOR UPDATE USING (
|
|
432
|
+
organisation_id IN (
|
|
433
|
+
SELECT organisation_id FROM user_organisation_memberships
|
|
434
|
+
WHERE user_id = auth.uid()
|
|
435
|
+
)
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
CREATE POLICY "Users can delete files in their organisation" ON file_references
|
|
439
|
+
FOR DELETE USING (
|
|
440
|
+
organisation_id IN (
|
|
441
|
+
SELECT organisation_id FROM user_organisation_memberships
|
|
442
|
+
WHERE user_id = auth.uid()
|
|
443
|
+
)
|
|
444
|
+
);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Storage Configuration
|
|
448
|
+
|
|
449
|
+
### Supabase Storage Setup
|
|
450
|
+
|
|
451
|
+
1. **Create Storage Bucket:**
|
|
452
|
+
```sql
|
|
453
|
+
INSERT INTO storage.buckets (id, name, public)
|
|
454
|
+
VALUES ('pace-files', 'pace-files', false);
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
2. **Set Bucket Policies:**
|
|
458
|
+
```sql
|
|
459
|
+
-- Allow authenticated users to upload
|
|
460
|
+
CREATE POLICY "Authenticated users can upload files" ON storage.objects
|
|
461
|
+
FOR INSERT WITH CHECK (bucket_id = 'pace-files' AND auth.role() = 'authenticated');
|
|
462
|
+
|
|
463
|
+
-- Allow users to view files in their organisation
|
|
464
|
+
CREATE POLICY "Users can view files in their organisation" ON storage.objects
|
|
465
|
+
FOR SELECT USING (
|
|
466
|
+
bucket_id = 'pace-files' AND
|
|
467
|
+
(storage.foldername(name))[1] IN (
|
|
468
|
+
SELECT id::text FROM organisations
|
|
469
|
+
WHERE id IN (
|
|
470
|
+
SELECT organisation_id FROM user_organisation_memberships
|
|
471
|
+
WHERE user_id = auth.uid()
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
-- Allow users to delete files in their organisation
|
|
477
|
+
CREATE POLICY "Users can delete files in their organisation" ON storage.objects
|
|
478
|
+
FOR DELETE USING (
|
|
479
|
+
bucket_id = 'pace-files' AND
|
|
480
|
+
(storage.foldername(name))[1] IN (
|
|
481
|
+
SELECT id::text FROM organisations
|
|
482
|
+
WHERE id IN (
|
|
483
|
+
SELECT organisation_id FROM user_organisation_memberships
|
|
484
|
+
WHERE user_id = auth.uid()
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
);
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## Security Considerations
|
|
491
|
+
|
|
492
|
+
### Row Level Security (RLS)
|
|
493
|
+
|
|
494
|
+
All file operations are protected by RLS policies that ensure:
|
|
495
|
+
- Users can only access files within their organisation
|
|
496
|
+
- File metadata is properly scoped
|
|
497
|
+
- Storage paths are organisation-isolated
|
|
498
|
+
|
|
499
|
+
### File Validation
|
|
500
|
+
|
|
501
|
+
```tsx
|
|
502
|
+
import { validateFileUpload } from '@jmruthers/pace-core';
|
|
503
|
+
|
|
504
|
+
const fileUploadConfig = {
|
|
505
|
+
maxSize: 5 * 1024 * 1024, // 5MB
|
|
506
|
+
allowedTypes: ['image/jpeg', 'image/png', 'application/pdf'],
|
|
507
|
+
allowedExtensions: ['.jpg', '.jpeg', '.png', '.pdf']
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
function FileUpload() {
|
|
511
|
+
const handleFileUpload = async (file: File) => {
|
|
512
|
+
try {
|
|
513
|
+
const validation = await validateFileUpload(file, fileUploadConfig);
|
|
514
|
+
if (!validation.isValid) {
|
|
515
|
+
throw new Error(validation.errors.join(', '));
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Proceed with upload
|
|
519
|
+
await uploadFile({ file, category: FileCategory.DOCUMENT });
|
|
520
|
+
} catch (error) {
|
|
521
|
+
console.error('File validation failed:', error);
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<FileUpload onUploadComplete={handleFileUpload}>
|
|
527
|
+
<UploadArea />
|
|
528
|
+
</FileUpload>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
## Migration from Old File Columns
|
|
534
|
+
|
|
535
|
+
If you're migrating from direct file columns to the file reference system:
|
|
536
|
+
|
|
537
|
+
### 1. Create Migration Script
|
|
538
|
+
|
|
539
|
+
```sql
|
|
540
|
+
-- Example migration for pace_person table
|
|
541
|
+
INSERT INTO file_references (
|
|
542
|
+
organisation_id,
|
|
543
|
+
record_type,
|
|
544
|
+
record_id,
|
|
545
|
+
file_name,
|
|
546
|
+
file_size,
|
|
547
|
+
file_type,
|
|
548
|
+
file_category,
|
|
549
|
+
storage_path,
|
|
550
|
+
created_at,
|
|
551
|
+
created_by
|
|
552
|
+
)
|
|
553
|
+
SELECT
|
|
554
|
+
organisation_id,
|
|
555
|
+
'pace_person',
|
|
556
|
+
id,
|
|
557
|
+
COALESCE(avatar_filename, 'unknown'),
|
|
558
|
+
COALESCE(avatar_size, 0),
|
|
559
|
+
COALESCE(avatar_type, 'application/octet-stream'),
|
|
560
|
+
'image',
|
|
561
|
+
COALESCE(avatar_path, ''),
|
|
562
|
+
created_at,
|
|
563
|
+
created_by
|
|
564
|
+
FROM pace_person
|
|
565
|
+
WHERE avatar_filename IS NOT NULL;
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
### 2. Update Application Code
|
|
569
|
+
|
|
570
|
+
```tsx
|
|
571
|
+
// Before: Direct file column access
|
|
572
|
+
const avatarUrl = person.avatar_url;
|
|
573
|
+
|
|
574
|
+
// After: File reference system
|
|
575
|
+
const { fileUrl } = useFileReferenceForRecord(
|
|
576
|
+
supabase,
|
|
577
|
+
'pace_person',
|
|
578
|
+
person.id,
|
|
579
|
+
organisationId
|
|
580
|
+
);
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## Complete Examples
|
|
584
|
+
|
|
585
|
+
### Profile Photo Management
|
|
586
|
+
|
|
587
|
+
```tsx
|
|
588
|
+
import {
|
|
589
|
+
FileUpload,
|
|
590
|
+
FileDisplay,
|
|
591
|
+
useFileReferenceForRecord,
|
|
592
|
+
FileCategory
|
|
593
|
+
} from '@jmruthers/pace-core';
|
|
594
|
+
|
|
595
|
+
function ProfilePhotoManager({ personId }: { personId: string }) {
|
|
596
|
+
const { fileUrl, uploadFile, deleteFile, isLoading } = useFileReferenceForRecord(
|
|
597
|
+
supabase,
|
|
598
|
+
'pace_person',
|
|
599
|
+
personId,
|
|
600
|
+
organisationId
|
|
601
|
+
);
|
|
602
|
+
|
|
603
|
+
const handleUpload = async (file: File) => {
|
|
604
|
+
try {
|
|
605
|
+
await uploadFile(file);
|
|
606
|
+
toast.success('Photo updated successfully');
|
|
607
|
+
} catch (error) {
|
|
608
|
+
toast.error('Failed to update photo');
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
const handleDelete = async () => {
|
|
613
|
+
try {
|
|
614
|
+
await deleteFile(fileRef);
|
|
615
|
+
toast.success('Photo removed');
|
|
616
|
+
} catch (error) {
|
|
617
|
+
toast.error('Failed to remove photo');
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
return (
|
|
622
|
+
<div className="space-y-4">
|
|
623
|
+
<div className="flex items-center space-x-4">
|
|
624
|
+
{fileUrl ? (
|
|
625
|
+
<img
|
|
626
|
+
src={fileUrl}
|
|
627
|
+
alt="Profile"
|
|
628
|
+
className="w-24 h-24 rounded-full object-cover"
|
|
629
|
+
/>
|
|
630
|
+
) : (
|
|
631
|
+
<div className="w-24 h-24 rounded-full bg-sec-200 flex items-center justify-center">
|
|
632
|
+
<UserIcon className="w-8 h-8 text-sec-500" />
|
|
633
|
+
</div>
|
|
634
|
+
)}
|
|
635
|
+
|
|
636
|
+
<div className="space-y-2">
|
|
637
|
+
<FileUpload
|
|
638
|
+
supabase={supabase}
|
|
639
|
+
appName="my-app"
|
|
640
|
+
orgId={organisationId}
|
|
641
|
+
onUploadComplete={handleUpload}
|
|
642
|
+
accept="image/*"
|
|
643
|
+
maxSize={2 * 1024 * 1024}
|
|
644
|
+
>
|
|
645
|
+
<Button size="sm">Change Photo</Button>
|
|
646
|
+
</FileUpload>
|
|
647
|
+
|
|
648
|
+
{fileUrl && (
|
|
649
|
+
<Button
|
|
650
|
+
size="sm"
|
|
651
|
+
variant="destructive"
|
|
652
|
+
onClick={handleDelete}
|
|
653
|
+
>
|
|
654
|
+
Remove Photo
|
|
655
|
+
</Button>
|
|
656
|
+
)}
|
|
657
|
+
</div>
|
|
658
|
+
</div>
|
|
659
|
+
|
|
660
|
+
{isLoading && <div>Processing...</div>}
|
|
661
|
+
</div>
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Document Management
|
|
667
|
+
|
|
668
|
+
```tsx
|
|
669
|
+
import { FileDisplay, FileCategory } from '@jmruthers/pace-core';
|
|
670
|
+
|
|
671
|
+
function DocumentManager({ recordId, recordType }: {
|
|
672
|
+
recordId: string;
|
|
673
|
+
recordType: string;
|
|
674
|
+
}) {
|
|
675
|
+
return (
|
|
676
|
+
<div className="space-y-4">
|
|
677
|
+
<h3>Documents</h3>
|
|
678
|
+
|
|
679
|
+
<FileDisplay
|
|
680
|
+
supabase={supabase}
|
|
681
|
+
appName="my-app"
|
|
682
|
+
orgId={organisationId}
|
|
683
|
+
recordType={recordType}
|
|
684
|
+
recordId={recordId}
|
|
685
|
+
category={FileCategory.DOCUMENT}
|
|
686
|
+
showUpload={true}
|
|
687
|
+
showDelete={true}
|
|
688
|
+
showPreview={true}
|
|
689
|
+
/>
|
|
690
|
+
</div>
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
## Troubleshooting
|
|
696
|
+
|
|
697
|
+
### Common Issues
|
|
698
|
+
|
|
699
|
+
**Issue: Files not uploading**
|
|
700
|
+
- Check Supabase storage bucket configuration
|
|
701
|
+
- Verify RLS policies are correctly set
|
|
702
|
+
- Ensure organisation context is available
|
|
703
|
+
|
|
704
|
+
**Issue: Files not displaying**
|
|
705
|
+
- Check file reference database records
|
|
706
|
+
- Verify storage path is correct
|
|
707
|
+
- Ensure user has organisation access
|
|
708
|
+
|
|
709
|
+
**Issue: Permission denied errors**
|
|
710
|
+
- Verify RLS policies allow user access
|
|
711
|
+
- Check organisation membership
|
|
712
|
+
- Ensure file belongs to user's organisation
|
|
713
|
+
|
|
714
|
+
### Debug Mode
|
|
715
|
+
|
|
716
|
+
Enable debug logging to troubleshoot file operations:
|
|
717
|
+
|
|
718
|
+
```tsx
|
|
719
|
+
import { setRBACConfig } from '@jmruthers/pace-core/rbac';
|
|
720
|
+
|
|
721
|
+
// Enable debug mode
|
|
722
|
+
setRBACConfig({
|
|
723
|
+
logLevel: 'debug',
|
|
724
|
+
enableAuditLogging: true
|
|
725
|
+
});
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
## Next Steps
|
|
729
|
+
|
|
730
|
+
- **[Storage Utilities](../api-reference/utilities.md#storage)** - Additional storage utilities
|
|
731
|
+
- **[Security Best Practices](../best-practices/security.md)** - Security guidelines
|
|
732
|
+
- **[RBAC Integration](./permission-enforcement.md)** - Permission-based file access
|
|
733
|
+
- **[Performance Optimization](./performance.md)** - Large file handling
|