@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.
Files changed (290) hide show
  1. package/dist/{DataTable-IX2NBUTP.js → DataTable-GUFUNZ3N.js} +7 -7
  2. package/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
  3. package/dist/{PublicPageProvider-DIzEzwKl.d.ts → PublicPageProvider-DrLDztHt.d.ts} +211 -106
  4. package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-643PUAIM.js} +2 -2
  5. package/dist/{api-BMFCXVQX.js → api-YP7XD5L6.js} +3 -3
  6. package/dist/{audit-WRS3KJKI.js → audit-B5P6FFIR.js} +2 -2
  7. package/dist/{chunk-HGPQUCBC.js → chunk-2UUZZJFT.js} +3 -3
  8. package/dist/{chunk-445GEP27.js → chunk-3GOZZZYH.js} +33 -8
  9. package/dist/chunk-3GOZZZYH.js.map +1 -0
  10. package/dist/{chunk-FSFQFJCU.js → chunk-63FOKYGO.js} +174 -6
  11. package/dist/chunk-63FOKYGO.js.map +1 -0
  12. package/dist/{chunk-DAGICKHT.js → chunk-DDM4CCYT.js} +3 -3
  13. package/dist/{chunk-XAUHJD3L.js → chunk-E7UAOUMY.js} +2 -2
  14. package/dist/{chunk-HDCUMOOI.js → chunk-EFCLXK7F.js} +792 -559
  15. package/dist/chunk-EFCLXK7F.js.map +1 -0
  16. package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
  17. package/dist/chunk-HEHYGYOX.js.map +1 -0
  18. package/dist/{chunk-GRIQLQ52.js → chunk-IM4QE42D.js} +27 -23
  19. package/dist/chunk-IM4QE42D.js.map +1 -0
  20. package/dist/{chunk-OALXJH4Y.js → chunk-IPCH26AG.js} +8 -8
  21. package/dist/chunk-IPCH26AG.js.map +1 -0
  22. package/dist/{chunk-UQWSHFVX.js → chunk-SAUPYVLF.js} +1 -1
  23. package/dist/{chunk-UQWSHFVX.js.map → chunk-SAUPYVLF.js.map} +1 -1
  24. package/dist/{chunk-TC7D3CR3.js → chunk-UNOTYLQF.js} +556 -101
  25. package/dist/chunk-UNOTYLQF.js.map +1 -0
  26. package/dist/{chunk-FXFJRTKI.js → chunk-VGZZXKBR.js} +5 -5
  27. package/dist/chunk-VGZZXKBR.js.map +1 -0
  28. package/dist/chunk-YHCN776L.js +447 -0
  29. package/dist/chunk-YHCN776L.js.map +1 -0
  30. package/dist/components.d.ts +4 -4
  31. package/dist/components.js +12 -10
  32. package/dist/components.js.map +1 -1
  33. package/dist/{file-reference-PRTSLxKx.d.ts → file-reference-D037xOFK.d.ts} +0 -1
  34. package/dist/hooks.d.ts +221 -6
  35. package/dist/hooks.js +146 -49
  36. package/dist/hooks.js.map +1 -1
  37. package/dist/index.d.ts +24 -9
  38. package/dist/index.js +62 -28
  39. package/dist/index.js.map +1 -1
  40. package/dist/providers.js +1 -1
  41. package/dist/rbac/index.d.ts +124 -7
  42. package/dist/rbac/index.js +27 -7
  43. package/dist/{types-DUyCRSTj.d.ts → types-Bwgl--Xo.d.ts} +162 -1
  44. package/dist/types.d.ts +1 -1
  45. package/dist/types.js +1 -1
  46. package/dist/{usePublicRouteParams-D71QLlg4.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +2 -2
  47. package/dist/utils.d.ts +213 -3
  48. package/dist/utils.js +22 -2
  49. package/dist/utils.js.map +1 -1
  50. package/docs/api/classes/ColumnFactory.md +1 -1
  51. package/docs/api/classes/ErrorBoundary.md +1 -1
  52. package/docs/api/classes/InvalidScopeError.md +1 -1
  53. package/docs/api/classes/Logger.md +1 -1
  54. package/docs/api/classes/MissingUserContextError.md +1 -1
  55. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  56. package/docs/api/classes/PermissionDeniedError.md +1 -1
  57. package/docs/api/classes/RBACAuditManager.md +21 -17
  58. package/docs/api/classes/RBACCache.md +31 -23
  59. package/docs/api/classes/RBACEngine.md +5 -5
  60. package/docs/api/classes/RBACError.md +1 -1
  61. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  62. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  63. package/docs/api/classes/StorageUtils.md +1 -1
  64. package/docs/api/enums/FileCategory.md +1 -1
  65. package/docs/api/enums/LogLevel.md +1 -1
  66. package/docs/api/enums/RBACErrorCode.md +1 -1
  67. package/docs/api/enums/RPCFunction.md +1 -1
  68. package/docs/api/interfaces/AddressFieldProps.md +241 -0
  69. package/docs/api/interfaces/AddressFieldRef.md +94 -0
  70. package/docs/api/interfaces/AggregateConfig.md +1 -1
  71. package/docs/api/interfaces/AutocompleteOptions.md +75 -0
  72. package/docs/api/interfaces/BadgeProps.md +1 -1
  73. package/docs/api/interfaces/ButtonProps.md +1 -1
  74. package/docs/api/interfaces/CalendarProps.md +1 -1
  75. package/docs/api/interfaces/CardProps.md +1 -1
  76. package/docs/api/interfaces/ColorPalette.md +1 -1
  77. package/docs/api/interfaces/ColorShade.md +1 -1
  78. package/docs/api/interfaces/ComplianceResult.md +1 -1
  79. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  80. package/docs/api/interfaces/DataRecord.md +1 -1
  81. package/docs/api/interfaces/DataTableAction.md +1 -1
  82. package/docs/api/interfaces/DataTableColumn.md +1 -1
  83. package/docs/api/interfaces/DataTableProps.md +1 -1
  84. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  85. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  86. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  87. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  88. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  89. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  90. package/docs/api/interfaces/ExportColumn.md +1 -1
  91. package/docs/api/interfaces/ExportOptions.md +1 -1
  92. package/docs/api/interfaces/FileDisplayProps.md +15 -15
  93. package/docs/api/interfaces/FileMetadata.md +1 -1
  94. package/docs/api/interfaces/FileReference.md +1 -1
  95. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  96. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  97. package/docs/api/interfaces/FileUploadProps.md +1 -1
  98. package/docs/api/interfaces/FooterProps.md +1 -1
  99. package/docs/api/interfaces/FormFieldProps.md +1 -1
  100. package/docs/api/interfaces/FormProps.md +1 -1
  101. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoggerConfig.md +1 -1
  106. package/docs/api/interfaces/LoginFormProps.md +1 -1
  107. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  108. package/docs/api/interfaces/NavigationContextType.md +1 -1
  109. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  110. package/docs/api/interfaces/NavigationItem.md +1 -1
  111. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  112. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  113. package/docs/api/interfaces/Organisation.md +1 -1
  114. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  115. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  116. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  117. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  118. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  119. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  120. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  121. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  122. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  123. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  124. package/docs/api/interfaces/PaletteData.md +1 -1
  125. package/docs/api/interfaces/ParsedAddress.md +120 -0
  126. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  127. package/docs/api/interfaces/ProgressProps.md +1 -1
  128. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  131. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  132. package/docs/api/interfaces/QuickFix.md +1 -1
  133. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  134. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  135. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  136. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  137. package/docs/api/interfaces/RBACConfig.md +26 -3
  138. package/docs/api/interfaces/RBACContext.md +1 -1
  139. package/docs/api/interfaces/RBACLogger.md +5 -5
  140. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  141. package/docs/api/interfaces/RBACPerformanceMetrics.md +138 -0
  142. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  143. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  144. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  146. package/docs/api/interfaces/RBACResult.md +1 -1
  147. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  148. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  149. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  150. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  151. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  152. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  153. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  154. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  155. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  156. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  157. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  158. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  159. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  160. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  161. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  162. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  163. package/docs/api/interfaces/RouteConfig.md +1 -1
  164. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  165. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  166. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  167. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  168. package/docs/api/interfaces/SetupIssue.md +1 -1
  169. package/docs/api/interfaces/StorageConfig.md +1 -1
  170. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  171. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  172. package/docs/api/interfaces/StorageListOptions.md +1 -1
  173. package/docs/api/interfaces/StorageListResult.md +1 -1
  174. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  175. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  176. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  177. package/docs/api/interfaces/StyleImport.md +1 -1
  178. package/docs/api/interfaces/SwitchProps.md +1 -1
  179. package/docs/api/interfaces/TabsContentProps.md +1 -1
  180. package/docs/api/interfaces/TabsListProps.md +1 -1
  181. package/docs/api/interfaces/TabsProps.md +1 -1
  182. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  183. package/docs/api/interfaces/TextareaProps.md +1 -1
  184. package/docs/api/interfaces/ToastActionElement.md +1 -1
  185. package/docs/api/interfaces/ToastProps.md +1 -1
  186. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  187. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  188. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  189. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  190. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  191. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  192. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  193. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  194. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  195. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  196. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  197. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  198. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  199. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  200. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  201. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  202. package/docs/api/interfaces/UserEventAccess.md +1 -1
  203. package/docs/api/interfaces/UserMenuProps.md +1 -1
  204. package/docs/api/interfaces/UserProfile.md +1 -1
  205. package/docs/api/modules.md +318 -59
  206. package/docs/best-practices/performance.md +11 -0
  207. package/docs/getting-started/examples/README.md +2 -2
  208. package/docs/implementation-guides/file-upload-storage.md +29 -0
  209. package/docs/implementation-guides/public-pages.md +140 -1230
  210. package/docs/rbac/README.md +2 -1
  211. package/docs/rbac/api-reference.md +11 -0
  212. package/docs/rbac/performance.md +320 -0
  213. package/docs/standards/01-architecture-standard.md +5 -0
  214. package/docs/standards/05-security-standard.md +14 -0
  215. package/docs/standards/07-rbac-and-rls-standard.md +356 -0
  216. package/package.json +1 -1
  217. package/src/__tests__/public-recipe-view.test.ts +199 -0
  218. package/src/__tests__/rls-policies.test.ts +333 -0
  219. package/src/components/AddressField/AddressField.test.tsx +411 -0
  220. package/src/components/AddressField/AddressField.tsx +323 -0
  221. package/src/components/AddressField/README.md +336 -0
  222. package/src/components/AddressField/index.ts +10 -0
  223. package/src/components/AddressField/types.ts +65 -0
  224. package/src/components/FileDisplay/FileDisplay.test.tsx +454 -0
  225. package/src/components/FileDisplay/FileDisplay.tsx +28 -1
  226. package/src/components/index.ts +2 -0
  227. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +30 -5
  228. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +11 -10
  229. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +31 -6
  230. package/src/hooks/index.ts +6 -0
  231. package/src/hooks/public/usePublicFileDisplay.ts +8 -10
  232. package/src/hooks/useAddressAutocomplete.test.ts +318 -0
  233. package/src/hooks/useAddressAutocomplete.ts +268 -0
  234. package/src/hooks/useFileDisplay.ts +3 -15
  235. package/src/hooks/useFileReference.test.ts +20 -3
  236. package/src/hooks/useFileReference.ts +3 -24
  237. package/src/hooks/useFileUrlCache.ts +246 -0
  238. package/src/hooks/useInactivityTracker.ts +31 -20
  239. package/src/hooks/useOrganisationSecurity.test.ts +10 -7
  240. package/src/hooks/useOrganisationSecurity.ts +3 -3
  241. package/src/hooks/useQueryCache.ts +315 -0
  242. package/src/index.ts +2 -0
  243. package/src/providers/services/EventServiceProvider.tsx +4 -1
  244. package/src/rbac/api.test.ts +21 -6
  245. package/src/rbac/api.ts +32 -11
  246. package/src/rbac/audit-batched.ts +223 -0
  247. package/src/rbac/audit-enhanced.ts +2 -2
  248. package/src/rbac/audit.test.ts +6 -5
  249. package/src/rbac/audit.ts +34 -6
  250. package/src/rbac/cache-invalidation.ts +63 -12
  251. package/src/rbac/cache.test.ts +2 -2
  252. package/src/rbac/cache.ts +61 -14
  253. package/src/rbac/components/PagePermissionGuard.tsx +19 -10
  254. package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +248 -0
  255. package/src/rbac/config.ts +9 -0
  256. package/src/rbac/engine.ts +2 -21
  257. package/src/rbac/hooks/usePermissions.ts +21 -5
  258. package/src/rbac/index.ts +19 -0
  259. package/src/rbac/performance.ts +210 -0
  260. package/src/rbac/request-deduplication.ts +87 -0
  261. package/src/rbac/utils/deep-equal.ts +93 -0
  262. package/src/services/OrganisationService.ts +5 -4
  263. package/src/types/file-reference.ts +0 -1
  264. package/src/utils/file-reference/__tests__/file-reference.test.ts +31 -4
  265. package/src/utils/file-reference/index.ts +44 -15
  266. package/src/utils/google-places/googlePlacesUtils.test.ts +403 -0
  267. package/src/utils/google-places/googlePlacesUtils.ts +475 -0
  268. package/src/utils/google-places/index.ts +26 -0
  269. package/src/utils/google-places/loadGoogleMapsScript.ts +207 -0
  270. package/src/utils/google-places/types.ts +94 -0
  271. package/src/utils/index.ts +23 -0
  272. package/src/utils/request-deduplication.ts +165 -0
  273. package/src/utils/storage/helpers.ts +143 -4
  274. package/dist/chunk-445GEP27.js.map +0 -1
  275. package/dist/chunk-FMUCXFII.js +0 -76
  276. package/dist/chunk-FMUCXFII.js.map +0 -1
  277. package/dist/chunk-FSFQFJCU.js.map +0 -1
  278. package/dist/chunk-FXFJRTKI.js.map +0 -1
  279. package/dist/chunk-GRIQLQ52.js.map +0 -1
  280. package/dist/chunk-HDCUMOOI.js.map +0 -1
  281. package/dist/chunk-OALXJH4Y.js.map +0 -1
  282. package/dist/chunk-TC7D3CR3.js.map +0 -1
  283. package/dist/chunk-U6WNSFX5.js.map +0 -1
  284. /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-GUFUNZ3N.js.map} +0 -0
  285. /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-643PUAIM.js.map} +0 -0
  286. /package/dist/{api-BMFCXVQX.js.map → api-YP7XD5L6.js.map} +0 -0
  287. /package/dist/{audit-WRS3KJKI.js.map → audit-B5P6FFIR.js.map} +0 -0
  288. /package/dist/{chunk-HGPQUCBC.js.map → chunk-2UUZZJFT.js.map} +0 -0
  289. /package/dist/{chunk-DAGICKHT.js.map → chunk-DDM4CCYT.js.map} +0 -0
  290. /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-XAUHJD3L.js";
