@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
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useEvents
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-K2JGDXGU.js";
|
|
4
4
|
import {
|
|
5
5
|
useUnifiedAuth
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WK2Y6TGA.js";
|
|
7
7
|
import {
|
|
8
8
|
assertAppId
|
|
9
9
|
} from "./chunk-QXHPKYJV.js";
|
|
10
10
|
import {
|
|
11
|
+
createAddressFromPlaceResult,
|
|
12
|
+
fetchPlaceAutocomplete,
|
|
13
|
+
fetchPlaceDetails,
|
|
14
|
+
getAddressByPlaceId,
|
|
11
15
|
performanceBudgetMonitor
|
|
12
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-YHCN776L.js";
|
|
13
17
|
import {
|
|
14
18
|
setOrganisationContext
|
|
15
19
|
} from "./chunk-VBXEHIUJ.js";
|
|
@@ -23,10 +27,354 @@ import {
|
|
|
23
27
|
logger
|
|
24
28
|
} from "./chunk-PWLANIRT.js";
|
|
25
29
|
|
|
30
|
+
// src/hooks/useDebounce.ts
|
|
31
|
+
import { useState, useEffect } from "react";
|
|
32
|
+
function useDebounce(value, delay) {
|
|
33
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const handler = setTimeout(() => {
|
|
36
|
+
setDebouncedValue(value);
|
|
37
|
+
}, delay);
|
|
38
|
+
return () => {
|
|
39
|
+
clearTimeout(handler);
|
|
40
|
+
};
|
|
41
|
+
}, [value, delay]);
|
|
42
|
+
return debouncedValue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/hooks/useQueryCache.ts
|
|
46
|
+
import { useCallback } from "react";
|
|
47
|
+
var log = createLogger("useQueryCache");
|
|
48
|
+
var queryCache = /* @__PURE__ */ new Map();
|
|
49
|
+
var CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
50
|
+
var cleanupTimer = null;
|
|
51
|
+
function runCacheCleanup() {
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
const expiredKeys = [];
|
|
54
|
+
queryCache.forEach((entry, key) => {
|
|
55
|
+
if (entry.expiresAt <= now) {
|
|
56
|
+
expiredKeys.push(key);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
expiredKeys.forEach((key) => {
|
|
60
|
+
queryCache.delete(key);
|
|
61
|
+
log.debug(`Removed expired query from cache: ${key}`);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (typeof window !== "undefined" && !cleanupTimer) {
|
|
65
|
+
cleanupTimer = setInterval(runCacheCleanup, CLEANUP_INTERVAL_MS);
|
|
66
|
+
log.debug("Query cache cleanup initialized.");
|
|
67
|
+
}
|
|
68
|
+
function useQueryCache(supabase) {
|
|
69
|
+
const getCachedQuery = useCallback(async (table, filterKey, filterValue, fetchFn, options = {}) => {
|
|
70
|
+
const { ttl = 300, enabled = true } = options;
|
|
71
|
+
const cacheKey = `${table}:${filterKey}:${filterValue}`;
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
if (!enabled) {
|
|
74
|
+
return fetchFn();
|
|
75
|
+
}
|
|
76
|
+
const cached = queryCache.get(cacheKey);
|
|
77
|
+
if (cached) {
|
|
78
|
+
if (cached.expiresAt > now && cached.data !== void 0) {
|
|
79
|
+
log.debug(`Cache hit for query: ${cacheKey}`);
|
|
80
|
+
return cached.data;
|
|
81
|
+
}
|
|
82
|
+
if (cached.promise) {
|
|
83
|
+
log.debug(`Waiting for in-flight request: ${cacheKey}`);
|
|
84
|
+
return cached.promise;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
log.debug(`Cache miss for query: ${cacheKey}, fetching...`);
|
|
88
|
+
const fetchPromise = fetchFn();
|
|
89
|
+
queryCache.set(cacheKey, {
|
|
90
|
+
data: void 0,
|
|
91
|
+
expiresAt: now + ttl * 1e3,
|
|
92
|
+
promise: fetchPromise
|
|
93
|
+
});
|
|
94
|
+
try {
|
|
95
|
+
const data = await fetchPromise;
|
|
96
|
+
queryCache.set(cacheKey, {
|
|
97
|
+
data,
|
|
98
|
+
expiresAt: now + ttl * 1e3
|
|
99
|
+
});
|
|
100
|
+
log.debug(`Cached query result: ${cacheKey}, expires in ${ttl}s`);
|
|
101
|
+
return data;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
queryCache.delete(cacheKey);
|
|
104
|
+
log.error(`Query failed for ${cacheKey}:`, error);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}, []);
|
|
108
|
+
const invalidateQuery = useCallback((table, filterKey, filterValue) => {
|
|
109
|
+
const cacheKey = `${table}:${filterKey}:${filterValue}`;
|
|
110
|
+
queryCache.delete(cacheKey);
|
|
111
|
+
log.debug(`Invalidated query cache: ${cacheKey}`);
|
|
112
|
+
}, []);
|
|
113
|
+
const clearCache = useCallback(() => {
|
|
114
|
+
queryCache.clear();
|
|
115
|
+
log.debug("Cleared all query cache entries.");
|
|
116
|
+
}, []);
|
|
117
|
+
const getCacheStats = useCallback(() => {
|
|
118
|
+
return {
|
|
119
|
+
size: queryCache.size,
|
|
120
|
+
keys: Array.from(queryCache.keys())
|
|
121
|
+
};
|
|
122
|
+
}, []);
|
|
123
|
+
return {
|
|
124
|
+
getCachedQuery,
|
|
125
|
+
invalidateQuery,
|
|
126
|
+
clearCache,
|
|
127
|
+
getCacheStats
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
var queryCacheHelpers = {
|
|
131
|
+
/**
|
|
132
|
+
* Cache pace_person queries by user_id
|
|
133
|
+
* TTL: 5 minutes
|
|
134
|
+
*/
|
|
135
|
+
pacePersonByUserId: (supabase, userId, fetchFn) => {
|
|
136
|
+
const cacheKey = `pace_person:user_id:${userId}`;
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
const ttl = 300 * 1e3;
|
|
139
|
+
const cached = queryCache.get(cacheKey);
|
|
140
|
+
if (cached && cached.expiresAt > now && cached.data !== void 0) {
|
|
141
|
+
return Promise.resolve(cached.data);
|
|
142
|
+
}
|
|
143
|
+
if (cached?.promise) {
|
|
144
|
+
return cached.promise;
|
|
145
|
+
}
|
|
146
|
+
const promise = fetchFn();
|
|
147
|
+
queryCache.set(cacheKey, {
|
|
148
|
+
data: void 0,
|
|
149
|
+
expiresAt: now + ttl,
|
|
150
|
+
promise
|
|
151
|
+
});
|
|
152
|
+
promise.then((data) => {
|
|
153
|
+
queryCache.set(cacheKey, { data, expiresAt: now + ttl });
|
|
154
|
+
}).catch(() => {
|
|
155
|
+
queryCache.delete(cacheKey);
|
|
156
|
+
});
|
|
157
|
+
return promise;
|
|
158
|
+
},
|
|
159
|
+
/**
|
|
160
|
+
* Cache pace_member queries by person_id
|
|
161
|
+
* TTL: 5 minutes
|
|
162
|
+
*/
|
|
163
|
+
paceMemberByPersonId: (supabase, personId, fetchFn) => {
|
|
164
|
+
const cacheKey = `pace_member:person_id:${personId}`;
|
|
165
|
+
const now = Date.now();
|
|
166
|
+
const ttl = 300 * 1e3;
|
|
167
|
+
const cached = queryCache.get(cacheKey);
|
|
168
|
+
if (cached && cached.expiresAt > now && cached.data !== void 0) {
|
|
169
|
+
return Promise.resolve(cached.data);
|
|
170
|
+
}
|
|
171
|
+
if (cached?.promise) {
|
|
172
|
+
return cached.promise;
|
|
173
|
+
}
|
|
174
|
+
const promise = fetchFn();
|
|
175
|
+
queryCache.set(cacheKey, {
|
|
176
|
+
data: void 0,
|
|
177
|
+
expiresAt: now + ttl,
|
|
178
|
+
promise
|
|
179
|
+
});
|
|
180
|
+
promise.then((data) => {
|
|
181
|
+
queryCache.set(cacheKey, { data, expiresAt: now + ttl });
|
|
182
|
+
}).catch(() => {
|
|
183
|
+
queryCache.delete(cacheKey);
|
|
184
|
+
});
|
|
185
|
+
return promise;
|
|
186
|
+
},
|
|
187
|
+
/**
|
|
188
|
+
* Cache rbac_app_pages queries by app_id
|
|
189
|
+
* TTL: 15 minutes (app pages are relatively static)
|
|
190
|
+
*/
|
|
191
|
+
rbacAppPagesByAppId: (supabase, appId, fetchFn) => {
|
|
192
|
+
const cacheKey = `rbac_app_pages:app_id:${appId}`;
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
const ttl = 15 * 60 * 1e3;
|
|
195
|
+
const cached = queryCache.get(cacheKey);
|
|
196
|
+
if (cached && cached.expiresAt > now && cached.data !== void 0) {
|
|
197
|
+
return Promise.resolve(cached.data);
|
|
198
|
+
}
|
|
199
|
+
if (cached?.promise) {
|
|
200
|
+
return cached.promise;
|
|
201
|
+
}
|
|
202
|
+
const promise = fetchFn();
|
|
203
|
+
queryCache.set(cacheKey, {
|
|
204
|
+
data: void 0,
|
|
205
|
+
expiresAt: now + ttl,
|
|
206
|
+
promise
|
|
207
|
+
});
|
|
208
|
+
promise.then((data) => {
|
|
209
|
+
queryCache.set(cacheKey, { data, expiresAt: now + ttl });
|
|
210
|
+
}).catch(() => {
|
|
211
|
+
queryCache.delete(cacheKey);
|
|
212
|
+
});
|
|
213
|
+
return promise;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// src/hooks/useAddressAutocomplete.ts
|
|
218
|
+
import { useState as useState2, useEffect as useEffect3, useCallback as useCallback2, useRef as useRef2, useMemo } from "react";
|
|
219
|
+
function useAddressAutocomplete(apiKey, inputValue, options = {}) {
|
|
220
|
+
const {
|
|
221
|
+
debounceDelay = 300,
|
|
222
|
+
cacheEnabled = true,
|
|
223
|
+
cacheTTL = {
|
|
224
|
+
autocomplete: 3600,
|
|
225
|
+
// 1 hour
|
|
226
|
+
placeDetails: 86400
|
|
227
|
+
// 24 hours
|
|
228
|
+
},
|
|
229
|
+
autocompleteOptions
|
|
230
|
+
} = options;
|
|
231
|
+
const [suggestions, setSuggestions] = useState2([]);
|
|
232
|
+
const [isLoading, setIsLoading] = useState2(false);
|
|
233
|
+
const [error, setError] = useState2(null);
|
|
234
|
+
const debouncedInput = useDebounce(inputValue, debounceDelay);
|
|
235
|
+
const { getCachedQuery } = useQueryCache();
|
|
236
|
+
const abortControllerRef = useRef2(null);
|
|
237
|
+
const memoizedAutocompleteOptions = useMemo(
|
|
238
|
+
() => autocompleteOptions,
|
|
239
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
240
|
+
[JSON.stringify(autocompleteOptions)]
|
|
241
|
+
);
|
|
242
|
+
useEffect3(() => {
|
|
243
|
+
if (abortControllerRef.current) {
|
|
244
|
+
abortControllerRef.current.abort();
|
|
245
|
+
}
|
|
246
|
+
if (!debouncedInput.trim()) {
|
|
247
|
+
setSuggestions([]);
|
|
248
|
+
setIsLoading(false);
|
|
249
|
+
setError(null);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!apiKey) {
|
|
253
|
+
setError(new Error("Google Places API key is required"));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
setIsLoading(true);
|
|
257
|
+
setError(null);
|
|
258
|
+
const fetchSuggestions = async () => {
|
|
259
|
+
try {
|
|
260
|
+
let predictions;
|
|
261
|
+
if (cacheEnabled) {
|
|
262
|
+
predictions = await getCachedQuery(
|
|
263
|
+
"google-places-autocomplete",
|
|
264
|
+
"query",
|
|
265
|
+
debouncedInput,
|
|
266
|
+
async () => {
|
|
267
|
+
return fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
|
|
268
|
+
},
|
|
269
|
+
{ ttl: cacheTTL.autocomplete, enabled: true }
|
|
270
|
+
);
|
|
271
|
+
} else {
|
|
272
|
+
predictions = await fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
|
|
273
|
+
}
|
|
274
|
+
setSuggestions(predictions);
|
|
275
|
+
setIsLoading(false);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const error2 = err instanceof Error ? err : new Error("Failed to fetch autocomplete suggestions");
|
|
281
|
+
setError(error2);
|
|
282
|
+
setSuggestions([]);
|
|
283
|
+
setIsLoading(false);
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
fetchSuggestions();
|
|
287
|
+
return () => {
|
|
288
|
+
if (abortControllerRef.current) {
|
|
289
|
+
abortControllerRef.current.abort();
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}, [debouncedInput, apiKey, cacheEnabled, cacheTTL.autocomplete]);
|
|
293
|
+
const selectAddress = useCallback2(
|
|
294
|
+
async (placeId) => {
|
|
295
|
+
if (!placeId || !apiKey) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
setIsLoading(true);
|
|
299
|
+
setError(null);
|
|
300
|
+
try {
|
|
301
|
+
let placeDetails;
|
|
302
|
+
if (cacheEnabled) {
|
|
303
|
+
placeDetails = await getCachedQuery(
|
|
304
|
+
"google-places-details",
|
|
305
|
+
"place_id",
|
|
306
|
+
placeId,
|
|
307
|
+
async () => {
|
|
308
|
+
return fetchPlaceDetails(placeId, apiKey);
|
|
309
|
+
},
|
|
310
|
+
{ ttl: cacheTTL.placeDetails, enabled: true }
|
|
311
|
+
);
|
|
312
|
+
} else {
|
|
313
|
+
placeDetails = await fetchPlaceDetails(placeId, apiKey);
|
|
314
|
+
}
|
|
315
|
+
const parsedAddress = createAddressFromPlaceResult(placeDetails);
|
|
316
|
+
setIsLoading(false);
|
|
317
|
+
return parsedAddress;
|
|
318
|
+
} catch (err) {
|
|
319
|
+
const error2 = err instanceof Error ? err : new Error("Failed to fetch place details");
|
|
320
|
+
setError(error2);
|
|
321
|
+
setIsLoading(false);
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
326
|
+
[apiKey, cacheEnabled, cacheTTL.placeDetails]
|
|
327
|
+
);
|
|
328
|
+
const getAddressByPlaceIdFn = useCallback2(
|
|
329
|
+
async (placeId) => {
|
|
330
|
+
if (!placeId || !apiKey) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
if (cacheEnabled) {
|
|
335
|
+
return await getCachedQuery(
|
|
336
|
+
"google-places-details",
|
|
337
|
+
"place_id",
|
|
338
|
+
placeId,
|
|
339
|
+
async () => {
|
|
340
|
+
const result = await getAddressByPlaceId(placeId, apiKey);
|
|
341
|
+
if (!result) {
|
|
342
|
+
throw new Error("Failed to fetch address");
|
|
343
|
+
}
|
|
344
|
+
return result;
|
|
345
|
+
},
|
|
346
|
+
{ ttl: cacheTTL.placeDetails, enabled: true }
|
|
347
|
+
);
|
|
348
|
+
} else {
|
|
349
|
+
return await getAddressByPlaceId(placeId, apiKey);
|
|
350
|
+
}
|
|
351
|
+
} catch (err) {
|
|
352
|
+
const error2 = err instanceof Error ? err : new Error("Failed to get address by place_id");
|
|
353
|
+
setError(error2);
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
358
|
+
[apiKey, cacheEnabled, cacheTTL.placeDetails]
|
|
359
|
+
);
|
|
360
|
+
const clearSuggestions = useCallback2(() => {
|
|
361
|
+
setSuggestions([]);
|
|
362
|
+
setError(null);
|
|
363
|
+
}, []);
|
|
364
|
+
return {
|
|
365
|
+
suggestions,
|
|
366
|
+
isLoading,
|
|
367
|
+
error,
|
|
368
|
+
selectAddress,
|
|
369
|
+
getAddressByPlaceId: getAddressByPlaceIdFn,
|
|
370
|
+
clearSuggestions
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
26
374
|
// src/hooks/useEventTheme.ts
|
|
27
|
-
import { useEffect } from "react";
|
|
375
|
+
import { useEffect as useEffect4 } from "react";
|
|
28
376
|
import { useLocation } from "react-router-dom";
|
|
29
|
-
var
|
|
377
|
+
var log2 = createLogger("useEventTheme");
|
|
30
378
|
function useEventTheme(event) {
|
|
31
379
|
const location = useLocation();
|
|
32
380
|
let selectedEvent;
|
|
@@ -44,7 +392,7 @@ function useEventTheme(event) {
|
|
|
44
392
|
selectedEvent = null;
|
|
45
393
|
}
|
|
46
394
|
}
|
|
47
|
-
|
|
395
|
+
useEffect4(() => {
|
|
48
396
|
const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login");
|
|
49
397
|
if (isOnLoginRoute) {
|
|
50
398
|
clearPalette();
|
|
@@ -63,13 +411,55 @@ function useEventTheme(event) {
|
|
|
63
411
|
try {
|
|
64
412
|
applyPalette(normalized);
|
|
65
413
|
} catch (error) {
|
|
66
|
-
|
|
414
|
+
log2.error("Failed to apply event palette:", error);
|
|
67
415
|
}
|
|
68
416
|
return () => {
|
|
69
417
|
};
|
|
70
418
|
}, [selectedEvent, location.pathname]);
|
|
71
419
|
}
|
|
72
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
|
+
|
|
73
463
|
// src/components/ErrorBoundary/ErrorBoundary.tsx
|
|
74
464
|
import { Component } from "react";
|
|
75
465
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -217,7 +607,7 @@ var ErrorBoundary = class extends Component {
|
|
|
217
607
|
};
|
|
218
608
|
|
|
219
609
|
// src/components/PublicLayout/PublicPageProvider.tsx
|
|
220
|
-
import { createContext, useContext, useMemo } from "react";
|
|
610
|
+
import { createContext, useContext, useMemo as useMemo2 } from "react";
|
|
221
611
|
import { createClient } from "@supabase/supabase-js";
|
|
222
612
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
223
613
|
var PublicPageContext = createContext(void 0);
|
|
@@ -234,7 +624,7 @@ function PublicPageProvider({ children, appName }) {
|
|
|
234
624
|
};
|
|
235
625
|
const supabaseUrl = getEnvVar("VITE_SUPABASE_URL") || getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || null;
|
|
236
626
|
const supabaseKey = getEnvVar("VITE_SUPABASE_ANON_KEY") || getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || null;
|
|
237
|
-
const supabase =
|
|
627
|
+
const supabase = useMemo2(() => {
|
|
238
628
|
if (!supabaseUrl || !supabaseKey) {
|
|
239
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.");
|
|
240
630
|
return null;
|
|
@@ -267,7 +657,7 @@ function useIsPublicPage() {
|
|
|
267
657
|
}
|
|
268
658
|
|
|
269
659
|
// src/hooks/useAppConfig.ts
|
|
270
|
-
import { useMemo as
|
|
660
|
+
import { useMemo as useMemo3, useContext as useContext2 } from "react";
|
|
271
661
|
function useAppConfig() {
|
|
272
662
|
const isPublicPage = useIsPublicPage();
|
|
273
663
|
const publicPageContext = useContext2(PublicPageContext);
|
|
@@ -285,7 +675,7 @@ function useAppConfig() {
|
|
|
285
675
|
}
|
|
286
676
|
return "PACE";
|
|
287
677
|
};
|
|
288
|
-
return
|
|
678
|
+
return useMemo3(() => ({
|
|
289
679
|
supportsDirectAccess: false,
|
|
290
680
|
// Public pages don't support direct access
|
|
291
681
|
requiresEvent: true,
|
|
@@ -296,14 +686,14 @@ function useAppConfig() {
|
|
|
296
686
|
}
|
|
297
687
|
try {
|
|
298
688
|
const { appConfig, appName } = useUnifiedAuth();
|
|
299
|
-
return
|
|
689
|
+
return useMemo3(() => ({
|
|
300
690
|
supportsDirectAccess: !(appConfig?.requires_event ?? true),
|
|
301
691
|
requiresEvent: appConfig?.requires_event ?? true,
|
|
302
692
|
isLoading: appConfig === null,
|
|
303
693
|
appName
|
|
304
694
|
}), [appConfig?.requires_event, appName]);
|
|
305
695
|
} catch (error) {
|
|
306
|
-
return
|
|
696
|
+
return useMemo3(() => ({
|
|
307
697
|
supportsDirectAccess: false,
|
|
308
698
|
requiresEvent: true,
|
|
309
699
|
isLoading: false,
|
|
@@ -382,7 +772,7 @@ function validateFileSize(file) {
|
|
|
382
772
|
}
|
|
383
773
|
|
|
384
774
|
// src/utils/storage/helpers.ts
|
|
385
|
-
var
|
|
775
|
+
var log3 = createLogger("StorageHelpers");
|
|
386
776
|
function generateFilePath(options, fileName) {
|
|
387
777
|
const { orgId, isPublic = false, customPath } = options;
|
|
388
778
|
if (!orgId) {
|
|
@@ -397,8 +787,8 @@ function generateFilePath(options, fileName) {
|
|
|
397
787
|
if (customPath) {
|
|
398
788
|
return `${orgId}/${customPath}/${fileName}`;
|
|
399
789
|
}
|
|
400
|
-
const
|
|
401
|
-
return `${orgId}/${
|
|
790
|
+
const pathFolder = customPath || "files";
|
|
791
|
+
return `${orgId}/${pathFolder}/${fileName}`;
|
|
402
792
|
}
|
|
403
793
|
function generateUniqueFileName(originalName) {
|
|
404
794
|
const timestamp = Date.now();
|
|
@@ -459,6 +849,33 @@ async function generateFileHash(file) {
|
|
|
459
849
|
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
460
850
|
return `sha256:${hashHex}`;
|
|
461
851
|
}
|
|
852
|
+
async function ensureFolderExists(supabase, folderPath, bucketName) {
|
|
853
|
+
try {
|
|
854
|
+
const { data, error } = await supabase.storage.from(bucketName).list(folderPath, {
|
|
855
|
+
limit: 1
|
|
856
|
+
});
|
|
857
|
+
if (!error) {
|
|
858
|
+
return true;
|
|
859
|
+
}
|
|
860
|
+
const placeholderPath = `${folderPath}/.keep`;
|
|
861
|
+
const placeholderBlob = new Blob([""], { type: "text/plain" });
|
|
862
|
+
const placeholderFile = new File([placeholderBlob], ".keep", { type: "text/plain" });
|
|
863
|
+
const { error: uploadError } = await supabase.storage.from(bucketName).upload(placeholderPath, placeholderFile, {
|
|
864
|
+
cacheControl: "3600",
|
|
865
|
+
upsert: true,
|
|
866
|
+
// Use upsert to avoid errors if file already exists
|
|
867
|
+
contentType: "text/plain"
|
|
868
|
+
});
|
|
869
|
+
if (uploadError) {
|
|
870
|
+
log3.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
return true;
|
|
874
|
+
} catch (error) {
|
|
875
|
+
log3.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
876
|
+
return true;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
462
879
|
async function uploadFile(supabase, file, options) {
|
|
463
880
|
try {
|
|
464
881
|
const sizeValidation = validateFileSize(file);
|
|
@@ -470,8 +887,10 @@ async function uploadFile(supabase, file, options) {
|
|
|
470
887
|
}
|
|
471
888
|
const uniqueFileName = generateUniqueFileName(file.name);
|
|
472
889
|
const filePath = generateFilePath(options, uniqueFileName);
|
|
890
|
+
const folderPath = filePath.substring(0, filePath.lastIndexOf("/"));
|
|
473
891
|
const metadata = await extractFileMetadata(file, options, "current-user");
|
|
474
892
|
const bucketName = getBucketName(options.isPublic || false);
|
|
893
|
+
await ensureFolderExists(supabase, folderPath, bucketName);
|
|
475
894
|
const { data, error } = await supabase.storage.from(bucketName).upload(filePath, file, {
|
|
476
895
|
cacheControl: "3600",
|
|
477
896
|
upsert: false,
|
|
@@ -560,7 +979,7 @@ async function getSignedUrl(supabase, path, options) {
|
|
|
560
979
|
const bucketName = getBucketName(false);
|
|
561
980
|
const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(path, options.expiresIn || 3600);
|
|
562
981
|
if (error) {
|
|
563
|
-
|
|
982
|
+
log3.error("Failed to create signed URL:", error);
|
|
564
983
|
return null;
|
|
565
984
|
}
|
|
566
985
|
return {
|
|
@@ -568,10 +987,105 @@ async function getSignedUrl(supabase, path, options) {
|
|
|
568
987
|
expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1e3).toISOString()
|
|
569
988
|
};
|
|
570
989
|
} catch (error) {
|
|
571
|
-
|
|
990
|
+
log3.error("Failed to create signed URL:", error);
|
|
572
991
|
return null;
|
|
573
992
|
}
|
|
574
993
|
}
|
|
994
|
+
var globalUrlCache = /* @__PURE__ */ new Map();
|
|
995
|
+
var MAX_CACHE_SIZE = 500;
|
|
996
|
+
var DEFAULT_TTL_MS = 3600 * 1e3;
|
|
997
|
+
function getCacheKey(fileId, filePath, isPublic) {
|
|
998
|
+
return `file-url:${fileId}:${isPublic ? "public" : "private"}`;
|
|
999
|
+
}
|
|
1000
|
+
function cleanupUrlCache() {
|
|
1001
|
+
const now = Date.now();
|
|
1002
|
+
for (const [key, value] of globalUrlCache.entries()) {
|
|
1003
|
+
if (value.expiresAt < now) {
|
|
1004
|
+
globalUrlCache.delete(key);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (globalUrlCache.size > MAX_CACHE_SIZE) {
|
|
1008
|
+
const entries = Array.from(globalUrlCache.entries());
|
|
1009
|
+
entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);
|
|
1010
|
+
const toRemove = Math.floor(MAX_CACHE_SIZE * 0.2);
|
|
1011
|
+
for (let i = 0; i < toRemove && i < entries.length; i++) {
|
|
1012
|
+
globalUrlCache.delete(entries[i][0]);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
async function generateFileUrlsBatch(supabase, fileReferences, options) {
|
|
1017
|
+
const urlMap = /* @__PURE__ */ new Map();
|
|
1018
|
+
if (fileReferences.length === 0) {
|
|
1019
|
+
return urlMap;
|
|
1020
|
+
}
|
|
1021
|
+
const now = Date.now();
|
|
1022
|
+
const ttl = (options.expiresIn || 3600) * 1e3;
|
|
1023
|
+
const publicFiles = [];
|
|
1024
|
+
const privateFiles = [];
|
|
1025
|
+
const uncachedFiles = [];
|
|
1026
|
+
for (const fileRef of fileReferences) {
|
|
1027
|
+
const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);
|
|
1028
|
+
const cached = globalUrlCache.get(cacheKey);
|
|
1029
|
+
if (cached && cached.expiresAt > now) {
|
|
1030
|
+
urlMap.set(fileRef.id, cached.url);
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
if (fileRef.is_public) {
|
|
1034
|
+
publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
|
|
1035
|
+
} else {
|
|
1036
|
+
privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
|
|
1037
|
+
}
|
|
1038
|
+
uncachedFiles.push(fileRef);
|
|
1039
|
+
}
|
|
1040
|
+
for (const file of publicFiles) {
|
|
1041
|
+
try {
|
|
1042
|
+
const url = getPublicUrl(supabase, file.file_path, true);
|
|
1043
|
+
if (url) {
|
|
1044
|
+
urlMap.set(file.id, url);
|
|
1045
|
+
const cacheKey = getCacheKey(file.id, file.file_path, true);
|
|
1046
|
+
globalUrlCache.set(cacheKey, {
|
|
1047
|
+
url,
|
|
1048
|
+
expiresAt: now + ttl
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
} catch (err) {
|
|
1052
|
+
log3.error(`Failed to generate public URL for file ${file.id}:`, err);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
if (privateFiles.length > 0) {
|
|
1056
|
+
const signedUrlPromises = privateFiles.map(async (file) => {
|
|
1057
|
+
try {
|
|
1058
|
+
const signedUrlResult = await getSignedUrl(supabase, file.file_path, {
|
|
1059
|
+
appName: options.appName || "pace-core",
|
|
1060
|
+
orgId: options.orgId,
|
|
1061
|
+
expiresIn: options.expiresIn || 3600
|
|
1062
|
+
});
|
|
1063
|
+
const url = signedUrlResult?.url || null;
|
|
1064
|
+
if (url) {
|
|
1065
|
+
const cacheKey = getCacheKey(file.id, file.file_path, false);
|
|
1066
|
+
globalUrlCache.set(cacheKey, {
|
|
1067
|
+
url,
|
|
1068
|
+
expiresAt: now + ttl
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
return { id: file.id, url };
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
log3.error(`Failed to generate signed URL for file ${file.id}:`, err);
|
|
1074
|
+
return { id: file.id, url: null };
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
const signedUrlResults = await Promise.all(signedUrlPromises);
|
|
1078
|
+
for (const result of signedUrlResults) {
|
|
1079
|
+
if (result.url) {
|
|
1080
|
+
urlMap.set(result.id, result.url);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
if (uncachedFiles.length > 0) {
|
|
1085
|
+
cleanupUrlCache();
|
|
1086
|
+
}
|
|
1087
|
+
return urlMap;
|
|
1088
|
+
}
|
|
575
1089
|
async function deleteFile(supabase, path, isPublic = false) {
|
|
576
1090
|
try {
|
|
577
1091
|
const bucketName = getBucketName(isPublic);
|
|
@@ -601,7 +1115,7 @@ async function listFiles(supabase, options) {
|
|
|
601
1115
|
sortBy: { column: "created_at", order: "desc" }
|
|
602
1116
|
});
|
|
603
1117
|
if (error) {
|
|
604
|
-
|
|
1118
|
+
log3.error("Failed to list files:", error);
|
|
605
1119
|
return { files: [], totalCount: 0, hasMore: false };
|
|
606
1120
|
}
|
|
607
1121
|
const files = (data || []).map((item) => ({
|
|
@@ -626,7 +1140,7 @@ async function listFiles(supabase, options) {
|
|
|
626
1140
|
hasMore: files.length >= (options.limit || 100)
|
|
627
1141
|
};
|
|
628
1142
|
} catch (error) {
|
|
629
|
-
|
|
1143
|
+
log3.error("Failed to list files:", error);
|
|
630
1144
|
return { files: [], totalCount: 0, hasMore: false };
|
|
631
1145
|
}
|
|
632
1146
|
}
|
|
@@ -635,7 +1149,7 @@ async function downloadFile(supabase, path, isPublic = false) {
|
|
|
635
1149
|
const bucketName = getBucketName(isPublic);
|
|
636
1150
|
const { data, error } = await supabase.storage.from(bucketName).download(path);
|
|
637
1151
|
if (error) {
|
|
638
|
-
|
|
1152
|
+
log3.error("Failed to download file:", error);
|
|
639
1153
|
return null;
|
|
640
1154
|
}
|
|
641
1155
|
if (!data) {
|
|
@@ -655,7 +1169,7 @@ async function downloadFile(supabase, path, isPublic = false) {
|
|
|
655
1169
|
}
|
|
656
1170
|
};
|
|
657
1171
|
} catch (error) {
|
|
658
|
-
|
|
1172
|
+
log3.error("Failed to download file:", error);
|
|
659
1173
|
return null;
|
|
660
1174
|
}
|
|
661
1175
|
}
|
|
@@ -684,10 +1198,10 @@ async function archiveFile(supabase, path, options) {
|
|
|
684
1198
|
}
|
|
685
1199
|
|
|
686
1200
|
// src/hooks/useFileDisplay.ts
|
|
687
|
-
import { useState, useEffect as
|
|
1201
|
+
import { useState as useState3, useEffect as useEffect6, useCallback as useCallback3 } from "react";
|
|
688
1202
|
|
|
689
1203
|
// src/utils/file-reference/index.ts
|
|
690
|
-
var
|
|
1204
|
+
var log4 = createLogger("FileReferenceService");
|
|
691
1205
|
var FileReferenceServiceImpl = class {
|
|
692
1206
|
constructor(supabase) {
|
|
693
1207
|
this.supabase = supabase;
|
|
@@ -697,7 +1211,7 @@ var FileReferenceServiceImpl = class {
|
|
|
697
1211
|
*
|
|
698
1212
|
* Storage Flow:
|
|
699
1213
|
* 1. Upload file to storage bucket first (files or public-files based on is_public flag)
|
|
700
|
-
* - Path format: {orgId}/{
|
|
1214
|
+
* - Path format: {orgId}/{folder}/{timestamp-uuid-filename}
|
|
701
1215
|
* - Bucket selection: 'files' (private) or 'public-files' (public)
|
|
702
1216
|
* 2. Extract file metadata (dimensions, hash, etc.)
|
|
703
1217
|
* 3. Set organisation context for RLS policies
|
|
@@ -717,12 +1231,15 @@ var FileReferenceServiceImpl = class {
|
|
|
717
1231
|
if (!options.record_id) {
|
|
718
1232
|
throw new Error("record_id is required for file upload");
|
|
719
1233
|
}
|
|
1234
|
+
if (!options.folder) {
|
|
1235
|
+
throw new Error("folder is required for file upload. The folder prop determines the storage path.");
|
|
1236
|
+
}
|
|
720
1237
|
const uploadResult = await uploadFile(this.supabase, file, {
|
|
721
1238
|
appName: "file-reference",
|
|
722
1239
|
orgId: options.organisation_id,
|
|
723
1240
|
isPublic: options.is_public || false,
|
|
724
|
-
customPath: options.
|
|
725
|
-
// Use
|
|
1241
|
+
customPath: options.folder
|
|
1242
|
+
// Use folder prop as the custom path segment
|
|
726
1243
|
});
|
|
727
1244
|
if (!uploadResult.success) {
|
|
728
1245
|
throw new Error(`Failed to upload file: ${uploadResult.error}`);
|
|
@@ -745,6 +1262,8 @@ var FileReferenceServiceImpl = class {
|
|
|
745
1262
|
p_organisation_id: options.organisation_id,
|
|
746
1263
|
p_app_id: options.app_id,
|
|
747
1264
|
p_page_context: options.pageContext,
|
|
1265
|
+
p_event_id: options.event_id || null,
|
|
1266
|
+
// Pass event_id for event-based apps
|
|
748
1267
|
p_file_metadata: {
|
|
749
1268
|
fileName: file.name,
|
|
750
1269
|
fileType: file.type,
|
|
@@ -759,8 +1278,13 @@ var FileReferenceServiceImpl = class {
|
|
|
759
1278
|
await deleteFile(this.supabase, filePath, options.is_public || false);
|
|
760
1279
|
throw new Error(`Failed to create file reference: ${error.message}`);
|
|
761
1280
|
}
|
|
762
|
-
|
|
1281
|
+
if (!data || data === null) {
|
|
1282
|
+
await deleteFile(this.supabase, filePath, options.is_public || false);
|
|
1283
|
+
throw new Error(`File upload denied: insufficient permissions. You need 'create:page.${options.pageContext}' or 'update:page.${options.pageContext}' permission for the '${options.pageContext}' page. Make sure the page exists in rbac_app_pages table.`);
|
|
1284
|
+
}
|
|
1285
|
+
const { data: fileRef, error: fetchError } = await 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("id", data).single();
|
|
763
1286
|
if (fetchError || !fileRef) {
|
|
1287
|
+
await deleteFile(this.supabase, filePath, options.is_public || false);
|
|
764
1288
|
throw new Error(`Failed to fetch created file reference: ${fetchError?.message}`);
|
|
765
1289
|
}
|
|
766
1290
|
invalidateFileDisplayCache(
|
|
@@ -771,13 +1295,13 @@ var FileReferenceServiceImpl = class {
|
|
|
771
1295
|
);
|
|
772
1296
|
return fileRef;
|
|
773
1297
|
} catch (error) {
|
|
774
|
-
|
|
1298
|
+
log4.error("Error creating file reference:", error);
|
|
775
1299
|
throw error;
|
|
776
1300
|
}
|
|
777
1301
|
}
|
|
778
1302
|
async getFileReference(table_name, record_id, organisation_id) {
|
|
779
1303
|
try {
|
|
780
|
-
const { data, error } = await this.supabase.from("file_references").select("
|
|
1304
|
+
const { data, error } = await 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).eq("organisation_id", organisation_id).single();
|
|
781
1305
|
if (error) {
|
|
782
1306
|
if (error.code === "PGRST116") {
|
|
783
1307
|
return null;
|
|
@@ -786,7 +1310,7 @@ var FileReferenceServiceImpl = class {
|
|
|
786
1310
|
}
|
|
787
1311
|
return data;
|
|
788
1312
|
} catch (error) {
|
|
789
|
-
|
|
1313
|
+
log4.error("Error getting file reference:", error);
|
|
790
1314
|
throw error;
|
|
791
1315
|
}
|
|
792
1316
|
}
|
|
@@ -810,7 +1334,7 @@ var FileReferenceServiceImpl = class {
|
|
|
810
1334
|
return await this.getSignedUrl(table_name, record_id, organisation_id);
|
|
811
1335
|
}
|
|
812
1336
|
} catch (error) {
|
|
813
|
-
|
|
1337
|
+
log4.error("Error getting file URL:", error);
|
|
814
1338
|
throw error;
|
|
815
1339
|
}
|
|
816
1340
|
}
|
|
@@ -835,7 +1359,7 @@ var FileReferenceServiceImpl = class {
|
|
|
835
1359
|
});
|
|
836
1360
|
return signedUrlResult?.url || null;
|
|
837
1361
|
} catch (error) {
|
|
838
|
-
|
|
1362
|
+
log4.error("Error getting signed URL:", error);
|
|
839
1363
|
throw error;
|
|
840
1364
|
}
|
|
841
1365
|
}
|
|
@@ -847,7 +1371,7 @@ var FileReferenceServiceImpl = class {
|
|
|
847
1371
|
}
|
|
848
1372
|
return data;
|
|
849
1373
|
} catch (error) {
|
|
850
|
-
|
|
1374
|
+
log4.error("Error updating file reference:", error);
|
|
851
1375
|
throw error;
|
|
852
1376
|
}
|
|
853
1377
|
}
|
|
@@ -868,7 +1392,7 @@ var FileReferenceServiceImpl = class {
|
|
|
868
1392
|
}
|
|
869
1393
|
return true;
|
|
870
1394
|
} catch (error) {
|
|
871
|
-
|
|
1395
|
+
log4.error("Error deleting file reference:", error);
|
|
872
1396
|
throw error;
|
|
873
1397
|
}
|
|
874
1398
|
}
|
|
@@ -885,14 +1409,32 @@ var FileReferenceServiceImpl = class {
|
|
|
885
1409
|
if (!data || data.length === 0) {
|
|
886
1410
|
return [];
|
|
887
1411
|
}
|
|
888
|
-
const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1412
|
+
const fileReferences = data.filter((item) => item.id && item.file_path && item.file_metadata).map((item) => {
|
|
1413
|
+
const fileName = item.file_path.split("/").pop() || "unknown";
|
|
1414
|
+
const fileType = fileName.split(".").pop() || "unknown";
|
|
1415
|
+
const fileRef = {
|
|
1416
|
+
id: item.id,
|
|
1417
|
+
table_name,
|
|
1418
|
+
record_id,
|
|
1419
|
+
file_path: item.file_path,
|
|
1420
|
+
file_metadata: {
|
|
1421
|
+
fileName,
|
|
1422
|
+
fileType,
|
|
1423
|
+
...item.file_metadata || {}
|
|
1424
|
+
},
|
|
1425
|
+
organisation_id,
|
|
1426
|
+
app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
|
|
1427
|
+
// May not be in metadata, use empty string
|
|
1428
|
+
is_public: item.is_public ?? false,
|
|
1429
|
+
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1430
|
+
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1431
|
+
// RPC doesn't return updated_at, use created_at
|
|
1432
|
+
};
|
|
1433
|
+
return fileRef;
|
|
1434
|
+
});
|
|
1435
|
+
return fileReferences;
|
|
894
1436
|
} catch (error) {
|
|
895
|
-
|
|
1437
|
+
log4.error("Error listing file references:", error);
|
|
896
1438
|
throw error;
|
|
897
1439
|
}
|
|
898
1440
|
}
|
|
@@ -908,7 +1450,7 @@ var FileReferenceServiceImpl = class {
|
|
|
908
1450
|
}
|
|
909
1451
|
return data || 0;
|
|
910
1452
|
} catch (error) {
|
|
911
|
-
|
|
1453
|
+
log4.error("Error getting file count:", error);
|
|
912
1454
|
throw error;
|
|
913
1455
|
}
|
|
914
1456
|
}
|
|
@@ -926,7 +1468,7 @@ var FileReferenceServiceImpl = class {
|
|
|
926
1468
|
}
|
|
927
1469
|
return data[0];
|
|
928
1470
|
} catch (error) {
|
|
929
|
-
|
|
1471
|
+
log4.error("Error getting file reference by ID:", error);
|
|
930
1472
|
throw error;
|
|
931
1473
|
}
|
|
932
1474
|
}
|
|
@@ -939,7 +1481,7 @@ var FileReferenceServiceImpl = class {
|
|
|
939
1481
|
p_organisation_id: organisation_id
|
|
940
1482
|
});
|
|
941
1483
|
if (error) {
|
|
942
|
-
|
|
1484
|
+
log4.error("RPC ERROR getting files by category:", {
|
|
943
1485
|
error,
|
|
944
1486
|
errorCode: error.code,
|
|
945
1487
|
errorMessage: error.message,
|
|
@@ -959,7 +1501,7 @@ var FileReferenceServiceImpl = class {
|
|
|
959
1501
|
const fileCategory = item.file_metadata?.category;
|
|
960
1502
|
const matches = fileCategory === category;
|
|
961
1503
|
if (!matches) {
|
|
962
|
-
|
|
1504
|
+
log4.warn("File category mismatch in RPC response:", {
|
|
963
1505
|
fileId: item.id,
|
|
964
1506
|
expectedCategory: category,
|
|
965
1507
|
actualCategory: fileCategory
|
|
@@ -992,7 +1534,7 @@ var FileReferenceServiceImpl = class {
|
|
|
992
1534
|
});
|
|
993
1535
|
return fileReferences;
|
|
994
1536
|
} catch (error) {
|
|
995
|
-
|
|
1537
|
+
log4.error("Error getting files by category:", error);
|
|
996
1538
|
throw error;
|
|
997
1539
|
}
|
|
998
1540
|
}
|
|
@@ -1047,7 +1589,7 @@ async function uploadFileWithReference(supabase, options, file) {
|
|
|
1047
1589
|
|
|
1048
1590
|
// src/hooks/useFileDisplay.ts
|
|
1049
1591
|
var authenticatedFileCache = /* @__PURE__ */ new Map();
|
|
1050
|
-
var
|
|
1592
|
+
var MAX_CACHE_SIZE2 = 100;
|
|
1051
1593
|
function cleanupCache() {
|
|
1052
1594
|
const now = Date.now();
|
|
1053
1595
|
const entries = Array.from(authenticatedFileCache.entries());
|
|
@@ -1058,9 +1600,9 @@ function cleanupCache() {
|
|
|
1058
1600
|
}
|
|
1059
1601
|
});
|
|
1060
1602
|
expiredKeys.forEach((key) => authenticatedFileCache.delete(key));
|
|
1061
|
-
if (authenticatedFileCache.size >
|
|
1603
|
+
if (authenticatedFileCache.size > MAX_CACHE_SIZE2) {
|
|
1062
1604
|
const sorted = entries.filter(([key]) => !expiredKeys.includes(key)).sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
1063
|
-
const toRemove = sorted.slice(0, authenticatedFileCache.size -
|
|
1605
|
+
const toRemove = sorted.slice(0, authenticatedFileCache.size - MAX_CACHE_SIZE2);
|
|
1064
1606
|
toRemove.forEach(([key]) => authenticatedFileCache.delete(key));
|
|
1065
1607
|
}
|
|
1066
1608
|
}
|
|
@@ -1071,14 +1613,14 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1071
1613
|
enableCache = true,
|
|
1072
1614
|
supabase
|
|
1073
1615
|
} = options;
|
|
1074
|
-
const [fileUrl, setFileUrl] =
|
|
1075
|
-
const [fileReference, setFileReference] =
|
|
1076
|
-
const [fileReferences, setFileReferences] =
|
|
1077
|
-
const [fileUrls, setFileUrls] =
|
|
1078
|
-
const [fileCount, setFileCount] =
|
|
1079
|
-
const [isLoading, setIsLoading] =
|
|
1080
|
-
const [error, setError] =
|
|
1081
|
-
const fetchFiles =
|
|
1616
|
+
const [fileUrl, setFileUrl] = useState3(null);
|
|
1617
|
+
const [fileReference, setFileReference] = useState3(null);
|
|
1618
|
+
const [fileReferences, setFileReferences] = useState3([]);
|
|
1619
|
+
const [fileUrls, setFileUrls] = useState3(/* @__PURE__ */ new Map());
|
|
1620
|
+
const [fileCount, setFileCount] = useState3(0);
|
|
1621
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
1622
|
+
const [error, setError] = useState3(null);
|
|
1623
|
+
const fetchFiles = useCallback3(async () => {
|
|
1082
1624
|
if (!table_name || !record_id || !organisation_id || !supabase) {
|
|
1083
1625
|
setFileUrl(null);
|
|
1084
1626
|
setFileReference(null);
|
|
@@ -1196,23 +1738,11 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1196
1738
|
logger.debug("useFileDisplay", "Setting file URL:", url ? "URL set" : "URL is null");
|
|
1197
1739
|
setFileUrl(url);
|
|
1198
1740
|
} else {
|
|
1199
|
-
const urlMap =
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
} else {
|
|
1205
|
-
const signedUrlResult = await getSignedUrl(supabase, fileRef.file_path, {
|
|
1206
|
-
appName: "pace-core",
|
|
1207
|
-
orgId: organisation_id,
|
|
1208
|
-
expiresIn: 3600
|
|
1209
|
-
});
|
|
1210
|
-
url = signedUrlResult?.url || null;
|
|
1211
|
-
}
|
|
1212
|
-
if (url) {
|
|
1213
|
-
urlMap.set(fileRef.id, url);
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1741
|
+
const urlMap = await generateFileUrlsBatch(supabase, files, {
|
|
1742
|
+
appName: "pace-core",
|
|
1743
|
+
orgId: organisation_id,
|
|
1744
|
+
expiresIn: 3600
|
|
1745
|
+
});
|
|
1216
1746
|
setFileUrls(urlMap);
|
|
1217
1747
|
setFileReference(null);
|
|
1218
1748
|
setFileUrl(null);
|
|
@@ -1263,7 +1793,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1263
1793
|
setIsLoading(false);
|
|
1264
1794
|
}
|
|
1265
1795
|
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1266
|
-
|
|
1796
|
+
useEffect6(() => {
|
|
1267
1797
|
if (table_name && record_id && organisation_id && supabase) {
|
|
1268
1798
|
fetchFiles();
|
|
1269
1799
|
} else {
|
|
@@ -1276,7 +1806,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1276
1806
|
setError(null);
|
|
1277
1807
|
}
|
|
1278
1808
|
}, [fetchFiles, table_name, record_id, organisation_id, supabase]);
|
|
1279
|
-
const refetch =
|
|
1809
|
+
const refetch = useCallback3(async () => {
|
|
1280
1810
|
if (!table_name || !record_id || !organisation_id || !supabase) return;
|
|
1281
1811
|
if (enableCache) {
|
|
1282
1812
|
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
@@ -1319,7 +1849,7 @@ function invalidateFileDisplayCache(table_name, record_id, organisation_id, cate
|
|
|
1319
1849
|
}
|
|
1320
1850
|
|
|
1321
1851
|
// src/hooks/public/usePublicFileDisplay.ts
|
|
1322
|
-
import { useState as
|
|
1852
|
+
import { useState as useState4, useEffect as useEffect7, useCallback as useCallback4 } from "react";
|
|
1323
1853
|
var publicFileCache = /* @__PURE__ */ new Map();
|
|
1324
1854
|
function usePublicFileDisplay(table_name, record_id, organisation_id, category, options) {
|
|
1325
1855
|
const {
|
|
@@ -1328,14 +1858,14 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1328
1858
|
enableCache = true,
|
|
1329
1859
|
supabase
|
|
1330
1860
|
} = options;
|
|
1331
|
-
const [fileUrl, setFileUrl] =
|
|
1332
|
-
const [fileReference, setFileReference] =
|
|
1333
|
-
const [fileReferences, setFileReferences] =
|
|
1334
|
-
const [fileUrls, setFileUrls] =
|
|
1335
|
-
const [fileCount, setFileCount] =
|
|
1336
|
-
const [isLoading, setIsLoading] =
|
|
1337
|
-
const [error, setError] =
|
|
1338
|
-
const fetchFiles =
|
|
1861
|
+
const [fileUrl, setFileUrl] = useState4(null);
|
|
1862
|
+
const [fileReference, setFileReference] = useState4(null);
|
|
1863
|
+
const [fileReferences, setFileReferences] = useState4([]);
|
|
1864
|
+
const [fileUrls, setFileUrls] = useState4(/* @__PURE__ */ new Map());
|
|
1865
|
+
const [fileCount, setFileCount] = useState4(0);
|
|
1866
|
+
const [isLoading, setIsLoading] = useState4(false);
|
|
1867
|
+
const [error, setError] = useState4(null);
|
|
1868
|
+
const fetchFiles = useCallback4(async () => {
|
|
1339
1869
|
if (!table_name || !record_id || !organisation_id || !supabase) {
|
|
1340
1870
|
setFileUrl(null);
|
|
1341
1871
|
setFileReference(null);
|
|
@@ -1434,7 +1964,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1434
1964
|
files = [];
|
|
1435
1965
|
} else {
|
|
1436
1966
|
const ids = fileIds.map((item) => item.id);
|
|
1437
|
-
const { data: fullData, error: fetchError } = await supabase.from("file_references").select("
|
|
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);
|
|
1438
1968
|
if (fetchError) {
|
|
1439
1969
|
throw new Error(fetchError.message || "Failed to fetch file references");
|
|
1440
1970
|
}
|
|
@@ -1477,13 +2007,11 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1477
2007
|
const url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
1478
2008
|
setFileUrl(url);
|
|
1479
2009
|
} else {
|
|
1480
|
-
const urlMap =
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
2010
|
+
const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
|
|
2011
|
+
appName: "pace-core",
|
|
2012
|
+
orgId: organisation_id,
|
|
2013
|
+
expiresIn: 3600
|
|
2014
|
+
});
|
|
1487
2015
|
setFileUrls(urlMap);
|
|
1488
2016
|
setFileReference(null);
|
|
1489
2017
|
setFileUrl(null);
|
|
@@ -1530,7 +2058,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1530
2058
|
setIsLoading(false);
|
|
1531
2059
|
}
|
|
1532
2060
|
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1533
|
-
|
|
2061
|
+
useEffect7(() => {
|
|
1534
2062
|
if (table_name && record_id && organisation_id) {
|
|
1535
2063
|
fetchFiles();
|
|
1536
2064
|
} else {
|
|
@@ -1543,7 +2071,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1543
2071
|
setError(null);
|
|
1544
2072
|
}
|
|
1545
2073
|
}, [fetchFiles, table_name, record_id, organisation_id]);
|
|
1546
|
-
const refetch =
|
|
2074
|
+
const refetch = useCallback4(async () => {
|
|
1547
2075
|
if (!table_name || !record_id || !organisation_id) return;
|
|
1548
2076
|
if (enableCache) {
|
|
1549
2077
|
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
@@ -1578,7 +2106,12 @@ function getPublicFileDisplayCacheStats() {
|
|
|
1578
2106
|
}
|
|
1579
2107
|
|
|
1580
2108
|
export {
|
|
2109
|
+
useDebounce,
|
|
2110
|
+
useQueryCache,
|
|
2111
|
+
queryCacheHelpers,
|
|
2112
|
+
useAddressAutocomplete,
|
|
1581
2113
|
useEventTheme,
|
|
2114
|
+
usePreventTabReload,
|
|
1582
2115
|
ErrorBoundary,
|
|
1583
2116
|
PublicPageContext,
|
|
1584
2117
|
PublicPageProvider,
|
|
@@ -1598,6 +2131,7 @@ export {
|
|
|
1598
2131
|
uploadFile,
|
|
1599
2132
|
getPublicUrl,
|
|
1600
2133
|
getSignedUrl,
|
|
2134
|
+
generateFileUrlsBatch,
|
|
1601
2135
|
deleteFile,
|
|
1602
2136
|
listFiles,
|
|
1603
2137
|
downloadFile,
|
|
@@ -1612,4 +2146,4 @@ export {
|
|
|
1612
2146
|
clearPublicFileDisplayCache,
|
|
1613
2147
|
getPublicFileDisplayCacheStats
|
|
1614
2148
|
};
|
|
1615
|
-
//# sourceMappingURL=chunk-
|
|
2149
|
+
//# sourceMappingURL=chunk-C4OYJOV4.js.map
|