@jmruthers/pace-core 0.5.185 → 0.5.187
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/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
- package/dist/{DataTable-IX2NBUTP.js → DataTable-K3RJRSOX.js} +7 -7
- package/dist/{PublicPageProvider-BABf6JCh.d.ts → PublicPageProvider-DrLDztHt.d.ts} +214 -107
- package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-B76OWOAT.js} +2 -2
- package/dist/{api-BMFCXVQX.js → api-YP7XD5L6.js} +3 -3
- package/dist/{audit-WRS3KJKI.js → audit-B5P6FFIR.js} +2 -2
- package/dist/{chunk-445GEP27.js → chunk-3IC5WCMO.js} +33 -8
- package/dist/chunk-3IC5WCMO.js.map +1 -0
- package/dist/{chunk-OKI34GZD.js → chunk-3NFNJOO7.js} +8 -8
- package/dist/chunk-3NFNJOO7.js.map +1 -0
- package/dist/{chunk-FSFQFJCU.js → chunk-63FOKYGO.js} +174 -6
- package/dist/chunk-63FOKYGO.js.map +1 -0
- package/dist/{chunk-MX3EIJGQ.js → chunk-C4OYJOV4.js} +631 -97
- package/dist/chunk-C4OYJOV4.js.map +1 -0
- package/dist/{chunk-HGPQUCBC.js → chunk-FMTK4XNN.js} +3 -3
- package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
- package/dist/chunk-HEHYGYOX.js.map +1 -0
- package/dist/{chunk-XAUHJD3L.js → chunk-K2JGDXGU.js} +2 -2
- package/dist/{chunk-HC67NW5K.js → chunk-LBBUPSSC.js} +863 -552
- package/dist/chunk-LBBUPSSC.js.map +1 -0
- package/dist/{chunk-IXSNYUCT.js → chunk-SAUPYVLF.js} +1 -1
- package/dist/chunk-SAUPYVLF.js.map +1 -0
- package/dist/{chunk-AISXLWGZ.js → chunk-T6ZJVI3A.js} +27 -23
- package/dist/chunk-T6ZJVI3A.js.map +1 -0
- package/dist/{chunk-STTZQK2I.js → chunk-ULX5FYEM.js} +9 -7
- package/dist/chunk-ULX5FYEM.js.map +1 -0
- package/dist/{chunk-FXFJRTKI.js → chunk-WK2Y6TGA.js} +3 -3
- package/dist/chunk-WK2Y6TGA.js.map +1 -0
- package/dist/chunk-YHCN776L.js +447 -0
- package/dist/chunk-YHCN776L.js.map +1 -0
- package/dist/components.d.ts +4 -4
- package/dist/components.js +12 -10
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
- package/dist/{file-reference-BjR39ktt.d.ts → file-reference-D037xOFK.d.ts} +3 -1
- package/dist/hooks.d.ts +265 -6
- package/dist/hooks.js +148 -49
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +25 -10
- package/dist/index.js +65 -30
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- package/dist/rbac/index.d.ts +125 -8
- package/dist/rbac/index.js +27 -7
- package/dist/{types-DUyCRSTj.d.ts → types-Bwgl--Xo.d.ts} +162 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CvnC3d-e.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +3 -3
- package/dist/utils.d.ts +214 -4
- package/dist/utils.js +22 -2
- package/dist/utils.js.map +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +21 -17
- package/docs/api/classes/RBACCache.md +31 -23
- package/docs/api/classes/RBACEngine.md +6 -6
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- 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 +241 -0
- package/docs/api/interfaces/AddressFieldRef.md +94 -0
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +75 -0
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +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 +15 -15
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +33 -9
- package/docs/api/interfaces/FileUploadProps.md +36 -14
- 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 +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +120 -0
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +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 +27 -4
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +5 -5
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +138 -0
- 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 +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +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 +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +328 -69
- package/docs/api-reference/components.md +26 -12
- package/docs/best-practices/performance.md +11 -0
- package/docs/implementation-guides/file-reference-system.md +24 -2
- package/docs/implementation-guides/file-upload-storage.md +38 -1
- package/docs/rbac/README.md +2 -1
- package/docs/rbac/api-reference.md +11 -0
- package/docs/rbac/performance.md +320 -0
- package/docs/standards/01-architecture-standard.md +5 -0
- package/docs/standards/05-security-standard.md +12 -0
- package/package.json +1 -1
- package/scripts/check-pace-core-compliance.js +512 -0
- package/src/components/AddressField/AddressField.test.tsx +411 -0
- package/src/components/AddressField/AddressField.tsx +323 -0
- package/src/components/AddressField/README.md +336 -0
- package/src/components/AddressField/index.ts +10 -0
- package/src/components/AddressField/types.ts +65 -0
- package/src/components/FileDisplay/FileDisplay.test.tsx +454 -0
- package/src/components/FileDisplay/FileDisplay.tsx +28 -1
- package/src/components/FileUpload/FileUpload.test.tsx +2 -0
- package/src/components/FileUpload/FileUpload.tsx +7 -1
- package/src/components/Header/Header.tsx +2 -5
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +134 -1
- package/src/components/index.ts +2 -0
- package/src/hooks/__tests__/useFileDisplay.unit.test.ts +30 -5
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +11 -10
- package/src/hooks/__tests__/usePublicFileDisplay.test.ts +31 -6
- package/src/hooks/index.ts +9 -0
- package/src/hooks/public/usePublicFileDisplay.ts +8 -10
- package/src/hooks/useAddressAutocomplete.test.ts +318 -0
- package/src/hooks/useAddressAutocomplete.ts +268 -0
- package/src/hooks/useFileDisplay.ts +3 -15
- package/src/hooks/useFileReference.test.ts +21 -3
- package/src/hooks/useFileReference.ts +3 -24
- package/src/hooks/useFileUrlCache.ts +246 -0
- package/src/hooks/useInactivityTracker.ts +31 -20
- package/src/hooks/useOrganisationSecurity.test.ts +10 -7
- package/src/hooks/useOrganisationSecurity.ts +3 -3
- package/src/hooks/usePreventTabReload.ts +106 -0
- package/src/hooks/useQueryCache.ts +315 -0
- package/src/hooks/useSecureDataAccess.ts +2 -2
- package/src/index.ts +2 -0
- package/src/providers/services/EventServiceProvider.tsx +4 -1
- package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
- package/src/rbac/api.test.ts +21 -6
- package/src/rbac/api.ts +32 -11
- package/src/rbac/audit-batched.ts +223 -0
- package/src/rbac/audit-enhanced.ts +2 -2
- package/src/rbac/audit.test.ts +6 -5
- package/src/rbac/audit.ts +34 -6
- package/src/rbac/cache-invalidation.ts +63 -12
- package/src/rbac/cache.test.ts +2 -2
- package/src/rbac/cache.ts +61 -14
- package/src/rbac/components/PagePermissionGuard.tsx +19 -10
- package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +248 -0
- package/src/rbac/config.ts +9 -0
- package/src/rbac/engine.ts +2 -21
- package/src/rbac/hooks/usePermissions.ts +21 -5
- package/src/rbac/index.ts +19 -0
- package/src/rbac/performance.ts +210 -0
- package/src/rbac/request-deduplication.ts +87 -0
- package/src/rbac/utils/deep-equal.ts +93 -0
- package/src/styles/core.css +5 -5
- package/src/types/database.generated.ts +63 -9
- package/src/types/file-reference.ts +3 -1
- package/src/utils/file-reference/__tests__/file-reference.test.ts +89 -8
- package/src/utils/file-reference/index.ts +56 -17
- package/src/utils/google-places/googlePlacesUtils.test.ts +403 -0
- package/src/utils/google-places/googlePlacesUtils.ts +475 -0
- package/src/utils/google-places/index.ts +26 -0
- package/src/utils/google-places/loadGoogleMapsScript.ts +207 -0
- package/src/utils/google-places/types.ts +94 -0
- package/src/utils/index.ts +23 -0
- package/src/utils/request-deduplication.ts +165 -0
- package/src/utils/security/secureDataAccess.ts +1 -1
- package/src/utils/storage/helpers.ts +211 -4
- package/dist/chunk-445GEP27.js.map +0 -1
- package/dist/chunk-AISXLWGZ.js.map +0 -1
- package/dist/chunk-FMUCXFII.js +0 -76
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-FSFQFJCU.js.map +0 -1
- package/dist/chunk-FXFJRTKI.js.map +0 -1
- package/dist/chunk-HC67NW5K.js.map +0 -1
- package/dist/chunk-IXSNYUCT.js.map +0 -1
- package/dist/chunk-MX3EIJGQ.js.map +0 -1
- package/dist/chunk-OKI34GZD.js.map +0 -1
- package/dist/chunk-STTZQK2I.js.map +0 -1
- package/dist/chunk-U6WNSFX5.js.map +0 -1
- /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-K3RJRSOX.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-B76OWOAT.js.map} +0 -0
- /package/dist/{api-BMFCXVQX.js.map → api-YP7XD5L6.js.map} +0 -0
- /package/dist/{audit-WRS3KJKI.js.map → audit-B5P6FFIR.js.map} +0 -0
- /package/dist/{chunk-HGPQUCBC.js.map → chunk-FMTK4XNN.js.map} +0 -0
- /package/dist/{chunk-XAUHJD3L.js.map → chunk-K2JGDXGU.js.map} +0 -0
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
* - LoadingSpinner - Loading state UI
|
|
69
69
|
*/
|
|
70
70
|
|
|
71
|
-
import React, { useMemo } from 'react';
|
|
71
|
+
import React, { useMemo, useEffect, useRef, useState } from 'react';
|
|
72
72
|
import { Navigate, Outlet } from 'react-router-dom';
|
|
73
73
|
import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
|
|
74
74
|
import { useSessionRestoration } from '../../hooks/useSessionRestoration';
|
|
@@ -77,6 +77,7 @@ import { LoadingSpinner } from '../LoadingSpinner/LoadingSpinner';
|
|
|
77
77
|
import { SessionRestorationLoader } from '../SessionRestorationLoader';
|
|
78
78
|
import { Alert, AlertDescription, AlertTitle } from '../Alert/Alert';
|
|
79
79
|
import { logger } from '../../utils/core/logger';
|
|
80
|
+
import { usePreventTabReload } from '../../hooks/usePreventTabReload';
|
|
80
81
|
|
|
81
82
|
export interface ProtectedRouteProps {
|
|
82
83
|
/**
|
|
@@ -148,6 +149,96 @@ export function ProtectedRoute({
|
|
|
148
149
|
|
|
149
150
|
const sessionRestoration = useSessionRestoration();
|
|
150
151
|
|
|
152
|
+
// Prevent full page reloads when switching tabs (handles bfcache and visibility changes)
|
|
153
|
+
usePreventTabReload({ enabled: true, gracePeriodMs: 2000 });
|
|
154
|
+
|
|
155
|
+
// Track if user was previously authenticated to prevent redirects during session refresh
|
|
156
|
+
const wasAuthenticatedRef = useRef(false);
|
|
157
|
+
const [shouldRedirect, setShouldRedirect] = useState(false);
|
|
158
|
+
const tabJustBecameVisibleRef = useRef(false);
|
|
159
|
+
|
|
160
|
+
// Track authentication state to detect when user was previously logged in
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (isAuthenticated) {
|
|
163
|
+
wasAuthenticatedRef.current = true;
|
|
164
|
+
setShouldRedirect(false);
|
|
165
|
+
tabJustBecameVisibleRef.current = false; // Clear visibility flag when authenticated
|
|
166
|
+
}
|
|
167
|
+
}, [isAuthenticated]);
|
|
168
|
+
|
|
169
|
+
// Handle tab visibility changes - prevent immediate redirects when tab becomes visible
|
|
170
|
+
// This prevents the page from refreshing when switching back to the tab
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (typeof document === 'undefined') return;
|
|
173
|
+
|
|
174
|
+
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
175
|
+
let wasHidden = document.hidden;
|
|
176
|
+
|
|
177
|
+
const handleVisibilityChange = () => {
|
|
178
|
+
const isNowVisible = !document.hidden;
|
|
179
|
+
|
|
180
|
+
// When tab becomes visible, immediately prevent redirects and give session refresh time
|
|
181
|
+
if (isNowVisible && wasHidden) {
|
|
182
|
+
// Tab just became visible - immediately prevent redirects
|
|
183
|
+
if (!isAuthenticated && wasAuthenticatedRef.current) {
|
|
184
|
+
tabJustBecameVisibleRef.current = true;
|
|
185
|
+
setShouldRedirect(false); // Immediately clear redirect flag
|
|
186
|
+
|
|
187
|
+
// Clear any existing timeout
|
|
188
|
+
if (timeoutId) {
|
|
189
|
+
clearTimeout(timeoutId);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Wait a bit to see if session refresh completes
|
|
193
|
+
timeoutId = setTimeout(() => {
|
|
194
|
+
// Only allow redirect if still not authenticated after delay
|
|
195
|
+
tabJustBecameVisibleRef.current = false;
|
|
196
|
+
// Use a function to get the latest state
|
|
197
|
+
setShouldRedirect((prev) => {
|
|
198
|
+
// Only set to true if we're still not authenticated
|
|
199
|
+
// This will be checked again in the render logic
|
|
200
|
+
return prev;
|
|
201
|
+
});
|
|
202
|
+
}, 2000); // 2 second grace period for session refresh
|
|
203
|
+
}
|
|
204
|
+
} else if (!isNowVisible) {
|
|
205
|
+
// Tab became hidden - clear the visibility flag
|
|
206
|
+
tabJustBecameVisibleRef.current = false;
|
|
207
|
+
if (timeoutId) {
|
|
208
|
+
clearTimeout(timeoutId);
|
|
209
|
+
timeoutId = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
wasHidden = !isNowVisible;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Check initial state - if tab is visible and user appears logged out, give grace period
|
|
217
|
+
if (!document.hidden && !isAuthenticated && wasAuthenticatedRef.current) {
|
|
218
|
+
tabJustBecameVisibleRef.current = true;
|
|
219
|
+
setShouldRedirect(false);
|
|
220
|
+
timeoutId = setTimeout(() => {
|
|
221
|
+
tabJustBecameVisibleRef.current = false;
|
|
222
|
+
}, 2000);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
226
|
+
return () => {
|
|
227
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
228
|
+
if (timeoutId) {
|
|
229
|
+
clearTimeout(timeoutId);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}, [isAuthenticated]);
|
|
233
|
+
|
|
234
|
+
// Reset redirect flag when authenticated
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
if (isAuthenticated) {
|
|
237
|
+
setShouldRedirect(false);
|
|
238
|
+
tabJustBecameVisibleRef.current = false;
|
|
239
|
+
}
|
|
240
|
+
}, [isAuthenticated]);
|
|
241
|
+
|
|
151
242
|
const isRestoringSession = useMemo(() => {
|
|
152
243
|
return sessionRestoration.isRestoring &&
|
|
153
244
|
!sessionRestoration.restorationComplete &&
|
|
@@ -182,13 +273,55 @@ export function ProtectedRoute({
|
|
|
182
273
|
}
|
|
183
274
|
|
|
184
275
|
// Redirect to login if not authenticated
|
|
276
|
+
// Priority order:
|
|
277
|
+
// 1. If session restoration has timed out or errored → redirect immediately (even if loading)
|
|
278
|
+
// 2. If user was never authenticated → redirect immediately (even if loading)
|
|
279
|
+
// 3. If tab just became visible → show loading (prevent redirect during grace period)
|
|
280
|
+
// 4. If we've confirmed they should redirect (after visibility change grace period) → redirect
|
|
281
|
+
// 5. Otherwise, if loading → show loading spinner (session might be refreshing)
|
|
282
|
+
// 6. Otherwise → redirect (user is not authenticated and not loading)
|
|
185
283
|
if (!isAuthenticated) {
|
|
284
|
+
// Session restoration timeout/error always redirects immediately
|
|
186
285
|
if (sessionRestoration.hasTimedOut || sessionRestoration.restorationError) {
|
|
187
286
|
logger.warn('ProtectedRoute', 'Session restoration failed, redirecting to login', {
|
|
188
287
|
timedOut: sessionRestoration.hasTimedOut,
|
|
189
288
|
error: sessionRestoration.restorationError?.message
|
|
190
289
|
});
|
|
290
|
+
return <Navigate to={loginPath} replace />;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// User was never authenticated → redirect immediately
|
|
294
|
+
if (!wasAuthenticatedRef.current) {
|
|
295
|
+
return <Navigate to={loginPath} replace />;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Tab just became visible - show loading to prevent redirect during grace period
|
|
299
|
+
// Also check document visibility state directly as a fallback
|
|
300
|
+
const isTabVisible = typeof document !== 'undefined' && !document.hidden;
|
|
301
|
+
if (tabJustBecameVisibleRef.current || (isTabVisible && wasAuthenticatedRef.current && isLoading)) {
|
|
302
|
+
return loadingFallback || (
|
|
303
|
+
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
|
|
304
|
+
<LoadingSpinner />
|
|
305
|
+
</div>
|
|
306
|
+
);
|
|
191
307
|
}
|
|
308
|
+
|
|
309
|
+
// We've confirmed redirect after grace period → redirect
|
|
310
|
+
if (shouldRedirect) {
|
|
311
|
+
return <Navigate to={loginPath} replace />;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// User was authenticated before but now appears logged out
|
|
315
|
+
// Show loading state while we wait for session refresh (unless we're not loading)
|
|
316
|
+
if (isLoading) {
|
|
317
|
+
return loadingFallback || (
|
|
318
|
+
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh' }}>
|
|
319
|
+
<LoadingSpinner />
|
|
320
|
+
</div>
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Not loading and not authenticated → redirect
|
|
192
325
|
return <Navigate to={loginPath} replace />;
|
|
193
326
|
}
|
|
194
327
|
|
package/src/components/index.ts
CHANGED
|
@@ -43,6 +43,8 @@ export type { CardProps, CardActionsProps } from './Card';
|
|
|
43
43
|
|
|
44
44
|
export { Input } from './Input';
|
|
45
45
|
export type { InputProps } from './Input';
|
|
46
|
+
export { AddressField } from './AddressField';
|
|
47
|
+
export type { AddressFieldProps, AddressFieldRef, ParsedAddress, AutocompleteOptions } from './AddressField';
|
|
46
48
|
export { Label } from './Label';
|
|
47
49
|
export type { LabelProps } from './Label';
|
|
48
50
|
|
|
@@ -25,7 +25,18 @@ import { FileCategory as FileCategoryEnum } from '../../types/file-reference';
|
|
|
25
25
|
// Mock storage helpers
|
|
26
26
|
vi.mock('../../utils/storage/helpers', () => ({
|
|
27
27
|
getPublicUrl: vi.fn((supabase: any, path: string) => `https://example.com/${path}`),
|
|
28
|
-
getSignedUrl: vi.fn().mockResolvedValue({ url: 'https://example.com/signed-file.jpg', expiresAt: new Date().toISOString() })
|
|
28
|
+
getSignedUrl: vi.fn().mockResolvedValue({ url: 'https://example.com/signed-file.jpg', expiresAt: new Date().toISOString() }),
|
|
29
|
+
generateFileUrlsBatch: vi.fn().mockImplementation(async (supabase, files) => {
|
|
30
|
+
const urlMap = new Map<string, string>();
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
if (file.is_public) {
|
|
33
|
+
urlMap.set(file.id, `https://example.com/${file.file_path}`);
|
|
34
|
+
} else {
|
|
35
|
+
urlMap.set(file.id, `https://example.com/signed/${file.file_path}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return urlMap;
|
|
39
|
+
})
|
|
29
40
|
}));
|
|
30
41
|
|
|
31
42
|
// Mock file reference service
|
|
@@ -38,7 +49,7 @@ vi.mock('../../utils/file-reference', () => ({
|
|
|
38
49
|
createFileReferenceService: vi.fn(() => mockService)
|
|
39
50
|
}));
|
|
40
51
|
|
|
41
|
-
import { getPublicUrl, getSignedUrl } from '../../utils/storage/helpers';
|
|
52
|
+
import { getPublicUrl, getSignedUrl, generateFileUrlsBatch } from '../../utils/storage/helpers';
|
|
42
53
|
import { createFileReferenceService } from '../../utils/file-reference';
|
|
43
54
|
|
|
44
55
|
describe('useFileDisplay Hook', () => {
|
|
@@ -87,6 +98,19 @@ describe('useFileDisplay Hook', () => {
|
|
|
87
98
|
mockSupabase = createMockSupabaseClient() as any;
|
|
88
99
|
mockService.getFilesByCategory.mockResolvedValue([]);
|
|
89
100
|
mockService.listFileReferences.mockResolvedValue([]);
|
|
101
|
+
|
|
102
|
+
// Reset generateFileUrlsBatch mock to ensure it returns a Map
|
|
103
|
+
vi.mocked(generateFileUrlsBatch).mockImplementation(async (supabase, files) => {
|
|
104
|
+
const urlMap = new Map<string, string>();
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
if (file.is_public) {
|
|
107
|
+
urlMap.set(file.id, `https://example.com/${file.file_path}`);
|
|
108
|
+
} else {
|
|
109
|
+
urlMap.set(file.id, `https://example.com/signed/${file.file_path}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return urlMap;
|
|
113
|
+
});
|
|
90
114
|
});
|
|
91
115
|
|
|
92
116
|
afterEach(() => {
|
|
@@ -305,13 +329,14 @@ describe('useFileDisplay Hook', () => {
|
|
|
305
329
|
await waitFor(
|
|
306
330
|
() => {
|
|
307
331
|
expect(result.current.isLoading).toBe(false);
|
|
332
|
+
expect(result.current.fileCount).toBe(2);
|
|
333
|
+
expect(result.current.fileReferences.length).toBe(2);
|
|
334
|
+
expect(result.current.fileUrls).toBeDefined();
|
|
335
|
+
expect(result.current.fileUrls.size).toBe(2);
|
|
308
336
|
},
|
|
309
337
|
{ timeout: 2000 }
|
|
310
338
|
);
|
|
311
339
|
|
|
312
|
-
expect(result.current.fileCount).toBe(2);
|
|
313
|
-
expect(result.current.fileReferences.length).toBe(2);
|
|
314
|
-
expect(result.current.fileUrls.size).toBe(2);
|
|
315
340
|
expect(result.current.fileUrl).toBe(null); // No single file URL in multiple mode
|
|
316
341
|
expect(result.current.fileReference).toBe(null); // No single file reference in multiple mode
|
|
317
342
|
});
|
|
@@ -19,6 +19,7 @@ vi.mock('../../hooks/useOrganisations', () => ({
|
|
|
19
19
|
// Mock the RBAC API
|
|
20
20
|
vi.mock('../../rbac/api', () => ({
|
|
21
21
|
isPermitted: vi.fn(),
|
|
22
|
+
isPermittedCached: vi.fn(),
|
|
22
23
|
getPermissionMap: vi.fn(),
|
|
23
24
|
emitAuditEvent: vi.fn()
|
|
24
25
|
}));
|
|
@@ -451,8 +452,8 @@ describe('useOrganisationSecurity', () => {
|
|
|
451
452
|
const mockOrg = { id: 'org-123' };
|
|
452
453
|
|
|
453
454
|
// Mock the RBAC API
|
|
454
|
-
const {
|
|
455
|
-
vi.mocked(
|
|
455
|
+
const { isPermittedCached } = await import('../../rbac/api');
|
|
456
|
+
vi.mocked(isPermittedCached).mockResolvedValue(true);
|
|
456
457
|
|
|
457
458
|
vi.mocked(useUnifiedAuth).mockReturnValue({
|
|
458
459
|
...mockUseUnifiedAuth,
|
|
@@ -469,7 +470,7 @@ describe('useOrganisationSecurity', () => {
|
|
|
469
470
|
const hasPermission = await result.current.hasPermission('view_basic');
|
|
470
471
|
|
|
471
472
|
expect(hasPermission).toBe(true);
|
|
472
|
-
expect(
|
|
473
|
+
expect(isPermittedCached).toHaveBeenCalledWith({
|
|
473
474
|
userId: 'user-123',
|
|
474
475
|
scope: {
|
|
475
476
|
organisationId: 'org-123',
|
|
@@ -485,8 +486,8 @@ describe('useOrganisationSecurity', () => {
|
|
|
485
486
|
const mockOrg = { id: 'org-123' };
|
|
486
487
|
|
|
487
488
|
// Mock the RBAC API to throw an error
|
|
488
|
-
const {
|
|
489
|
-
vi.mocked(
|
|
489
|
+
const { isPermittedCached } = await import('../../rbac/api');
|
|
490
|
+
vi.mocked(isPermittedCached).mockRejectedValue(new Error('RBAC API error'));
|
|
490
491
|
|
|
491
492
|
vi.mocked(useUnifiedAuth).mockReturnValue({
|
|
492
493
|
...mockUseUnifiedAuth,
|
|
@@ -510,8 +511,8 @@ describe('useOrganisationSecurity', () => {
|
|
|
510
511
|
const mockOrg = { id: 'org-123' };
|
|
511
512
|
|
|
512
513
|
// Mock the RBAC API to throw an error
|
|
513
|
-
const {
|
|
514
|
-
vi.mocked(
|
|
514
|
+
const { isPermittedCached } = await import('../../rbac/api');
|
|
515
|
+
vi.mocked(isPermittedCached).mockImplementation(() => {
|
|
515
516
|
throw new Error('RBAC API exception');
|
|
516
517
|
});
|
|
517
518
|
|
|
@@ -536,8 +537,8 @@ describe('useOrganisationSecurity', () => {
|
|
|
536
537
|
const mockUser = { id: 'user-123', user_metadata: { eventId: 'event-123', appId: 'app-123' } };
|
|
537
538
|
|
|
538
539
|
// Mock the RBAC API
|
|
539
|
-
const {
|
|
540
|
-
vi.mocked(
|
|
540
|
+
const { isPermittedCached } = await import('../../rbac/api');
|
|
541
|
+
vi.mocked(isPermittedCached).mockResolvedValue(true);
|
|
541
542
|
|
|
542
543
|
vi.mocked(useUnifiedAuth).mockReturnValue({
|
|
543
544
|
...mockUseUnifiedAuth,
|
|
@@ -549,7 +550,7 @@ describe('useOrganisationSecurity', () => {
|
|
|
549
550
|
|
|
550
551
|
await result.current.hasPermission('view_basic', 'org-456');
|
|
551
552
|
|
|
552
|
-
expect(
|
|
553
|
+
expect(isPermittedCached).toHaveBeenCalledWith({
|
|
553
554
|
userId: 'user-123',
|
|
554
555
|
scope: {
|
|
555
556
|
organisationId: 'org-456',
|
|
@@ -21,9 +21,20 @@ import type { Database } from '../../types/database';
|
|
|
21
21
|
import type { FileCategory } from '../../types/file-reference';
|
|
22
22
|
import { FileCategory as FileCategoryEnum } from '../../types/file-reference';
|
|
23
23
|
|
|
24
|
-
// Mock getPublicUrl
|
|
24
|
+
// Mock getPublicUrl and generateFileUrlsBatch
|
|
25
25
|
vi.mock('../../utils/storage/helpers', () => ({
|
|
26
|
-
getPublicUrl: vi.fn((supabase: any, path: string) => `https://example.com/${path}`)
|
|
26
|
+
getPublicUrl: vi.fn((supabase: any, path: string) => `https://example.com/${path}`),
|
|
27
|
+
generateFileUrlsBatch: vi.fn().mockImplementation(async (supabase, files) => {
|
|
28
|
+
const urlMap = new Map<string, string>();
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
if (file.is_public) {
|
|
31
|
+
urlMap.set(file.id, `https://example.com/${file.file_path}`);
|
|
32
|
+
} else {
|
|
33
|
+
urlMap.set(file.id, `https://example.com/signed/${file.file_path}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return urlMap;
|
|
37
|
+
})
|
|
27
38
|
}));
|
|
28
39
|
|
|
29
40
|
// Mock logger
|
|
@@ -36,7 +47,7 @@ vi.mock('../../utils/core/logger', () => ({
|
|
|
36
47
|
},
|
|
37
48
|
}));
|
|
38
49
|
|
|
39
|
-
import { getPublicUrl } from '../../utils/storage/helpers';
|
|
50
|
+
import { getPublicUrl, generateFileUrlsBatch } from '../../utils/storage/helpers';
|
|
40
51
|
|
|
41
52
|
describe('usePublicFileDisplay Hook', () => {
|
|
42
53
|
let mockSupabase: SupabaseClient<Database>;
|
|
@@ -61,6 +72,19 @@ describe('usePublicFileDisplay Hook', () => {
|
|
|
61
72
|
vi.clearAllMocks();
|
|
62
73
|
clearPublicFileDisplayCache();
|
|
63
74
|
mockSupabase = createMockSupabaseClient() as any;
|
|
75
|
+
|
|
76
|
+
// Reset generateFileUrlsBatch mock to ensure it returns a Map
|
|
77
|
+
vi.mocked(generateFileUrlsBatch).mockImplementation(async (supabase, files) => {
|
|
78
|
+
const urlMap = new Map<string, string>();
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
if (file.is_public) {
|
|
81
|
+
urlMap.set(file.id, `https://example.com/${file.file_path}`);
|
|
82
|
+
} else {
|
|
83
|
+
urlMap.set(file.id, `https://example.com/signed/${file.file_path}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return urlMap;
|
|
87
|
+
});
|
|
64
88
|
});
|
|
65
89
|
|
|
66
90
|
afterEach(() => {
|
|
@@ -256,13 +280,14 @@ describe('usePublicFileDisplay Hook', () => {
|
|
|
256
280
|
await waitFor(
|
|
257
281
|
() => {
|
|
258
282
|
expect(result.current.isLoading).toBe(false);
|
|
283
|
+
expect(result.current.fileCount).toBe(2);
|
|
284
|
+
expect(result.current.fileReferences.length).toBe(2);
|
|
285
|
+
expect(result.current.fileUrls).toBeDefined();
|
|
286
|
+
expect(result.current.fileUrls.size).toBe(2);
|
|
259
287
|
},
|
|
260
288
|
{ timeout: 2000 }
|
|
261
289
|
);
|
|
262
290
|
|
|
263
|
-
expect(result.current.fileCount).toBe(2);
|
|
264
|
-
expect(result.current.fileReferences.length).toBe(2);
|
|
265
|
-
expect(result.current.fileUrls.size).toBe(2);
|
|
266
291
|
expect(result.current.fileUrls.get('file-123')).toBe('https://example.com/org-123/logos/logo.png');
|
|
267
292
|
expect(result.current.fileUrls.get('file-456')).toBe('https://example.com/org-123/logos/logo2.png');
|
|
268
293
|
expect(result.current.fileUrl).toBe(null); // No single file URL in multiple mode
|
package/src/hooks/index.ts
CHANGED
|
@@ -28,6 +28,8 @@ export { useEventTheme } from './useEventTheme';
|
|
|
28
28
|
// === DATA & STATE HOOKS ===
|
|
29
29
|
export { useDebounce } from './useDebounce';
|
|
30
30
|
export { useDataTableState } from './useDataTableState';
|
|
31
|
+
export { useAddressAutocomplete } from './useAddressAutocomplete';
|
|
32
|
+
export type { UseAddressAutocompleteOptions, UseAddressAutocompleteReturn } from './useAddressAutocomplete';
|
|
31
33
|
|
|
32
34
|
// === ORGANISATION HOOKS ===
|
|
33
35
|
export { useOrganisationPermissions } from './useOrganisationPermissions';
|
|
@@ -48,6 +50,8 @@ export { useComponentPerformance } from './useComponentPerformance';
|
|
|
48
50
|
export { useAppConfig } from './useAppConfig';
|
|
49
51
|
export type { UseAppConfigReturn } from './useAppConfig';
|
|
50
52
|
export { usePerformanceMonitor } from './usePerformanceMonitor';
|
|
53
|
+
export { useQueryCache, queryCacheHelpers } from './useQueryCache';
|
|
54
|
+
export type { UseQueryCacheReturn, UseQueryCacheOptions } from './useQueryCache';
|
|
51
55
|
|
|
52
56
|
// DataTable performance hook
|
|
53
57
|
export { useDataTablePerformance } from './useDataTablePerformance';
|
|
@@ -56,6 +60,8 @@ export type { UseDataTablePerformanceOptions, UseDataTablePerformanceReturn } fr
|
|
|
56
60
|
// === FILE DISPLAY HOOKS ===
|
|
57
61
|
export { useFileDisplay, clearFileDisplayCache, getFileDisplayCacheStats, invalidateFileDisplayCache } from './useFileDisplay';
|
|
58
62
|
export type { UseFileDisplayReturn, UseFileDisplayOptions } from './useFileDisplay';
|
|
63
|
+
export { useFileUrlCache } from './useFileUrlCache';
|
|
64
|
+
export type { UseFileUrlCacheReturn } from './useFileUrlCache';
|
|
59
65
|
|
|
60
66
|
// === STORAGE HOOKS ===
|
|
61
67
|
export { useStorage, useFileUpload } from './useStorage';
|
|
@@ -64,6 +70,9 @@ export type { UseStorageOptions, UseStorageReturn } from './useStorage';
|
|
|
64
70
|
// === PUBLIC DATA ACCESS HOOKS ===
|
|
65
71
|
export * from './public';
|
|
66
72
|
|
|
73
|
+
// === PAGE LIFECYCLE HOOKS ===
|
|
74
|
+
export { usePreventTabReload } from './usePreventTabReload';
|
|
75
|
+
export type { UsePreventTabReloadOptions } from './usePreventTabReload';
|
|
67
76
|
|
|
68
77
|
// RBAC Hooks - Use @jmruthers/pace-core/rbac instead
|
|
69
78
|
// Note: RBAC functionality has been moved to the dedicated RBAC module
|
|
@@ -40,7 +40,7 @@ import { useState, useEffect, useCallback } from 'react';
|
|
|
40
40
|
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
41
41
|
import type { Database } from '../../types/database';
|
|
42
42
|
import { FileReference, FileCategory } from '../../types/file-reference';
|
|
43
|
-
import { getPublicUrl } from '../../utils/storage/helpers';
|
|
43
|
+
import { getPublicUrl, generateFileUrlsBatch } from '../../utils/storage/helpers';
|
|
44
44
|
import { logger } from '../../utils/core/logger';
|
|
45
45
|
|
|
46
46
|
// Simple in-memory cache for public file data
|
|
@@ -236,7 +236,7 @@ export function usePublicFileDisplay(
|
|
|
236
236
|
const ids = fileIds.map((item: any) => item.id);
|
|
237
237
|
const { data: fullData, error: fetchError } = await supabase
|
|
238
238
|
.from('file_references')
|
|
239
|
-
.select('
|
|
239
|
+
.select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')
|
|
240
240
|
.in('id', ids)
|
|
241
241
|
.eq('is_public', true); // Only public files in public context
|
|
242
242
|
|
|
@@ -295,14 +295,12 @@ export function usePublicFileDisplay(
|
|
|
295
295
|
const url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
296
296
|
setFileUrl(url);
|
|
297
297
|
} else {
|
|
298
|
-
// Multiple files mode - generate URLs for all files
|
|
299
|
-
const urlMap =
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
}
|
|
298
|
+
// Multiple files mode - generate URLs for all files in batch
|
|
299
|
+
const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
|
|
300
|
+
appName: 'pace-core',
|
|
301
|
+
orgId: organisation_id,
|
|
302
|
+
expiresIn: 3600
|
|
303
|
+
});
|
|
306
304
|
setFileUrls(urlMap);
|
|
307
305
|
setFileReference(null);
|
|
308
306
|
setFileUrl(null);
|