@jmruthers/pace-core 0.5.185 → 0.5.187

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/dist/{DataTable-Z9NLVJh0.d.ts → DataTable-IVYljGJ6.d.ts} +1 -1
  2. package/dist/{DataTable-IX2NBUTP.js → DataTable-K3RJRSOX.js} +7 -7
  3. package/dist/{PublicPageProvider-BABf6JCh.d.ts → PublicPageProvider-DrLDztHt.d.ts} +214 -107
  4. package/dist/{UnifiedAuthProvider-A4BCQRJY.js → UnifiedAuthProvider-B76OWOAT.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-445GEP27.js → chunk-3IC5WCMO.js} +33 -8
  8. package/dist/chunk-3IC5WCMO.js.map +1 -0
  9. package/dist/{chunk-OKI34GZD.js → chunk-3NFNJOO7.js} +8 -8
  10. package/dist/chunk-3NFNJOO7.js.map +1 -0
  11. package/dist/{chunk-FSFQFJCU.js → chunk-63FOKYGO.js} +174 -6
  12. package/dist/chunk-63FOKYGO.js.map +1 -0
  13. package/dist/{chunk-MX3EIJGQ.js → chunk-C4OYJOV4.js} +631 -97
  14. package/dist/chunk-C4OYJOV4.js.map +1 -0
  15. package/dist/{chunk-HGPQUCBC.js → chunk-FMTK4XNN.js} +3 -3
  16. package/dist/{chunk-U6WNSFX5.js → chunk-HEHYGYOX.js} +279 -44
  17. package/dist/chunk-HEHYGYOX.js.map +1 -0
  18. package/dist/{chunk-XAUHJD3L.js → chunk-K2JGDXGU.js} +2 -2
  19. package/dist/{chunk-HC67NW5K.js → chunk-LBBUPSSC.js} +863 -552
  20. package/dist/chunk-LBBUPSSC.js.map +1 -0
  21. package/dist/{chunk-IXSNYUCT.js → chunk-SAUPYVLF.js} +1 -1
  22. package/dist/chunk-SAUPYVLF.js.map +1 -0
  23. package/dist/{chunk-AISXLWGZ.js → chunk-T6ZJVI3A.js} +27 -23
  24. package/dist/chunk-T6ZJVI3A.js.map +1 -0
  25. package/dist/{chunk-STTZQK2I.js → chunk-ULX5FYEM.js} +9 -7
  26. package/dist/chunk-ULX5FYEM.js.map +1 -0
  27. package/dist/{chunk-FXFJRTKI.js → chunk-WK2Y6TGA.js} +3 -3
  28. package/dist/chunk-WK2Y6TGA.js.map +1 -0
  29. package/dist/chunk-YHCN776L.js +447 -0
  30. package/dist/chunk-YHCN776L.js.map +1 -0
  31. package/dist/components.d.ts +4 -4
  32. package/dist/components.js +12 -10
  33. package/dist/components.js.map +1 -1
  34. package/dist/{database.generated-CBmg2950.d.ts → database.generated-DI89OQeI.d.ts} +63 -9
  35. package/dist/{file-reference-BjR39ktt.d.ts → file-reference-D037xOFK.d.ts} +3 -1
  36. package/dist/hooks.d.ts +265 -6
  37. package/dist/hooks.js +148 -49
  38. package/dist/hooks.js.map +1 -1
  39. package/dist/index.d.ts +25 -10
  40. package/dist/index.js +65 -30
  41. package/dist/index.js.map +1 -1
  42. package/dist/providers.js +1 -1
  43. package/dist/rbac/index.d.ts +125 -8
  44. package/dist/rbac/index.js +27 -7
  45. package/dist/{types-DUyCRSTj.d.ts → types-Bwgl--Xo.d.ts} +162 -1
  46. package/dist/types.d.ts +2 -2
  47. package/dist/types.js +1 -1
  48. package/dist/{usePublicRouteParams-CvnC3d-e.d.ts → usePublicRouteParams-CTDELQ7H.d.ts} +3 -3
  49. package/dist/utils.d.ts +214 -4
  50. package/dist/utils.js +22 -2
  51. package/dist/utils.js.map +1 -1
  52. package/docs/api/classes/ColumnFactory.md +1 -1
  53. package/docs/api/classes/ErrorBoundary.md +1 -1
  54. package/docs/api/classes/InvalidScopeError.md +1 -1
  55. package/docs/api/classes/Logger.md +1 -1
  56. package/docs/api/classes/MissingUserContextError.md +1 -1
  57. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  58. package/docs/api/classes/PermissionDeniedError.md +1 -1
  59. package/docs/api/classes/RBACAuditManager.md +21 -17
  60. package/docs/api/classes/RBACCache.md +31 -23
  61. package/docs/api/classes/RBACEngine.md +6 -6
  62. package/docs/api/classes/RBACError.md +1 -1
  63. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  64. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  65. package/docs/api/classes/StorageUtils.md +1 -1
  66. package/docs/api/enums/FileCategory.md +1 -1
  67. package/docs/api/enums/LogLevel.md +1 -1
  68. package/docs/api/enums/RBACErrorCode.md +1 -1
  69. package/docs/api/enums/RPCFunction.md +1 -1
  70. package/docs/api/interfaces/AddressFieldProps.md +241 -0
  71. package/docs/api/interfaces/AddressFieldRef.md +94 -0
  72. package/docs/api/interfaces/AggregateConfig.md +1 -1
  73. package/docs/api/interfaces/AutocompleteOptions.md +75 -0
  74. package/docs/api/interfaces/BadgeProps.md +1 -1
  75. package/docs/api/interfaces/ButtonProps.md +1 -1
  76. package/docs/api/interfaces/CalendarProps.md +1 -1
  77. package/docs/api/interfaces/CardProps.md +1 -1
  78. package/docs/api/interfaces/ColorPalette.md +1 -1
  79. package/docs/api/interfaces/ColorShade.md +1 -1
  80. package/docs/api/interfaces/ComplianceResult.md +1 -1
  81. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  82. package/docs/api/interfaces/DataRecord.md +1 -1
  83. package/docs/api/interfaces/DataTableAction.md +1 -1
  84. package/docs/api/interfaces/DataTableColumn.md +1 -1
  85. package/docs/api/interfaces/DataTableProps.md +1 -1
  86. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  87. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  88. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  89. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  90. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  91. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  92. package/docs/api/interfaces/ExportColumn.md +1 -1
  93. package/docs/api/interfaces/ExportOptions.md +1 -1
  94. package/docs/api/interfaces/FileDisplayProps.md +15 -15
  95. package/docs/api/interfaces/FileMetadata.md +1 -1
  96. package/docs/api/interfaces/FileReference.md +1 -1
  97. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  98. package/docs/api/interfaces/FileUploadOptions.md +33 -9
  99. package/docs/api/interfaces/FileUploadProps.md +36 -14
  100. package/docs/api/interfaces/FooterProps.md +1 -1
  101. package/docs/api/interfaces/FormFieldProps.md +1 -1
  102. package/docs/api/interfaces/FormProps.md +1 -1
  103. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  104. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  105. package/docs/api/interfaces/InputProps.md +1 -1
  106. package/docs/api/interfaces/LabelProps.md +1 -1
  107. package/docs/api/interfaces/LoggerConfig.md +1 -1
  108. package/docs/api/interfaces/LoginFormProps.md +1 -1
  109. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  110. package/docs/api/interfaces/NavigationContextType.md +1 -1
  111. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  112. package/docs/api/interfaces/NavigationItem.md +1 -1
  113. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  114. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  115. package/docs/api/interfaces/Organisation.md +1 -1
  116. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  117. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  118. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  119. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  120. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  121. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  122. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  123. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  124. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  125. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  126. package/docs/api/interfaces/PaletteData.md +1 -1
  127. package/docs/api/interfaces/ParsedAddress.md +120 -0
  128. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  129. package/docs/api/interfaces/ProgressProps.md +1 -1
  130. package/docs/api/interfaces/ProtectedRouteProps.md +6 -6
  131. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  132. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  133. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  134. package/docs/api/interfaces/QuickFix.md +1 -1
  135. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  136. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  137. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  138. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  139. package/docs/api/interfaces/RBACConfig.md +27 -4
  140. package/docs/api/interfaces/RBACContext.md +1 -1
  141. package/docs/api/interfaces/RBACLogger.md +5 -5
  142. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  143. package/docs/api/interfaces/RBACPerformanceMetrics.md +138 -0
  144. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  146. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  148. package/docs/api/interfaces/RBACResult.md +1 -1
  149. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  150. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  151. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  152. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  153. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  154. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  155. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  156. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  157. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  158. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  159. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  160. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  161. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  162. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  163. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  164. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  165. package/docs/api/interfaces/RouteConfig.md +1 -1
  166. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  167. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  168. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  169. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  170. package/docs/api/interfaces/SetupIssue.md +1 -1
  171. package/docs/api/interfaces/StorageConfig.md +1 -1
  172. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  173. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  174. package/docs/api/interfaces/StorageListOptions.md +1 -1
  175. package/docs/api/interfaces/StorageListResult.md +1 -1
  176. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  177. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  178. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  179. package/docs/api/interfaces/StyleImport.md +1 -1
  180. package/docs/api/interfaces/SwitchProps.md +1 -1
  181. package/docs/api/interfaces/TabsContentProps.md +1 -1
  182. package/docs/api/interfaces/TabsListProps.md +1 -1
  183. package/docs/api/interfaces/TabsProps.md +1 -1
  184. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  185. package/docs/api/interfaces/TextareaProps.md +1 -1
  186. package/docs/api/interfaces/ToastActionElement.md +1 -1
  187. package/docs/api/interfaces/ToastProps.md +1 -1
  188. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  189. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  190. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  191. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  192. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  193. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  194. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  195. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  196. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  198. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  199. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  200. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  201. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  202. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  203. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  204. package/docs/api/interfaces/UserEventAccess.md +1 -1
  205. package/docs/api/interfaces/UserMenuProps.md +1 -1
  206. package/docs/api/interfaces/UserProfile.md +1 -1
  207. package/docs/api/modules.md +328 -69
  208. package/docs/api-reference/components.md +26 -12
  209. package/docs/best-practices/performance.md +11 -0
  210. package/docs/implementation-guides/file-reference-system.md +24 -2
  211. package/docs/implementation-guides/file-upload-storage.md +38 -1
  212. package/docs/rbac/README.md +2 -1
  213. package/docs/rbac/api-reference.md +11 -0
  214. package/docs/rbac/performance.md +320 -0
  215. package/docs/standards/01-architecture-standard.md +5 -0
  216. package/docs/standards/05-security-standard.md +12 -0
  217. package/package.json +1 -1
  218. package/scripts/check-pace-core-compliance.js +512 -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/FileUpload/FileUpload.test.tsx +2 -0
  227. package/src/components/FileUpload/FileUpload.tsx +7 -1
  228. package/src/components/Header/Header.tsx +2 -5
  229. package/src/components/ProtectedRoute/ProtectedRoute.tsx +134 -1
  230. package/src/components/index.ts +2 -0
  231. package/src/hooks/__tests__/useFileDisplay.unit.test.ts +30 -5
  232. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +11 -10
  233. package/src/hooks/__tests__/usePublicFileDisplay.test.ts +31 -6
  234. package/src/hooks/index.ts +9 -0
  235. package/src/hooks/public/usePublicFileDisplay.ts +8 -10
  236. package/src/hooks/useAddressAutocomplete.test.ts +318 -0
  237. package/src/hooks/useAddressAutocomplete.ts +268 -0
  238. package/src/hooks/useFileDisplay.ts +3 -15
  239. package/src/hooks/useFileReference.test.ts +21 -3
  240. package/src/hooks/useFileReference.ts +3 -24
  241. package/src/hooks/useFileUrlCache.ts +246 -0
  242. package/src/hooks/useInactivityTracker.ts +31 -20
  243. package/src/hooks/useOrganisationSecurity.test.ts +10 -7
  244. package/src/hooks/useOrganisationSecurity.ts +3 -3
  245. package/src/hooks/usePreventTabReload.ts +106 -0
  246. package/src/hooks/useQueryCache.ts +315 -0
  247. package/src/hooks/useSecureDataAccess.ts +2 -2
  248. package/src/index.ts +2 -0
  249. package/src/providers/services/EventServiceProvider.tsx +4 -1
  250. package/src/rbac/__tests__/rbac-role-isolation.test.ts +456 -0
  251. package/src/rbac/api.test.ts +21 -6
  252. package/src/rbac/api.ts +32 -11
  253. package/src/rbac/audit-batched.ts +223 -0
  254. package/src/rbac/audit-enhanced.ts +2 -2
  255. package/src/rbac/audit.test.ts +6 -5
  256. package/src/rbac/audit.ts +34 -6
  257. package/src/rbac/cache-invalidation.ts +63 -12
  258. package/src/rbac/cache.test.ts +2 -2
  259. package/src/rbac/cache.ts +61 -14
  260. package/src/rbac/components/PagePermissionGuard.tsx +19 -10
  261. package/src/rbac/components/__tests__/PagePermissionGuard.performance.test.tsx +248 -0
  262. package/src/rbac/config.ts +9 -0
  263. package/src/rbac/engine.ts +2 -21
  264. package/src/rbac/hooks/usePermissions.ts +21 -5
  265. package/src/rbac/index.ts +19 -0
  266. package/src/rbac/performance.ts +210 -0
  267. package/src/rbac/request-deduplication.ts +87 -0
  268. package/src/rbac/utils/deep-equal.ts +93 -0
  269. package/src/styles/core.css +5 -5
  270. package/src/types/database.generated.ts +63 -9
  271. package/src/types/file-reference.ts +3 -1
  272. package/src/utils/file-reference/__tests__/file-reference.test.ts +89 -8
  273. package/src/utils/file-reference/index.ts +56 -17
  274. package/src/utils/google-places/googlePlacesUtils.test.ts +403 -0
  275. package/src/utils/google-places/googlePlacesUtils.ts +475 -0
  276. package/src/utils/google-places/index.ts +26 -0
  277. package/src/utils/google-places/loadGoogleMapsScript.ts +207 -0
  278. package/src/utils/google-places/types.ts +94 -0
  279. package/src/utils/index.ts +23 -0
  280. package/src/utils/request-deduplication.ts +165 -0
  281. package/src/utils/security/secureDataAccess.ts +1 -1
  282. package/src/utils/storage/helpers.ts +211 -4
  283. package/dist/chunk-445GEP27.js.map +0 -1
  284. package/dist/chunk-AISXLWGZ.js.map +0 -1
  285. package/dist/chunk-FMUCXFII.js +0 -76
  286. package/dist/chunk-FMUCXFII.js.map +0 -1
  287. package/dist/chunk-FSFQFJCU.js.map +0 -1
  288. package/dist/chunk-FXFJRTKI.js.map +0 -1
  289. package/dist/chunk-HC67NW5K.js.map +0 -1
  290. package/dist/chunk-IXSNYUCT.js.map +0 -1
  291. package/dist/chunk-MX3EIJGQ.js.map +0 -1
  292. package/dist/chunk-OKI34GZD.js.map +0 -1
  293. package/dist/chunk-STTZQK2I.js.map +0 -1
  294. package/dist/chunk-U6WNSFX5.js.map +0 -1
  295. /package/dist/{DataTable-IX2NBUTP.js.map → DataTable-K3RJRSOX.js.map} +0 -0
  296. /package/dist/{UnifiedAuthProvider-A4BCQRJY.js.map → UnifiedAuthProvider-B76OWOAT.js.map} +0 -0
  297. /package/dist/{api-BMFCXVQX.js.map → api-YP7XD5L6.js.map} +0 -0
  298. /package/dist/{audit-WRS3KJKI.js.map → audit-B5P6FFIR.js.map} +0 -0
  299. /package/dist/{chunk-HGPQUCBC.js.map → chunk-FMTK4XNN.js.map} +0 -0
  300. /package/dist/{chunk-XAUHJD3L.js.map → chunk-K2JGDXGU.js.map} +0 -0