3
+ } from "./chunk-E7UAOUMY.js";
4
4
  import {
5
5
  useUnifiedAuth
6
- } from "./chunk-FXFJRTKI.js";
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-FMUCXFII.js";
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 log = createLogger("useEventTheme");
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
- useEffect(() => {
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
- log.error("Failed to apply event palette:", error);
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 useEffect2, useRef } from "react";
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 = useRef(false);
78
- const gracePeriodTimeoutRef = useRef(null);
79
- useEffect2(() => {
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 = useMemo(() => {
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 useMemo2, useContext as useContext2 } from "react";
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 useMemo2(() => ({
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 useMemo2(() => ({
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 useMemo2(() => ({
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 log2 = createLogger("StorageHelpers");
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 pathCategory = customPath || "files";
443
- return `${orgId}/${pathCategory}/${fileName}`;
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
- log2.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
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
- log2.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : "Unknown error"}`);
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
- log2.error("Failed to create signed URL:", error);
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
- log2.error("Failed to create signed URL:", error);
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
- log2.error("Failed to list files:", error);
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
- log2.error("Failed to list files:", error);
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
- log2.error("Failed to download file:", error);
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
- log2.error("Failed to download file:", error);
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 useEffect3, useCallback } from "react";
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 log3 = createLogger("FileReferenceService");
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}/{category}/{timestamp-uuid-filename}
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("*").eq("id", data).single();
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
- log3.error("Error creating file reference:", error);
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("*").eq("table_name", table_name).eq("record_id", record_id).eq("organisation_id", organisation_id).single();
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
- log3.error("Error getting file reference:", error);
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
- log3.error("Error getting file URL:", error);
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
- log3.error("Error getting signed URL:", error);
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
- log3.error("Error updating file reference:", error);
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
- log3.error("Error deleting file reference:", error);
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 ids = data.map((item) => item.id);
967
- const { data: fullData, error: fetchError } = await this.supabase.from("file_references").select("*").in("id", ids);
968
- if (fetchError) {
969
- throw new Error(`Failed to fetch file references: ${fetchError.message}`);
970
- }
971
- return fullData || [];
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
- log3.error("Error listing file references:", error);
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
- log3.error("Error getting file count:", error);
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
- log3.error("Error getting file reference by ID:", error);
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
- log3.error("RPC ERROR getting files by category:", {
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
- log3.warn("File category mismatch in RPC response:", {
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
- log3.error("Error getting files by category:", error);
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 MAX_CACHE_SIZE = 100;
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 > MAX_CACHE_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 - MAX_CACHE_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] = useState(null);
1153
- const [fileReference, setFileReference] = useState(null);
1154
- const [fileReferences, setFileReferences] = useState([]);
1155
- const [fileUrls, setFileUrls] = useState(/* @__PURE__ */ new Map());
1156
- const [fileCount, setFileCount] = useState(0);
1157
- const [isLoading, setIsLoading] = useState(false);
1158
- const [error, setError] = useState(null);
1159
- const fetchFiles = useCallback(async () => {
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 = /* @__PURE__ */ new Map();
1278
- for (const fileRef of files) {
1279
- let url = null;
1280
- if (fileRef.is_public) {
1281
- url = getPublicUrl(supabase, fileRef.file_path, true);
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
- useEffect3(() => {
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 = useCallback(async () => {
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 useState2, useEffect as useEffect4, useCallback as useCallback2 } from "react";
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] = useState2(null);
1410
- const [fileReference, setFileReference] = useState2(null);
1411
- const [fileReferences, setFileReferences] = useState2([]);
1412
- const [fileUrls, setFileUrls] = useState2(/* @__PURE__ */ new Map());
1413
- const [fileCount, setFileCount] = useState2(0);
1414
- const [isLoading, setIsLoading] = useState2(false);
1415
- const [error, setError] = useState2(null);
1416
- const fetchFiles = useCallback2(async () => {
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("*").in("id", ids).eq("is_public", true);
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 = /* @__PURE__ */ new Map();
1559
- for (const fileRef of fileRefs) {
1560
- const url = getPublicUrl(supabase, fileRef.file_path, true);
1561
- if (url) {
1562
- urlMap.set(fileRef.id, url);
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
- useEffect4(() => {
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 = useCallback2(async () => {
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-TC7D3CR3.js.map
2149
+ //# sourceMappingURL=chunk-UNOTYLQF.js.map