@jmruthers/pace-core 0.5.188 → 0.5.190
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/core-usage-manifest.json +0 -4
- package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
- package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
- package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-C4uxosp6.d.ts} +129 -40
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
- package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
- package/dist/chunk-4QYC5L4K.js.map +1 -0
- package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
- package/dist/chunk-73HSNNOQ.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
- package/dist/chunk-DZWK57KZ.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
- package/dist/chunk-HQVPB5MZ.js.map +1 -0
- package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
- package/dist/chunk-HW3OVDUF.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
- package/dist/chunk-I7PSE6JW.js.map +1 -0
- package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
- package/dist/chunk-J2XXC7R5.js.map +1 -0
- package/dist/{chunk-UNOTYLQF.js → chunk-NIU6J6OX.js} +772 -725
- package/dist/chunk-NIU6J6OX.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
- package/dist/chunk-RUYZKXOD.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
- package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
- package/dist/{chunk-IPCH26AG.js → chunk-STYK4OH2.js} +11 -11
- package/dist/chunk-STYK4OH2.js.map +1 -0
- package/dist/{chunk-EFCLXK7F.js → chunk-VVBAW5A5.js} +4201 -3809
- package/dist/chunk-VVBAW5A5.js.map +1 -0
- package/dist/chunk-Y4BUBBHD.js +614 -0
- package/dist/chunk-Y4BUBBHD.js.map +1 -0
- package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
- package/dist/chunk-ZSAAAMVR.js.map +1 -0
- package/dist/components.d.ts +3 -5
- package/dist/components.js +19 -23
- package/dist/components.js.map +1 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
- package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
- package/dist/hooks.d.ts +10 -5
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +13 -12
- package/dist/index.js +79 -73
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +76 -12
- package/dist/rbac/index.js +12 -9
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
- package/dist/utils.js +16 -16
- package/docs/README.md +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +2 -2
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +2 -2
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +4 -4
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +21 -16
- package/docs/api/classes/StorageUtils.md +7 -4
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +128 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +20 -6
- 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 +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- 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 +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +62 -16
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +26 -12
- package/docs/api/interfaces/FileUploadProps.md +30 -19
- 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 +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/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- 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 +7 -7
- 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 +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +3 -11
- 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 +1 -1
- 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 +1 -1
- 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/RBACPerformanceMetrics.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 +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +10 -10
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +4 -4
- package/docs/api/interfaces/StorageFileInfo.md +7 -7
- package/docs/api/interfaces/StorageFileMetadata.md +25 -14
- package/docs/api/interfaces/StorageListOptions.md +22 -9
- package/docs/api/interfaces/StorageListResult.md +4 -4
- package/docs/api/interfaces/StorageUploadOptions.md +21 -8
- package/docs/api/interfaces/StorageUploadResult.md +6 -6
- package/docs/api/interfaces/StorageUrlOptions.md +19 -6
- 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 +53 -53
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.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/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
- package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +155 -135
- package/docs/api-reference/components.md +72 -29
- package/docs/api-reference/providers.md +2 -2
- package/docs/api-reference/rpc-functions.md +1 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/deployment.md +8 -8
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/getting-started/installation-guide.md +4 -4
- package/docs/getting-started/quick-start.md +3 -3
- package/docs/migration/MIGRATION_GUIDE.md +3 -3
- package/docs/rbac/compliance/compliance-guide.md +2 -2
- package/docs/rbac/event-based-apps.md +2 -2
- package/docs/rbac/getting-started.md +2 -2
- package/docs/rbac/quick-start.md +2 -2
- package/docs/security/README.md +4 -4
- package/docs/standards/07-rbac-and-rls-standard.md +430 -7
- package/docs/troubleshooting/README.md +2 -2
- package/docs/troubleshooting/migration.md +3 -3
- package/package.json +1 -4
- package/scripts/check-pace-core-compliance.cjs +1 -1
- package/scripts/check-pace-core-compliance.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +301 -0
- package/src/__tests__/public-recipe-view.test.ts +9 -9
- package/src/__tests__/rls-policies.test.ts +197 -61
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +1 -0
- package/src/components/Alert/Alert.test.tsx +50 -10
- package/src/components/Alert/Alert.tsx +5 -3
- package/src/components/Avatar/Avatar.test.tsx +252 -226
- package/src/components/Avatar/Avatar.tsx +179 -53
- package/src/components/Avatar/index.ts +1 -1
- package/src/components/Button/Button.test.tsx +2 -1
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Calendar/Calendar.test.tsx +53 -37
- package/src/components/Calendar/Calendar.tsx +409 -82
- package/src/components/Card/Card.test.tsx +7 -4
- package/src/components/Card/Card.tsx +3 -6
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +5 -5
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
- package/src/components/DataTable/components/DataTableBody.tsx +12 -12
- package/src/components/DataTable/components/DataTableCore.tsx +3 -3
- package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
- package/src/components/DataTable/components/EditableRow.tsx +2 -2
- package/src/components/DataTable/components/EmptyState.tsx +3 -3
- package/src/components/DataTable/components/GroupHeader.tsx +2 -2
- package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
- package/src/components/DataTable/components/ImportModal.tsx +4 -4
- package/src/components/DataTable/components/LoadingState.tsx +1 -1
- package/src/components/DataTable/components/PaginationControls.tsx +11 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
- package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
- package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/EventSelector/EventSelector.tsx +7 -7
- package/src/components/FileDisplay/FileDisplay.tsx +291 -179
- package/src/components/FileUpload/FileUpload.tsx +7 -4
- package/src/components/Header/Header.test.tsx +28 -0
- package/src/components/Header/Header.tsx +22 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
- package/src/components/PaceAppLayout/test-setup.tsx +1 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
- package/src/components/Progress/Progress.test.tsx +18 -19
- package/src/components/Progress/Progress.tsx +31 -32
- package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
- package/src/components/Select/Select.tsx +5 -5
- package/src/components/Switch/Switch.test.tsx +2 -1
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.test.tsx +8 -2
- package/src/components/UserMenu/UserMenu.test.tsx +7 -9
- package/src/components/UserMenu/UserMenu.tsx +10 -8
- package/src/components/index.ts +2 -1
- package/src/eslint-rules/pace-core-compliance.cjs +0 -2
- package/src/eslint-rules/pace-core-compliance.js +0 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
- package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
- package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
- package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
- package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +297 -34
- package/src/hooks/useFileReference.ts +56 -11
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/useInactivityTracker.ts +16 -7
- package/src/hooks/usePermissionCache.test.ts +85 -8
- package/src/hooks/useQueryCache.ts +21 -0
- package/src/hooks/useSecureDataAccess.test.ts +80 -35
- package/src/hooks/useSecureDataAccess.ts +80 -37
- package/src/index.ts +2 -1
- package/src/providers/services/EventServiceProvider.tsx +37 -17
- package/src/providers/services/InactivityServiceProvider.tsx +4 -4
- package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
- package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
- package/src/rbac/api.ts +240 -36
- package/src/rbac/cache-invalidation.ts +21 -7
- package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
- package/src/rbac/components/NavigationGuard.tsx +23 -63
- package/src/rbac/components/NavigationProvider.test.tsx +52 -23
- package/src/rbac/components/NavigationProvider.tsx +13 -11
- package/src/rbac/components/PagePermissionGuard.tsx +77 -203
- package/src/rbac/components/PagePermissionProvider.tsx +13 -11
- package/src/rbac/components/PermissionEnforcer.tsx +24 -62
- package/src/rbac/components/RoleBasedRouter.tsx +14 -12
- package/src/rbac/components/SecureDataProvider.tsx +13 -11
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
- package/src/rbac/engine.ts +4 -2
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
- package/src/rbac/hooks/index.ts +3 -0
- package/src/rbac/hooks/useCan.test.ts +101 -53
- package/src/rbac/hooks/usePermissions.ts +108 -41
- package/src/rbac/hooks/useRBAC.test.ts +11 -3
- package/src/rbac/hooks/useRBAC.ts +83 -40
- package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
- package/src/rbac/hooks/useResolvedScope.ts +128 -70
- package/src/rbac/hooks/useSecureSupabase.ts +36 -19
- package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
- package/src/rbac/request-deduplication.ts +1 -1
- package/src/rbac/secureClient.ts +72 -12
- package/src/rbac/security.ts +29 -23
- package/src/rbac/types.ts +10 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
- package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +48 -2
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +37 -2
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/types/file-reference.ts +13 -10
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +61 -33
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- package/src/utils/storage/helpers.test.ts +1 -1
- package/src/utils/storage/helpers.ts +38 -19
- package/src/utils/storage/types.ts +15 -8
- package/src/utils/validation/__tests__/csrf.test.ts +105 -0
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
- package/src/vite-env.d.ts +2 -2
- package/dist/chunk-3GOZZZYH.js.map +0 -1
- package/dist/chunk-DDM4CCYT.js.map +0 -1
- package/dist/chunk-E7UAOUMY.js +0 -75
- package/dist/chunk-E7UAOUMY.js.map +0 -1
- package/dist/chunk-EFCLXK7F.js.map +0 -1
- package/dist/chunk-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-IPCH26AG.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UNOTYLQF.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YHCN776L.js.map +0 -1
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
- package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useEvents
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import {
|
|
5
|
-
useUnifiedAuth
|
|
6
|
-
} from "./chunk-VGZZXKBR.js";
|
|
3
|
+
} from "./chunk-Y4BUBBHD.js";
|
|
7
4
|
import {
|
|
8
5
|
assertAppId
|
|
9
6
|
} from "./chunk-QXHPKYJV.js";
|
|
@@ -11,9 +8,8 @@ import {
|
|
|
11
8
|
createAddressFromPlaceResult,
|
|
12
9
|
fetchPlaceAutocomplete,
|
|
13
10
|
fetchPlaceDetails,
|
|
14
|
-
getAddressByPlaceId
|
|
15
|
-
|
|
16
|
-
} from "./chunk-YHCN776L.js";
|
|
11
|
+
getAddressByPlaceId
|
|
12
|
+
} from "./chunk-DZWK57KZ.js";
|
|
17
13
|
import {
|
|
18
14
|
setOrganisationContext
|
|
19
15
|
} from "./chunk-VBXEHIUJ.js";
|
|
@@ -64,6 +60,20 @@ function runCacheCleanup() {
|
|
|
64
60
|
if (typeof window !== "undefined" && !cleanupTimer) {
|
|
65
61
|
cleanupTimer = setInterval(runCacheCleanup, CLEANUP_INTERVAL_MS);
|
|
66
62
|
log.debug("Query cache cleanup initialized.");
|
|
63
|
+
window.addEventListener("beforeunload", () => {
|
|
64
|
+
if (cleanupTimer) {
|
|
65
|
+
clearInterval(cleanupTimer);
|
|
66
|
+
cleanupTimer = null;
|
|
67
|
+
log.debug("Query cache cleanup timer cleared on page unload.");
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function cleanupQueryCache() {
|
|
72
|
+
if (cleanupTimer) {
|
|
73
|
+
clearInterval(cleanupTimer);
|
|
74
|
+
cleanupTimer = null;
|
|
75
|
+
log.debug("Query cache cleanup timer cleared.");
|
|
76
|
+
}
|
|
67
77
|
}
|
|
68
78
|
function useQueryCache(supabase) {
|
|
69
79
|
const getCachedQuery = useCallback(async (table, filterKey, filterValue, fetchFn, options = {}) => {
|
|
@@ -371,337 +381,6 @@ function useAddressAutocomplete(apiKey, inputValue, options = {}) {
|
|
|
371
381
|
};
|
|
372
382
|
}
|
|
373
383
|
|
|
374
|
-
// src/hooks/useEventTheme.ts
|
|
375
|
-
import { useEffect as useEffect4 } from "react";
|
|
376
|
-
import { useLocation } from "react-router-dom";
|
|
377
|
-
var log2 = createLogger("useEventTheme");
|
|
378
|
-
function useEventTheme(event) {
|
|
379
|
-
const location = useLocation();
|
|
380
|
-
let selectedEvent;
|
|
381
|
-
try {
|
|
382
|
-
if (event === void 0) {
|
|
383
|
-
const eventsContext = useEvents();
|
|
384
|
-
selectedEvent = eventsContext.selectedEvent;
|
|
385
|
-
} else {
|
|
386
|
-
selectedEvent = event;
|
|
387
|
-
}
|
|
388
|
-
} catch (error) {
|
|
389
|
-
if (event !== void 0) {
|
|
390
|
-
selectedEvent = event;
|
|
391
|
-
} else {
|
|
392
|
-
selectedEvent = null;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
useEffect4(() => {
|
|
396
|
-
const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login");
|
|
397
|
-
if (isOnLoginRoute) {
|
|
398
|
-
clearPalette();
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
if (!selectedEvent) {
|
|
402
|
-
clearPalette();
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
const eventColours = selectedEvent.event_colours;
|
|
406
|
-
const normalized = parseAndNormalizeEventColours(eventColours);
|
|
407
|
-
if (!normalized) {
|
|
408
|
-
clearPalette();
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
try {
|
|
412
|
-
applyPalette(normalized);
|
|
413
|
-
} catch (error) {
|
|
414
|
-
log2.error("Failed to apply event palette:", error);
|
|
415
|
-
}
|
|
416
|
-
return () => {
|
|
417
|
-
};
|
|
418
|
-
}, [selectedEvent, location.pathname]);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// src/hooks/usePreventTabReload.ts
|
|
422
|
-
import { useEffect as useEffect5, useRef as useRef3 } from "react";
|
|
423
|
-
function usePreventTabReload(options = {}) {
|
|
424
|
-
const { enabled = true, gracePeriodMs = 2e3 } = options;
|
|
425
|
-
const isRestoringFromCacheRef = useRef3(false);
|
|
426
|
-
const gracePeriodTimeoutRef = useRef3(null);
|
|
427
|
-
useEffect5(() => {
|
|
428
|
-
if (!enabled || typeof window === "undefined") return;
|
|
429
|
-
const handlePageShow = (event) => {
|
|
430
|
-
if (event.persisted) {
|
|
431
|
-
isRestoringFromCacheRef.current = true;
|
|
432
|
-
if (gracePeriodTimeoutRef.current) {
|
|
433
|
-
clearTimeout(gracePeriodTimeoutRef.current);
|
|
434
|
-
}
|
|
435
|
-
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
436
|
-
isRestoringFromCacheRef.current = false;
|
|
437
|
-
}, gracePeriodMs);
|
|
438
|
-
}
|
|
439
|
-
};
|
|
440
|
-
const handleVisibilityChange = () => {
|
|
441
|
-
if (!document.hidden) {
|
|
442
|
-
isRestoringFromCacheRef.current = true;
|
|
443
|
-
if (gracePeriodTimeoutRef.current) {
|
|
444
|
-
clearTimeout(gracePeriodTimeoutRef.current);
|
|
445
|
-
}
|
|
446
|
-
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
447
|
-
isRestoringFromCacheRef.current = false;
|
|
448
|
-
}, gracePeriodMs);
|
|
449
|
-
}
|
|
450
|
-
};
|
|
451
|
-
window.addEventListener("pageshow", handlePageShow);
|
|
452
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
453
|
-
return () => {
|
|
454
|
-
window.removeEventListener("pageshow", handlePageShow);
|
|
455
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
456
|
-
if (gracePeriodTimeoutRef.current) {
|
|
457
|
-
clearTimeout(gracePeriodTimeoutRef.current);
|
|
458
|
-
}
|
|
459
|
-
};
|
|
460
|
-
}, [enabled, gracePeriodMs]);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// src/components/ErrorBoundary/ErrorBoundary.tsx
|
|
464
|
-
import { Component } from "react";
|
|
465
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
466
|
-
var ErrorBoundary = class extends Component {
|
|
467
|
-
constructor(props) {
|
|
468
|
-
super(props);
|
|
469
|
-
this.retryTimeoutId = null;
|
|
470
|
-
this.reportError = (errorId, componentName) => {
|
|
471
|
-
if (import.meta.env.MODE === "production") {
|
|
472
|
-
logger.warn("ErrorBoundary", "Error reporting would be triggered in production:", { errorId, componentName });
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
this.handleRetry = () => {
|
|
476
|
-
const { maxRetries = 3 } = this.props;
|
|
477
|
-
const { retryCount } = this.state;
|
|
478
|
-
if (retryCount < maxRetries) {
|
|
479
|
-
logger.debug("ErrorBoundary", `Retrying component render (attempt ${retryCount + 1}/${maxRetries})`);
|
|
480
|
-
this.setState((prevState) => ({
|
|
481
|
-
hasError: false,
|
|
482
|
-
error: void 0,
|
|
483
|
-
errorInfo: void 0,
|
|
484
|
-
errorId: void 0,
|
|
485
|
-
retryCount: prevState.retryCount + 1
|
|
486
|
-
}));
|
|
487
|
-
}
|
|
488
|
-
};
|
|
489
|
-
this.state = {
|
|
490
|
-
hasError: false,
|
|
491
|
-
retryCount: 0
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
static getDerivedStateFromError(error) {
|
|
495
|
-
const errorId = `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
496
|
-
return {
|
|
497
|
-
hasError: true,
|
|
498
|
-
error,
|
|
499
|
-
errorId
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
componentDidCatch(error, errorInfo) {
|
|
503
|
-
const { componentName = "Unknown Component", onError, enableReporting = true } = this.props;
|
|
504
|
-
const errorId = this.state.errorId;
|
|
505
|
-
this.setState({ errorInfo });
|
|
506
|
-
logger.error("ErrorBoundary", `[${componentName}] Caught error ${errorId}:`, error, errorInfo);
|
|
507
|
-
performanceBudgetMonitor.measure("ERROR_BOUNDARY_TRIGGER", 1, {
|
|
508
|
-
componentName,
|
|
509
|
-
errorId,
|
|
510
|
-
errorMessage: error.message,
|
|
511
|
-
stack: error.stack?.substring(0, 200)
|
|
512
|
-
// Truncated stack trace
|
|
513
|
-
});
|
|
514
|
-
if (enableReporting) {
|
|
515
|
-
this.reportError(errorId, componentName);
|
|
516
|
-
}
|
|
517
|
-
if (onError) {
|
|
518
|
-
onError(error, errorInfo, errorId);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
componentWillUnmount() {
|
|
522
|
-
if (this.retryTimeoutId) {
|
|
523
|
-
clearTimeout(this.retryTimeoutId);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
render() {
|
|
527
|
-
if (this.state.hasError) {
|
|
528
|
-
const {
|
|
529
|
-
componentName = "Component",
|
|
530
|
-
fallback,
|
|
531
|
-
enableRetry = true,
|
|
532
|
-
maxRetries = 3
|
|
533
|
-
} = this.props;
|
|
534
|
-
const { retryCount, errorId } = this.state;
|
|
535
|
-
if (fallback) {
|
|
536
|
-
return fallback;
|
|
537
|
-
}
|
|
538
|
-
return /* @__PURE__ */ jsx(
|
|
539
|
-
"div",
|
|
540
|
-
{
|
|
541
|
-
role: "alert",
|
|
542
|
-
className: "p-6 bg-destructive/10 border border-destructive/20 rounded-lg",
|
|
543
|
-
"data-error-boundary": errorId,
|
|
544
|
-
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
545
|
-
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 text-destructive", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) }) }),
|
|
546
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
547
|
-
/* @__PURE__ */ jsxs("h3", { className: "text-destructive", children: [
|
|
548
|
-
"Error in ",
|
|
549
|
-
componentName
|
|
550
|
-
] }),
|
|
551
|
-
/* @__PURE__ */ jsx("p", { className: "text-destructive/80", children: this.state.error?.message || "An unexpected error occurred." }),
|
|
552
|
-
enableRetry && retryCount < maxRetries && /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mb-4", children: [
|
|
553
|
-
/* @__PURE__ */ jsxs(
|
|
554
|
-
"button",
|
|
555
|
-
{
|
|
556
|
-
onClick: this.handleRetry,
|
|
557
|
-
className: "px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors text-sm font-medium",
|
|
558
|
-
children: [
|
|
559
|
-
"Retry (",
|
|
560
|
-
retryCount + 1,
|
|
561
|
-
"/",
|
|
562
|
-
maxRetries,
|
|
563
|
-
")"
|
|
564
|
-
]
|
|
565
|
-
}
|
|
566
|
-
),
|
|
567
|
-
/* @__PURE__ */ jsx(
|
|
568
|
-
"button",
|
|
569
|
-
{
|
|
570
|
-
onClick: () => window.location.reload(),
|
|
571
|
-
className: "px-4 py-2 bg-sec-600 text-main-50 rounded-md hover:bg-sec-700 transition-colors text-sm font-medium",
|
|
572
|
-
children: "Reload Page"
|
|
573
|
-
}
|
|
574
|
-
)
|
|
575
|
-
] }),
|
|
576
|
-
retryCount >= maxRetries && /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 bg-acc-50 border border-acc-200 rounded-md", children: [
|
|
577
|
-
/* @__PURE__ */ jsx("p", { className: "text-acc-800", children: "Maximum retry attempts reached. Please reload the page or contact support." }),
|
|
578
|
-
/* @__PURE__ */ jsx(
|
|
579
|
-
"button",
|
|
580
|
-
{
|
|
581
|
-
onClick: () => window.location.reload(),
|
|
582
|
-
className: "mt-2 px-3 py-1 bg-acc-600 text-main-50 rounded text-sm hover:bg-acc-700",
|
|
583
|
-
children: "Reload Page"
|
|
584
|
-
}
|
|
585
|
-
)
|
|
586
|
-
] }),
|
|
587
|
-
import.meta.env.MODE === "development" && this.state.error && /* @__PURE__ */ jsxs("details", { className: "text-sm text-destructive/70", children: [
|
|
588
|
-
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer font-medium mb-2", children: "Error Details (Development)" }),
|
|
589
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-destructive/5 p-3 rounded border", children: [
|
|
590
|
-
/* @__PURE__ */ jsxs("p", { className: "font-mono", children: [
|
|
591
|
-
"Error ID: ",
|
|
592
|
-
errorId
|
|
593
|
-
] }),
|
|
594
|
-
/* @__PURE__ */ jsxs("pre", { className: "whitespace-pre-wrap text-xs overflow-auto max-h-32", children: [
|
|
595
|
-
this.state.error.toString(),
|
|
596
|
-
this.state.errorInfo?.componentStack
|
|
597
|
-
] })
|
|
598
|
-
] })
|
|
599
|
-
] })
|
|
600
|
-
] })
|
|
601
|
-
] })
|
|
602
|
-
}
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
return this.props.children;
|
|
606
|
-
}
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
// src/components/PublicLayout/PublicPageProvider.tsx
|
|
610
|
-
import { createContext, useContext, useMemo as useMemo2 } from "react";
|
|
611
|
-
import { createClient } from "@supabase/supabase-js";
|
|
612
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
613
|
-
var PublicPageContext = createContext(void 0);
|
|
614
|
-
function PublicPageProvider({ children, appName }) {
|
|
615
|
-
const getEnvVar = (key) => {
|
|
616
|
-
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
617
|
-
const env = import.meta.env;
|
|
618
|
-
return env[key];
|
|
619
|
-
}
|
|
620
|
-
if (typeof process !== "undefined" && process.env) {
|
|
621
|
-
return process.env[key];
|
|
622
|
-
}
|
|
623
|
-
return void 0;
|
|
624
|
-
};
|
|
625
|
-
const supabaseUrl = getEnvVar("VITE_SUPABASE_URL") || getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || null;
|
|
626
|
-
const supabaseKey = getEnvVar("VITE_SUPABASE_ANON_KEY") || getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || null;
|
|
627
|
-
const supabase = useMemo2(() => {
|
|
628
|
-
if (!supabaseUrl || !supabaseKey) {
|
|
629
|
-
logger.warn("PublicPageProvider", "Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are set in your environment. Use publishable key if anon key is disabled.");
|
|
630
|
-
return null;
|
|
631
|
-
}
|
|
632
|
-
const client = createClient(supabaseUrl, supabaseKey);
|
|
633
|
-
logger.info("PublicPageProvider", "Supabase client created successfully for public pages");
|
|
634
|
-
return client;
|
|
635
|
-
}, [supabaseUrl, supabaseKey]);
|
|
636
|
-
const contextValue = {
|
|
637
|
-
isPublicPage: true,
|
|
638
|
-
supabase,
|
|
639
|
-
appName: appName || null,
|
|
640
|
-
environment: {
|
|
641
|
-
supabaseUrl,
|
|
642
|
-
supabaseKey
|
|
643
|
-
}
|
|
644
|
-
};
|
|
645
|
-
return /* @__PURE__ */ jsx2(PublicPageContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(ErrorBoundary, { componentName: "PublicPageProvider", children }) });
|
|
646
|
-
}
|
|
647
|
-
function usePublicPageContext() {
|
|
648
|
-
const context = useContext(PublicPageContext);
|
|
649
|
-
if (!context) {
|
|
650
|
-
throw new Error("usePublicPageContext must be used within a PublicPageProvider");
|
|
651
|
-
}
|
|
652
|
-
return context;
|
|
653
|
-
}
|
|
654
|
-
function useIsPublicPage() {
|
|
655
|
-
const context = useContext(PublicPageContext);
|
|
656
|
-
return context !== void 0;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// src/hooks/useAppConfig.ts
|
|
660
|
-
import { useMemo as useMemo3, useContext as useContext2 } from "react";
|
|
661
|
-
function useAppConfig() {
|
|
662
|
-
const isPublicPage = useIsPublicPage();
|
|
663
|
-
const publicPageContext = useContext2(PublicPageContext);
|
|
664
|
-
const contextAppName = publicPageContext?.appName || null;
|
|
665
|
-
if (isPublicPage) {
|
|
666
|
-
const getAppName = () => {
|
|
667
|
-
if (contextAppName) {
|
|
668
|
-
return contextAppName;
|
|
669
|
-
}
|
|
670
|
-
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
671
|
-
return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
|
|
672
|
-
}
|
|
673
|
-
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
674
|
-
return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
|
|
675
|
-
}
|
|
676
|
-
return "PACE";
|
|
677
|
-
};
|
|
678
|
-
return useMemo3(() => ({
|
|
679
|
-
supportsDirectAccess: false,
|
|
680
|
-
// Public pages don't support direct access
|
|
681
|
-
requiresEvent: true,
|
|
682
|
-
// Public pages always require an event
|
|
683
|
-
isLoading: false,
|
|
684
|
-
appName: getAppName()
|
|
685
|
-
}), [contextAppName]);
|
|
686
|
-
}
|
|
687
|
-
try {
|
|
688
|
-
const { appConfig, appName } = useUnifiedAuth();
|
|
689
|
-
return useMemo3(() => ({
|
|
690
|
-
supportsDirectAccess: !(appConfig?.requires_event ?? true),
|
|
691
|
-
requiresEvent: appConfig?.requires_event ?? true,
|
|
692
|
-
isLoading: appConfig === null,
|
|
693
|
-
appName
|
|
694
|
-
}), [appConfig?.requires_event, appName]);
|
|
695
|
-
} catch (error) {
|
|
696
|
-
return useMemo3(() => ({
|
|
697
|
-
supportsDirectAccess: false,
|
|
698
|
-
requiresEvent: true,
|
|
699
|
-
isLoading: false,
|
|
700
|
-
appName: "PACE"
|
|
701
|
-
}), []);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
384
|
// src/utils/storage/config.ts
|
|
706
385
|
var FILE_SIZE_LIMITS = {
|
|
707
386
|
// Images
|
|
@@ -772,23 +451,24 @@ function validateFileSize(file) {
|
|
|
772
451
|
}
|
|
773
452
|
|
|
774
453
|
// src/utils/storage/helpers.ts
|
|
775
|
-
var
|
|
454
|
+
var log2 = createLogger("StorageHelpers");
|
|
776
455
|
function generateFilePath(options, fileName) {
|
|
777
|
-
const { orgId, isPublic = false, customPath } = options;
|
|
778
|
-
if (!orgId) {
|
|
779
|
-
throw new Error("orgId is required for file path generation");
|
|
456
|
+
const { orgId, userId, isPublic = false, customPath } = options;
|
|
457
|
+
if (!orgId && !userId) {
|
|
458
|
+
throw new Error("Either orgId or userId is required for file path generation");
|
|
780
459
|
}
|
|
460
|
+
const basePath = orgId ? orgId : `users/${userId}`;
|
|
781
461
|
if (isPublic) {
|
|
782
462
|
if (customPath) {
|
|
783
|
-
return `${
|
|
463
|
+
return `${basePath}/${customPath}/${fileName}`;
|
|
784
464
|
}
|
|
785
|
-
return `${
|
|
465
|
+
return `${basePath}/public/${fileName}`;
|
|
786
466
|
}
|
|
787
467
|
if (customPath) {
|
|
788
|
-
return `${
|
|
468
|
+
return `${basePath}/${customPath}/${fileName}`;
|
|
789
469
|
}
|
|
790
470
|
const pathFolder = customPath || "files";
|
|
791
|
-
return `${
|
|
471
|
+
return `${basePath}/${pathFolder}/${fileName}`;
|
|
792
472
|
}
|
|
793
473
|
function generateUniqueFileName(originalName) {
|
|
794
474
|
const timestamp = Date.now();
|
|
@@ -805,7 +485,8 @@ async function extractFileMetadata(file, options, uploadedBy) {
|
|
|
805
485
|
const metadata = {
|
|
806
486
|
mimeType: file.type,
|
|
807
487
|
size: file.size,
|
|
808
|
-
orgId: options.orgId,
|
|
488
|
+
...options.orgId && { orgId: options.orgId },
|
|
489
|
+
...options.userId && { userId: options.userId },
|
|
809
490
|
appName: options.appName || "pace-core",
|
|
810
491
|
uploadedBy,
|
|
811
492
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -867,12 +548,12 @@ async function ensureFolderExists(supabase, folderPath, bucketName) {
|
|
|
867
548
|
contentType: "text/plain"
|
|
868
549
|
});
|
|
869
550
|
if (uploadError) {
|
|
870
|
-
|
|
551
|
+
log2.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
|
|
871
552
|
return true;
|
|
872
553
|
}
|
|
873
554
|
return true;
|
|
874
555
|
} catch (error) {
|
|
875
|
-
|
|
556
|
+
log2.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
876
557
|
return true;
|
|
877
558
|
}
|
|
878
559
|
}
|
|
@@ -979,7 +660,7 @@ async function getSignedUrl(supabase, path, options) {
|
|
|
979
660
|
const bucketName = getBucketName(false);
|
|
980
661
|
const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(path, options.expiresIn || 3600);
|
|
981
662
|
if (error) {
|
|
982
|
-
|
|
663
|
+
log2.error("Failed to create signed URL:", error);
|
|
983
664
|
return null;
|
|
984
665
|
}
|
|
985
666
|
return {
|
|
@@ -987,7 +668,7 @@ async function getSignedUrl(supabase, path, options) {
|
|
|
987
668
|
expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1e3).toISOString()
|
|
988
669
|
};
|
|
989
670
|
} catch (error) {
|
|
990
|
-
|
|
671
|
+
log2.error("Failed to create signed URL:", error);
|
|
991
672
|
return null;
|
|
992
673
|
}
|
|
993
674
|
}
|
|
@@ -1049,7 +730,7 @@ async function generateFileUrlsBatch(supabase, fileReferences, options) {
|
|
|
1049
730
|
});
|
|
1050
731
|
}
|
|
1051
732
|
} catch (err) {
|
|
1052
|
-
|
|
733
|
+
log2.error(`Failed to generate public URL for file ${file.id}:`, err);
|
|
1053
734
|
}
|
|
1054
735
|
}
|
|
1055
736
|
if (privateFiles.length > 0) {
|
|
@@ -1070,7 +751,7 @@ async function generateFileUrlsBatch(supabase, fileReferences, options) {
|
|
|
1070
751
|
}
|
|
1071
752
|
return { id: file.id, url };
|
|
1072
753
|
} catch (err) {
|
|
1073
|
-
|
|
754
|
+
log2.error(`Failed to generate signed URL for file ${file.id}:`, err);
|
|
1074
755
|
return { id: file.id, url: null };
|
|
1075
756
|
}
|
|
1076
757
|
});
|
|
@@ -1107,7 +788,11 @@ async function deleteFile(supabase, path, isPublic = false) {
|
|
|
1107
788
|
async function listFiles(supabase, options) {
|
|
1108
789
|
try {
|
|
1109
790
|
const bucketName = getBucketName(options.isPublic || false);
|
|
1110
|
-
|
|
791
|
+
if (!options.orgId && !options.userId) {
|
|
792
|
+
throw new Error("Either orgId or userId is required for listing files");
|
|
793
|
+
}
|
|
794
|
+
const basePath = options.orgId ? options.orgId : `users/${options.userId}`;
|
|
795
|
+
const pathPrefix = `${basePath}/`;
|
|
1111
796
|
const searchPath = options.pathPrefix ? `${pathPrefix}${options.pathPrefix}` : pathPrefix;
|
|
1112
797
|
const { data, error } = await supabase.storage.from(bucketName).list(searchPath, {
|
|
1113
798
|
limit: options.limit || 100,
|
|
@@ -1115,7 +800,7 @@ async function listFiles(supabase, options) {
|
|
|
1115
800
|
sortBy: { column: "created_at", order: "desc" }
|
|
1116
801
|
});
|
|
1117
802
|
if (error) {
|
|
1118
|
-
|
|
803
|
+
log2.error("Failed to list files:", error);
|
|
1119
804
|
return { files: [], totalCount: 0, hasMore: false };
|
|
1120
805
|
}
|
|
1121
806
|
const files = (data || []).map((item) => ({
|
|
@@ -1127,7 +812,8 @@ async function listFiles(supabase, options) {
|
|
|
1127
812
|
metadata: {
|
|
1128
813
|
mimeType: item.metadata?.mimetype || "application/octet-stream",
|
|
1129
814
|
size: item.metadata?.size || 0,
|
|
1130
|
-
orgId: options.orgId,
|
|
815
|
+
...options.orgId && { orgId: options.orgId },
|
|
816
|
+
...options.userId && { userId: options.userId },
|
|
1131
817
|
appName: options.appName,
|
|
1132
818
|
uploadedBy: "unknown",
|
|
1133
819
|
uploadedAt: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1140,7 +826,7 @@ async function listFiles(supabase, options) {
|
|
|
1140
826
|
hasMore: files.length >= (options.limit || 100)
|
|
1141
827
|
};
|
|
1142
828
|
} catch (error) {
|
|
1143
|
-
|
|
829
|
+
log2.error("Failed to list files:", error);
|
|
1144
830
|
return { files: [], totalCount: 0, hasMore: false };
|
|
1145
831
|
}
|
|
1146
832
|
}
|
|
@@ -1149,7 +835,7 @@ async function downloadFile(supabase, path, isPublic = false) {
|
|
|
1149
835
|
const bucketName = getBucketName(isPublic);
|
|
1150
836
|
const { data, error } = await supabase.storage.from(bucketName).download(path);
|
|
1151
837
|
if (error) {
|
|
1152
|
-
|
|
838
|
+
log2.error("Failed to download file:", error);
|
|
1153
839
|
return null;
|
|
1154
840
|
}
|
|
1155
841
|
if (!data) {
|
|
@@ -1169,14 +855,21 @@ async function downloadFile(supabase, path, isPublic = false) {
|
|
|
1169
855
|
}
|
|
1170
856
|
};
|
|
1171
857
|
} catch (error) {
|
|
1172
|
-
|
|
858
|
+
log2.error("Failed to download file:", error);
|
|
1173
859
|
return null;
|
|
1174
860
|
}
|
|
1175
861
|
}
|
|
1176
862
|
async function archiveFile(supabase, path, options) {
|
|
1177
863
|
try {
|
|
1178
864
|
const bucketName = getBucketName(options.isPublic || false);
|
|
1179
|
-
|
|
865
|
+
let archivedPath;
|
|
866
|
+
if (options.orgId) {
|
|
867
|
+
archivedPath = path.replace(`${options.orgId}/`, `archived/${options.orgId}/`);
|
|
868
|
+
} else if (options.userId) {
|
|
869
|
+
archivedPath = path.replace(`users/${options.userId}/`, `archived/users/${options.userId}/`);
|
|
870
|
+
} else {
|
|
871
|
+
throw new Error("Either orgId or userId is required for archiving files");
|
|
872
|
+
}
|
|
1180
873
|
const { error: copyError } = await supabase.storage.from(bucketName).copy(path, archivedPath);
|
|
1181
874
|
if (copyError) {
|
|
1182
875
|
return {
|
|
@@ -1197,50 +890,378 @@ async function archiveFile(supabase, path, options) {
|
|
|
1197
890
|
}
|
|
1198
891
|
}
|
|
1199
892
|
|
|
1200
|
-
// src/hooks/
|
|
1201
|
-
import { useState as useState3, useEffect as
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
893
|
+
// src/hooks/public/usePublicFileDisplay.ts
|
|
894
|
+
import { useState as useState3, useEffect as useEffect4, useCallback as useCallback3 } from "react";
|
|
895
|
+
var publicFileCache = /* @__PURE__ */ new Map();
|
|
896
|
+
function usePublicFileDisplay(table_name, record_id, organisation_id, category, options) {
|
|
897
|
+
const {
|
|
898
|
+
cacheTtl = 30 * 60 * 1e3,
|
|
899
|
+
// 30 minutes
|
|
900
|
+
enableCache = true,
|
|
901
|
+
supabase
|
|
902
|
+
} = options;
|
|
903
|
+
const [fileUrl, setFileUrl] = useState3(null);
|
|
904
|
+
const [fileReference, setFileReference] = useState3(null);
|
|
905
|
+
const [fileReferences, setFileReferences] = useState3([]);
|
|
906
|
+
const [fileUrls, setFileUrls] = useState3(/* @__PURE__ */ new Map());
|
|
907
|
+
const [fileCount, setFileCount] = useState3(0);
|
|
908
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
909
|
+
const [error, setError] = useState3(null);
|
|
910
|
+
const fetchFiles = useCallback3(async () => {
|
|
911
|
+
if (!table_name || !record_id || !supabase) {
|
|
912
|
+
setFileUrl(null);
|
|
913
|
+
setFileReference(null);
|
|
914
|
+
setFileReferences([]);
|
|
915
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
916
|
+
setFileCount(0);
|
|
917
|
+
setIsLoading(false);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (organisation_id) {
|
|
921
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
922
|
+
if (!uuidRegex.test(organisation_id)) {
|
|
923
|
+
logger.warn("usePublicFileDisplay", "Invalid organisationId format (not a valid UUID)", { organisation_id });
|
|
1230
924
|
}
|
|
1231
|
-
|
|
1232
|
-
|
|
925
|
+
}
|
|
926
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
927
|
+
if (enableCache) {
|
|
928
|
+
const cached = publicFileCache.get(cacheKey);
|
|
929
|
+
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
930
|
+
const cachedData = cached.data;
|
|
931
|
+
setFileUrl(cachedData.fileUrl || null);
|
|
932
|
+
setFileReference(cachedData.fileReference || null);
|
|
933
|
+
setFileReferences(cachedData.fileReferences || []);
|
|
934
|
+
setFileUrls(cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
935
|
+
setFileCount(cachedData.fileCount || 0);
|
|
936
|
+
setIsLoading(false);
|
|
937
|
+
setError(null);
|
|
938
|
+
return;
|
|
1233
939
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
940
|
+
}
|
|
941
|
+
try {
|
|
942
|
+
setIsLoading(true);
|
|
943
|
+
setError(null);
|
|
944
|
+
let files = [];
|
|
945
|
+
if (organisation_id === void 0) {
|
|
946
|
+
logger.debug("usePublicFileDisplay", "organisation_id is undefined, searching both user-scoped and organisation-scoped files:", {
|
|
947
|
+
table_name,
|
|
948
|
+
record_id,
|
|
949
|
+
category
|
|
950
|
+
});
|
|
951
|
+
let userScopedFiles = [];
|
|
952
|
+
let orgScopedFiles = [];
|
|
953
|
+
if (category) {
|
|
954
|
+
const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_by_category_list", {
|
|
955
|
+
p_table_name: table_name,
|
|
956
|
+
p_record_id: record_id,
|
|
957
|
+
p_category: category,
|
|
958
|
+
p_organisation_id: null
|
|
959
|
+
});
|
|
960
|
+
if (!userRpcError && userData) {
|
|
961
|
+
userScopedFiles = userData.filter((item) => item.is_public === true && item.id && item.file_path && item.file_metadata).map((item) => ({
|
|
962
|
+
id: item.id,
|
|
963
|
+
table_name,
|
|
964
|
+
record_id,
|
|
965
|
+
file_path: item.file_path,
|
|
966
|
+
file_metadata: item.file_metadata || {},
|
|
967
|
+
organisation_id: null,
|
|
968
|
+
app_id: item.file_metadata?.app_id || null,
|
|
969
|
+
is_public: true,
|
|
970
|
+
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
971
|
+
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
972
|
+
}));
|
|
973
|
+
}
|
|
974
|
+
} else {
|
|
975
|
+
const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_list", {
|
|
976
|
+
p_table_name: table_name,
|
|
977
|
+
p_record_id: record_id,
|
|
978
|
+
p_organisation_id: null
|
|
979
|
+
});
|
|
980
|
+
if (!userRpcError && userData) {
|
|
981
|
+
const ids = userData.map((item) => item.id);
|
|
982
|
+
if (ids.length > 0) {
|
|
983
|
+
const { data: fullData } = await supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").in("id", ids).eq("is_public", true);
|
|
984
|
+
userScopedFiles = fullData || [];
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
989
|
+
allFiles.sort((a, b) => {
|
|
990
|
+
const aTime = new Date(a.created_at).getTime();
|
|
991
|
+
const bTime = new Date(b.created_at).getTime();
|
|
992
|
+
return bTime - aTime;
|
|
993
|
+
});
|
|
994
|
+
files = allFiles;
|
|
995
|
+
logger.debug("usePublicFileDisplay", "Found files with undefined organisation_id:", {
|
|
996
|
+
userScopedCount: userScopedFiles.length,
|
|
997
|
+
orgScopedCount: orgScopedFiles.length,
|
|
998
|
+
totalCount: files.length
|
|
999
|
+
});
|
|
1000
|
+
} else {
|
|
1001
|
+
if (category) {
|
|
1002
|
+
const rpcParams = {
|
|
1003
|
+
p_table_name: table_name,
|
|
1004
|
+
p_record_id: record_id,
|
|
1005
|
+
p_category: category,
|
|
1006
|
+
p_organisation_id: organisation_id ?? null
|
|
1007
|
+
};
|
|
1008
|
+
const { data, error: rpcError } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
|
|
1009
|
+
if (rpcError) {
|
|
1010
|
+
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1011
|
+
function: "data_file_reference_by_category_list",
|
|
1012
|
+
table_name,
|
|
1013
|
+
record_id,
|
|
1014
|
+
category,
|
|
1015
|
+
organisation_id,
|
|
1016
|
+
error: rpcError.message,
|
|
1017
|
+
errorCode: rpcError.code,
|
|
1018
|
+
errorDetails: rpcError.details,
|
|
1019
|
+
errorHint: rpcError.hint
|
|
1020
|
+
});
|
|
1021
|
+
throw new Error(rpcError.message || "Failed to fetch file reference");
|
|
1022
|
+
}
|
|
1023
|
+
if (!data || data.length === 0) {
|
|
1024
|
+
files = [];
|
|
1025
|
+
} else {
|
|
1026
|
+
files = data.filter((item) => {
|
|
1027
|
+
return item.is_public === true && item.id && item.file_path && item.file_metadata;
|
|
1028
|
+
}).map((item) => {
|
|
1029
|
+
return {
|
|
1030
|
+
id: item.id,
|
|
1031
|
+
table_name,
|
|
1032
|
+
record_id,
|
|
1033
|
+
file_path: item.file_path,
|
|
1034
|
+
file_metadata: item.file_metadata || {},
|
|
1035
|
+
organisation_id: organisation_id ?? null,
|
|
1036
|
+
app_id: item.file_metadata?.app_id || null,
|
|
1037
|
+
is_public: true,
|
|
1038
|
+
// RPC already filtered for public files
|
|
1039
|
+
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1040
|
+
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1041
|
+
};
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
} else {
|
|
1045
|
+
const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
|
|
1046
|
+
p_table_name: table_name,
|
|
1047
|
+
p_record_id: record_id,
|
|
1048
|
+
p_organisation_id: organisation_id ?? null
|
|
1049
|
+
});
|
|
1050
|
+
if (rpcError) {
|
|
1051
|
+
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1052
|
+
function: "data_file_reference_list",
|
|
1053
|
+
table_name,
|
|
1054
|
+
record_id,
|
|
1055
|
+
organisation_id,
|
|
1056
|
+
error: rpcError.message,
|
|
1057
|
+
errorCode: rpcError.code,
|
|
1058
|
+
errorDetails: rpcError.details,
|
|
1059
|
+
errorHint: rpcError.hint
|
|
1060
|
+
});
|
|
1061
|
+
throw new Error(rpcError.message || "Failed to fetch file references");
|
|
1062
|
+
}
|
|
1063
|
+
if (!fileIds || fileIds.length === 0) {
|
|
1064
|
+
files = [];
|
|
1065
|
+
} else {
|
|
1066
|
+
const ids = fileIds.map((item) => item.id);
|
|
1067
|
+
const { data: fullData, error: fetchError } = await supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").in("id", ids).eq("is_public", true);
|
|
1068
|
+
if (fetchError) {
|
|
1069
|
+
throw new Error(fetchError.message || "Failed to fetch file references");
|
|
1070
|
+
}
|
|
1071
|
+
files = fullData || [];
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1236
1074
|
}
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1075
|
+
const publicFiles = files.filter((f) => f.is_public === true);
|
|
1076
|
+
if (publicFiles.length === 0) {
|
|
1077
|
+
setFileUrl(null);
|
|
1078
|
+
setFileReference(null);
|
|
1079
|
+
setFileReferences([]);
|
|
1080
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
1081
|
+
setFileCount(0);
|
|
1082
|
+
if (enableCache) {
|
|
1083
|
+
publicFileCache.set(cacheKey, {
|
|
1084
|
+
data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: /* @__PURE__ */ new Map(), fileCount: 0 },
|
|
1085
|
+
timestamp: Date.now(),
|
|
1086
|
+
ttl: cacheTtl
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
const fileRefs = publicFiles.map((f) => ({
|
|
1092
|
+
id: f.id,
|
|
1093
|
+
table_name: f.table_name,
|
|
1094
|
+
record_id: f.record_id,
|
|
1095
|
+
file_path: f.file_path,
|
|
1096
|
+
file_metadata: f.file_metadata || {},
|
|
1097
|
+
organisation_id: f.organisation_id,
|
|
1098
|
+
app_id: f.app_id,
|
|
1099
|
+
is_public: f.is_public ?? true,
|
|
1100
|
+
created_at: f.created_at,
|
|
1101
|
+
updated_at: f.updated_at
|
|
1102
|
+
}));
|
|
1103
|
+
setFileReferences(fileRefs);
|
|
1104
|
+
setFileCount(fileRefs.length);
|
|
1105
|
+
if (category && fileRefs.length > 0) {
|
|
1106
|
+
const firstFile = fileRefs[0];
|
|
1107
|
+
setFileReference(firstFile);
|
|
1108
|
+
const url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
1109
|
+
setFileUrl(url);
|
|
1110
|
+
} else {
|
|
1111
|
+
const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
|
|
1112
|
+
appName: "pace-core",
|
|
1113
|
+
orgId: organisation_id,
|
|
1114
|
+
expiresIn: 3600
|
|
1115
|
+
});
|
|
1116
|
+
setFileUrls(urlMap);
|
|
1117
|
+
setFileReference(null);
|
|
1118
|
+
setFileUrl(null);
|
|
1119
|
+
}
|
|
1120
|
+
if (enableCache) {
|
|
1121
|
+
publicFileCache.set(cacheKey, {
|
|
1122
|
+
data: {
|
|
1123
|
+
fileUrl: category ? fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null : null,
|
|
1124
|
+
fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,
|
|
1125
|
+
fileReferences: fileRefs,
|
|
1126
|
+
fileUrls: category ? /* @__PURE__ */ new Map() : (() => {
|
|
1127
|
+
const urlMap = /* @__PURE__ */ new Map();
|
|
1128
|
+
for (const fileRef of fileRefs) {
|
|
1129
|
+
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
1130
|
+
if (url) {
|
|
1131
|
+
urlMap.set(fileRef.id, url);
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return urlMap;
|
|
1135
|
+
})(),
|
|
1136
|
+
fileCount: fileRefs.length
|
|
1137
|
+
},
|
|
1138
|
+
timestamp: Date.now(),
|
|
1139
|
+
ttl: cacheTtl
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
logger.error("usePublicFileDisplay", "Error fetching files", {
|
|
1144
|
+
table_name,
|
|
1145
|
+
record_id,
|
|
1146
|
+
organisation_id,
|
|
1147
|
+
category,
|
|
1148
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
1149
|
+
errorDetails: err instanceof Error ? err.stack : String(err)
|
|
1150
|
+
});
|
|
1151
|
+
const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
1152
|
+
setError(error2);
|
|
1153
|
+
setFileUrl(null);
|
|
1154
|
+
setFileReference(null);
|
|
1155
|
+
setFileReferences([]);
|
|
1156
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
1157
|
+
setFileCount(0);
|
|
1158
|
+
} finally {
|
|
1159
|
+
setIsLoading(false);
|
|
1160
|
+
}
|
|
1161
|
+
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1162
|
+
useEffect4(() => {
|
|
1163
|
+
if (table_name && record_id) {
|
|
1164
|
+
fetchFiles();
|
|
1165
|
+
} else {
|
|
1166
|
+
setFileUrl(null);
|
|
1167
|
+
setFileReference(null);
|
|
1168
|
+
setFileReferences([]);
|
|
1169
|
+
setFileUrls(/* @__PURE__ */ new Map());
|
|
1170
|
+
setFileCount(0);
|
|
1171
|
+
setIsLoading(false);
|
|
1172
|
+
setError(null);
|
|
1173
|
+
}
|
|
1174
|
+
}, [fetchFiles, table_name, record_id, organisation_id]);
|
|
1175
|
+
const refetch = useCallback3(async () => {
|
|
1176
|
+
if (!table_name || !record_id) return;
|
|
1177
|
+
if (enableCache) {
|
|
1178
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1179
|
+
publicFileCache.delete(cacheKey);
|
|
1180
|
+
}
|
|
1181
|
+
await fetchFiles();
|
|
1182
|
+
}, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);
|
|
1183
|
+
return {
|
|
1184
|
+
fileUrl,
|
|
1185
|
+
fileReference,
|
|
1186
|
+
fileReferences,
|
|
1187
|
+
fileUrls,
|
|
1188
|
+
fileCount,
|
|
1189
|
+
isLoading,
|
|
1190
|
+
error,
|
|
1191
|
+
refetch
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
function clearPublicFileDisplayCache() {
|
|
1195
|
+
for (const [key] of publicFileCache) {
|
|
1196
|
+
if (key.startsWith("public_file_")) {
|
|
1197
|
+
publicFileCache.delete(key);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
function getPublicFileDisplayCacheStats() {
|
|
1202
|
+
const keys = Array.from(publicFileCache.keys()).filter((key) => key.startsWith("public_file_"));
|
|
1203
|
+
return {
|
|
1204
|
+
size: keys.length,
|
|
1205
|
+
keys
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// src/hooks/useFileDisplay.ts
|
|
1210
|
+
import { useState as useState4, useEffect as useEffect5, useCallback as useCallback4 } from "react";
|
|
1211
|
+
|
|
1212
|
+
// src/utils/file-reference/index.ts
|
|
1213
|
+
var log3 = createLogger("FileReferenceService");
|
|
1214
|
+
var FileReferenceServiceImpl = class {
|
|
1215
|
+
constructor(supabase) {
|
|
1216
|
+
this.supabase = supabase;
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Creates a file reference by uploading a file to storage and linking it in the database.
|
|
1220
|
+
*
|
|
1221
|
+
* Storage Flow:
|
|
1222
|
+
* 1. Upload file to storage bucket first (files or public-files based on is_public flag)
|
|
1223
|
+
* - Path format: {orgId}/{folder}/{timestamp-uuid-filename}
|
|
1224
|
+
* - Bucket selection: 'files' (private) or 'public-files' (public)
|
|
1225
|
+
* 2. Extract file metadata (dimensions, hash, etc.)
|
|
1226
|
+
* 3. Set organisation context for RLS policies
|
|
1227
|
+
* 4. Create database reference via RPC function
|
|
1228
|
+
* 5. If DB insert fails, rollback by deleting uploaded file
|
|
1229
|
+
*
|
|
1230
|
+
* This ensures atomicity: either both storage and DB succeed, or both are cleaned up.
|
|
1231
|
+
*/
|
|
1232
|
+
async createFileReference(options, file) {
|
|
1233
|
+
try {
|
|
1234
|
+
const isUserScoped = !options.organisation_id && options.userId;
|
|
1235
|
+
if (!isUserScoped && !options.organisation_id) {
|
|
1236
|
+
throw new Error("organisation_id is required for file upload, or userId must be provided for user-scoped files");
|
|
1237
|
+
}
|
|
1238
|
+
if (!options.table_name) {
|
|
1239
|
+
throw new Error("table_name is required for file upload");
|
|
1240
|
+
}
|
|
1241
|
+
if (!options.record_id) {
|
|
1242
|
+
throw new Error("record_id is required for file upload");
|
|
1243
|
+
}
|
|
1244
|
+
if (!options.folder) {
|
|
1245
|
+
throw new Error("folder is required for file upload. The folder prop determines the storage path.");
|
|
1246
|
+
}
|
|
1247
|
+
let authenticatedUserId = void 0;
|
|
1248
|
+
if (isUserScoped) {
|
|
1249
|
+
const { data: { user: authUser }, error: authError } = await this.supabase.auth.getUser();
|
|
1250
|
+
if (authError || !authUser) {
|
|
1251
|
+
throw new Error("User must be authenticated to upload user-scoped files");
|
|
1252
|
+
}
|
|
1253
|
+
authenticatedUserId = authUser.id;
|
|
1254
|
+
log3.debug("Using authenticated user ID for user-scoped file upload", { userId: authenticatedUserId });
|
|
1255
|
+
}
|
|
1256
|
+
const uploadResult = await uploadFile(this.supabase, file, {
|
|
1257
|
+
appName: "file-reference",
|
|
1258
|
+
orgId: options.organisation_id || void 0,
|
|
1259
|
+
userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
|
|
1260
|
+
// Use auth.uid() for user-scoped files
|
|
1261
|
+
isPublic: options.is_public || false,
|
|
1262
|
+
customPath: options.folder
|
|
1263
|
+
// Use folder prop as the custom path segment
|
|
1264
|
+
});
|
|
1244
1265
|
if (!uploadResult.success) {
|
|
1245
1266
|
throw new Error(`Failed to upload file: ${uploadResult.error}`);
|
|
1246
1267
|
}
|
|
@@ -1250,16 +1271,20 @@ var FileReferenceServiceImpl = class {
|
|
|
1250
1271
|
const filePath = uploadResult.path;
|
|
1251
1272
|
const metadata = await extractFileMetadata(file, {
|
|
1252
1273
|
appName: "file-reference",
|
|
1253
|
-
orgId: options.organisation_id,
|
|
1274
|
+
orgId: options.organisation_id || void 0,
|
|
1275
|
+
userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
|
|
1276
|
+
// Use auth.uid() for user-scoped files
|
|
1254
1277
|
isPublic: options.is_public || false
|
|
1255
1278
|
}, "system");
|
|
1256
|
-
|
|
1279
|
+
if (!isUserScoped && options.organisation_id) {
|
|
1280
|
+
await setOrganisationContext(this.supabase, options.organisation_id);
|
|
1281
|
+
}
|
|
1257
1282
|
const { data, error } = await this.supabase.rpc("data_file_reference_create", {
|
|
1258
1283
|
p_table_name: options.table_name,
|
|
1259
1284
|
p_record_id: options.record_id,
|
|
1260
1285
|
p_file_path: filePath,
|
|
1261
1286
|
// Storage path from step 1
|
|
1262
|
-
p_organisation_id: options.organisation_id,
|
|
1287
|
+
p_organisation_id: options.organisation_id ?? null,
|
|
1263
1288
|
p_app_id: options.app_id,
|
|
1264
1289
|
p_page_context: options.pageContext,
|
|
1265
1290
|
p_event_id: options.event_id || null,
|
|
@@ -1272,7 +1297,9 @@ var FileReferenceServiceImpl = class {
|
|
|
1272
1297
|
...metadata,
|
|
1273
1298
|
...options.custom_metadata
|
|
1274
1299
|
},
|
|
1275
|
-
p_is_public: options.is_public || false
|
|
1300
|
+
p_is_public: options.is_public || false,
|
|
1301
|
+
p_user_id: authenticatedUserId || options.userId || null
|
|
1302
|
+
// Pass authenticated user ID for user-scoped files
|
|
1276
1303
|
});
|
|
1277
1304
|
if (error) {
|
|
1278
1305
|
await deleteFile(this.supabase, filePath, options.is_public || false);
|
|
@@ -1290,18 +1317,24 @@ var FileReferenceServiceImpl = class {
|
|
|
1290
1317
|
invalidateFileDisplayCache(
|
|
1291
1318
|
options.table_name,
|
|
1292
1319
|
options.record_id,
|
|
1293
|
-
options.organisation_id,
|
|
1320
|
+
options.organisation_id || null,
|
|
1294
1321
|
options.category
|
|
1295
1322
|
);
|
|
1296
1323
|
return fileRef;
|
|
1297
1324
|
} catch (error) {
|
|
1298
|
-
|
|
1325
|
+
log3.error("Error creating file reference:", error);
|
|
1299
1326
|
throw error;
|
|
1300
1327
|
}
|
|
1301
1328
|
}
|
|
1302
1329
|
async getFileReference(table_name, record_id, organisation_id) {
|
|
1303
1330
|
try {
|
|
1304
|
-
|
|
1331
|
+
let query = this.supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("table_name", table_name).eq("record_id", record_id);
|
|
1332
|
+
if (organisation_id === null || organisation_id === void 0) {
|
|
1333
|
+
query = query.is("organisation_id", null);
|
|
1334
|
+
} else {
|
|
1335
|
+
query = query.eq("organisation_id", organisation_id);
|
|
1336
|
+
}
|
|
1337
|
+
const { data, error } = await query.single();
|
|
1305
1338
|
if (error) {
|
|
1306
1339
|
if (error.code === "PGRST116") {
|
|
1307
1340
|
return null;
|
|
@@ -1310,7 +1343,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1310
1343
|
}
|
|
1311
1344
|
return data;
|
|
1312
1345
|
} catch (error) {
|
|
1313
|
-
|
|
1346
|
+
log3.error("Error getting file reference:", error);
|
|
1314
1347
|
throw error;
|
|
1315
1348
|
}
|
|
1316
1349
|
}
|
|
@@ -1334,7 +1367,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1334
1367
|
return await this.getSignedUrl(table_name, record_id, organisation_id);
|
|
1335
1368
|
}
|
|
1336
1369
|
} catch (error) {
|
|
1337
|
-
|
|
1370
|
+
log3.error("Error getting file URL:", error);
|
|
1338
1371
|
throw error;
|
|
1339
1372
|
}
|
|
1340
1373
|
}
|
|
@@ -1343,7 +1376,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1343
1376
|
const { data: filePath, error } = await this.supabase.rpc("data_file_reference_signed_url_get", {
|
|
1344
1377
|
p_table_name: table_name,
|
|
1345
1378
|
p_record_id: record_id,
|
|
1346
|
-
p_organisation_id: organisation_id,
|
|
1379
|
+
p_organisation_id: organisation_id ?? null,
|
|
1347
1380
|
p_expires_in: expires_in
|
|
1348
1381
|
});
|
|
1349
1382
|
if (error) {
|
|
@@ -1355,11 +1388,12 @@ var FileReferenceServiceImpl = class {
|
|
|
1355
1388
|
const signedUrlResult = await getSignedUrl(this.supabase, filePath, {
|
|
1356
1389
|
appName: "file-reference",
|
|
1357
1390
|
orgId: organisation_id,
|
|
1391
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1358
1392
|
expiresIn: expires_in
|
|
1359
1393
|
});
|
|
1360
1394
|
return signedUrlResult?.url || null;
|
|
1361
1395
|
} catch (error) {
|
|
1362
|
-
|
|
1396
|
+
log3.error("Error getting signed URL:", error);
|
|
1363
1397
|
throw error;
|
|
1364
1398
|
}
|
|
1365
1399
|
}
|
|
@@ -1371,7 +1405,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1371
1405
|
}
|
|
1372
1406
|
return data;
|
|
1373
1407
|
} catch (error) {
|
|
1374
|
-
|
|
1408
|
+
log3.error("Error updating file reference:", error);
|
|
1375
1409
|
throw error;
|
|
1376
1410
|
}
|
|
1377
1411
|
}
|
|
@@ -1381,7 +1415,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1381
1415
|
const { error } = await this.supabase.rpc("data_file_reference_delete", {
|
|
1382
1416
|
p_table_name: table_name,
|
|
1383
1417
|
p_record_id: record_id,
|
|
1384
|
-
p_organisation_id: organisation_id,
|
|
1418
|
+
p_organisation_id: organisation_id ?? null,
|
|
1385
1419
|
p_delete_file: delete_file
|
|
1386
1420
|
});
|
|
1387
1421
|
if (error) {
|
|
@@ -1392,7 +1426,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1392
1426
|
}
|
|
1393
1427
|
return true;
|
|
1394
1428
|
} catch (error) {
|
|
1395
|
-
|
|
1429
|
+
log3.error("Error deleting file reference:", error);
|
|
1396
1430
|
throw error;
|
|
1397
1431
|
}
|
|
1398
1432
|
}
|
|
@@ -1401,7 +1435,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1401
1435
|
const { data, error } = await this.supabase.rpc("data_file_reference_list", {
|
|
1402
1436
|
p_table_name: table_name,
|
|
1403
1437
|
p_record_id: record_id,
|
|
1404
|
-
p_organisation_id: organisation_id
|
|
1438
|
+
p_organisation_id: organisation_id ?? null
|
|
1405
1439
|
});
|
|
1406
1440
|
if (error) {
|
|
1407
1441
|
throw new Error(`Failed to list file references: ${error.message}`);
|
|
@@ -1422,7 +1456,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1422
1456
|
fileType,
|
|
1423
1457
|
...item.file_metadata || {}
|
|
1424
1458
|
},
|
|
1425
|
-
organisation_id,
|
|
1459
|
+
organisation_id: organisation_id ?? null,
|
|
1426
1460
|
app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
|
|
1427
1461
|
// May not be in metadata, use empty string
|
|
1428
1462
|
is_public: item.is_public ?? false,
|
|
@@ -1434,7 +1468,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1434
1468
|
});
|
|
1435
1469
|
return fileReferences;
|
|
1436
1470
|
} catch (error) {
|
|
1437
|
-
|
|
1471
|
+
log3.error("Error listing file references:", error);
|
|
1438
1472
|
throw error;
|
|
1439
1473
|
}
|
|
1440
1474
|
}
|
|
@@ -1443,14 +1477,14 @@ var FileReferenceServiceImpl = class {
|
|
|
1443
1477
|
const { data, error } = await this.supabase.rpc("data_file_reference_count_get", {
|
|
1444
1478
|
p_table_name: table_name,
|
|
1445
1479
|
p_record_id: record_id,
|
|
1446
|
-
p_organisation_id: organisation_id
|
|
1480
|
+
p_organisation_id: organisation_id ?? null
|
|
1447
1481
|
});
|
|
1448
1482
|
if (error) {
|
|
1449
1483
|
throw new Error(`Failed to get file count: ${error.message}`);
|
|
1450
1484
|
}
|
|
1451
1485
|
return data || 0;
|
|
1452
1486
|
} catch (error) {
|
|
1453
|
-
|
|
1487
|
+
log3.error("Error getting file count:", error);
|
|
1454
1488
|
throw error;
|
|
1455
1489
|
}
|
|
1456
1490
|
}
|
|
@@ -1458,7 +1492,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1458
1492
|
try {
|
|
1459
1493
|
const { data, error } = await this.supabase.rpc("data_file_reference_get", {
|
|
1460
1494
|
p_file_reference_id: id,
|
|
1461
|
-
p_organisation_id: organisation_id
|
|
1495
|
+
p_organisation_id: organisation_id ?? null
|
|
1462
1496
|
});
|
|
1463
1497
|
if (error) {
|
|
1464
1498
|
throw new Error(`Failed to get file reference by ID: ${error.message}`);
|
|
@@ -1468,7 +1502,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1468
1502
|
}
|
|
1469
1503
|
return data[0];
|
|
1470
1504
|
} catch (error) {
|
|
1471
|
-
|
|
1505
|
+
log3.error("Error getting file reference by ID:", error);
|
|
1472
1506
|
throw error;
|
|
1473
1507
|
}
|
|
1474
1508
|
}
|
|
@@ -1478,10 +1512,10 @@ var FileReferenceServiceImpl = class {
|
|
|
1478
1512
|
p_table_name: table_name,
|
|
1479
1513
|
p_record_id: record_id,
|
|
1480
1514
|
p_category: category,
|
|
1481
|
-
p_organisation_id: organisation_id
|
|
1515
|
+
p_organisation_id: organisation_id ?? null
|
|
1482
1516
|
});
|
|
1483
1517
|
if (error) {
|
|
1484
|
-
|
|
1518
|
+
log3.error("RPC ERROR getting files by category:", {
|
|
1485
1519
|
error,
|
|
1486
1520
|
errorCode: error.code,
|
|
1487
1521
|
errorMessage: error.message,
|
|
@@ -1501,7 +1535,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1501
1535
|
const fileCategory = item.file_metadata?.category;
|
|
1502
1536
|
const matches = fileCategory === category;
|
|
1503
1537
|
if (!matches) {
|
|
1504
|
-
|
|
1538
|
+
log3.warn("File category mismatch in RPC response:", {
|
|
1505
1539
|
fileId: item.id,
|
|
1506
1540
|
expectedCategory: category,
|
|
1507
1541
|
actualCategory: fileCategory
|
|
@@ -1522,7 +1556,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1522
1556
|
category: item.file_metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
|
|
1523
1557
|
...item.file_metadata || {}
|
|
1524
1558
|
},
|
|
1525
|
-
organisation_id,
|
|
1559
|
+
organisation_id: organisation_id ?? null,
|
|
1526
1560
|
app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
|
|
1527
1561
|
// May not be in metadata, use empty string
|
|
1528
1562
|
is_public: item.is_public ?? false,
|
|
@@ -1534,7 +1568,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1534
1568
|
});
|
|
1535
1569
|
return fileReferences;
|
|
1536
1570
|
} catch (error) {
|
|
1537
|
-
|
|
1571
|
+
log3.error("Error getting files by category:", error);
|
|
1538
1572
|
throw error;
|
|
1539
1573
|
}
|
|
1540
1574
|
}
|
|
@@ -1576,7 +1610,8 @@ async function uploadFileWithReference(supabase, options, file) {
|
|
|
1576
1610
|
const fileReference = await service.createFileReference(options, file);
|
|
1577
1611
|
const fileUrl = options.is_public ? getPublicUrl(supabase, fileReference.file_path, true) : await getSignedUrl(supabase, fileReference.file_path, {
|
|
1578
1612
|
appName: "file-reference",
|
|
1579
|
-
orgId: options.organisation_id,
|
|
1613
|
+
orgId: options.organisation_id || void 0,
|
|
1614
|
+
userId: options.userId || void 0,
|
|
1580
1615
|
expiresIn: 3600
|
|
1581
1616
|
});
|
|
1582
1617
|
const urlString = typeof fileUrl === "string" ? fileUrl : fileUrl?.url || "";
|
|
@@ -1613,15 +1648,15 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1613
1648
|
enableCache = true,
|
|
1614
1649
|
supabase
|
|
1615
1650
|
} = options;
|
|
1616
|
-
const [fileUrl, setFileUrl] =
|
|
1617
|
-
const [fileReference, setFileReference] =
|
|
1618
|
-
const [fileReferences, setFileReferences] =
|
|
1619
|
-
const [fileUrls, setFileUrls] =
|
|
1620
|
-
const [fileCount, setFileCount] =
|
|
1621
|
-
const [isLoading, setIsLoading] =
|
|
1622
|
-
const [error, setError] =
|
|
1623
|
-
const fetchFiles =
|
|
1624
|
-
if (!table_name || !record_id || !
|
|
1651
|
+
const [fileUrl, setFileUrl] = useState4(null);
|
|
1652
|
+
const [fileReference, setFileReference] = useState4(null);
|
|
1653
|
+
const [fileReferences, setFileReferences] = useState4([]);
|
|
1654
|
+
const [fileUrls, setFileUrls] = useState4(/* @__PURE__ */ new Map());
|
|
1655
|
+
const [fileCount, setFileCount] = useState4(0);
|
|
1656
|
+
const [isLoading, setIsLoading] = useState4(false);
|
|
1657
|
+
const [error, setError] = useState4(null);
|
|
1658
|
+
const fetchFiles = useCallback4(async () => {
|
|
1659
|
+
if (!table_name || !record_id || !supabase) {
|
|
1625
1660
|
setFileUrl(null);
|
|
1626
1661
|
setFileReference(null);
|
|
1627
1662
|
setFileReferences([]);
|
|
@@ -1630,11 +1665,13 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1630
1665
|
setIsLoading(false);
|
|
1631
1666
|
return;
|
|
1632
1667
|
}
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1668
|
+
if (organisation_id) {
|
|
1669
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1670
|
+
if (!uuidRegex.test(organisation_id)) {
|
|
1671
|
+
logger.warn("useFileDisplay", "Invalid organisationId format (not a valid UUID):", organisation_id);
|
|
1672
|
+
}
|
|
1636
1673
|
}
|
|
1637
|
-
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
1674
|
+
const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1638
1675
|
if (enableCache) {
|
|
1639
1676
|
const cached = authenticatedFileCache.get(cacheKey);
|
|
1640
1677
|
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
@@ -1644,6 +1681,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1644
1681
|
const signedUrlResult = await getSignedUrl(supabase, cachedData.fileReference.file_path, {
|
|
1645
1682
|
appName: "pace-core",
|
|
1646
1683
|
orgId: organisation_id,
|
|
1684
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1647
1685
|
expiresIn: 3600
|
|
1648
1686
|
});
|
|
1649
1687
|
const regeneratedUrl = signedUrlResult?.url || null;
|
|
@@ -1674,25 +1712,205 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1674
1712
|
setError(null);
|
|
1675
1713
|
const service = createFileReferenceService(supabase);
|
|
1676
1714
|
let files = [];
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1715
|
+
const shouldSearchBothScopes = organisation_id === void 0;
|
|
1716
|
+
if (shouldSearchBothScopes) {
|
|
1717
|
+
let userScopedFiles = [];
|
|
1718
|
+
let orgScopedFiles = [];
|
|
1719
|
+
try {
|
|
1720
|
+
if (category) {
|
|
1721
|
+
userScopedFiles = await service.getFilesByCategory(
|
|
1722
|
+
table_name,
|
|
1723
|
+
record_id,
|
|
1724
|
+
category,
|
|
1725
|
+
void 0
|
|
1726
|
+
// Explicitly pass undefined for user-scoped files (service converts to null for RPC)
|
|
1727
|
+
);
|
|
1728
|
+
} else {
|
|
1729
|
+
userScopedFiles = await service.listFileReferences(
|
|
1730
|
+
table_name,
|
|
1731
|
+
record_id,
|
|
1732
|
+
void 0
|
|
1733
|
+
// Explicitly pass undefined for user-scoped files (service converts to null for RPC)
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
} catch (err) {
|
|
1737
|
+
logger.warn("useFileDisplay", "Error querying user-scoped files:", err);
|
|
1738
|
+
userScopedFiles = [];
|
|
1739
|
+
}
|
|
1740
|
+
try {
|
|
1741
|
+
const { data: { user }, error: userError } = await supabase.auth.getUser();
|
|
1742
|
+
if (userError) {
|
|
1743
|
+
logger.warn("useFileDisplay", "Error getting user:", userError);
|
|
1744
|
+
}
|
|
1745
|
+
if (user) {
|
|
1746
|
+
const { data: memberships, error: membershipError } = await supabase.from("organisation_memberships").select("organisation_id").eq("user_id", user.id).or("status.is.null,status.eq.active");
|
|
1747
|
+
if (membershipError) {
|
|
1748
|
+
logger.warn("useFileDisplay", "Error querying organisation memberships:", membershipError);
|
|
1749
|
+
}
|
|
1750
|
+
if (memberships && memberships.length > 0) {
|
|
1751
|
+
const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
|
|
1752
|
+
const orgQueries = orgIds.map(async (orgId) => {
|
|
1753
|
+
try {
|
|
1754
|
+
if (category) {
|
|
1755
|
+
return await service.getFilesByCategory(
|
|
1756
|
+
table_name,
|
|
1757
|
+
record_id,
|
|
1758
|
+
category,
|
|
1759
|
+
orgId
|
|
1760
|
+
);
|
|
1761
|
+
} else {
|
|
1762
|
+
return await service.listFileReferences(
|
|
1763
|
+
table_name,
|
|
1764
|
+
record_id,
|
|
1765
|
+
orgId
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
} catch (err) {
|
|
1769
|
+
return [];
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
const orgResults = await Promise.all(orgQueries);
|
|
1773
|
+
orgScopedFiles = orgResults.flat();
|
|
1774
|
+
} else {
|
|
1775
|
+
try {
|
|
1776
|
+
let fallbackQuery = supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("table_name", table_name).eq("record_id", record_id).order("created_at", { ascending: false });
|
|
1777
|
+
if (category) {
|
|
1778
|
+
fallbackQuery = fallbackQuery.eq("file_metadata->>category", category);
|
|
1779
|
+
}
|
|
1780
|
+
const { data: fallbackFiles } = await fallbackQuery;
|
|
1781
|
+
if (fallbackFiles && fallbackFiles.length > 0) {
|
|
1782
|
+
orgScopedFiles = fallbackFiles.map((f) => ({
|
|
1783
|
+
id: f.id,
|
|
1784
|
+
table_name: f.table_name,
|
|
1785
|
+
record_id: f.record_id,
|
|
1786
|
+
file_path: f.file_path,
|
|
1787
|
+
file_metadata: f.file_metadata || {},
|
|
1788
|
+
organisation_id: f.organisation_id,
|
|
1789
|
+
app_id: f.app_id,
|
|
1790
|
+
is_public: f.is_public ?? false,
|
|
1791
|
+
created_at: f.created_at,
|
|
1792
|
+
updated_at: f.updated_at
|
|
1793
|
+
}));
|
|
1794
|
+
}
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
} catch (err) {
|
|
1800
|
+
logger.warn("useFileDisplay", "Error querying organisation-scoped files:", err);
|
|
1801
|
+
orgScopedFiles = [];
|
|
1802
|
+
}
|
|
1803
|
+
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
1804
|
+
allFiles.sort((a, b) => {
|
|
1805
|
+
const aTime = new Date(a.created_at).getTime();
|
|
1806
|
+
const bTime = new Date(b.created_at).getTime();
|
|
1807
|
+
return bTime - aTime;
|
|
1683
1808
|
});
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
)
|
|
1809
|
+
if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
|
|
1810
|
+
files = allFiles.filter((f) => f.organisation_id !== null);
|
|
1811
|
+
} else {
|
|
1812
|
+
files = allFiles;
|
|
1813
|
+
}
|
|
1814
|
+
if (files.length === 0) {
|
|
1815
|
+
try {
|
|
1816
|
+
let directQuery = supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("table_name", table_name).eq("record_id", record_id).order("created_at", { ascending: false });
|
|
1817
|
+
if (category) {
|
|
1818
|
+
directQuery = directQuery.eq("file_metadata->>category", category);
|
|
1819
|
+
}
|
|
1820
|
+
const { data: directFiles } = await directQuery;
|
|
1821
|
+
if (directFiles && directFiles.length > 0) {
|
|
1822
|
+
files = directFiles.map((f) => ({
|
|
1823
|
+
id: f.id,
|
|
1824
|
+
table_name: f.table_name,
|
|
1825
|
+
record_id: f.record_id,
|
|
1826
|
+
file_path: f.file_path,
|
|
1827
|
+
file_metadata: f.file_metadata || {},
|
|
1828
|
+
organisation_id: f.organisation_id,
|
|
1829
|
+
app_id: f.app_id,
|
|
1830
|
+
is_public: f.is_public ?? false,
|
|
1831
|
+
created_at: f.created_at,
|
|
1832
|
+
updated_at: f.updated_at
|
|
1833
|
+
}));
|
|
1834
|
+
}
|
|
1835
|
+
} catch (err) {
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1690
1838
|
} else {
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1839
|
+
if (category) {
|
|
1840
|
+
logger.debug("useFileDisplay", "Using RPC function for category filtering:", {
|
|
1841
|
+
table_name,
|
|
1842
|
+
record_id,
|
|
1843
|
+
category,
|
|
1844
|
+
organisation_id
|
|
1845
|
+
});
|
|
1846
|
+
files = await service.getFilesByCategory(
|
|
1847
|
+
table_name,
|
|
1848
|
+
record_id,
|
|
1849
|
+
category,
|
|
1850
|
+
organisation_id
|
|
1851
|
+
);
|
|
1852
|
+
} else {
|
|
1853
|
+
files = await service.listFileReferences(
|
|
1854
|
+
table_name,
|
|
1855
|
+
record_id,
|
|
1856
|
+
organisation_id
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
if (files.length === 0 && (!organisation_id || organisation_id === "")) {
|
|
1860
|
+
let userScopedFiles = [];
|
|
1861
|
+
let orgScopedFiles = [];
|
|
1862
|
+
try {
|
|
1863
|
+
if (category) {
|
|
1864
|
+
userScopedFiles = await service.getFilesByCategory(
|
|
1865
|
+
table_name,
|
|
1866
|
+
record_id,
|
|
1867
|
+
category,
|
|
1868
|
+
void 0
|
|
1869
|
+
);
|
|
1870
|
+
} else {
|
|
1871
|
+
userScopedFiles = await service.listFileReferences(
|
|
1872
|
+
table_name,
|
|
1873
|
+
record_id,
|
|
1874
|
+
void 0
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
} catch (err) {
|
|
1878
|
+
}
|
|
1879
|
+
try {
|
|
1880
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
1881
|
+
if (user) {
|
|
1882
|
+
const { data: memberships } = await supabase.from("organisation_memberships").select("organisation_id").eq("user_id", user.id).or("status.is.null,status.eq.active");
|
|
1883
|
+
if (memberships && memberships.length > 0) {
|
|
1884
|
+
const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
|
|
1885
|
+
const orgQueries = orgIds.map(async (orgId) => {
|
|
1886
|
+
try {
|
|
1887
|
+
if (category) {
|
|
1888
|
+
return await service.getFilesByCategory(table_name, record_id, category, orgId);
|
|
1889
|
+
} else {
|
|
1890
|
+
return await service.listFileReferences(table_name, record_id, orgId);
|
|
1891
|
+
}
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
return [];
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
const orgResults = await Promise.all(orgQueries);
|
|
1897
|
+
orgScopedFiles = orgResults.flat();
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
} catch (err) {
|
|
1901
|
+
}
|
|
1902
|
+
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
1903
|
+
allFiles.sort((a, b) => {
|
|
1904
|
+
const aTime = new Date(a.created_at).getTime();
|
|
1905
|
+
const bTime = new Date(b.created_at).getTime();
|
|
1906
|
+
return bTime - aTime;
|
|
1907
|
+
});
|
|
1908
|
+
if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
|
|
1909
|
+
files = allFiles.filter((f) => f.organisation_id !== null);
|
|
1910
|
+
} else {
|
|
1911
|
+
files = allFiles;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1696
1914
|
}
|
|
1697
1915
|
if (files.length === 0) {
|
|
1698
1916
|
setFileUrl(null);
|
|
@@ -1730,6 +1948,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1730
1948
|
const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {
|
|
1731
1949
|
appName: "pace-core",
|
|
1732
1950
|
orgId: organisation_id,
|
|
1951
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1733
1952
|
expiresIn: 3600
|
|
1734
1953
|
});
|
|
1735
1954
|
url = signedUrlResult?.url || null;
|
|
@@ -1741,6 +1960,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1741
1960
|
const urlMap = await generateFileUrlsBatch(supabase, files, {
|
|
1742
1961
|
appName: "pace-core",
|
|
1743
1962
|
orgId: organisation_id,
|
|
1963
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1744
1964
|
expiresIn: 3600
|
|
1745
1965
|
});
|
|
1746
1966
|
setFileUrls(urlMap);
|
|
@@ -1793,8 +2013,8 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1793
2013
|
setIsLoading(false);
|
|
1794
2014
|
}
|
|
1795
2015
|
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1796
|
-
|
|
1797
|
-
if (table_name && record_id &&
|
|
2016
|
+
useEffect5(() => {
|
|
2017
|
+
if (table_name && record_id && supabase) {
|
|
1798
2018
|
fetchFiles();
|
|
1799
2019
|
} else {
|
|
1800
2020
|
setFileUrl(null);
|
|
@@ -1805,11 +2025,11 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1805
2025
|
setIsLoading(false);
|
|
1806
2026
|
setError(null);
|
|
1807
2027
|
}
|
|
1808
|
-
}, [
|
|
1809
|
-
const refetch =
|
|
1810
|
-
if (!table_name || !record_id || !
|
|
2028
|
+
}, [table_name, record_id, organisation_id, supabase]);
|
|
2029
|
+
const refetch = useCallback4(async () => {
|
|
2030
|
+
if (!table_name || !record_id || !supabase) return;
|
|
1811
2031
|
if (enableCache) {
|
|
1812
|
-
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
2032
|
+
const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1813
2033
|
authenticatedFileCache.delete(cacheKey);
|
|
1814
2034
|
}
|
|
1815
2035
|
await fetchFiles();
|
|
@@ -1840,284 +2060,109 @@ function getFileDisplayCacheStats() {
|
|
|
1840
2060
|
};
|
|
1841
2061
|
}
|
|
1842
2062
|
function invalidateFileDisplayCache(table_name, record_id, organisation_id, category) {
|
|
1843
|
-
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
2063
|
+
const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1844
2064
|
authenticatedFileCache.delete(cacheKey);
|
|
1845
2065
|
if (category) {
|
|
1846
|
-
const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id}_all`;
|
|
2066
|
+
const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_all`;
|
|
1847
2067
|
authenticatedFileCache.delete(allCategoryKey);
|
|
1848
2068
|
}
|
|
1849
2069
|
}
|
|
1850
2070
|
|
|
1851
|
-
// src/hooks/
|
|
1852
|
-
import {
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2071
|
+
// src/hooks/useEventTheme.ts
|
|
2072
|
+
import { useEffect as useEffect6 } from "react";
|
|
2073
|
+
import { useLocation } from "react-router-dom";
|
|
2074
|
+
var log4 = createLogger("useEventTheme");
|
|
2075
|
+
function useEventTheme(event) {
|
|
2076
|
+
const location = useLocation();
|
|
2077
|
+
let selectedEvent;
|
|
2078
|
+
try {
|
|
2079
|
+
if (event === void 0) {
|
|
2080
|
+
const eventsContext = useEvents();
|
|
2081
|
+
selectedEvent = eventsContext.selectedEvent;
|
|
2082
|
+
} else {
|
|
2083
|
+
selectedEvent = event;
|
|
2084
|
+
}
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
if (event !== void 0) {
|
|
2087
|
+
selectedEvent = event;
|
|
2088
|
+
} else {
|
|
2089
|
+
selectedEvent = null;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
useEffect6(() => {
|
|
2093
|
+
const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login");
|
|
2094
|
+
if (isOnLoginRoute) {
|
|
2095
|
+
clearPalette();
|
|
1876
2096
|
return;
|
|
1877
2097
|
}
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
2098
|
+
if (!selectedEvent) {
|
|
2099
|
+
clearPalette();
|
|
2100
|
+
return;
|
|
1881
2101
|
}
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
setFileUrl(cachedData.fileUrl || null);
|
|
1888
|
-
setFileReference(cachedData.fileReference || null);
|
|
1889
|
-
setFileReferences(cachedData.fileReferences || []);
|
|
1890
|
-
setFileUrls(cachedData.fileUrls || /* @__PURE__ */ new Map());
|
|
1891
|
-
setFileCount(cachedData.fileCount || 0);
|
|
1892
|
-
setIsLoading(false);
|
|
1893
|
-
setError(null);
|
|
1894
|
-
return;
|
|
1895
|
-
}
|
|
2102
|
+
const eventColours = selectedEvent.event_colours;
|
|
2103
|
+
const normalized = parseAndNormalizeEventColours(eventColours);
|
|
2104
|
+
if (!normalized) {
|
|
2105
|
+
clearPalette();
|
|
2106
|
+
return;
|
|
1896
2107
|
}
|
|
1897
2108
|
try {
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
});
|
|
1921
|
-
throw new Error(rpcError.message || "Failed to fetch file reference");
|
|
1922
|
-
}
|
|
1923
|
-
if (!data || data.length === 0) {
|
|
1924
|
-
files = [];
|
|
1925
|
-
} else {
|
|
1926
|
-
files = data.filter((item) => {
|
|
1927
|
-
return item.is_public === true && item.id && item.file_path && item.file_metadata;
|
|
1928
|
-
}).map((item) => {
|
|
1929
|
-
return {
|
|
1930
|
-
id: item.id,
|
|
1931
|
-
table_name,
|
|
1932
|
-
record_id,
|
|
1933
|
-
file_path: item.file_path,
|
|
1934
|
-
file_metadata: item.file_metadata || {},
|
|
1935
|
-
organisation_id,
|
|
1936
|
-
app_id: item.file_metadata?.app_id || null,
|
|
1937
|
-
is_public: true,
|
|
1938
|
-
// RPC already filtered for public files
|
|
1939
|
-
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1940
|
-
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1941
|
-
};
|
|
1942
|
-
});
|
|
1943
|
-
}
|
|
1944
|
-
} else {
|
|
1945
|
-
const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
|
|
1946
|
-
p_table_name: table_name,
|
|
1947
|
-
p_record_id: record_id,
|
|
1948
|
-
p_organisation_id: organisation_id
|
|
1949
|
-
});
|
|
1950
|
-
if (rpcError) {
|
|
1951
|
-
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1952
|
-
function: "data_file_reference_list",
|
|
1953
|
-
table_name,
|
|
1954
|
-
record_id,
|
|
1955
|
-
organisation_id,
|
|
1956
|
-
error: rpcError.message,
|
|
1957
|
-
errorCode: rpcError.code,
|
|
1958
|
-
errorDetails: rpcError.details,
|
|
1959
|
-
errorHint: rpcError.hint
|
|
1960
|
-
});
|
|
1961
|
-
throw new Error(rpcError.message || "Failed to fetch file references");
|
|
1962
|
-
}
|
|
1963
|
-
if (!fileIds || fileIds.length === 0) {
|
|
1964
|
-
files = [];
|
|
1965
|
-
} else {
|
|
1966
|
-
const ids = fileIds.map((item) => item.id);
|
|
1967
|
-
const { data: fullData, error: fetchError } = await supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").in("id", ids).eq("is_public", true);
|
|
1968
|
-
if (fetchError) {
|
|
1969
|
-
throw new Error(fetchError.message || "Failed to fetch file references");
|
|
1970
|
-
}
|
|
1971
|
-
files = fullData || [];
|
|
2109
|
+
applyPalette(normalized);
|
|
2110
|
+
} catch (error) {
|
|
2111
|
+
log4.error("Failed to apply event palette:", error);
|
|
2112
|
+
}
|
|
2113
|
+
return () => {
|
|
2114
|
+
};
|
|
2115
|
+
}, [selectedEvent, location.pathname]);
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
// src/hooks/usePreventTabReload.ts
|
|
2119
|
+
import { useEffect as useEffect7, useRef as useRef3 } from "react";
|
|
2120
|
+
function usePreventTabReload(options = {}) {
|
|
2121
|
+
const { enabled = true, gracePeriodMs = 2e3 } = options;
|
|
2122
|
+
const isRestoringFromCacheRef = useRef3(false);
|
|
2123
|
+
const gracePeriodTimeoutRef = useRef3(null);
|
|
2124
|
+
useEffect7(() => {
|
|
2125
|
+
if (!enabled || typeof window === "undefined") return;
|
|
2126
|
+
const handlePageShow = (event) => {
|
|
2127
|
+
if (event.persisted) {
|
|
2128
|
+
isRestoringFromCacheRef.current = true;
|
|
2129
|
+
if (gracePeriodTimeoutRef.current) {
|
|
2130
|
+
clearTimeout(gracePeriodTimeoutRef.current);
|
|
1972
2131
|
}
|
|
2132
|
+
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
2133
|
+
isRestoringFromCacheRef.current = false;
|
|
2134
|
+
}, gracePeriodMs);
|
|
1973
2135
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
setFileCount(0);
|
|
1981
|
-
if (enableCache) {
|
|
1982
|
-
publicFileCache.set(cacheKey, {
|
|
1983
|
-
data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: /* @__PURE__ */ new Map(), fileCount: 0 },
|
|
1984
|
-
timestamp: Date.now(),
|
|
1985
|
-
ttl: cacheTtl
|
|
1986
|
-
});
|
|
2136
|
+
};
|
|
2137
|
+
const handleVisibilityChange = () => {
|
|
2138
|
+
if (!document.hidden) {
|
|
2139
|
+
isRestoringFromCacheRef.current = true;
|
|
2140
|
+
if (gracePeriodTimeoutRef.current) {
|
|
2141
|
+
clearTimeout(gracePeriodTimeoutRef.current);
|
|
1987
2142
|
}
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
id: f.id,
|
|
1992
|
-
table_name: f.table_name,
|
|
1993
|
-
record_id: f.record_id,
|
|
1994
|
-
file_path: f.file_path,
|
|
1995
|
-
file_metadata: f.file_metadata || {},
|
|
1996
|
-
organisation_id: f.organisation_id,
|
|
1997
|
-
app_id: f.app_id,
|
|
1998
|
-
is_public: f.is_public ?? true,
|
|
1999
|
-
created_at: f.created_at,
|
|
2000
|
-
updated_at: f.updated_at
|
|
2001
|
-
}));
|
|
2002
|
-
setFileReferences(fileRefs);
|
|
2003
|
-
setFileCount(fileRefs.length);
|
|
2004
|
-
if (category && fileRefs.length > 0) {
|
|
2005
|
-
const firstFile = fileRefs[0];
|
|
2006
|
-
setFileReference(firstFile);
|
|
2007
|
-
const url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
2008
|
-
setFileUrl(url);
|
|
2009
|
-
} else {
|
|
2010
|
-
const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
|
|
2011
|
-
appName: "pace-core",
|
|
2012
|
-
orgId: organisation_id,
|
|
2013
|
-
expiresIn: 3600
|
|
2014
|
-
});
|
|
2015
|
-
setFileUrls(urlMap);
|
|
2016
|
-
setFileReference(null);
|
|
2017
|
-
setFileUrl(null);
|
|
2143
|
+
gracePeriodTimeoutRef.current = setTimeout(() => {
|
|
2144
|
+
isRestoringFromCacheRef.current = false;
|
|
2145
|
+
}, gracePeriodMs);
|
|
2018
2146
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
for (const fileRef of fileRefs) {
|
|
2028
|
-
const url = getPublicUrl(supabase, fileRef.file_path, true);
|
|
2029
|
-
if (url) {
|
|
2030
|
-
urlMap.set(fileRef.id, url);
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
return urlMap;
|
|
2034
|
-
})(),
|
|
2035
|
-
fileCount: fileRefs.length
|
|
2036
|
-
},
|
|
2037
|
-
timestamp: Date.now(),
|
|
2038
|
-
ttl: cacheTtl
|
|
2039
|
-
});
|
|
2147
|
+
};
|
|
2148
|
+
window.addEventListener("pageshow", handlePageShow);
|
|
2149
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
2150
|
+
return () => {
|
|
2151
|
+
window.removeEventListener("pageshow", handlePageShow);
|
|
2152
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
2153
|
+
if (gracePeriodTimeoutRef.current) {
|
|
2154
|
+
clearTimeout(gracePeriodTimeoutRef.current);
|
|
2040
2155
|
}
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
table_name,
|
|
2044
|
-
record_id,
|
|
2045
|
-
organisation_id,
|
|
2046
|
-
category,
|
|
2047
|
-
error: err instanceof Error ? err.message : "Unknown error",
|
|
2048
|
-
errorDetails: err instanceof Error ? err.stack : String(err)
|
|
2049
|
-
});
|
|
2050
|
-
const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
2051
|
-
setError(error2);
|
|
2052
|
-
setFileUrl(null);
|
|
2053
|
-
setFileReference(null);
|
|
2054
|
-
setFileReferences([]);
|
|
2055
|
-
setFileUrls(/* @__PURE__ */ new Map());
|
|
2056
|
-
setFileCount(0);
|
|
2057
|
-
} finally {
|
|
2058
|
-
setIsLoading(false);
|
|
2059
|
-
}
|
|
2060
|
-
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
2061
|
-
useEffect7(() => {
|
|
2062
|
-
if (table_name && record_id && organisation_id) {
|
|
2063
|
-
fetchFiles();
|
|
2064
|
-
} else {
|
|
2065
|
-
setFileUrl(null);
|
|
2066
|
-
setFileReference(null);
|
|
2067
|
-
setFileReferences([]);
|
|
2068
|
-
setFileUrls(/* @__PURE__ */ new Map());
|
|
2069
|
-
setFileCount(0);
|
|
2070
|
-
setIsLoading(false);
|
|
2071
|
-
setError(null);
|
|
2072
|
-
}
|
|
2073
|
-
}, [fetchFiles, table_name, record_id, organisation_id]);
|
|
2074
|
-
const refetch = useCallback4(async () => {
|
|
2075
|
-
if (!table_name || !record_id || !organisation_id) return;
|
|
2076
|
-
if (enableCache) {
|
|
2077
|
-
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
2078
|
-
publicFileCache.delete(cacheKey);
|
|
2079
|
-
}
|
|
2080
|
-
await fetchFiles();
|
|
2081
|
-
}, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);
|
|
2082
|
-
return {
|
|
2083
|
-
fileUrl,
|
|
2084
|
-
fileReference,
|
|
2085
|
-
fileReferences,
|
|
2086
|
-
fileUrls,
|
|
2087
|
-
fileCount,
|
|
2088
|
-
isLoading,
|
|
2089
|
-
error,
|
|
2090
|
-
refetch
|
|
2091
|
-
};
|
|
2092
|
-
}
|
|
2093
|
-
function clearPublicFileDisplayCache() {
|
|
2094
|
-
for (const [key] of publicFileCache) {
|
|
2095
|
-
if (key.startsWith("public_file_")) {
|
|
2096
|
-
publicFileCache.delete(key);
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
function getPublicFileDisplayCacheStats() {
|
|
2101
|
-
const keys = Array.from(publicFileCache.keys()).filter((key) => key.startsWith("public_file_"));
|
|
2102
|
-
return {
|
|
2103
|
-
size: keys.length,
|
|
2104
|
-
keys
|
|
2105
|
-
};
|
|
2156
|
+
};
|
|
2157
|
+
}, [enabled, gracePeriodMs]);
|
|
2106
2158
|
}
|
|
2107
2159
|
|
|
2108
2160
|
export {
|
|
2109
2161
|
useDebounce,
|
|
2162
|
+
cleanupQueryCache,
|
|
2110
2163
|
useQueryCache,
|
|
2111
2164
|
queryCacheHelpers,
|
|
2112
2165
|
useAddressAutocomplete,
|
|
2113
|
-
useEventTheme,
|
|
2114
|
-
usePreventTabReload,
|
|
2115
|
-
ErrorBoundary,
|
|
2116
|
-
PublicPageContext,
|
|
2117
|
-
PublicPageProvider,
|
|
2118
|
-
usePublicPageContext,
|
|
2119
|
-
useIsPublicPage,
|
|
2120
|
-
useAppConfig,
|
|
2121
2166
|
FILE_SIZE_LIMITS,
|
|
2122
2167
|
DEFAULT_FILE_SIZE_LIMIT,
|
|
2123
2168
|
APP_PATH_MAPPING,
|
|
@@ -2136,14 +2181,16 @@ export {
|
|
|
2136
2181
|
listFiles,
|
|
2137
2182
|
downloadFile,
|
|
2138
2183
|
archiveFile,
|
|
2184
|
+
usePublicFileDisplay,
|
|
2185
|
+
clearPublicFileDisplayCache,
|
|
2186
|
+
getPublicFileDisplayCacheStats,
|
|
2187
|
+
createFileReferenceService,
|
|
2188
|
+
uploadFileWithReference,
|
|
2139
2189
|
useFileDisplay,
|
|
2140
2190
|
clearFileDisplayCache,
|
|
2141
2191
|
getFileDisplayCacheStats,
|
|
2142
2192
|
invalidateFileDisplayCache,
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
usePublicFileDisplay,
|
|
2146
|
-
clearPublicFileDisplayCache,
|
|
2147
|
-
getPublicFileDisplayCacheStats
|
|
2193
|
+
useEventTheme,
|
|
2194
|
+
usePreventTabReload
|
|
2148
2195
|
};
|
|
2149
|
-
//# sourceMappingURL=chunk-
|
|
2196
|
+
//# sourceMappingURL=chunk-NIU6J6OX.js.map
|