@@ -1,15 +1,19 @@
1
1
  import {
2
2
  useEvents
3
- } from "./chunk-XAUHJD3L.js";
3
+ } from "./chunk-K2JGDXGU.js";
4
4
  import {
5
5
  useUnifiedAuth
6
- } from "./chunk-FXFJRTKI.js";
6
+ } from "./chunk-WK2Y6TGA.js";
7
7
  import {
8
8
  assertAppId
9
9
  } from "./chunk-QXHPKYJV.js";
10
10
  import {
11
+ createAddressFromPlaceResult,
12
+ fetchPlaceAutocomplete,
13
+ fetchPlaceDetails,
14
+ getAddressByPlaceId,
11
15
  performanceBudgetMonitor
12
- } from "./chunk-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,13 +411,55 @@ 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
  };
70
418
  }, [selectedEvent, location.pathname]);
71
419
  }
72
420
 
421
+ // src/hooks/usePreventTabReload.ts
422
+ import { useEffect as useEffect5, useRef as useRef3 } from "react";
423
+ function usePreventTabReload(options = {}) {
424
+ const { enabled = true, gracePeriodMs = 2e3 } = options;
425
+ const isRestoringFromCacheRef = useRef3(false);
426
+ const gracePeriodTimeoutRef = useRef3(null);
427
+ useEffect5(() => {
428
+ if (!enabled || typeof window === "undefined") return;
429
+ const handlePageShow = (event) => {
430
+ if (event.persisted) {
431
+ isRestoringFromCacheRef.current = true;
432
+ if (gracePeriodTimeoutRef.current) {
433
+ clearTimeout(gracePeriodTimeoutRef.current);
434
+ }
435
+ gracePeriodTimeoutRef.current = setTimeout(() => {
436
+ isRestoringFromCacheRef.current = false;
437
+ }, gracePeriodMs);
438
+ }
439
+ };
440
+ const handleVisibilityChange = () => {
441
+ if (!document.hidden) {
442
+ isRestoringFromCacheRef.current = true;
443
+ if (gracePeriodTimeoutRef.current) {
444
+ clearTimeout(gracePeriodTimeoutRef.current);
445
+ }
446
+ gracePeriodTimeoutRef.current = setTimeout(() => {
447
+ isRestoringFromCacheRef.current = false;
448
+ }, gracePeriodMs);
449
+ }
450
+ };
451
+ window.addEventListener("pageshow", handlePageShow);
452
+ document.addEventListener("visibilitychange", handleVisibilityChange);
453
+ return () => {
454
+ window.removeEventListener("pageshow", handlePageShow);
455
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
456
+ if (gracePeriodTimeoutRef.current) {
457
+ clearTimeout(gracePeriodTimeoutRef.current);
458
+ }
459
+ };
460
+ }, [enabled, gracePeriodMs]);
461
+ }
462
+
73
463
  // src/components/ErrorBoundary/ErrorBoundary.tsx
