@jmruthers/pace-core 0.5.186 → 0.5.188
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-IX2NBUTP.js → DataTable-GUFUNZ3N.js} +7 -7
- package/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
- package/dist/{PublicPageProvider-DIzEzwKl.d.ts → PublicPageProvider-DrLDztHt.d.ts} +211 -106
- package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-643PUAIM.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-HGPQUCBC.js → chunk-2UUZZJFT.js} +3 -3
- package/dist/{chunk-445GEP27.js → chunk-3GOZZZYH.js} +33 -8
- package/dist/chunk-3GOZZZYH.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-DAGICKHT.js → chunk-DDM4CCYT.js} +3 -3
- package/dist/{chunk-XAUHJD3L.js → chunk-E7UAOUMY.js} +2 -2
- package/dist/{chunk-HDCUMOOI.js → chunk-EFCLXK7F.js} +792 -559
- package/dist/chunk-EFCLXK7F.js.map +1 -0
- package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
- package/dist/chunk-HEHYGYOX.js.map +1 -0
- package/dist/{chunk-GRIQLQ52.js → chunk-IM4QE42D.js} +27 -23
- package/dist/chunk-IM4QE42D.js.map +1 -0
- package/dist/{chunk-OALXJH4Y.js → chunk-IPCH26AG.js} +8 -8
- package/dist/chunk-IPCH26AG.js.map +1 -0
- package/dist/{chunk-UQWSHFVX.js → chunk-SAUPYVLF.js} +1 -1
- package/dist/{chunk-UQWSHFVX.js.map → chunk-SAUPYVLF.js.map} +1 -1
- package/dist/{chunk-TC7D3CR3.js → chunk-UNOTYLQF.js} +556 -101
- package/dist/chunk-UNOTYLQF.js.map +1 -0
- package/dist/{chunk-FXFJRTKI.js → chunk-VGZZXKBR.js} +5 -5
- package/dist/chunk-VGZZXKBR.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/{file-reference-PRTSLxKx.d.ts → file-reference-D037xOFK.d.ts} +0 -1
- package/dist/hooks.d.ts +221 -6
- package/dist/hooks.js +146 -49
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +24 -9
- package/dist/index.js +62 -28
- package/dist/index.js.map +1 -1
- package/dist/providers.js +1 -1
- package/dist/rbac/index.d.ts +124 -7
- 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 +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-D71QLlg4.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +2 -2
- package/dist/utils.d.ts +213 -3
- 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 +5 -5
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +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 +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +26 -3
- 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 +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- 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 +318 -59
- package/docs/best-practices/performance.md +11 -0
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/implementation-guides/file-upload-storage.md +29 -0
- package/docs/implementation-guides/public-pages.md +140 -1230
- 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 +14 -0
- package/docs/standards/07-rbac-and-rls-standard.md +356 -0
- package/package.json +1 -1
- package/src/__tests__/public-recipe-view.test.ts +199 -0
- package/src/__tests__/rls-policies.test.ts +333 -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/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 +6 -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 +20 -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/useQueryCache.ts +315 -0
- package/src/index.ts +2 -0
- package/src/providers/services/EventServiceProvider.tsx +4 -1
- 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/services/OrganisationService.ts +5 -4
- package/src/types/file-reference.ts +0 -1
- package/src/utils/file-reference/__tests__/file-reference.test.ts +31 -4
- package/src/utils/file-reference/index.ts +44 -15
- 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/storage/helpers.ts +143 -4
- package/dist/chunk-445GEP27.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-GRIQLQ52.js.map +0 -1
- package/dist/chunk-HDCUMOOI.js.map +0 -1
- package/dist/chunk-OALXJH4Y.js.map +0 -1
- package/dist/chunk-TC7D3CR3.js.map +0 -1
- package/dist/chunk-U6WNSFX5.js.map +0 -1
- /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-643PUAIM.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-2UUZZJFT.js.map} +0 -0
- /package/dist/{chunk-DAGICKHT.js.map → chunk-DDM4CCYT.js.map} +0 -0
- /package/dist/{chunk-XAUHJD3L.js.map → chunk-E7UAOUMY.js.map} +0 -0
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useEvents
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-E7UAOUMY.js";
|
|
4
4
|
import {
|
|
5
5
|
useUnifiedAuth
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VGZZXKBR.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,7 +411,7 @@ 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
|
};
|
|
@@ -71,12 +419,12 @@ function useEventTheme(event) {
|
|
|
71
419
|
}
|
|
72
420
|
|
|
73
421
|
// src/hooks/usePreventTabReload.ts
|
|
74
|
-
import { useEffect as
|
|
422
|
+
import { useEffect as useEffect5, useRef as useRef3 } from "react";
|
|
75
423
|
function usePreventTabReload(options = {}) {
|
|
76
424
|
const { enabled = true, gracePeriodMs = 2e3 } = options;
|
|
77
|
-
const isRestoringFromCacheRef =
|
|
78
|
-
const gracePeriodTimeoutRef =
|
|
79
|
-
|
|
425
|
+
const isRestoringFromCacheRef = useRef3(false);
|
|
426
|
+
const gracePeriodTimeoutRef = useRef3(null);
|
|
427
|
+
useEffect5(() => {
|
|
80
428
|
if (!enabled || typeof window === "undefined") return;
|
|
81
429
|
const handlePageShow = (event) => {
|
|
82
430
|
if (event.persisted) {
|
|
@@ -259,7 +607,7 @@ var ErrorBoundary = class extends Component {
|
|
|
259
607
|
};
|
|
260
608
|
|
|
261
609
|
// src/components/PublicLayout/PublicPageProvider.tsx
|
|
262
|
-
import { createContext, useContext, useMemo } from "react";
|
|
610
|
+
import { createContext, useContext, useMemo as useMemo2 } from "react";
|
|
263
611
|
import { createClient } from "@supabase/supabase-js";
|
|
264
612
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
265
613
|
var PublicPageContext = createContext(void 0);
|
|
@@ -276,7 +624,7 @@ function PublicPageProvider({ children, appName }) {
|
|
|
276
624
|
};
|
|
277
625
|
const supabaseUrl = getEnvVar("VITE_SUPABASE_URL") || getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || null;
|
|
278
626
|
const supabaseKey = getEnvVar("VITE_SUPABASE_ANON_KEY") || getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || null;
|
|
279
|
-
const supabase =
|
|
627
|
+
const supabase = useMemo2(() => {
|
|
280
628
|
if (!supabaseUrl || !supabaseKey) {
|
|
281
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.");
|
|
282
630
|
return null;
|
|
@@ -309,7 +657,7 @@ function useIsPublicPage() {
|
|
|
309
657
|
}
|
|
310
658
|
|
|
311
659
|
// src/hooks/useAppConfig.ts
|
|
312
|
-
import { useMemo as
|
|
660
|
+
import { useMemo as useMemo3, useContext as useContext2 } from "react";
|
|
313
661
|
function useAppConfig() {
|
|
314
662
|
const isPublicPage = useIsPublicPage();
|
|
315
663
|
const publicPageContext = useContext2(PublicPageContext);
|
|
@@ -327,7 +675,7 @@ function useAppConfig() {
|
|
|
327
675
|
}
|
|
328
676
|
return "PACE";
|
|
329
677
|
};
|
|
330
|
-
return
|
|
678
|
+
return useMemo3(() => ({
|
|
331
679
|
supportsDirectAccess: false,
|
|
332
680
|
// Public pages don't support direct access
|
|
333
681
|
requiresEvent: true,
|
|
@@ -338,14 +686,14 @@ function useAppConfig() {
|
|
|
338
686
|
}
|
|
339
687
|
try {
|
|
340
688
|
const { appConfig, appName } = useUnifiedAuth();
|
|
341
|
-
return
|
|
689
|
+
return useMemo3(() => ({
|
|
342
690
|
supportsDirectAccess: !(appConfig?.requires_event ?? true),
|
|
343
691
|
requiresEvent: appConfig?.requires_event ?? true,
|
|
344
692
|
isLoading: appConfig === null,
|
|
345
693
|
appName
|
|
346
694
|
}), [appConfig?.requires_event, appName]);
|
|
347
695
|
} catch (error) {
|
|
348
|
-
return
|
|
696
|
+
return useMemo3(() => ({
|
|
349
697
|
supportsDirectAccess: false,
|
|
350
698
|
requiresEvent: true,
|
|
351
699
|
isLoading: false,
|
|
@@ -424,7 +772,7 @@ function validateFileSize(file) {
|
|
|
424
772
|
}
|
|
425
773
|
|
|
426
774
|
// src/utils/storage/helpers.ts
|
|
427
|
-
var
|
|
775
|
+
var log3 = createLogger("StorageHelpers");
|
|
428
776
|
function generateFilePath(options, fileName) {
|
|
429
777
|
const { orgId, isPublic = false, customPath } = options;
|
|
430
778
|
if (!orgId) {
|
|
@@ -439,8 +787,8 @@ function generateFilePath(options, fileName) {
|
|
|
439
787
|
if (customPath) {
|
|
440
788
|
return `${orgId}/${customPath}/${fileName}`;
|
|
441
789
|
}
|
|
442
|
-
const
|
|
443
|
-
return `${orgId}/${
|
|
790
|
+
const pathFolder = customPath || "files";
|
|
791
|
+
return `${orgId}/${pathFolder}/${fileName}`;
|
|
444
792
|
}
|
|
445
793
|
function generateUniqueFileName(originalName) {
|
|
446
794
|
const timestamp = Date.now();
|
|
@@ -519,12 +867,12 @@ async function ensureFolderExists(supabase, folderPath, bucketName) {
|
|
|
519
867
|
contentType: "text/plain"
|
|
520
868
|
});
|
|
521
869
|
if (uploadError) {
|
|
522
|
-
|
|
870
|
+
log3.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
|
|
523
871
|
return true;
|
|
524
872
|
}
|
|
525
873
|
return true;
|
|
526
874
|
} catch (error) {
|
|
527
|
-
|
|
875
|
+
log3.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
528
876
|
return true;
|
|
529
877
|
}
|
|
530
878
|
}
|
|
@@ -631,7 +979,7 @@ async function getSignedUrl(supabase, path, options) {
|
|
|
631
979
|
const bucketName = getBucketName(false);
|
|
632
980
|
const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(path, options.expiresIn || 3600);
|
|
633
981
|
if (error) {
|
|
634
|
-
|
|
982
|
+
log3.error("Failed to create signed URL:", error);
|
|
635
983
|
return null;
|
|
636
984
|
}
|
|
637
985
|
return {
|
|
@@ -639,10 +987,105 @@ async function getSignedUrl(supabase, path, options) {
|
|
|
639
987
|
expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1e3).toISOString()
|
|
640
988
|
};
|
|
641
989
|
} catch (error) {
|
|
642
|
-
|
|
990
|
+
log3.error("Failed to create signed URL:", error);
|
|
643
991
|
return null;
|
|
644
992
|
}
|
|
645
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
|
+
}
|
|
646
1089
|
async function deleteFile(supabase, path, isPublic = false) {
|
|
647
1090
|
try {
|
|
648
1091
|
const bucketName = getBucketName(isPublic);
|
|
@@ -672,7 +1115,7 @@ async function listFiles(supabase, options) {
|
|
|
672
1115
|
sortBy: { column: "created_at", order: "desc" }
|
|
673
1116
|
});
|
|
674
1117
|
if (error) {
|
|
675
|
-
|
|
1118
|
+
log3.error("Failed to list files:", error);
|
|
676
1119
|
return { files: [], totalCount: 0, hasMore: false };
|
|
677
1120
|
}
|
|
678
1121
|
const files = (data || []).map((item) => ({
|
|
@@ -697,7 +1140,7 @@ async function listFiles(supabase, options) {
|
|
|
697
1140
|
hasMore: files.length >= (options.limit || 100)
|
|
698
1141
|
};
|
|
699
1142
|
} catch (error) {
|
|
700
|
-
|
|
1143
|
+
log3.error("Failed to list files:", error);
|
|
701
1144
|
return { files: [], totalCount: 0, hasMore: false };
|
|
702
1145
|
}
|
|
703
1146
|
}
|
|
@@ -706,7 +1149,7 @@ async function downloadFile(supabase, path, isPublic = false) {
|
|
|
706
1149
|
const bucketName = getBucketName(isPublic);
|
|
707
1150
|
const { data, error } = await supabase.storage.from(bucketName).download(path);
|
|
708
1151
|
if (error) {
|
|
709
|
-
|
|
1152
|
+
log3.error("Failed to download file:", error);
|
|
710
1153
|
return null;
|
|
711
1154
|
}
|
|
712
1155
|
if (!data) {
|
|
@@ -726,7 +1169,7 @@ async function downloadFile(supabase, path, isPublic = false) {
|
|
|
726
1169
|
}
|
|
727
1170
|
};
|
|
728
1171
|
} catch (error) {
|
|
729
|
-
|
|
1172
|
+
log3.error("Failed to download file:", error);
|
|
730
1173
|
return null;
|
|
731
1174
|
}
|
|
732
1175
|
}
|
|
@@ -755,10 +1198,10 @@ async function archiveFile(supabase, path, options) {
|
|
|
755
1198
|
}
|
|
756
1199
|
|
|
757
1200
|
// src/hooks/useFileDisplay.ts
|
|
758
|
-
import { useState, useEffect as
|
|
1201
|
+
import { useState as useState3, useEffect as useEffect6, useCallback as useCallback3 } from "react";
|
|
759
1202
|
|
|
760
1203
|
// src/utils/file-reference/index.ts
|
|
761
|
-
var
|
|
1204
|
+
var log4 = createLogger("FileReferenceService");
|
|
762
1205
|
var FileReferenceServiceImpl = class {
|
|
763
1206
|
constructor(supabase) {
|
|
764
1207
|
this.supabase = supabase;
|
|
@@ -768,7 +1211,7 @@ var FileReferenceServiceImpl = class {
|
|
|
768
1211
|
*
|
|
769
1212
|
* Storage Flow:
|
|
770
1213
|
* 1. Upload file to storage bucket first (files or public-files based on is_public flag)
|
|
771
|
-
* - Path format: {orgId}/{
|
|
1214
|
+
* - Path format: {orgId}/{folder}/{timestamp-uuid-filename}
|
|
772
1215
|
* - Bucket selection: 'files' (private) or 'public-files' (public)
|
|
773
1216
|
* 2. Extract file metadata (dimensions, hash, etc.)
|
|
774
1217
|
* 3. Set organisation context for RLS policies
|
|
@@ -788,6 +1231,9 @@ var FileReferenceServiceImpl = class {
|
|
|
788
1231
|
if (!options.record_id) {
|
|
789
1232
|
throw new Error("record_id is required for file upload");
|
|
790
1233
|
}
|
|
1234
|
+
if (!options.folder) {
|
|
1235
|
+
throw new Error("folder is required for file upload. The folder prop determines the storage path.");
|
|
1236
|
+
}
|
|
791
1237
|
const uploadResult = await uploadFile(this.supabase, file, {
|
|
792
1238
|
appName: "file-reference",
|
|
793
1239
|
orgId: options.organisation_id,
|
|
@@ -836,7 +1282,7 @@ var FileReferenceServiceImpl = class {
|
|
|
836
1282
|
await deleteFile(this.supabase, filePath, options.is_public || false);
|
|
837
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.`);
|
|
838
1284
|
}
|
|
839
|
-
const { data: fileRef, error: fetchError } = await this.supabase.from("file_references").select("
|
|
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();
|
|
840
1286
|
if (fetchError || !fileRef) {
|
|
841
1287
|
await deleteFile(this.supabase, filePath, options.is_public || false);
|
|
842
1288
|
throw new Error(`Failed to fetch created file reference: ${fetchError?.message}`);
|
|
@@ -849,13 +1295,13 @@ var FileReferenceServiceImpl = class {
|
|
|
849
1295
|
);
|
|
850
1296
|
return fileRef;
|
|
851
1297
|
} catch (error) {
|
|
852
|
-
|
|
1298
|
+
log4.error("Error creating file reference:", error);
|
|
853
1299
|
throw error;
|
|
854
1300
|
}
|
|
855
1301
|
}
|
|
856
1302
|
async getFileReference(table_name, record_id, organisation_id) {
|
|
857
1303
|
try {
|
|
858
|
-
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();
|
|
859
1305
|
if (error) {
|
|
860
1306
|
if (error.code === "PGRST116") {
|
|
861
1307
|
return null;
|
|
@@ -864,7 +1310,7 @@ var FileReferenceServiceImpl = class {
|
|
|
864
1310
|
}
|
|
865
1311
|
return data;
|
|
866
1312
|
} catch (error) {
|
|
867
|
-
|
|
1313
|
+
log4.error("Error getting file reference:", error);
|
|
868
1314
|
throw error;
|
|
869
1315
|
}
|
|
870
1316
|
}
|
|
@@ -888,7 +1334,7 @@ var FileReferenceServiceImpl = class {
|
|
|
888
1334
|
return await this.getSignedUrl(table_name, record_id, organisation_id);
|
|
889
1335
|
}
|
|
890
1336
|
} catch (error) {
|
|
891
|
-
|
|
1337
|
+
log4.error("Error getting file URL:", error);
|
|
892
1338
|
throw error;
|
|
893
1339
|
}
|
|
894
1340
|
}
|
|
@@ -913,7 +1359,7 @@ var FileReferenceServiceImpl = class {
|
|
|
913
1359
|
});
|
|
914
1360
|
return signedUrlResult?.url || null;
|
|
915
1361
|
} catch (error) {
|
|
916
|
-
|
|
1362
|
+
log4.error("Error getting signed URL:", error);
|
|
917
1363
|
throw error;
|
|
918
1364
|
}
|
|
919
1365
|
}
|
|
@@ -925,7 +1371,7 @@ var FileReferenceServiceImpl = class {
|
|
|
925
1371
|
}
|
|
926
1372
|
return data;
|
|
927
1373
|
} catch (error) {
|
|
928
|
-
|
|
1374
|
+
log4.error("Error updating file reference:", error);
|
|
929
1375
|
throw error;
|
|
930
1376
|
}
|
|
931
1377
|
}
|
|
@@ -946,7 +1392,7 @@ var FileReferenceServiceImpl = class {
|
|
|
946
1392
|
}
|
|
947
1393
|
return true;
|
|
948
1394
|
} catch (error) {
|
|
949
|
-
|
|
1395
|
+
log4.error("Error deleting file reference:", error);
|
|
950
1396
|
throw error;
|
|
951
1397
|
}
|
|
952
1398
|
}
|
|
@@ -963,14 +1409,32 @@ var FileReferenceServiceImpl = class {
|
|
|
963
1409
|
if (!data || data.length === 0) {
|
|
964
1410
|
return [];
|
|
965
1411
|
}
|
|
966
|
-
const
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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;
|
|
972
1436
|
} catch (error) {
|
|
973
|
-
|
|
1437
|
+
log4.error("Error listing file references:", error);
|
|
974
1438
|
throw error;
|
|
975
1439
|
}
|
|
976
1440
|
}
|
|
@@ -986,7 +1450,7 @@ var FileReferenceServiceImpl = class {
|
|
|
986
1450
|
}
|
|
987
1451
|
return data || 0;
|
|
988
1452
|
} catch (error) {
|
|
989
|
-
|
|
1453
|
+
log4.error("Error getting file count:", error);
|
|
990
1454
|
throw error;
|
|
991
1455
|
}
|
|
992
1456
|
}
|
|
@@ -1004,7 +1468,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1004
1468
|
}
|
|
1005
1469
|
return data[0];
|
|
1006
1470
|
} catch (error) {
|
|
1007
|
-
|
|
1471
|
+
log4.error("Error getting file reference by ID:", error);
|
|
1008
1472
|
throw error;
|
|
1009
1473
|
}
|
|
1010
1474
|
}
|
|
@@ -1017,7 +1481,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1017
1481
|
p_organisation_id: organisation_id
|
|
1018
1482
|
});
|
|
1019
1483
|
if (error) {
|
|
1020
|
-
|
|
1484
|
+
log4.error("RPC ERROR getting files by category:", {
|
|
1021
1485
|
error,
|
|
1022
1486
|
errorCode: error.code,
|
|
1023
1487
|
errorMessage: error.message,
|
|
@@ -1037,7 +1501,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1037
1501
|
const fileCategory = item.file_metadata?.category;
|
|
1038
1502
|
const matches = fileCategory === category;
|
|
1039
1503
|
if (!matches) {
|
|
1040
|
-
|
|
1504
|
+
log4.warn("File category mismatch in RPC response:", {
|
|
1041
1505
|
fileId: item.id,
|
|
1042
1506
|
expectedCategory: category,
|
|
1043
1507
|
actualCategory: fileCategory
|
|
@@ -1070,7 +1534,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1070
1534
|
});
|
|
1071
1535
|
return fileReferences;
|
|
1072
1536
|
} catch (error) {
|
|
1073
|
-
|
|
1537
|
+
log4.error("Error getting files by category:", error);
|
|
1074
1538
|
throw error;
|
|
1075
1539
|
}
|
|
1076
1540
|
}
|
|
@@ -1125,7 +1589,7 @@ async function uploadFileWithReference(supabase, options, file) {
|
|
|
1125
1589
|
|
|
1126
1590
|
// src/hooks/useFileDisplay.ts
|
|
1127
1591
|
var authenticatedFileCache = /* @__PURE__ */ new Map();
|
|
1128
|
-
var
|
|
1592
|
+
var MAX_CACHE_SIZE2 = 100;
|
|
1129
1593
|
function cleanupCache() {
|
|
1130
1594
|
const now = Date.now();
|
|
1131
1595
|
const entries = Array.from(authenticatedFileCache.entries());
|
|
@@ -1136,9 +1600,9 @@ function cleanupCache() {
|
|
|
1136
1600
|
}
|
|
1137
1601
|
});
|
|
1138
1602
|
expiredKeys.forEach((key) => authenticatedFileCache.delete(key));
|
|
1139
|
-
if (authenticatedFileCache.size >
|
|
1603
|
+
if (authenticatedFileCache.size > MAX_CACHE_SIZE2) {
|
|
1140
1604
|
const sorted = entries.filter(([key]) => !expiredKeys.includes(key)).sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
1141
|
-
const toRemove = sorted.slice(0, authenticatedFileCache.size -
|
|
1605
|
+
const toRemove = sorted.slice(0, authenticatedFileCache.size - MAX_CACHE_SIZE2);
|
|
1142
1606
|
toRemove.forEach(([key]) => authenticatedFileCache.delete(key));
|
|
1143
1607
|
}
|
|
1144
1608
|
}
|
|
@@ -1149,14 +1613,14 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1149
1613
|
enableCache = true,
|
|
1150
1614
|
supabase
|
|
1151
1615
|
} = options;
|
|
1152
|
-
const [fileUrl, setFileUrl] =
|
|
1153
|
-
const [fileReference, setFileReference] =
|
|
1154
|
-
const [fileReferences, setFileReferences] =
|
|
1155
|
-
const [fileUrls, setFileUrls] =
|
|
1156
|
-
const [fileCount, setFileCount] =
|
|
1157
|
-
const [isLoading, setIsLoading] =
|
|
1158
|
-
const [error, setError] =
|
|
1159
|
-
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 () => {
|
|
1160
1624
|
if (!table_name || !record_id || !organisation_id || !supabase) {
|
|
1161
1625
|
setFileUrl(null);
|
|
1162
1626
|
setFileReference(null);
|
|
@@ -1274,23 +1738,11 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1274
1738
|
logger.debug("useFileDisplay", "Setting file URL:", url ? "URL set" : "URL is null");
|
|
1275
1739
|
setFileUrl(url);
|
|
1276
1740
|
} else {
|
|
1277
|
-
const urlMap =
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
} else {
|
|
1283
|
-
const signedUrlResult = await getSignedUrl(supabase, fileRef.file_path, {
|
|
1284
|
-
appName: "pace-core",
|
|
1285
|
-
orgId: organisation_id,
|
|
1286
|
-
expiresIn: 3600
|
|
1287
|
-
});
|
|
1288
|
-
url = signedUrlResult?.url || null;
|
|
1289
|
-
}
|
|
1290
|
-
if (url) {
|
|
1291
|
-
urlMap.set(fileRef.id, url);
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1741
|
+
const urlMap = await generateFileUrlsBatch(supabase, files, {
|
|
1742
|
+
appName: "pace-core",
|
|
1743
|
+
orgId: organisation_id,
|
|
1744
|
+
expiresIn: 3600
|
|
1745
|
+
});
|
|
1294
1746
|
setFileUrls(urlMap);
|
|
1295
1747
|
setFileReference(null);
|
|
1296
1748
|
setFileUrl(null);
|
|
@@ -1341,7 +1793,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1341
1793
|
setIsLoading(false);
|
|
1342
1794
|
}
|
|
1343
1795
|
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1344
|
-
|
|
1796
|
+
useEffect6(() => {
|
|
1345
1797
|
if (table_name && record_id && organisation_id && supabase) {
|
|
1346
1798
|
fetchFiles();
|
|
1347
1799
|
} else {
|
|
@@ -1354,7 +1806,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1354
1806
|
setError(null);
|
|
1355
1807
|
}
|
|
1356
1808
|
}, [fetchFiles, table_name, record_id, organisation_id, supabase]);
|
|
1357
|
-
const refetch =
|
|
1809
|
+
const refetch = useCallback3(async () => {
|
|
1358
1810
|
if (!table_name || !record_id || !organisation_id || !supabase) return;
|
|
1359
1811
|
if (enableCache) {
|
|
1360
1812
|
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
@@ -1397,7 +1849,7 @@ function invalidateFileDisplayCache(table_name, record_id, organisation_id, cate
|
|
|
1397
1849
|
}
|
|
1398
1850
|
|
|
1399
1851
|
// src/hooks/public/usePublicFileDisplay.ts
|
|
1400
|
-
import { useState as
|
|
1852
|
+
import { useState as useState4, useEffect as useEffect7, useCallback as useCallback4 } from "react";
|
|
1401
1853
|
var publicFileCache = /* @__PURE__ */ new Map();
|
|
1402
1854
|
function usePublicFileDisplay(table_name, record_id, organisation_id, category, options) {
|
|
1403
1855
|
const {
|
|
@@ -1406,14 +1858,14 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1406
1858
|
enableCache = true,
|
|
1407
1859
|
supabase
|
|
1408
1860
|
} = options;
|
|
1409
|
-
const [fileUrl, setFileUrl] =
|
|
1410
|
-
const [fileReference, setFileReference] =
|
|
1411
|
-
const [fileReferences, setFileReferences] =
|
|
1412
|
-
const [fileUrls, setFileUrls] =
|
|
1413
|
-
const [fileCount, setFileCount] =
|
|
1414
|
-
const [isLoading, setIsLoading] =
|
|
1415
|
-
const [error, setError] =
|
|
1416
|
-
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 () => {
|
|
1417
1869
|
if (!table_name || !record_id || !organisation_id || !supabase) {
|
|
1418
1870
|
setFileUrl(null);
|
|
1419
1871
|
setFileReference(null);
|
|
@@ -1512,7 +1964,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1512
1964
|
files = [];
|
|
1513
1965
|
} else {
|
|
1514
1966
|
const ids = fileIds.map((item) => item.id);
|
|
1515
|
-
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);
|
|
1516
1968
|
if (fetchError) {
|
|
1517
1969
|
throw new Error(fetchError.message || "Failed to fetch file references");
|
|
1518
1970
|
}
|
|
@@ -1555,13 +2007,11 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1555
2007
|
const url = getPublicUrl(supabase, firstFile.file_path, true);
|
|
1556
2008
|
setFileUrl(url);
|
|
1557
2009
|
} else {
|
|
1558
|
-
const urlMap =
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
2010
|
+
const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
|
|
2011
|
+
appName: "pace-core",
|
|
2012
|
+
orgId: organisation_id,
|
|
2013
|
+
expiresIn: 3600
|
|
2014
|
+
});
|
|
1565
2015
|
setFileUrls(urlMap);
|
|
1566
2016
|
setFileReference(null);
|
|
1567
2017
|
setFileUrl(null);
|
|
@@ -1608,7 +2058,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1608
2058
|
setIsLoading(false);
|
|
1609
2059
|
}
|
|
1610
2060
|
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1611
|
-
|
|
2061
|
+
useEffect7(() => {
|
|
1612
2062
|
if (table_name && record_id && organisation_id) {
|
|
1613
2063
|
fetchFiles();
|
|
1614
2064
|
} else {
|
|
@@ -1621,7 +2071,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1621
2071
|
setError(null);
|
|
1622
2072
|
}
|
|
1623
2073
|
}, [fetchFiles, table_name, record_id, organisation_id]);
|
|
1624
|
-
const refetch =
|
|
2074
|
+
const refetch = useCallback4(async () => {
|
|
1625
2075
|
if (!table_name || !record_id || !organisation_id) return;
|
|
1626
2076
|
if (enableCache) {
|
|
1627
2077
|
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
@@ -1656,6 +2106,10 @@ function getPublicFileDisplayCacheStats() {
|
|
|
1656
2106
|
}
|
|
1657
2107
|
|
|
1658
2108
|
export {
|
|
2109
|
+
useDebounce,
|
|
2110
|
+
useQueryCache,
|
|
2111
|
+
queryCacheHelpers,
|
|
2112
|
+
useAddressAutocomplete,
|
|
1659
2113
|
useEventTheme,
|
|
1660
2114
|
usePreventTabReload,
|
|
1661
2115
|
ErrorBoundary,
|
|
@@ -1677,6 +2131,7 @@ export {
|
|
|
1677
2131
|
uploadFile,
|
|
1678
2132
|
getPublicUrl,
|
|
1679
2133
|
getSignedUrl,
|
|
2134
|
+
generateFileUrlsBatch,
|
|
1680
2135
|
deleteFile,
|
|
1681
2136
|
listFiles,
|
|
1682
2137
|
downloadFile,
|
|
@@ -1691,4 +2146,4 @@ export {
|
|
|
1691
2146
|
clearPublicFileDisplayCache,
|
|
1692
2147
|
getPublicFileDisplayCacheStats
|
|
1693
2148
|
};
|
|
1694
|
-
//# sourceMappingURL=chunk-
|
|
2149
|
+
//# sourceMappingURL=chunk-UNOTYLQF.js.map
|