74
464
  import { Component } from "react";
75
465
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -217,7 +607,7 @@ var ErrorBoundary = class extends Component {
217
607
  };
218
608
 
219
609
  // src/components/PublicLayout/PublicPageProvider.tsx
220
- import { createContext, useContext, useMemo } from "react";
610
+ import { createContext, useContext, useMemo as useMemo2 } from "react";
221
611
  import { createClient } from "@supabase/supabase-js";
222
612
  import { jsx as jsx2 } from "react/jsx-runtime";
223
613
  var PublicPageContext = createContext(void 0);
@@ -234,7 +624,7 @@ function PublicPageProvider({ children, appName }) {
234
624
  };
235
625
  const supabaseUrl = getEnvVar("VITE_SUPABASE_URL") || getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || null;
236
626
  const supabaseKey = getEnvVar("VITE_SUPABASE_ANON_KEY") || getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || null;
237
- const supabase = useMemo(() => {
627
+ const supabase = useMemo2(() => {
238
628
  if (!supabaseUrl || !supabaseKey) {
239
629
  logger.warn("PublicPageProvider", "Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are set in your environment. Use publishable key if anon key is disabled.");
240
630
  return null;
@@ -267,7 +657,7 @@ function useIsPublicPage() {
267
657
  }
268
658
 
269
659
  // src/hooks/useAppConfig.ts
270
- import { useMemo as useMemo2, useContext as useContext2 } from "react";
660
+ import { useMemo as useMemo3, useContext as useContext2 } from "react";
271
661
  function useAppConfig() {
272
662
  const isPublicPage = useIsPublicPage();
273
663
  const publicPageContext = useContext2(PublicPageContext);
@@ -285,7 +675,7 @@ function useAppConfig() {
285
675
  }
286
676
  return "PACE";
287
677
  };
288
- return useMemo2(() => ({
678
+ return useMemo3(() => ({
289
679
  supportsDirectAccess: false,
290
680
  // Public pages don't support direct access
291
681
  requiresEvent: true,
@@ -296,14 +686,14 @@ function useAppConfig() {
296
686
  }
297
687
  try {
298
688
  const { appConfig, appName } = useUnifiedAuth();
299
- return useMemo2(() => ({
689
+ return useMemo3(() => ({
300
690
  supportsDirectAccess: !(appConfig?.requires_event ?? true),
301
691
  requiresEvent: appConfig?.requires_event ?? true,
302
692
  isLoading: appConfig === null,
303
693
  appName
304
694
  }), [appConfig?.requires_event, appName]);
305
695
  } catch (error) {
306
- return useMemo2(() => ({
696
+ return useMemo3(() => ({
307
697
  supportsDirectAccess: false,
308
698
  requiresEvent: true,
309
699
  isLoading: false,
@@ -382,7 +772,7 @@ function validateFileSize(file) {
382
772
  }
383
773
 
384
774
  // src/utils/storage/helpers.ts
385
- var log2 = createLogger("StorageHelpers");
775
+ var log3 = createLogger("StorageHelpers");
386
776
  function generateFilePath(options, fileName) {
387
777
  const { orgId, isPublic = false, customPath } = options;
388
778
  if (!orgId) {
@@ -397,8 +787,8 @@ function generateFilePath(options, fileName) {
397
787
  if (customPath) {
398
788
  return `${orgId}/${customPath}/${fileName}`;
399
789
  }
400
- const pathCategory = customPath || "files";
401
- return `${orgId}/${pathCategory}/${fileName}`;
790
+ const pathFolder = customPath || "files";
791
+ return `${orgId}/${pathFolder}/${fileName}`;
402
792
  }
403
793
  function generateUniqueFileName(originalName) {
404
794
  const timestamp = Date.now();
@@ -459,6 +849,33 @@ async function generateFileHash(file) {
459
849
  const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
460
850
  return `sha256:${hashHex}`;
461
851
  }
852
+ async function ensureFolderExists(supabase, folderPath, bucketName) {
853
+ try {
854
+ const { data, error } = await supabase.storage.from(bucketName).list(folderPath, {
855
+ limit: 1
856
+ });
857
+ if (!error) {
858
+ return true;
859
+ }
860
+ const placeholderPath = `${folderPath}/.keep`;
861
+ const placeholderBlob = new Blob([""], { type: "text/plain" });
862
+ const placeholderFile = new File([placeholderBlob], ".keep", { type: "text/plain" });
863
+ const { error: uploadError } = await supabase.storage.from(bucketName).upload(placeholderPath, placeholderFile, {
864
+ cacheControl: "3600",
865
+ upsert: true,
866
+ // Use upsert to avoid errors if file already exists
867
+ contentType: "text/plain"
868
+ });
869
+ if (uploadError) {
870
+ log3.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
871
+ return true;
872
+ }
873
+ return true;
874
+ } catch (error) {
875
+ log3.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : "Unknown error"}`);
876
+ return true;
877
+ }
878
+ }
462
879
  async function uploadFile(supabase, file, options) {
463
880
  try {
464
881
  const sizeValidation = validateFileSize(file);
@@ -470,8 +887,10 @@ async function uploadFile(supabase, file, options) {
470
887
  }
471
888
  const uniqueFileName = generateUniqueFileName(file.name);
472
889
  const filePath = generateFilePath(options, uniqueFileName);
890
+ const folderPath = filePath.substring(0, filePath.lastIndexOf("/"));
473
891
  const metadata = await extractFileMetadata(file, options, "current-user");
474
892
  const bucketName = getBucketName(options.isPublic || false);
893
+ await ensureFolderExists(supabase, folderPath, bucketName);
475
894
  const { data, error } = await supabase.storage.from(bucketName).upload(filePath, file, {
476
895
  cacheControl: "3600",
477
896
  upsert: false,
@@ -560,7 +979,7 @@ async function getSignedUrl(supabase, path, options) {
560
979
  const bucketName = getBucketName(false);
561
980
  const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(path, options.expiresIn || 3600);
562
981
  if (error) {
563
- log2.error("Failed to create signed URL:", error);
982
+ log3.error("Failed to create signed URL:", error);
564
983
  return null;
565
984
  }
566
985
  return {
@@ -568,10 +987,105 @@ async function getSignedUrl(supabase, path, options) {
568
987
  expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1e3).toISOString()
569
988
  };
570
989
  } catch (error) {
571
- log2.error("Failed to create signed URL:", error);
990
+ log3.error("Failed to create signed URL:", error);
572
991
  return null;
573
992
  }
574
993
  }
994
+ var globalUrlCache = /* @__PURE__ */ new Map();
995
+ var MAX_CACHE_SIZE = 500;
996
+ var DEFAULT_TTL_MS = 3600 * 1e3;
997
+ function getCacheKey(fileId, filePath, isPublic) {
998
+ return `file-url:${fileId}:${isPublic ? "public" : "private"}`;
999
+ }
1000
+ function cleanupUrlCache() {
1001
+ const now = Date.now();
1002
+ for (const [key, value] of globalUrlCache.entries()) {
1003
+ if (value.expiresAt < now) {
1004
+ globalUrlCache.delete(key);
1005
+ }
1006
+ }
1007
+ if (globalUrlCache.size > MAX_CACHE_SIZE) {
1008
+ const entries = Array.from(globalUrlCache.entries());
1009
+ entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);
1010
+ const toRemove = Math.floor(MAX_CACHE_SIZE * 0.2);
1011
+ for (let i = 0; i < toRemove && i < entries.length; i++) {
1012
+ globalUrlCache.delete(entries[i][0]);
1013
+ }
1014
+ }
1015
+ }
1016
+ async function generateFileUrlsBatch(supabase, fileReferences, options) {
1017
+ const urlMap = /* @__PURE__ */ new Map();
1018
+ if (fileReferences.length === 0) {
1019
+ return urlMap;
1020
+ }
1021
+ const now = Date.now();
1022
+ const ttl = (options.expiresIn || 3600) * 1e3;
1023
+ const publicFiles = [];
1024
+ const privateFiles = [];
1025
+ const uncachedFiles = [];
1026
+ for (const fileRef of fileReferences) {
1027
+ const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);
1028
+ const cached = globalUrlCache.get(cacheKey);
1029
+ if (cached && cached.expiresAt > now) {
1030
+ urlMap.set(fileRef.id, cached.url);
1031
+ continue;
1032
+ }
1033
+ if (fileRef.is_public) {
1034
+ publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
1035
+ } else {
1036
+ privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
1037
+ }
1038
+ uncachedFiles.push(fileRef);
1039
+ }
1040
+ for (const file of publicFiles) {
1041
+ try {
1042
+ const url = getPublicUrl(supabase, file.file_path, true);
1043
+ if (url) {
1044
+ urlMap.set(file.id, url);
1045
+ const cacheKey = getCacheKey(file.id, file.file_path, true);
1046
+ globalUrlCache.set(cacheKey, {
1047
+ url,
1048
+ expiresAt: now + ttl
1049
+ });
1050
+ }
1051
+ } catch (err) {
1052
+ log3.error(`Failed to generate public URL for file ${file.id}:`, err);
1053
+ }
1054
+ }
1055
+ if (privateFiles.length > 0) {
1056
+ const signedUrlPromises = privateFiles.map(async (file) => {
1057
+ try {
1058
+ const signedUrlResult = await getSignedUrl(supabase, file.file_path, {
1059
+ appName: options.appName || "pace-core",
1060
+ orgId: options.orgId,
1061
+ expiresIn: options.expiresIn || 3600
1062
+ });
1063
+ const url = signedUrlResult?.url || null;
1064
+ if (url) {
1065
+ const cacheKey = getCacheKey(file.id, file.file_path, false);
1066
+ globalUrlCache.set(cacheKey, {
1067
+ url,
1068
+ expiresAt: now + ttl
1069
+ });
1070
+ }
1071
+ return { id: file.id, url };
1072
+ } catch (err) {
1073
+ log3.error(`Failed to generate signed URL for file ${file.id}:`, err);
1074
+ return { id: file.id, url: null };
1075
+ }
1076
+ });
1077
+ const signedUrlResults = await Promise.all(signedUrlPromises);
1078
+ for (const result of signedUrlResults) {
1079
+ if (result.url) {
1080
+ urlMap.set(result.id, result.url);
1081
+ }
1082
+ }
1083
+ }
1084
+ if (uncachedFiles.length > 0) {
1085
+ cleanupUrlCache();
1086
+ }
1087
+ return urlMap;
1088
+ }
575
1089
  async function deleteFile(supabase, path, isPublic = false) {
576
1090
  try {
577
1091
  const bucketName = getBucketName(isPublic);
@@ -601,7 +1115,7 @@ async function listFiles(supabase, options) {
601
1115
  sortBy: { column: "created_at", order: "desc" }
602
1116
  });
603
1117
  if (error) {
604
- log2.error("Failed to list files:", error);
1118
+ log3.error("Failed to list files:", error);
605
1119
  return { files: [], totalCount: 0, hasMore: false };
606
1120
  }
607
1121
  const files = (data || []).map((item) => ({
@@ -626,7 +1140,7 @@ async function listFiles(supabase, options) {
626
1140
  hasMore: files.length >= (options.limit || 100)
627
1141
  };
628
1142
  } catch (error) {
629
- log2.error("Failed to list files:", error);
1143
+ log3.error("Failed to list files:", error);
630
1144
  return { files: [], totalCount: 0, hasMore: false };
631
1145
  }
632
1146
  }
@@ -635,7 +1149,7 @@ async function downloadFile(supabase, path, isPublic = false) {
635
1149
  const bucketName = getBucketName(isPublic);
636
1150
  const { data, error } = await supabase.storage.from(bucketName).download(path);
637
1151
  if (error) {
638
- log2.error("Failed to download file:", error);
1152
+ log3.error("Failed to download file:", error);
639
1153
  return null;
640
1154
  }
641
1155
  if (!data) {
@@ -655,7 +1169,7 @@ async function downloadFile(supabase, path, isPublic = false) {
655
1169
  }
656
1170
  };
657
1171
  } catch (error) {
658
- log2.error("Failed to download file:", error);
1172
+ log3.error("Failed to download file:", error);
659
1173
  return null;
660
1174
  }
661
1175
  }
@@ -684,10 +1198,10 @@ async function archiveFile(supabase, path, options) {
684
1198
  }
685
1199
 
686
1200
  // src/hooks/useFileDisplay.ts
687
- import { useState, useEffect as useEffect2, useCallback } from "react";
1201
+ import { useState as useState3, useEffect as useEffect6, useCallback as useCallback3 } from "react";
688
1202
 
689
1203
  // src/utils/file-reference/index.ts
690
- var log3 = createLogger("FileReferenceService");
1204
+ var log4 = createLogger("FileReferenceService");
691
1205
  var FileReferenceServiceImpl = class {
692
1206
  constructor(supabase) {
693
1207
  this.supabase = supabase;
@@ -697,7 +1211,7 @@ var FileReferenceServiceImpl = class {
697
1211
  *
698
1212
  * Storage Flow:
699
1213
  * 1. Upload file to storage bucket first (files or public-files based on is_public flag)
700
- * - Path format: {orgId}/{category}/{timestamp-uuid-filename}
1214
+ * - Path format: {orgId}/{folder}/{timestamp-uuid-filename}
701
1215
  * - Bucket selection: 'files' (private) or 'public-files' (public)
702
1216
  * 2. Extract file metadata (dimensions, hash, etc.)
703
1217
  * 3. Set organisation context for RLS policies
@@ -717,12 +1231,15 @@ var FileReferenceServiceImpl = class {
717
1231
  if (!options.record_id) {
718
1232
  throw new Error("record_id is required for file upload");
719
1233
  }
1234
+ if (!options.folder) {
1235
+ throw new Error("folder is required for file upload. The folder prop determines the storage path.");
1236
+ }
720
1237
  const uploadResult = await uploadFile(this.supabase, file, {
721
1238
  appName: "file-reference",
722
1239
  orgId: options.organisation_id,
723
1240
  isPublic: options.is_public || false,
724
- customPath: options.category
725
- // Use category as the custom path segment
1241
+ customPath: options.folder
1242
+ // Use folder prop as the custom path segment
726
1243
  });
727
1244
  if (!uploadResult.success) {
728
1245
  throw new Error(`Failed to upload file: ${uploadResult.error}`);
@@ -745,6 +1262,8 @@ var FileReferenceServiceImpl = class {
745
1262
  p_organisation_id: options.organisation_id,
746
1263
  p_app_id: options.app_id,
747
1264
  p_page_context: options.pageContext,
1265
+ p_event_id: options.event_id || null,
1266
+ // Pass event_id for event-based apps
748
1267
  p_file_metadata: {
749
1268
  fileName: file.name,
750
1269
  fileType: file.type,
@@ -759,8 +1278,13 @@ var FileReferenceServiceImpl = class {
759
1278
  await deleteFile(this.supabase, filePath, options.is_public || false);
760
1279
  throw new Error(`Failed to create file reference: ${error.message}`);
761
1280
  }
762
- const { data: fileRef, error: fetchError } = await this.supabase.from("file_references").select("*").eq("id", data).single();
1281
+ if (!data || data === null) {
1282
+ await deleteFile(this.supabase, filePath, options.is_public || false);
1283
+ throw new Error(`File upload denied: insufficient permissions. You need 'create:page.${options.pageContext}' or 'update:page.${options.pageContext}' permission for the '${options.pageContext}' page. Make sure the page exists in rbac_app_pages table.`);
1284
+ }
1285
+ const { data: fileRef, error: fetchError } = await this.supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("id", data).single();
763
1286
  if (fetchError || !fileRef) {
1287
+ await deleteFile(this.supabase, filePath, options.is_public || false);
764
1288
  throw new Error(`Failed to fetch created file reference: ${fetchError?.message}`);
765
1289
  }
766
1290
  invalidateFileDisplayCache(
@@ -771,13 +1295,13 @@ var FileReferenceServiceImpl = class {
771
1295
  );
772
1296
  return fileRef;
773
1297
  } catch (error) {
774
- log3.error("Error creating file reference:", error);
1298
+ log4.error("Error creating file reference:", error);
775
1299
  throw error;
776
1300
  }
777
1301
  }
778
1302
  async getFileReference(table_name, record_id, organisation_id) {
779
1303
  try {
780
- const { data, error } = await this.supabase.from("file_references").select("*").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();
781
1305
  if (error) {
782
1306
  if (error.code === "PGRST116") {
783
1307
  return null;
@@ -786,7 +1310,7 @@ var FileReferenceServiceImpl = class {
786
1310
  }
787
1311
  return data;
788
1312
  } catch (error) {
789
- log3.error("Error getting file reference:", error);
1313
+ log4.error("Error getting file reference:", error);
790
1314
  throw error;
791
1315
  }
792
1316
  }
@@ -810,7 +1334,7 @@ var FileReferenceServiceImpl = class {
810
1334
  return await this.getSignedUrl(table_name, record_id, organisation_id);
811
1335
  }
812
1336
  } catch (error) {
813
- log3.error("Error getting file URL:", error);
1337
+ log4.error("Error getting file URL:", error);
814
1338
  throw error;
815
1339
  }
816
1340
  }
@@ -835,7 +1359,7 @@ var FileReferenceServiceImpl = class {
835
1359
  });
836
1360
  return signedUrlResult?.url || null;
837
1361
  } catch (error) {
838
- log3.error("Error getting signed URL:", error);
1362
+ log4.error("Error getting signed URL:", error);
839
1363
  throw error;
840
1364
  }
841
1365
  }
@@ -847,7 +1371,7 @@ var FileReferenceServiceImpl = class {
847
1371
  }
848
1372
  return data;
849
1373
  } catch (error) {
850
- log3.error("Error updating file reference:", error);
1374
+ log4.error("Error updating file reference:", error);
851
1375
  throw error;
852
1376
  }
853
1377
  }
@@ -868,7 +1392,7 @@ var FileReferenceServiceImpl = class {
868
1392
  }
869
1393
  return true;
870
1394
  } catch (error) {
871
- log3.error("Error deleting file reference:", error);
1395
+ log4.error("Error deleting file reference:", error);
872
1396
  throw error;
873
1397
  }
874
1398
  }
@@ -885,14 +1409,32 @@ var FileReferenceServiceImpl = class {
885
1409
  if (!data || data.length === 0) {
886
1410
  return [];
887
1411
  }
888
- const ids = data.map((item) => item.id);
889
- const { data: fullData, error: fetchError } = await this.supabase.from("file_references").select("*").in("id", ids);
890
- if (fetchError) {
891
- throw new Error(`Failed to fetch file references: ${fetchError.message}`);
892
- }
893
- 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;
894
1436
  } catch (error) {
895
- log3.error("Error listing file references:", error);
1437
+ log4.error("Error listing file references:", error);
896
1438
  throw error;
897
1439
  }
898
1440
  }
@@ -908,7 +1450,7 @@ var FileReferenceServiceImpl = class {
908
1450
  }
909
1451
  return data || 0;
910
1452
  } catch (error) {
911
- log3.error("Error getting file count:", error);
1453
+ log4.error("Error getting file count:", error);
912
1454
  throw error;
913
1455
  }
914
1456
  }
@@ -926,7 +1468,7 @@ var FileReferenceServiceImpl = class {
926
1468
  }
927
1469
  return data[0];
928
1470
  } catch (error) {
929
- log3.error("Error getting file reference by ID:", error);
1471
+ log4.error("Error getting file reference by ID:", error);
930
1472
  throw error;
931
1473
  }
932
1474
  }
@@ -939,7 +1481,7 @@ var FileReferenceServiceImpl = class {
939
1481
  p_organisation_id: organisation_id
940
1482
  });
941
1483
  if (error) {
942
- log3.error("RPC ERROR getting files by category:", {
1484
+ log4.error("RPC ERROR getting files by category:", {
943
1485
  error,
944
1486
  errorCode: error.code,
945
1487
  errorMessage: error.message,
@@ -959,7 +1501,7 @@ var FileReferenceServiceImpl = class {
959
1501
  const fileCategory = item.file_metadata?.category;
960
1502
  const matches = fileCategory === category;
961
1503
  if (!matches) {
962
- log3.warn("File category mismatch in RPC response:", {
1504
+ log4.warn("File category mismatch in RPC response:", {
963
1505
  fileId: item.id,
964
1506
  expectedCategory: category,
965
1507
  actualCategory: fileCategory
@@ -992,7 +1534,7 @@ var FileReferenceServiceImpl = class {
992
1534
  });
993
1535
  return fileReferences;
994
1536
  } catch (error) {
995
- log3.error("Error getting files by category:", error);
1537
+ log4.error("Error getting files by category:", error);
996
1538
  throw error;
997
1539
  }
998
1540
  }
@@ -1047,7 +1589,7 @@ async function uploadFileWithReference(supabase, options, file) {
1047
1589
 
1048
1590
  // src/hooks/useFileDisplay.ts
1049
1591
  var authenticatedFileCache = /* @__PURE__ */ new Map();
1050
- var MAX_CACHE_SIZE = 100;
1592
+ var MAX_CACHE_SIZE2 = 100;
1051
1593
  function cleanupCache() {
1052
1594
  const now = Date.now();
1053
1595
  const entries = Array.from(authenticatedFileCache.entries());
@@ -1058,9 +1600,9 @@ function cleanupCache() {
1058
1600
  }
1059
1601
  });
1060
1602
  expiredKeys.forEach((key) => authenticatedFileCache.delete(key));
1061
- if (authenticatedFileCache.size > MAX_CACHE_SIZE) {
1603
+ if (authenticatedFileCache.size > MAX_CACHE_SIZE2) {
1062
1604
  const sorted = entries.filter(([key]) => !expiredKeys.includes(key)).sort((a, b) => a[1].timestamp - b[1].timestamp);
1063
- const toRemove = sorted.slice(0, authenticatedFileCache.size - MAX_CACHE_SIZE);
1605
+ const toRemove = sorted.slice(0, authenticatedFileCache.size - MAX_CACHE_SIZE2);
1064
1606
  toRemove.forEach(([key]) => authenticatedFileCache.delete(key));
1065
1607
  }
1066
1608
  }
@@ -1071,14 +1613,14 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1071
1613
  enableCache = true,
1072
1614
  supabase
1073
1615
  } = options;
1074
- const [fileUrl, setFileUrl] = useState(null);
1075
- const [fileReference, setFileReference] = useState(null);
1076
- const [fileReferences, setFileReferences] = useState([]);
1077
- const [fileUrls, setFileUrls] = useState(/* @__PURE__ */ new Map());
1078
- const [fileCount, setFileCount] = useState(0);
1079
- const [isLoading, setIsLoading] = useState(false);
1080
- const [error, setError] = useState(null);
1081
- 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 () => {
1082
1624
  if (!table_name || !record_id || !organisation_id || !supabase) {
1083
1625
  setFileUrl(null);
1084
1626
  setFileReference(null);
@@ -1196,23 +1738,11 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1196
1738
  logger.debug("useFileDisplay", "Setting file URL:", url ? "URL set" : "URL is null");
1197
1739
  setFileUrl(url);
1198
1740
  } else {
1199
- const urlMap = /* @__PURE__ */ new Map();
1200
- for (const fileRef of files) {
1201
- let url = null;
1202
- if (fileRef.is_public) {
1203
- url = getPublicUrl(supabase, fileRef.file_path, true);
1204
- } else {
1205
- const signedUrlResult = await getSignedUrl(supabase, fileRef.file_path, {
1206
- appName: "pace-core",
1207
- orgId: organisation_id,
1208
- expiresIn: 3600
1209
- });
1210
- url = signedUrlResult?.url || null;
1211
- }
1212
- if (url) {
1213
- urlMap.set(fileRef.id, url);
1214
- }
1215
- }
1741
+ const urlMap = await generateFileUrlsBatch(supabase, files, {
1742
+ appName: "pace-core",
1743
+ orgId: organisation_id,
1744
+ expiresIn: 3600
1745
+ });
1216
1746
  setFileUrls(urlMap);
1217
1747
  setFileReference(null);
1218
1748
  setFileUrl(null);
@@ -1263,7 +1793,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1263
1793
  setIsLoading(false);
1264
1794
  }
1265
1795
  }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
1266
- useEffect2(() => {
1796
+ useEffect6(() => {
1267
1797
  if (table_name && record_id && organisation_id && supabase) {
1268
1798
  fetchFiles();
1269
1799
  } else {
@@ -1276,7 +1806,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1276
1806
  setError(null);
1277
1807
  }
1278
1808
  }, [fetchFiles, table_name, record_id, organisation_id, supabase]);
1279
- const refetch = useCallback(async () => {
1809
+ const refetch = useCallback3(async () => {
1280
1810
  if (!table_name || !record_id || !organisation_id || !supabase) return;
1281
1811
  if (enableCache) {
1282
1812
  const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
@@ -1319,7 +1849,7 @@ function invalidateFileDisplayCache(table_name, record_id, organisation_id, cate
1319
1849
  }
1320
1850
 
1321
1851
  // src/hooks/public/usePublicFileDisplay.ts
1322
- import { useState as useState2, useEffect as useEffect3, useCallback as useCallback2 } from "react";
1852
+ import { useState as useState4, useEffect as useEffect7, useCallback as useCallback4 } from "react";
1323
1853
  var publicFileCache = /* @__PURE__ */ new Map();
1324
1854
  function usePublicFileDisplay(table_name, record_id, organisation_id, category, options) {
1325
1855
  const {
@@ -1328,14 +1858,14 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
1328
1858
  enableCache = true,
1329
1859
  supabase
1330
1860
  } = options;
1331
- const [fileUrl, setFileUrl] = useState2(null);
1332
- const [fileReference, setFileReference] = useState2(null);
1333
- const [fileReferences, setFileReferences] = useState2([]);
1334
- const [fileUrls, setFileUrls] = useState2(/* @__PURE__ */ new Map());
1335
- const [fileCount, setFileCount] = useState2(0);
1336
- const [isLoading, setIsLoading] = useState2(false);
1337
- const [error, setError] = useState2(null);
1338
- 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 () => {
1339
1869
  if (!table_name || !record_id || !organisation_id || !supabase) {
1340
1870
  setFileUrl(null);
1341
1871
  setFileReference(null);
@@ -1434,7 +1964,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
1434
1964
  files = [];
1435
1965
  } else {
1436
1966
  const ids = fileIds.map((item) => item.id);
1437
- const { data: fullData, error: fetchError } = await supabase.from("file_references").select("*").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);
1438
1968
  if (fetchError) {
1439
1969
  throw new Error(fetchError.message || "Failed to fetch file references");
1440
1970
  }
@@ -1477,13 +2007,11 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
1477
2007
  const url = getPublicUrl(supabase, firstFile.file_path, true);
1478
2008
  setFileUrl(url);
1479
2009
  } else {
1480
- const urlMap = /* @__PURE__ */ new Map();
1481
- for (const fileRef of fileRefs) {
1482
- const url = getPublicUrl(supabase, fileRef.file_path, true);
1483
- if (url) {
1484
- urlMap.set(fileRef.id, url);
1485
- }
1486
- }
2010
+ const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
2011
+ appName: "pace-core",
2012
+ orgId: organisation_id,
2013
+ expiresIn: 3600
2014
+ });
1487
2015
  setFileUrls(urlMap);
1488
2016
  setFileReference(null);
1489
2017
  setFileUrl(null);
@@ -1530,7 +2058,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
1530
2058
  setIsLoading(false);
1531
2059
  }
1532
2060
  }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
1533
- useEffect3(() => {
2061
+ useEffect7(() => {
1534
2062
  if (table_name && record_id && organisation_id) {
1535
2063
  fetchFiles();
1536
2064
  } else {
@@ -1543,7 +2071,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
1543
2071
  setError(null);
1544
2072
  }
1545
2073
  }, [fetchFiles, table_name, record_id, organisation_id]);
1546
- const refetch = useCallback2(async () => {
2074
+ const refetch = useCallback4(async () => {
1547
2075
  if (!table_name || !record_id || !organisation_id) return;
1548
2076
  if (enableCache) {
1549
2077
  const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
@@ -1578,7 +2106,12 @@ function getPublicFileDisplayCacheStats() {
1578
2106
  }
1579
2107
 
1580
2108
  export {
2109
+ useDebounce,
2110
+ useQueryCache,
2111
+ queryCacheHelpers,
2112
+ useAddressAutocomplete,
1581
2113
  useEventTheme,
2114
+ usePreventTabReload,
1582
2115
  ErrorBoundary,
1583
2116
  PublicPageContext,
1584
2117
  PublicPageProvider,
@@ -1598,6 +2131,7 @@ export {
1598
2131
  uploadFile,
1599
2132
  getPublicUrl,
1600
2133
  getSignedUrl,
2134
+ generateFileUrlsBatch,
1601
2135
  deleteFile,
1602
2136
  listFiles,
1603
2137
  downloadFile,
@@ -1612,4 +2146,4 @@ export {
1612
2146
  clearPublicFileDisplayCache,
1613
2147
  getPublicFileDisplayCacheStats
1614
2148
  };
1615
- //# sourceMappingURL=chunk-MX3EIJGQ.js.map
2149
+ //# sourceMappingURL=chunk-C4OYJOV4.js.map