@jmruthers/pace-core 0.5.188 → 0.5.190

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 (424) hide show
  1. package/core-usage-manifest.json +0 -4
  2. package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
  3. package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
  4. package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-C4uxosp6.d.ts} +129 -40
  5. package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
  6. package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
  7. package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
  8. package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
  9. package/dist/chunk-4QYC5L4K.js.map +1 -0
  10. package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
  11. package/dist/chunk-73HSNNOQ.js.map +1 -0
  12. package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
  13. package/dist/chunk-DZWK57KZ.js.map +1 -0
  14. package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
  15. package/dist/chunk-HQVPB5MZ.js.map +1 -0
  16. package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
  17. package/dist/chunk-HW3OVDUF.js.map +1 -0
  18. package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
  19. package/dist/chunk-I7PSE6JW.js.map +1 -0
  20. package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
  21. package/dist/chunk-J2XXC7R5.js.map +1 -0
  22. package/dist/{chunk-UNOTYLQF.js → chunk-NIU6J6OX.js} +772 -725
  23. package/dist/chunk-NIU6J6OX.js.map +1 -0
  24. package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
  25. package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
  26. package/dist/chunk-RUYZKXOD.js.map +1 -0
  27. package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
  28. package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
  29. package/dist/{chunk-IPCH26AG.js → chunk-STYK4OH2.js} +11 -11
  30. package/dist/chunk-STYK4OH2.js.map +1 -0
  31. package/dist/{chunk-EFCLXK7F.js → chunk-VVBAW5A5.js} +4201 -3809
  32. package/dist/chunk-VVBAW5A5.js.map +1 -0
  33. package/dist/chunk-Y4BUBBHD.js +614 -0
  34. package/dist/chunk-Y4BUBBHD.js.map +1 -0
  35. package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
  36. package/dist/chunk-ZSAAAMVR.js.map +1 -0
  37. package/dist/components.d.ts +3 -5
  38. package/dist/components.js +19 -23
  39. package/dist/components.js.map +1 -1
  40. package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
  41. package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
  42. package/dist/hooks.d.ts +10 -5
  43. package/dist/hooks.js +14 -8
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +13 -12
  46. package/dist/index.js +79 -73
  47. package/dist/index.js.map +1 -1
  48. package/dist/providers.d.ts +3 -3
  49. package/dist/providers.js +3 -1
  50. package/dist/rbac/index.d.ts +76 -12
  51. package/dist/rbac/index.js +12 -9
  52. package/dist/types.d.ts +1 -1
  53. package/dist/types.js +1 -1
  54. package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
  55. package/dist/utils.js +16 -16
  56. package/docs/README.md +2 -2
  57. package/docs/api/classes/ColumnFactory.md +1 -1
  58. package/docs/api/classes/ErrorBoundary.md +1 -1
  59. package/docs/api/classes/InvalidScopeError.md +2 -2
  60. package/docs/api/classes/Logger.md +1 -1
  61. package/docs/api/classes/MissingUserContextError.md +2 -2
  62. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  63. package/docs/api/classes/PermissionDeniedError.md +1 -1
  64. package/docs/api/classes/RBACAuditManager.md +1 -1
  65. package/docs/api/classes/RBACCache.md +1 -1
  66. package/docs/api/classes/RBACEngine.md +4 -4
  67. package/docs/api/classes/RBACError.md +1 -1
  68. package/docs/api/classes/RBACNotInitializedError.md +2 -2
  69. package/docs/api/classes/SecureSupabaseClient.md +21 -16
  70. package/docs/api/classes/StorageUtils.md +7 -4
  71. package/docs/api/enums/FileCategory.md +1 -1
  72. package/docs/api/enums/LogLevel.md +1 -1
  73. package/docs/api/enums/RBACErrorCode.md +1 -1
  74. package/docs/api/enums/RPCFunction.md +1 -1
  75. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  76. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  77. package/docs/api/interfaces/AggregateConfig.md +1 -1
  78. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  79. package/docs/api/interfaces/AvatarProps.md +128 -0
  80. package/docs/api/interfaces/BadgeProps.md +1 -1
  81. package/docs/api/interfaces/ButtonProps.md +1 -1
  82. package/docs/api/interfaces/CalendarProps.md +20 -6
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/ComplianceResult.md +1 -1
  87. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  88. package/docs/api/interfaces/DataRecord.md +1 -1
  89. package/docs/api/interfaces/DataTableAction.md +1 -1
  90. package/docs/api/interfaces/DataTableColumn.md +1 -1
  91. package/docs/api/interfaces/DataTableProps.md +1 -1
  92. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  93. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  94. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  95. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  96. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  97. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  98. package/docs/api/interfaces/ExportColumn.md +1 -1
  99. package/docs/api/interfaces/ExportOptions.md +1 -1
  100. package/docs/api/interfaces/FileDisplayProps.md +62 -16
  101. package/docs/api/interfaces/FileMetadata.md +1 -1
  102. package/docs/api/interfaces/FileReference.md +2 -2
  103. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  104. package/docs/api/interfaces/FileUploadOptions.md +26 -12
  105. package/docs/api/interfaces/FileUploadProps.md +30 -19
  106. package/docs/api/interfaces/FooterProps.md +1 -1
  107. package/docs/api/interfaces/FormFieldProps.md +1 -1
  108. package/docs/api/interfaces/FormProps.md +1 -1
  109. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  110. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  111. package/docs/api/interfaces/InputProps.md +1 -1
  112. package/docs/api/interfaces/LabelProps.md +1 -1
  113. package/docs/api/interfaces/LoggerConfig.md +1 -1
  114. package/docs/api/interfaces/LoginFormProps.md +1 -1
  115. package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
  116. package/docs/api/interfaces/NavigationContextType.md +9 -9
  117. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  118. package/docs/api/interfaces/NavigationItem.md +1 -1
  119. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  120. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  121. package/docs/api/interfaces/Organisation.md +1 -1
  122. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  123. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  124. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  125. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  126. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  127. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  128. package/docs/api/interfaces/PageAccessRecord.md +8 -8
  129. package/docs/api/interfaces/PagePermissionContextType.md +8 -8
  130. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  131. package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
  132. package/docs/api/interfaces/PaletteData.md +1 -1
  133. package/docs/api/interfaces/ParsedAddress.md +1 -1
  134. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  135. package/docs/api/interfaces/ProgressProps.md +3 -11
  136. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  137. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  138. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  139. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  140. package/docs/api/interfaces/QuickFix.md +1 -1
  141. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  142. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  143. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  144. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  145. package/docs/api/interfaces/RBACConfig.md +1 -1
  146. package/docs/api/interfaces/RBACContext.md +1 -1
  147. package/docs/api/interfaces/RBACLogger.md +1 -1
  148. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  149. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  150. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  151. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  152. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  153. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  154. package/docs/api/interfaces/RBACResult.md +1 -1
  155. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  156. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  157. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  158. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  160. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  161. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  162. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  163. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  164. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  165. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  166. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  167. package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
  168. package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
  169. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  170. package/docs/api/interfaces/RouteAccessRecord.md +10 -10
  171. package/docs/api/interfaces/RouteConfig.md +10 -10
  172. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  173. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  174. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  175. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  176. package/docs/api/interfaces/SetupIssue.md +1 -1
  177. package/docs/api/interfaces/StorageConfig.md +4 -4
  178. package/docs/api/interfaces/StorageFileInfo.md +7 -7
  179. package/docs/api/interfaces/StorageFileMetadata.md +25 -14
  180. package/docs/api/interfaces/StorageListOptions.md +22 -9
  181. package/docs/api/interfaces/StorageListResult.md +4 -4
  182. package/docs/api/interfaces/StorageUploadOptions.md +21 -8
  183. package/docs/api/interfaces/StorageUploadResult.md +6 -6
  184. package/docs/api/interfaces/StorageUrlOptions.md +19 -6
  185. package/docs/api/interfaces/StyleImport.md +1 -1
  186. package/docs/api/interfaces/SwitchProps.md +1 -1
  187. package/docs/api/interfaces/TabsContentProps.md +1 -1
  188. package/docs/api/interfaces/TabsListProps.md +1 -1
  189. package/docs/api/interfaces/TabsProps.md +1 -1
  190. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  191. package/docs/api/interfaces/TextareaProps.md +1 -1
  192. package/docs/api/interfaces/ToastActionElement.md +1 -1
  193. package/docs/api/interfaces/ToastProps.md +1 -1
  194. package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
  195. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  196. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  197. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  198. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  199. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  200. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  201. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  202. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  203. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  205. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  207. package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
  208. package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
  209. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  210. package/docs/api/interfaces/UserEventAccess.md +11 -11
  211. package/docs/api/interfaces/UserMenuProps.md +1 -1
  212. package/docs/api/interfaces/UserProfile.md +1 -1
  213. package/docs/api/modules.md +155 -135
  214. package/docs/api-reference/components.md +72 -29
  215. package/docs/api-reference/providers.md +2 -2
  216. package/docs/api-reference/rpc-functions.md +1 -0
  217. package/docs/best-practices/README.md +1 -1
  218. package/docs/best-practices/deployment.md +8 -8
  219. package/docs/getting-started/examples/README.md +2 -2
  220. package/docs/getting-started/installation-guide.md +4 -4
  221. package/docs/getting-started/quick-start.md +3 -3
  222. package/docs/migration/MIGRATION_GUIDE.md +3 -3
  223. package/docs/rbac/compliance/compliance-guide.md +2 -2
  224. package/docs/rbac/event-based-apps.md +2 -2
  225. package/docs/rbac/getting-started.md +2 -2
  226. package/docs/rbac/quick-start.md +2 -2
  227. package/docs/security/README.md +4 -4
  228. package/docs/standards/07-rbac-and-rls-standard.md +430 -7
  229. package/docs/troubleshooting/README.md +2 -2
  230. package/docs/troubleshooting/migration.md +3 -3
  231. package/package.json +1 -4
  232. package/scripts/check-pace-core-compliance.cjs +1 -1
  233. package/scripts/check-pace-core-compliance.js +1 -1
  234. package/src/__tests__/fixtures/supabase.ts +301 -0
  235. package/src/__tests__/public-recipe-view.test.ts +9 -9
  236. package/src/__tests__/rls-policies.test.ts +197 -61
  237. package/src/components/AddressField/AddressField.test.tsx +42 -0
  238. package/src/components/AddressField/AddressField.tsx +71 -60
  239. package/src/components/AddressField/README.md +1 -0
  240. package/src/components/Alert/Alert.test.tsx +50 -10
  241. package/src/components/Alert/Alert.tsx +5 -3
  242. package/src/components/Avatar/Avatar.test.tsx +252 -226
  243. package/src/components/Avatar/Avatar.tsx +179 -53
  244. package/src/components/Avatar/index.ts +1 -1
  245. package/src/components/Button/Button.test.tsx +2 -1
  246. package/src/components/Button/Button.tsx +3 -3
  247. package/src/components/Calendar/Calendar.test.tsx +53 -37
  248. package/src/components/Calendar/Calendar.tsx +409 -82
  249. package/src/components/Card/Card.test.tsx +7 -4
  250. package/src/components/Card/Card.tsx +3 -6
  251. package/src/components/Checkbox/Checkbox.tsx +2 -2
  252. package/src/components/DataTable/components/ActionButtons.tsx +5 -5
  253. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  254. package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
  255. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
  256. package/src/components/DataTable/components/DataTableBody.tsx +12 -12
  257. package/src/components/DataTable/components/DataTableCore.tsx +3 -3
  258. package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
  259. package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
  260. package/src/components/DataTable/components/EditableRow.tsx +2 -2
  261. package/src/components/DataTable/components/EmptyState.tsx +3 -3
  262. package/src/components/DataTable/components/GroupHeader.tsx +2 -2
  263. package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
  264. package/src/components/DataTable/components/ImportModal.tsx +4 -4
  265. package/src/components/DataTable/components/LoadingState.tsx +1 -1
  266. package/src/components/DataTable/components/PaginationControls.tsx +11 -11
  267. package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
  268. package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
  269. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
  270. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
  271. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
  272. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
  273. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
  274. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
  275. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
  276. package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
  277. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
  278. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
  279. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
  280. package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
  281. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
  282. package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
  283. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
  284. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
  285. package/src/components/Dialog/Dialog.tsx +2 -2
  286. package/src/components/EventSelector/EventSelector.tsx +7 -7
  287. package/src/components/FileDisplay/FileDisplay.tsx +291 -179
  288. package/src/components/FileUpload/FileUpload.tsx +7 -4
  289. package/src/components/Header/Header.test.tsx +28 -0
  290. package/src/components/Header/Header.tsx +22 -9
  291. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
  292. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
  293. package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
  294. package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
  295. package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
  296. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
  297. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
  298. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
  299. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
  300. package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
  301. package/src/components/PaceAppLayout/test-setup.tsx +1 -0
  302. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
  303. package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
  304. package/src/components/Progress/Progress.test.tsx +18 -19
  305. package/src/components/Progress/Progress.tsx +31 -32
  306. package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
  307. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
  308. package/src/components/Select/Select.tsx +5 -5
  309. package/src/components/Switch/Switch.test.tsx +2 -1
  310. package/src/components/Switch/Switch.tsx +1 -1
  311. package/src/components/Toast/Toast.tsx +1 -1
  312. package/src/components/Tooltip/Tooltip.test.tsx +8 -2
  313. package/src/components/UserMenu/UserMenu.test.tsx +7 -9
  314. package/src/components/UserMenu/UserMenu.tsx +10 -8
  315. package/src/components/index.ts +2 -1
  316. package/src/eslint-rules/pace-core-compliance.cjs +0 -2
  317. package/src/eslint-rules/pace-core-compliance.js +0 -2
  318. package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
  319. package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
  320. package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
  321. package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
  322. package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
  323. package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
  324. package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
  325. package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
  326. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
  327. package/src/hooks/index.ts +1 -1
  328. package/src/hooks/public/usePublicEvent.ts +2 -2
  329. package/src/hooks/public/usePublicFileDisplay.ts +173 -87
  330. package/src/hooks/useAppConfig.ts +24 -5
  331. package/src/hooks/useFileDisplay.ts +297 -34
  332. package/src/hooks/useFileReference.ts +56 -11
  333. package/src/hooks/useFileUrl.ts +1 -1
  334. package/src/hooks/useInactivityTracker.ts +16 -7
  335. package/src/hooks/usePermissionCache.test.ts +85 -8
  336. package/src/hooks/useQueryCache.ts +21 -0
  337. package/src/hooks/useSecureDataAccess.test.ts +80 -35
  338. package/src/hooks/useSecureDataAccess.ts +80 -37
  339. package/src/index.ts +2 -1
  340. package/src/providers/services/EventServiceProvider.tsx +37 -17
  341. package/src/providers/services/InactivityServiceProvider.tsx +4 -4
  342. package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
  343. package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
  344. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
  345. package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
  346. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
  347. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
  348. package/src/rbac/api.ts +240 -36
  349. package/src/rbac/cache-invalidation.ts +21 -7
  350. package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
  351. package/src/rbac/components/NavigationGuard.tsx +23 -63
  352. package/src/rbac/components/NavigationProvider.test.tsx +52 -23
  353. package/src/rbac/components/NavigationProvider.tsx +13 -11
  354. package/src/rbac/components/PagePermissionGuard.tsx +77 -203
  355. package/src/rbac/components/PagePermissionProvider.tsx +13 -11
  356. package/src/rbac/components/PermissionEnforcer.tsx +24 -62
  357. package/src/rbac/components/RoleBasedRouter.tsx +14 -12
  358. package/src/rbac/components/SecureDataProvider.tsx +13 -11
  359. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
  360. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
  361. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
  362. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
  363. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
  364. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
  365. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
  366. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
  367. package/src/rbac/engine.ts +4 -2
  368. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
  369. package/src/rbac/hooks/index.ts +3 -0
  370. package/src/rbac/hooks/useCan.test.ts +101 -53
  371. package/src/rbac/hooks/usePermissions.ts +108 -41
  372. package/src/rbac/hooks/useRBAC.test.ts +11 -3
  373. package/src/rbac/hooks/useRBAC.ts +83 -40
  374. package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
  375. package/src/rbac/hooks/useResolvedScope.ts +128 -70
  376. package/src/rbac/hooks/useSecureSupabase.ts +36 -19
  377. package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
  378. package/src/rbac/request-deduplication.ts +1 -1
  379. package/src/rbac/secureClient.ts +72 -12
  380. package/src/rbac/security.ts +29 -23
  381. package/src/rbac/types.ts +10 -0
  382. package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
  383. package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
  384. package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
  385. package/src/rbac/utils/contextValidator.ts +288 -0
  386. package/src/rbac/utils/eventContext.ts +48 -2
  387. package/src/services/EventService.ts +165 -21
  388. package/src/services/OrganisationService.ts +37 -2
  389. package/src/services/__tests__/EventService.test.ts +26 -21
  390. package/src/types/file-reference.ts +13 -10
  391. package/src/utils/app/appNameResolver.test.ts +346 -73
  392. package/src/utils/context/superAdminOverride.ts +58 -0
  393. package/src/utils/file-reference/index.ts +61 -33
  394. package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
  395. package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
  396. package/src/utils/storage/helpers.test.ts +1 -1
  397. package/src/utils/storage/helpers.ts +38 -19
  398. package/src/utils/storage/types.ts +15 -8
  399. package/src/utils/validation/__tests__/csrf.test.ts +105 -0
  400. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
  401. package/src/vite-env.d.ts +2 -2
  402. package/dist/chunk-3GOZZZYH.js.map +0 -1
  403. package/dist/chunk-DDM4CCYT.js.map +0 -1
  404. package/dist/chunk-E7UAOUMY.js +0 -75
  405. package/dist/chunk-E7UAOUMY.js.map +0 -1
  406. package/dist/chunk-EFCLXK7F.js.map +0 -1
  407. package/dist/chunk-F2IMUDXZ.js.map +0 -1
  408. package/dist/chunk-HEHYGYOX.js.map +0 -1
  409. package/dist/chunk-IM4QE42D.js.map +0 -1
  410. package/dist/chunk-IPCH26AG.js.map +0 -1
  411. package/dist/chunk-SAUPYVLF.js.map +0 -1
  412. package/dist/chunk-THRPYOFK.js.map +0 -1
  413. package/dist/chunk-UNOTYLQF.js.map +0 -1
  414. package/dist/chunk-VGZZXKBR.js.map +0 -1
  415. package/dist/chunk-YHCN776L.js.map +0 -1
  416. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
  417. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
  418. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
  419. package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
  420. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
  421. /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
  422. /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
  423. /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
  424. /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
@@ -1,9 +1,6 @@
1
1
  import {
2
2
  useEvents
3
- } from "./chunk-E7UAOUMY.js";
4
- import {
5
- useUnifiedAuth
6
- } from "./chunk-VGZZXKBR.js";
3
+ } from "./chunk-Y4BUBBHD.js";
7
4
  import {
8
5
  assertAppId
9
6
  } from "./chunk-QXHPKYJV.js";
@@ -11,9 +8,8 @@ import {
11
8
  createAddressFromPlaceResult,
12
9
  fetchPlaceAutocomplete,
13
10
  fetchPlaceDetails,
14
- getAddressByPlaceId,
15
- performanceBudgetMonitor
16
- } from "./chunk-YHCN776L.js";
11
+ getAddressByPlaceId
12
+ } from "./chunk-DZWK57KZ.js";
17
13
  import {
18
14
  setOrganisationContext
19
15
  } from "./chunk-VBXEHIUJ.js";
@@ -64,6 +60,20 @@ function runCacheCleanup() {
64
60
  if (typeof window !== "undefined" && !cleanupTimer) {
65
61
  cleanupTimer = setInterval(runCacheCleanup, CLEANUP_INTERVAL_MS);
66
62
  log.debug("Query cache cleanup initialized.");
63
+ window.addEventListener("beforeunload", () => {
64
+ if (cleanupTimer) {
65
+ clearInterval(cleanupTimer);
66
+ cleanupTimer = null;
67
+ log.debug("Query cache cleanup timer cleared on page unload.");
68
+ }
69
+ });
70
+ }
71
+ function cleanupQueryCache() {
72
+ if (cleanupTimer) {
73
+ clearInterval(cleanupTimer);
74
+ cleanupTimer = null;
75
+ log.debug("Query cache cleanup timer cleared.");
76
+ }
67
77
  }
68
78
  function useQueryCache(supabase) {
69
79
  const getCachedQuery = useCallback(async (table, filterKey, filterValue, fetchFn, options = {}) => {
@@ -371,337 +381,6 @@ function useAddressAutocomplete(apiKey, inputValue, options = {}) {
371
381
  };
372
382
  }
373
383
 
374
- // src/hooks/useEventTheme.ts
375
- import { useEffect as useEffect4 } from "react";
376
- import { useLocation } from "react-router-dom";
377
- var log2 = createLogger("useEventTheme");
378
- function useEventTheme(event) {
379
- const location = useLocation();
380
- let selectedEvent;
381
- try {
382
- if (event === void 0) {
383
- const eventsContext = useEvents();
384
- selectedEvent = eventsContext.selectedEvent;
385
- } else {
386
- selectedEvent = event;
387
- }
388
- } catch (error) {
389
- if (event !== void 0) {
390
- selectedEvent = event;
391
- } else {
392
- selectedEvent = null;
393
- }
394
- }
395
- useEffect4(() => {
396
- const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login");
397
- if (isOnLoginRoute) {
398
- clearPalette();
399
- return;
400
- }
401
- if (!selectedEvent) {
402
- clearPalette();
403
- return;
404
- }
405
- const eventColours = selectedEvent.event_colours;
406
- const normalized = parseAndNormalizeEventColours(eventColours);
407
- if (!normalized) {
408
- clearPalette();
409
- return;
410
- }
411
- try {
412
- applyPalette(normalized);
413
- } catch (error) {
414
- log2.error("Failed to apply event palette:", error);
415
- }
416
- return () => {
417
- };
418
- }, [selectedEvent, location.pathname]);
419
- }
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
-
463
- // src/components/ErrorBoundary/ErrorBoundary.tsx
464
- import { Component } from "react";
465
- import { jsx, jsxs } from "react/jsx-runtime";
466
- var ErrorBoundary = class extends Component {
467
- constructor(props) {
468
- super(props);
469
- this.retryTimeoutId = null;
470
- this.reportError = (errorId, componentName) => {
471
- if (import.meta.env.MODE === "production") {
472
- logger.warn("ErrorBoundary", "Error reporting would be triggered in production:", { errorId, componentName });
473
- }
474
- };
475
- this.handleRetry = () => {
476
- const { maxRetries = 3 } = this.props;
477
- const { retryCount } = this.state;
478
- if (retryCount < maxRetries) {
479
- logger.debug("ErrorBoundary", `Retrying component render (attempt ${retryCount + 1}/${maxRetries})`);
480
- this.setState((prevState) => ({
481
- hasError: false,
482
- error: void 0,
483
- errorInfo: void 0,
484
- errorId: void 0,
485
- retryCount: prevState.retryCount + 1
486
- }));
487
- }
488
- };
489
- this.state = {
490
- hasError: false,
491
- retryCount: 0
492
- };
493
- }
494
- static getDerivedStateFromError(error) {
495
- const errorId = `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
496
- return {
497
- hasError: true,
498
- error,
499
- errorId
500
- };
501
- }
502
- componentDidCatch(error, errorInfo) {
503
- const { componentName = "Unknown Component", onError, enableReporting = true } = this.props;
504
- const errorId = this.state.errorId;
505
- this.setState({ errorInfo });
506
- logger.error("ErrorBoundary", `[${componentName}] Caught error ${errorId}:`, error, errorInfo);
507
- performanceBudgetMonitor.measure("ERROR_BOUNDARY_TRIGGER", 1, {
508
- componentName,
509
- errorId,
510
- errorMessage: error.message,
511
- stack: error.stack?.substring(0, 200)
512
- // Truncated stack trace
513
- });
514
- if (enableReporting) {
515
- this.reportError(errorId, componentName);
516
- }
517
- if (onError) {
518
- onError(error, errorInfo, errorId);
519
- }
520
- }
521
- componentWillUnmount() {
522
- if (this.retryTimeoutId) {
523
- clearTimeout(this.retryTimeoutId);
524
- }
525
- }
526
- render() {
527
- if (this.state.hasError) {
528
- const {
529
- componentName = "Component",
530
- fallback,
531
- enableRetry = true,
532
- maxRetries = 3
533
- } = this.props;
534
- const { retryCount, errorId } = this.state;
535
- if (fallback) {
536
- return fallback;
537
- }
538
- return /* @__PURE__ */ jsx(
539
- "div",
540
- {
541
- role: "alert",
542
- className: "p-6 bg-destructive/10 border border-destructive/20 rounded-lg",
543
- "data-error-boundary": errorId,
544
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
545
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 text-destructive", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) }) }),
546
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
547
- /* @__PURE__ */ jsxs("h3", { className: "text-destructive", children: [
548
- "Error in ",
549
- componentName
550
- ] }),
551
- /* @__PURE__ */ jsx("p", { className: "text-destructive/80", children: this.state.error?.message || "An unexpected error occurred." }),
552
- enableRetry && retryCount < maxRetries && /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mb-4", children: [
553
- /* @__PURE__ */ jsxs(
554
- "button",
555
- {
556
- onClick: this.handleRetry,
557
- className: "px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors text-sm font-medium",
558
- children: [
559
- "Retry (",
560
- retryCount + 1,
561
- "/",
562
- maxRetries,
563
- ")"
564
- ]
565
- }
566
- ),
567
- /* @__PURE__ */ jsx(
568
- "button",
569
- {
570
- onClick: () => window.location.reload(),
571
- className: "px-4 py-2 bg-sec-600 text-main-50 rounded-md hover:bg-sec-700 transition-colors text-sm font-medium",
572
- children: "Reload Page"
573
- }
574
- )
575
- ] }),
576
- retryCount >= maxRetries && /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 bg-acc-50 border border-acc-200 rounded-md", children: [
577
- /* @__PURE__ */ jsx("p", { className: "text-acc-800", children: "Maximum retry attempts reached. Please reload the page or contact support." }),
578
- /* @__PURE__ */ jsx(
579
- "button",
580
- {
581
- onClick: () => window.location.reload(),
582
- className: "mt-2 px-3 py-1 bg-acc-600 text-main-50 rounded text-sm hover:bg-acc-700",
583
- children: "Reload Page"
584
- }
585
- )
586
- ] }),
587
- import.meta.env.MODE === "development" && this.state.error && /* @__PURE__ */ jsxs("details", { className: "text-sm text-destructive/70", children: [
588
- /* @__PURE__ */ jsx("summary", { className: "cursor-pointer font-medium mb-2", children: "Error Details (Development)" }),
589
- /* @__PURE__ */ jsxs("div", { className: "bg-destructive/5 p-3 rounded border", children: [
590
- /* @__PURE__ */ jsxs("p", { className: "font-mono", children: [
591
- "Error ID: ",
592
- errorId
593
- ] }),
594
- /* @__PURE__ */ jsxs("pre", { className: "whitespace-pre-wrap text-xs overflow-auto max-h-32", children: [
595
- this.state.error.toString(),
596
- this.state.errorInfo?.componentStack
597
- ] })
598
- ] })
599
- ] })
600
- ] })
601
- ] })
602
- }
603
- );
604
- }
605
- return this.props.children;
606
- }
607
- };
608
-
609
- // src/components/PublicLayout/PublicPageProvider.tsx
610
- import { createContext, useContext, useMemo as useMemo2 } from "react";
611
- import { createClient } from "@supabase/supabase-js";
612
- import { jsx as jsx2 } from "react/jsx-runtime";
613
- var PublicPageContext = createContext(void 0);
614
- function PublicPageProvider({ children, appName }) {
615
- const getEnvVar = (key) => {
616
- if (typeof import.meta !== "undefined" && import.meta.env) {
617
- const env = import.meta.env;
618
- return env[key];
619
- }
620
- if (typeof process !== "undefined" && process.env) {
621
- return process.env[key];
622
- }
623
- return void 0;
624
- };
625
- const supabaseUrl = getEnvVar("VITE_SUPABASE_URL") || getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || null;
626
- const supabaseKey = getEnvVar("VITE_SUPABASE_ANON_KEY") || getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || null;
627
- const supabase = useMemo2(() => {
628
- if (!supabaseUrl || !supabaseKey) {
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.");
630
- return null;
631
- }
632
- const client = createClient(supabaseUrl, supabaseKey);
633
- logger.info("PublicPageProvider", "Supabase client created successfully for public pages");
634
- return client;
635
- }, [supabaseUrl, supabaseKey]);
636
- const contextValue = {
637
- isPublicPage: true,
638
- supabase,
639
- appName: appName || null,
640
- environment: {
641
- supabaseUrl,
642
- supabaseKey
643
- }
644
- };
645
- return /* @__PURE__ */ jsx2(PublicPageContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(ErrorBoundary, { componentName: "PublicPageProvider", children }) });
646
- }
647
- function usePublicPageContext() {
648
- const context = useContext(PublicPageContext);
649
- if (!context) {
650
- throw new Error("usePublicPageContext must be used within a PublicPageProvider");
651
- }
652
- return context;
653
- }
654
- function useIsPublicPage() {
655
- const context = useContext(PublicPageContext);
656
- return context !== void 0;
657
- }
658
-
659
- // src/hooks/useAppConfig.ts
660
- import { useMemo as useMemo3, useContext as useContext2 } from "react";
661
- function useAppConfig() {
662
- const isPublicPage = useIsPublicPage();
663
- const publicPageContext = useContext2(PublicPageContext);
664
- const contextAppName = publicPageContext?.appName || null;
665
- if (isPublicPage) {
666
- const getAppName = () => {
667
- if (contextAppName) {
668
- return contextAppName;
669
- }
670
- if (typeof import.meta !== "undefined" && import.meta.env) {
671
- return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
672
- }
673
- if (typeof import.meta !== "undefined" && import.meta.env) {
674
- return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
675
- }
676
- return "PACE";
677
- };
678
- return useMemo3(() => ({
679
- supportsDirectAccess: false,
680
- // Public pages don't support direct access
681
- requiresEvent: true,
682
- // Public pages always require an event
683
- isLoading: false,
684
- appName: getAppName()
685
- }), [contextAppName]);
686
- }
687
- try {
688
- const { appConfig, appName } = useUnifiedAuth();
689
- return useMemo3(() => ({
690
- supportsDirectAccess: !(appConfig?.requires_event ?? true),
691
- requiresEvent: appConfig?.requires_event ?? true,
692
- isLoading: appConfig === null,
693
- appName
694
- }), [appConfig?.requires_event, appName]);
695
- } catch (error) {
696
- return useMemo3(() => ({
697
- supportsDirectAccess: false,
698
- requiresEvent: true,
699
- isLoading: false,
700
- appName: "PACE"
701
- }), []);
702
- }
703
- }
704
-
705
384
  // src/utils/storage/config.ts
706
385
  var FILE_SIZE_LIMITS = {
707
386
  // Images
@@ -772,23 +451,24 @@ function validateFileSize(file) {
772
451
  }
773
452
 
774
453
  // src/utils/storage/helpers.ts
775
- var log3 = createLogger("StorageHelpers");
454
+ var log2 = createLogger("StorageHelpers");
776
455
  function generateFilePath(options, fileName) {
777
- const { orgId, isPublic = false, customPath } = options;
778
- if (!orgId) {
779
- throw new Error("orgId is required for file path generation");
456
+ const { orgId, userId, isPublic = false, customPath } = options;
457
+ if (!orgId && !userId) {
458
+ throw new Error("Either orgId or userId is required for file path generation");
780
459
  }
460
+ const basePath = orgId ? orgId : `users/${userId}`;
781
461
  if (isPublic) {
782
462
  if (customPath) {
783
- return `${orgId}/${customPath}/${fileName}`;
463
+ return `${basePath}/${customPath}/${fileName}`;
784
464
  }
785
- return `${orgId}/public/${fileName}`;
465
+ return `${basePath}/public/${fileName}`;
786
466
  }
787
467
  if (customPath) {
788
- return `${orgId}/${customPath}/${fileName}`;
468
+ return `${basePath}/${customPath}/${fileName}`;
789
469
  }
790
470
  const pathFolder = customPath || "files";
791
- return `${orgId}/${pathFolder}/${fileName}`;
471
+ return `${basePath}/${pathFolder}/${fileName}`;
792
472
  }
793
473
  function generateUniqueFileName(originalName) {
794
474
  const timestamp = Date.now();
@@ -805,7 +485,8 @@ async function extractFileMetadata(file, options, uploadedBy) {
805
485
  const metadata = {
806
486
  mimeType: file.type,
807
487
  size: file.size,
808
- orgId: options.orgId,
488
+ ...options.orgId && { orgId: options.orgId },
489
+ ...options.userId && { userId: options.userId },
809
490
  appName: options.appName || "pace-core",
810
491
  uploadedBy,
811
492
  uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -867,12 +548,12 @@ async function ensureFolderExists(supabase, folderPath, bucketName) {
867
548
  contentType: "text/plain"
868
549
  });
869
550
  if (uploadError) {
870
- log3.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
551
+ log2.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);
871
552
  return true;
872
553
  }
873
554
  return true;
874
555
  } catch (error) {
875
- log3.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : "Unknown error"}`);
556
+ log2.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : "Unknown error"}`);
876
557
  return true;
877
558
  }
878
559
  }
@@ -979,7 +660,7 @@ async function getSignedUrl(supabase, path, options) {
979
660
  const bucketName = getBucketName(false);
980
661
  const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(path, options.expiresIn || 3600);
981
662
  if (error) {
982
- log3.error("Failed to create signed URL:", error);
663
+ log2.error("Failed to create signed URL:", error);
983
664
  return null;
984
665
  }
985
666
  return {
@@ -987,7 +668,7 @@ async function getSignedUrl(supabase, path, options) {
987
668
  expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1e3).toISOString()
988
669
  };
989
670
  } catch (error) {
990
- log3.error("Failed to create signed URL:", error);
671
+ log2.error("Failed to create signed URL:", error);
991
672
  return null;
992
673
  }
993
674
  }
@@ -1049,7 +730,7 @@ async function generateFileUrlsBatch(supabase, fileReferences, options) {
1049
730
  });
1050
731
  }
1051
732
  } catch (err) {
1052
- log3.error(`Failed to generate public URL for file ${file.id}:`, err);
733
+ log2.error(`Failed to generate public URL for file ${file.id}:`, err);
1053
734
  }
1054
735
  }
1055
736
  if (privateFiles.length > 0) {
@@ -1070,7 +751,7 @@ async function generateFileUrlsBatch(supabase, fileReferences, options) {
1070
751
  }
1071
752
  return { id: file.id, url };
1072
753
  } catch (err) {
1073
- log3.error(`Failed to generate signed URL for file ${file.id}:`, err);
754
+ log2.error(`Failed to generate signed URL for file ${file.id}:`, err);
1074
755
  return { id: file.id, url: null };
1075
756
  }
1076
757
  });
@@ -1107,7 +788,11 @@ async function deleteFile(supabase, path, isPublic = false) {
1107
788
  async function listFiles(supabase, options) {
1108
789
  try {
1109
790
  const bucketName = getBucketName(options.isPublic || false);
1110
- const pathPrefix = `${options.orgId}/`;
791
+ if (!options.orgId && !options.userId) {
792
+ throw new Error("Either orgId or userId is required for listing files");
793
+ }
794
+ const basePath = options.orgId ? options.orgId : `users/${options.userId}`;
795
+ const pathPrefix = `${basePath}/`;
1111
796
  const searchPath = options.pathPrefix ? `${pathPrefix}${options.pathPrefix}` : pathPrefix;
1112
797
  const { data, error } = await supabase.storage.from(bucketName).list(searchPath, {
1113
798
  limit: options.limit || 100,
@@ -1115,7 +800,7 @@ async function listFiles(supabase, options) {
1115
800
  sortBy: { column: "created_at", order: "desc" }
1116
801
  });
1117
802
  if (error) {
1118
- log3.error("Failed to list files:", error);
803
+ log2.error("Failed to list files:", error);
1119
804
  return { files: [], totalCount: 0, hasMore: false };
1120
805
  }
1121
806
  const files = (data || []).map((item) => ({
@@ -1127,7 +812,8 @@ async function listFiles(supabase, options) {
1127
812
  metadata: {
1128
813
  mimeType: item.metadata?.mimetype || "application/octet-stream",
1129
814
  size: item.metadata?.size || 0,
1130
- orgId: options.orgId,
815
+ ...options.orgId && { orgId: options.orgId },
816
+ ...options.userId && { userId: options.userId },
1131
817
  appName: options.appName,
1132
818
  uploadedBy: "unknown",
1133
819
  uploadedAt: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
@@ -1140,7 +826,7 @@ async function listFiles(supabase, options) {
1140
826
  hasMore: files.length >= (options.limit || 100)
1141
827
  };
1142
828
  } catch (error) {
1143
- log3.error("Failed to list files:", error);
829
+ log2.error("Failed to list files:", error);
1144
830
  return { files: [], totalCount: 0, hasMore: false };
1145
831
  }
1146
832
  }
@@ -1149,7 +835,7 @@ async function downloadFile(supabase, path, isPublic = false) {
1149
835
  const bucketName = getBucketName(isPublic);
1150
836
  const { data, error } = await supabase.storage.from(bucketName).download(path);
1151
837
  if (error) {
1152
- log3.error("Failed to download file:", error);
838
+ log2.error("Failed to download file:", error);
1153
839
  return null;
1154
840
  }
1155
841
  if (!data) {
@@ -1169,14 +855,21 @@ async function downloadFile(supabase, path, isPublic = false) {
1169
855
  }
1170
856
  };
1171
857
  } catch (error) {
1172
- log3.error("Failed to download file:", error);
858
+ log2.error("Failed to download file:", error);
1173
859
  return null;
1174
860
  }
1175
861
  }
1176
862
  async function archiveFile(supabase, path, options) {
1177
863
  try {
1178
864
  const bucketName = getBucketName(options.isPublic || false);
1179
- const archivedPath = path.replace(`${options.orgId}/`, `archived/${options.orgId}/`);
865
+ let archivedPath;
866
+ if (options.orgId) {
867
+ archivedPath = path.replace(`${options.orgId}/`, `archived/${options.orgId}/`);
868
+ } else if (options.userId) {
869
+ archivedPath = path.replace(`users/${options.userId}/`, `archived/users/${options.userId}/`);
870
+ } else {
871
+ throw new Error("Either orgId or userId is required for archiving files");
872
+ }
1180
873
  const { error: copyError } = await supabase.storage.from(bucketName).copy(path, archivedPath);
1181
874
  if (copyError) {
1182
875
  return {
@@ -1197,50 +890,378 @@ async function archiveFile(supabase, path, options) {
1197
890
  }
1198
891
  }
1199
892
 
1200
- // src/hooks/useFileDisplay.ts
1201
- import { useState as useState3, useEffect as useEffect6, useCallback as useCallback3 } from "react";
1202
-
1203
- // src/utils/file-reference/index.ts
1204
- var log4 = createLogger("FileReferenceService");
1205
- var FileReferenceServiceImpl = class {
1206
- constructor(supabase) {
1207
- this.supabase = supabase;
1208
- }
1209
- /**
1210
- * Creates a file reference by uploading a file to storage and linking it in the database.
1211
- *
1212
- * Storage Flow:
1213
- * 1. Upload file to storage bucket first (files or public-files based on is_public flag)
1214
- * - Path format: {orgId}/{folder}/{timestamp-uuid-filename}
1215
- * - Bucket selection: 'files' (private) or 'public-files' (public)
1216
- * 2. Extract file metadata (dimensions, hash, etc.)
1217
- * 3. Set organisation context for RLS policies
1218
- * 4. Create database reference via RPC function
1219
- * 5. If DB insert fails, rollback by deleting uploaded file
1220
- *
1221
- * This ensures atomicity: either both storage and DB succeed, or both are cleaned up.
1222
- */
1223
- async createFileReference(options, file) {
1224
- try {
1225
- if (!options.organisation_id) {
1226
- throw new Error("organisation_id is required for file upload");
1227
- }
1228
- if (!options.table_name) {
1229
- throw new Error("table_name is required for file upload");
893
+ // src/hooks/public/usePublicFileDisplay.ts
894
+ import { useState as useState3, useEffect as useEffect4, useCallback as useCallback3 } from "react";
895
+ var publicFileCache = /* @__PURE__ */ new Map();
896
+ function usePublicFileDisplay(table_name, record_id, organisation_id, category, options) {
897
+ const {
898
+ cacheTtl = 30 * 60 * 1e3,
899
+ // 30 minutes
900
+ enableCache = true,
901
+ supabase
902
+ } = options;
903
+ const [fileUrl, setFileUrl] = useState3(null);
904
+ const [fileReference, setFileReference] = useState3(null);
905
+ const [fileReferences, setFileReferences] = useState3([]);
906
+ const [fileUrls, setFileUrls] = useState3(/* @__PURE__ */ new Map());
907
+ const [fileCount, setFileCount] = useState3(0);
908
+ const [isLoading, setIsLoading] = useState3(false);
909
+ const [error, setError] = useState3(null);
910
+ const fetchFiles = useCallback3(async () => {
911
+ if (!table_name || !record_id || !supabase) {
912
+ setFileUrl(null);
913
+ setFileReference(null);
914
+ setFileReferences([]);
915
+ setFileUrls(/* @__PURE__ */ new Map());
916
+ setFileCount(0);
917
+ setIsLoading(false);
918
+ return;
919
+ }
920
+ if (organisation_id) {
921
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
922
+ if (!uuidRegex.test(organisation_id)) {
923
+ logger.warn("usePublicFileDisplay", "Invalid organisationId format (not a valid UUID)", { organisation_id });
1230
924
  }
1231
- if (!options.record_id) {
1232
- throw new Error("record_id is required for file upload");
925
+ }
926
+ const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
927
+ if (enableCache) {
928
+ const cached = publicFileCache.get(cacheKey);
929
+ if (cached && Date.now() - cached.timestamp < cached.ttl) {
930
+ const cachedData = cached.data;
931
+ setFileUrl(cachedData.fileUrl || null);
932
+ setFileReference(cachedData.fileReference || null);
933
+ setFileReferences(cachedData.fileReferences || []);
934
+ setFileUrls(cachedData.fileUrls || /* @__PURE__ */ new Map());
935
+ setFileCount(cachedData.fileCount || 0);
936
+ setIsLoading(false);
937
+ setError(null);
938
+ return;
1233
939
  }
1234
- if (!options.folder) {
1235
- throw new Error("folder is required for file upload. The folder prop determines the storage path.");
940
+ }
941
+ try {
942
+ setIsLoading(true);
943
+ setError(null);
944
+ let files = [];
945
+ if (organisation_id === void 0) {
946
+ logger.debug("usePublicFileDisplay", "organisation_id is undefined, searching both user-scoped and organisation-scoped files:", {
947
+ table_name,
948
+ record_id,
949
+ category
950
+ });
951
+ let userScopedFiles = [];
952
+ let orgScopedFiles = [];
953
+ if (category) {
954
+ const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_by_category_list", {
955
+ p_table_name: table_name,
956
+ p_record_id: record_id,
957
+ p_category: category,
958
+ p_organisation_id: null
959
+ });
960
+ if (!userRpcError && userData) {
961
+ userScopedFiles = userData.filter((item) => item.is_public === true && item.id && item.file_path && item.file_metadata).map((item) => ({
962
+ id: item.id,
963
+ table_name,
964
+ record_id,
965
+ file_path: item.file_path,
966
+ file_metadata: item.file_metadata || {},
967
+ organisation_id: null,
968
+ app_id: item.file_metadata?.app_id || null,
969
+ is_public: true,
970
+ created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
971
+ updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
972
+ }));
973
+ }
974
+ } else {
975
+ const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_list", {
976
+ p_table_name: table_name,
977
+ p_record_id: record_id,
978
+ p_organisation_id: null
979
+ });
980
+ if (!userRpcError && userData) {
981
+ const ids = userData.map((item) => item.id);
982
+ if (ids.length > 0) {
983
+ const { data: fullData } = 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);
984
+ userScopedFiles = fullData || [];
985
+ }
986
+ }
987
+ }
988
+ const allFiles = [...userScopedFiles, ...orgScopedFiles];
989
+ allFiles.sort((a, b) => {
990
+ const aTime = new Date(a.created_at).getTime();
991
+ const bTime = new Date(b.created_at).getTime();
992
+ return bTime - aTime;
993
+ });
994
+ files = allFiles;
995
+ logger.debug("usePublicFileDisplay", "Found files with undefined organisation_id:", {
996
+ userScopedCount: userScopedFiles.length,
997
+ orgScopedCount: orgScopedFiles.length,
998
+ totalCount: files.length
999
+ });
1000
+ } else {
1001
+ if (category) {
1002
+ const rpcParams = {
1003
+ p_table_name: table_name,
1004
+ p_record_id: record_id,
1005
+ p_category: category,
1006
+ p_organisation_id: organisation_id ?? null
1007
+ };
1008
+ const { data, error: rpcError } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
1009
+ if (rpcError) {
1010
+ logger.error("usePublicFileDisplay", "RPC function error", {
1011
+ function: "data_file_reference_by_category_list",
1012
+ table_name,
1013
+ record_id,
1014
+ category,
1015
+ organisation_id,
1016
+ error: rpcError.message,
1017
+ errorCode: rpcError.code,
1018
+ errorDetails: rpcError.details,
1019
+ errorHint: rpcError.hint
1020
+ });
1021
+ throw new Error(rpcError.message || "Failed to fetch file reference");
1022
+ }
1023
+ if (!data || data.length === 0) {
1024
+ files = [];
1025
+ } else {
1026
+ files = data.filter((item) => {
1027
+ return item.is_public === true && item.id && item.file_path && item.file_metadata;
1028
+ }).map((item) => {
1029
+ return {
1030
+ id: item.id,
1031
+ table_name,
1032
+ record_id,
1033
+ file_path: item.file_path,
1034
+ file_metadata: item.file_metadata || {},
1035
+ organisation_id: organisation_id ?? null,
1036
+ app_id: item.file_metadata?.app_id || null,
1037
+ is_public: true,
1038
+ // RPC already filtered for public files
1039
+ created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1040
+ updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
1041
+ };
1042
+ });
1043
+ }
1044
+ } else {
1045
+ const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
1046
+ p_table_name: table_name,
1047
+ p_record_id: record_id,
1048
+ p_organisation_id: organisation_id ?? null
1049
+ });
1050
+ if (rpcError) {
1051
+ logger.error("usePublicFileDisplay", "RPC function error", {
1052
+ function: "data_file_reference_list",
1053
+ table_name,
1054
+ record_id,
1055
+ organisation_id,
1056
+ error: rpcError.message,
1057
+ errorCode: rpcError.code,
1058
+ errorDetails: rpcError.details,
1059
+ errorHint: rpcError.hint
1060
+ });
1061
+ throw new Error(rpcError.message || "Failed to fetch file references");
1062
+ }
1063
+ if (!fileIds || fileIds.length === 0) {
1064
+ files = [];
1065
+ } else {
1066
+ const ids = fileIds.map((item) => item.id);
1067
+ 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);
1068
+ if (fetchError) {
1069
+ throw new Error(fetchError.message || "Failed to fetch file references");
1070
+ }
1071
+ files = fullData || [];
1072
+ }
1073
+ }
1236
1074
  }
1237
- const uploadResult = await uploadFile(this.supabase, file, {
1238
- appName: "file-reference",
1239
- orgId: options.organisation_id,
1240
- isPublic: options.is_public || false,
1241
- customPath: options.folder
1242
- // Use folder prop as the custom path segment
1243
- });
1075
+ const publicFiles = files.filter((f) => f.is_public === true);
1076
+ if (publicFiles.length === 0) {
1077
+ setFileUrl(null);
1078
+ setFileReference(null);
1079
+ setFileReferences([]);
1080
+ setFileUrls(/* @__PURE__ */ new Map());
1081
+ setFileCount(0);
1082
+ if (enableCache) {
1083
+ publicFileCache.set(cacheKey, {
1084
+ data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: /* @__PURE__ */ new Map(), fileCount: 0 },
1085
+ timestamp: Date.now(),
1086
+ ttl: cacheTtl
1087
+ });
1088
+ }
1089
+ return;
1090
+ }
1091
+ const fileRefs = publicFiles.map((f) => ({
1092
+ id: f.id,
1093
+ table_name: f.table_name,
1094
+ record_id: f.record_id,
1095
+ file_path: f.file_path,
1096
+ file_metadata: f.file_metadata || {},
1097
+ organisation_id: f.organisation_id,
1098
+ app_id: f.app_id,
1099
+ is_public: f.is_public ?? true,
1100
+ created_at: f.created_at,
1101
+ updated_at: f.updated_at
1102
+ }));
1103
+ setFileReferences(fileRefs);
1104
+ setFileCount(fileRefs.length);
1105
+ if (category && fileRefs.length > 0) {
1106
+ const firstFile = fileRefs[0];
1107
+ setFileReference(firstFile);
1108
+ const url = getPublicUrl(supabase, firstFile.file_path, true);
1109
+ setFileUrl(url);
1110
+ } else {
1111
+ const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
1112
+ appName: "pace-core",
1113
+ orgId: organisation_id,
1114
+ expiresIn: 3600
1115
+ });
1116
+ setFileUrls(urlMap);
1117
+ setFileReference(null);
1118
+ setFileUrl(null);
1119
+ }
1120
+ if (enableCache) {
1121
+ publicFileCache.set(cacheKey, {
1122
+ data: {
1123
+ fileUrl: category ? fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null : null,
1124
+ fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,
1125
+ fileReferences: fileRefs,
1126
+ fileUrls: category ? /* @__PURE__ */ new Map() : (() => {
1127
+ const urlMap = /* @__PURE__ */ new Map();
1128
+ for (const fileRef of fileRefs) {
1129
+ const url = getPublicUrl(supabase, fileRef.file_path, true);
1130
+ if (url) {
1131
+ urlMap.set(fileRef.id, url);
1132
+ }
1133
+ }
1134
+ return urlMap;
1135
+ })(),
1136
+ fileCount: fileRefs.length
1137
+ },
1138
+ timestamp: Date.now(),
1139
+ ttl: cacheTtl
1140
+ });
1141
+ }
1142
+ } catch (err) {
1143
+ logger.error("usePublicFileDisplay", "Error fetching files", {
1144
+ table_name,
1145
+ record_id,
1146
+ organisation_id,
1147
+ category,
1148
+ error: err instanceof Error ? err.message : "Unknown error",
1149
+ errorDetails: err instanceof Error ? err.stack : String(err)
1150
+ });
1151
+ const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
1152
+ setError(error2);
1153
+ setFileUrl(null);
1154
+ setFileReference(null);
1155
+ setFileReferences([]);
1156
+ setFileUrls(/* @__PURE__ */ new Map());
1157
+ setFileCount(0);
1158
+ } finally {
1159
+ setIsLoading(false);
1160
+ }
1161
+ }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
1162
+ useEffect4(() => {
1163
+ if (table_name && record_id) {
1164
+ fetchFiles();
1165
+ } else {
1166
+ setFileUrl(null);
1167
+ setFileReference(null);
1168
+ setFileReferences([]);
1169
+ setFileUrls(/* @__PURE__ */ new Map());
1170
+ setFileCount(0);
1171
+ setIsLoading(false);
1172
+ setError(null);
1173
+ }
1174
+ }, [fetchFiles, table_name, record_id, organisation_id]);
1175
+ const refetch = useCallback3(async () => {
1176
+ if (!table_name || !record_id) return;
1177
+ if (enableCache) {
1178
+ const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
1179
+ publicFileCache.delete(cacheKey);
1180
+ }
1181
+ await fetchFiles();
1182
+ }, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);
1183
+ return {
1184
+ fileUrl,
1185
+ fileReference,
1186
+ fileReferences,
1187
+ fileUrls,
1188
+ fileCount,
1189
+ isLoading,
1190
+ error,
1191
+ refetch
1192
+ };
1193
+ }
1194
+ function clearPublicFileDisplayCache() {
1195
+ for (const [key] of publicFileCache) {
1196
+ if (key.startsWith("public_file_")) {
1197
+ publicFileCache.delete(key);
1198
+ }
1199
+ }
1200
+ }
1201
+ function getPublicFileDisplayCacheStats() {
1202
+ const keys = Array.from(publicFileCache.keys()).filter((key) => key.startsWith("public_file_"));
1203
+ return {
1204
+ size: keys.length,
1205
+ keys
1206
+ };
1207
+ }
1208
+
1209
+ // src/hooks/useFileDisplay.ts
1210
+ import { useState as useState4, useEffect as useEffect5, useCallback as useCallback4 } from "react";
1211
+
1212
+ // src/utils/file-reference/index.ts
1213
+ var log3 = createLogger("FileReferenceService");
1214
+ var FileReferenceServiceImpl = class {
1215
+ constructor(supabase) {
1216
+ this.supabase = supabase;
1217
+ }
1218
+ /**
1219
+ * Creates a file reference by uploading a file to storage and linking it in the database.
1220
+ *
1221
+ * Storage Flow:
1222
+ * 1. Upload file to storage bucket first (files or public-files based on is_public flag)
1223
+ * - Path format: {orgId}/{folder}/{timestamp-uuid-filename}
1224
+ * - Bucket selection: 'files' (private) or 'public-files' (public)
1225
+ * 2. Extract file metadata (dimensions, hash, etc.)
1226
+ * 3. Set organisation context for RLS policies
1227
+ * 4. Create database reference via RPC function
1228
+ * 5. If DB insert fails, rollback by deleting uploaded file
1229
+ *
1230
+ * This ensures atomicity: either both storage and DB succeed, or both are cleaned up.
1231
+ */
1232
+ async createFileReference(options, file) {
1233
+ try {
1234
+ const isUserScoped = !options.organisation_id && options.userId;
1235
+ if (!isUserScoped && !options.organisation_id) {
1236
+ throw new Error("organisation_id is required for file upload, or userId must be provided for user-scoped files");
1237
+ }
1238
+ if (!options.table_name) {
1239
+ throw new Error("table_name is required for file upload");
1240
+ }
1241
+ if (!options.record_id) {
1242
+ throw new Error("record_id is required for file upload");
1243
+ }
1244
+ if (!options.folder) {
1245
+ throw new Error("folder is required for file upload. The folder prop determines the storage path.");
1246
+ }
1247
+ let authenticatedUserId = void 0;
1248
+ if (isUserScoped) {
1249
+ const { data: { user: authUser }, error: authError } = await this.supabase.auth.getUser();
1250
+ if (authError || !authUser) {
1251
+ throw new Error("User must be authenticated to upload user-scoped files");
1252
+ }
1253
+ authenticatedUserId = authUser.id;
1254
+ log3.debug("Using authenticated user ID for user-scoped file upload", { userId: authenticatedUserId });
1255
+ }
1256
+ const uploadResult = await uploadFile(this.supabase, file, {
1257
+ appName: "file-reference",
1258
+ orgId: options.organisation_id || void 0,
1259
+ userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
1260
+ // Use auth.uid() for user-scoped files
1261
+ isPublic: options.is_public || false,
1262
+ customPath: options.folder
1263
+ // Use folder prop as the custom path segment
1264
+ });
1244
1265
  if (!uploadResult.success) {
1245
1266
  throw new Error(`Failed to upload file: ${uploadResult.error}`);
1246
1267
  }
@@ -1250,16 +1271,20 @@ var FileReferenceServiceImpl = class {
1250
1271
  const filePath = uploadResult.path;
1251
1272
  const metadata = await extractFileMetadata(file, {
1252
1273
  appName: "file-reference",
1253
- orgId: options.organisation_id,
1274
+ orgId: options.organisation_id || void 0,
1275
+ userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
1276
+ // Use auth.uid() for user-scoped files
1254
1277
  isPublic: options.is_public || false
1255
1278
  }, "system");
1256
- await setOrganisationContext(this.supabase, options.organisation_id);
1279
+ if (!isUserScoped && options.organisation_id) {
1280
+ await setOrganisationContext(this.supabase, options.organisation_id);
1281
+ }
1257
1282
  const { data, error } = await this.supabase.rpc("data_file_reference_create", {
1258
1283
  p_table_name: options.table_name,
1259
1284
  p_record_id: options.record_id,
1260
1285
  p_file_path: filePath,
1261
1286
  // Storage path from step 1
1262
- p_organisation_id: options.organisation_id,
1287
+ p_organisation_id: options.organisation_id ?? null,
1263
1288
  p_app_id: options.app_id,
1264
1289
  p_page_context: options.pageContext,
1265
1290
  p_event_id: options.event_id || null,
@@ -1272,7 +1297,9 @@ var FileReferenceServiceImpl = class {
1272
1297
  ...metadata,
1273
1298
  ...options.custom_metadata
1274
1299
  },
1275
- p_is_public: options.is_public || false
1300
+ p_is_public: options.is_public || false,
1301
+ p_user_id: authenticatedUserId || options.userId || null
1302
+ // Pass authenticated user ID for user-scoped files
1276
1303
  });
1277
1304
  if (error) {
1278
1305
  await deleteFile(this.supabase, filePath, options.is_public || false);
@@ -1290,18 +1317,24 @@ var FileReferenceServiceImpl = class {
1290
1317
  invalidateFileDisplayCache(
1291
1318
  options.table_name,
1292
1319
  options.record_id,
1293
- options.organisation_id,
1320
+ options.organisation_id || null,
1294
1321
  options.category
1295
1322
  );
1296
1323
  return fileRef;
1297
1324
  } catch (error) {
1298
- log4.error("Error creating file reference:", error);
1325
+ log3.error("Error creating file reference:", error);
1299
1326
  throw error;
1300
1327
  }
1301
1328
  }
1302
1329
  async getFileReference(table_name, record_id, organisation_id) {
1303
1330
  try {
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();
1331
+ let query = 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);
1332
+ if (organisation_id === null || organisation_id === void 0) {
1333
+ query = query.is("organisation_id", null);
1334
+ } else {
1335
+ query = query.eq("organisation_id", organisation_id);
1336
+ }
1337
+ const { data, error } = await query.single();
1305
1338
  if (error) {
1306
1339
  if (error.code === "PGRST116") {
1307
1340
  return null;
@@ -1310,7 +1343,7 @@ var FileReferenceServiceImpl = class {
1310
1343
  }
1311
1344
  return data;
1312
1345
  } catch (error) {
1313
- log4.error("Error getting file reference:", error);
1346
+ log3.error("Error getting file reference:", error);
1314
1347
  throw error;
1315
1348
  }
1316
1349
  }
@@ -1334,7 +1367,7 @@ var FileReferenceServiceImpl = class {
1334
1367
  return await this.getSignedUrl(table_name, record_id, organisation_id);
1335
1368
  }
1336
1369
  } catch (error) {
1337
- log4.error("Error getting file URL:", error);
1370
+ log3.error("Error getting file URL:", error);
1338
1371
  throw error;
1339
1372
  }
1340
1373
  }
@@ -1343,7 +1376,7 @@ var FileReferenceServiceImpl = class {
1343
1376
  const { data: filePath, error } = await this.supabase.rpc("data_file_reference_signed_url_get", {
1344
1377
  p_table_name: table_name,
1345
1378
  p_record_id: record_id,
1346
- p_organisation_id: organisation_id,
1379
+ p_organisation_id: organisation_id ?? null,
1347
1380
  p_expires_in: expires_in
1348
1381
  });
1349
1382
  if (error) {
@@ -1355,11 +1388,12 @@ var FileReferenceServiceImpl = class {
1355
1388
  const signedUrlResult = await getSignedUrl(this.supabase, filePath, {
1356
1389
  appName: "file-reference",
1357
1390
  orgId: organisation_id,
1391
+ userId: organisation_id ? void 0 : record_id,
1358
1392
  expiresIn: expires_in
1359
1393
  });
1360
1394
  return signedUrlResult?.url || null;
1361
1395
  } catch (error) {
1362
- log4.error("Error getting signed URL:", error);
1396
+ log3.error("Error getting signed URL:", error);
1363
1397
  throw error;
1364
1398
  }
1365
1399
  }
@@ -1371,7 +1405,7 @@ var FileReferenceServiceImpl = class {
1371
1405
  }
1372
1406
  return data;
1373
1407
  } catch (error) {
1374
- log4.error("Error updating file reference:", error);
1408
+ log3.error("Error updating file reference:", error);
1375
1409
  throw error;
1376
1410
  }
1377
1411
  }
@@ -1381,7 +1415,7 @@ var FileReferenceServiceImpl = class {
1381
1415
  const { error } = await this.supabase.rpc("data_file_reference_delete", {
1382
1416
  p_table_name: table_name,
1383
1417
  p_record_id: record_id,
1384
- p_organisation_id: organisation_id,
1418
+ p_organisation_id: organisation_id ?? null,
1385
1419
  p_delete_file: delete_file
1386
1420
  });
1387
1421
  if (error) {
@@ -1392,7 +1426,7 @@ var FileReferenceServiceImpl = class {
1392
1426
  }
1393
1427
  return true;
1394
1428
  } catch (error) {
1395
- log4.error("Error deleting file reference:", error);
1429
+ log3.error("Error deleting file reference:", error);
1396
1430
  throw error;
1397
1431
  }
1398
1432
  }
@@ -1401,7 +1435,7 @@ var FileReferenceServiceImpl = class {
1401
1435
  const { data, error } = await this.supabase.rpc("data_file_reference_list", {
1402
1436
  p_table_name: table_name,
1403
1437
  p_record_id: record_id,
1404
- p_organisation_id: organisation_id
1438
+ p_organisation_id: organisation_id ?? null
1405
1439
  });
1406
1440
  if (error) {
1407
1441
  throw new Error(`Failed to list file references: ${error.message}`);
@@ -1422,7 +1456,7 @@ var FileReferenceServiceImpl = class {
1422
1456
  fileType,
1423
1457
  ...item.file_metadata || {}
1424
1458
  },
1425
- organisation_id,
1459
+ organisation_id: organisation_id ?? null,
1426
1460
  app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
1427
1461
  // May not be in metadata, use empty string
1428
1462
  is_public: item.is_public ?? false,
@@ -1434,7 +1468,7 @@ var FileReferenceServiceImpl = class {
1434
1468
  });
1435
1469
  return fileReferences;
1436
1470
  } catch (error) {
1437
- log4.error("Error listing file references:", error);
1471
+ log3.error("Error listing file references:", error);
1438
1472
  throw error;
1439
1473
  }
1440
1474
  }
@@ -1443,14 +1477,14 @@ var FileReferenceServiceImpl = class {
1443
1477
  const { data, error } = await this.supabase.rpc("data_file_reference_count_get", {
1444
1478
  p_table_name: table_name,
1445
1479
  p_record_id: record_id,
1446
- p_organisation_id: organisation_id
1480
+ p_organisation_id: organisation_id ?? null
1447
1481
  });
1448
1482
  if (error) {
1449
1483
  throw new Error(`Failed to get file count: ${error.message}`);
1450
1484
  }
1451
1485
  return data || 0;
1452
1486
  } catch (error) {
1453
- log4.error("Error getting file count:", error);
1487
+ log3.error("Error getting file count:", error);
1454
1488
  throw error;
1455
1489
  }
1456
1490
  }
@@ -1458,7 +1492,7 @@ var FileReferenceServiceImpl = class {
1458
1492
  try {
1459
1493
  const { data, error } = await this.supabase.rpc("data_file_reference_get", {
1460
1494
  p_file_reference_id: id,
1461
- p_organisation_id: organisation_id
1495
+ p_organisation_id: organisation_id ?? null
1462
1496
  });
1463
1497
  if (error) {
1464
1498
  throw new Error(`Failed to get file reference by ID: ${error.message}`);
@@ -1468,7 +1502,7 @@ var FileReferenceServiceImpl = class {
1468
1502
  }
1469
1503
  return data[0];
1470
1504
  } catch (error) {
1471
- log4.error("Error getting file reference by ID:", error);
1505
+ log3.error("Error getting file reference by ID:", error);
1472
1506
  throw error;
1473
1507
  }
1474
1508
  }
@@ -1478,10 +1512,10 @@ var FileReferenceServiceImpl = class {
1478
1512
  p_table_name: table_name,
1479
1513
  p_record_id: record_id,
1480
1514
  p_category: category,
1481
- p_organisation_id: organisation_id
1515
+ p_organisation_id: organisation_id ?? null
1482
1516
  });
1483
1517
  if (error) {
1484
- log4.error("RPC ERROR getting files by category:", {
1518
+ log3.error("RPC ERROR getting files by category:", {
1485
1519
  error,
1486
1520
  errorCode: error.code,
1487
1521
  errorMessage: error.message,
@@ -1501,7 +1535,7 @@ var FileReferenceServiceImpl = class {
1501
1535
  const fileCategory = item.file_metadata?.category;
1502
1536
  const matches = fileCategory === category;
1503
1537
  if (!matches) {
1504
- log4.warn("File category mismatch in RPC response:", {
1538
+ log3.warn("File category mismatch in RPC response:", {
1505
1539
  fileId: item.id,
1506
1540
  expectedCategory: category,
1507
1541
  actualCategory: fileCategory
@@ -1522,7 +1556,7 @@ var FileReferenceServiceImpl = class {
1522
1556
  category: item.file_metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
1523
1557
  ...item.file_metadata || {}
1524
1558
  },
1525
- organisation_id,
1559
+ organisation_id: organisation_id ?? null,
1526
1560
  app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
1527
1561
  // May not be in metadata, use empty string
1528
1562
  is_public: item.is_public ?? false,
@@ -1534,7 +1568,7 @@ var FileReferenceServiceImpl = class {
1534
1568
  });
1535
1569
  return fileReferences;
1536
1570
  } catch (error) {
1537
- log4.error("Error getting files by category:", error);
1571
+ log3.error("Error getting files by category:", error);
1538
1572
  throw error;
1539
1573
  }
1540
1574
  }
@@ -1576,7 +1610,8 @@ async function uploadFileWithReference(supabase, options, file) {
1576
1610
  const fileReference = await service.createFileReference(options, file);
1577
1611
  const fileUrl = options.is_public ? getPublicUrl(supabase, fileReference.file_path, true) : await getSignedUrl(supabase, fileReference.file_path, {
1578
1612
  appName: "file-reference",
1579
- orgId: options.organisation_id,
1613
+ orgId: options.organisation_id || void 0,
1614
+ userId: options.userId || void 0,
1580
1615
  expiresIn: 3600
1581
1616
  });
1582
1617
  const urlString = typeof fileUrl === "string" ? fileUrl : fileUrl?.url || "";
@@ -1613,15 +1648,15 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1613
1648
  enableCache = true,
1614
1649
  supabase
1615
1650
  } = options;
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 () => {
1624
- if (!table_name || !record_id || !organisation_id || !supabase) {
1651
+ const [fileUrl, setFileUrl] = useState4(null);
1652
+ const [fileReference, setFileReference] = useState4(null);
1653
+ const [fileReferences, setFileReferences] = useState4([]);
1654
+ const [fileUrls, setFileUrls] = useState4(/* @__PURE__ */ new Map());
1655
+ const [fileCount, setFileCount] = useState4(0);
1656
+ const [isLoading, setIsLoading] = useState4(false);
1657
+ const [error, setError] = useState4(null);
1658
+ const fetchFiles = useCallback4(async () => {
1659
+ if (!table_name || !record_id || !supabase) {
1625
1660
  setFileUrl(null);
1626
1661
  setFileReference(null);
1627
1662
  setFileReferences([]);
@@ -1630,11 +1665,13 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1630
1665
  setIsLoading(false);
1631
1666
  return;
1632
1667
  }
1633
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1634
- if (!uuidRegex.test(organisation_id)) {
1635
- logger.warn("useFileDisplay", "Invalid organisationId format (not a valid UUID):", organisation_id);
1668
+ if (organisation_id) {
1669
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1670
+ if (!uuidRegex.test(organisation_id)) {
1671
+ logger.warn("useFileDisplay", "Invalid organisationId format (not a valid UUID):", organisation_id);
1672
+ }
1636
1673
  }
1637
- const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
1674
+ const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
1638
1675
  if (enableCache) {
1639
1676
  const cached = authenticatedFileCache.get(cacheKey);
1640
1677
  if (cached && Date.now() - cached.timestamp < cached.ttl) {
@@ -1644,6 +1681,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1644
1681
  const signedUrlResult = await getSignedUrl(supabase, cachedData.fileReference.file_path, {
1645
1682
  appName: "pace-core",
1646
1683
  orgId: organisation_id,
1684
+ userId: organisation_id ? void 0 : record_id,
1647
1685
  expiresIn: 3600
1648
1686
  });
1649
1687
  const regeneratedUrl = signedUrlResult?.url || null;
@@ -1674,25 +1712,205 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1674
1712
  setError(null);
1675
1713
  const service = createFileReferenceService(supabase);
1676
1714
  let files = [];
1677
- if (category) {
1678
- logger.debug("useFileDisplay", "Using RPC function for category filtering:", {
1679
- table_name,
1680
- record_id,
1681
- category,
1682
- organisation_id
1715
+ const shouldSearchBothScopes = organisation_id === void 0;
1716
+ if (shouldSearchBothScopes) {
1717
+ let userScopedFiles = [];
1718
+ let orgScopedFiles = [];
1719
+ try {
1720
+ if (category) {
1721
+ userScopedFiles = await service.getFilesByCategory(
1722
+ table_name,
1723
+ record_id,
1724
+ category,
1725
+ void 0
1726
+ // Explicitly pass undefined for user-scoped files (service converts to null for RPC)
1727
+ );
1728
+ } else {
1729
+ userScopedFiles = await service.listFileReferences(
1730
+ table_name,
1731
+ record_id,
1732
+ void 0
1733
+ // Explicitly pass undefined for user-scoped files (service converts to null for RPC)
1734
+ );
1735
+ }
1736
+ } catch (err) {
1737
+ logger.warn("useFileDisplay", "Error querying user-scoped files:", err);
1738
+ userScopedFiles = [];
1739
+ }
1740
+ try {
1741
+ const { data: { user }, error: userError } = await supabase.auth.getUser();
1742
+ if (userError) {
1743
+ logger.warn("useFileDisplay", "Error getting user:", userError);
1744
+ }
1745
+ if (user) {
1746
+ const { data: memberships, error: membershipError } = await supabase.from("organisation_memberships").select("organisation_id").eq("user_id", user.id).or("status.is.null,status.eq.active");
1747
+ if (membershipError) {
1748
+ logger.warn("useFileDisplay", "Error querying organisation memberships:", membershipError);
1749
+ }
1750
+ if (memberships && memberships.length > 0) {
1751
+ const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
1752
+ const orgQueries = orgIds.map(async (orgId) => {
1753
+ try {
1754
+ if (category) {
1755
+ return await service.getFilesByCategory(
1756
+ table_name,
1757
+ record_id,
1758
+ category,
1759
+ orgId
1760
+ );
1761
+ } else {
1762
+ return await service.listFileReferences(
1763
+ table_name,
1764
+ record_id,
1765
+ orgId
1766
+ );
1767
+ }
1768
+ } catch (err) {
1769
+ return [];
1770
+ }
1771
+ });
1772
+ const orgResults = await Promise.all(orgQueries);
1773
+ orgScopedFiles = orgResults.flat();
1774
+ } else {
1775
+ try {
1776
+ let fallbackQuery = 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).order("created_at", { ascending: false });
1777
+ if (category) {
1778
+ fallbackQuery = fallbackQuery.eq("file_metadata->>category", category);
1779
+ }
1780
+ const { data: fallbackFiles } = await fallbackQuery;
1781
+ if (fallbackFiles && fallbackFiles.length > 0) {
1782
+ orgScopedFiles = fallbackFiles.map((f) => ({
1783
+ id: f.id,
1784
+ table_name: f.table_name,
1785
+ record_id: f.record_id,
1786
+ file_path: f.file_path,
1787
+ file_metadata: f.file_metadata || {},
1788
+ organisation_id: f.organisation_id,
1789
+ app_id: f.app_id,
1790
+ is_public: f.is_public ?? false,
1791
+ created_at: f.created_at,
1792
+ updated_at: f.updated_at
1793
+ }));
1794
+ }
1795
+ } catch (err) {
1796
+ }
1797
+ }
1798
+ }
1799
+ } catch (err) {
1800
+ logger.warn("useFileDisplay", "Error querying organisation-scoped files:", err);
1801
+ orgScopedFiles = [];
1802
+ }
1803
+ const allFiles = [...userScopedFiles, ...orgScopedFiles];
1804
+ allFiles.sort((a, b) => {
1805
+ const aTime = new Date(a.created_at).getTime();
1806
+ const bTime = new Date(b.created_at).getTime();
1807
+ return bTime - aTime;
1683
1808
  });
1684
- files = await service.getFilesByCategory(
1685
- table_name,
1686
- record_id,
1687
- category,
1688
- organisation_id
1689
- );
1809
+ if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
1810
+ files = allFiles.filter((f) => f.organisation_id !== null);
1811
+ } else {
1812
+ files = allFiles;
1813
+ }
1814
+ if (files.length === 0) {
1815
+ try {
1816
+ let directQuery = 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).order("created_at", { ascending: false });
1817
+ if (category) {
1818
+ directQuery = directQuery.eq("file_metadata->>category", category);
1819
+ }
1820
+ const { data: directFiles } = await directQuery;
1821
+ if (directFiles && directFiles.length > 0) {
1822
+ files = directFiles.map((f) => ({
1823
+ id: f.id,
1824
+ table_name: f.table_name,
1825
+ record_id: f.record_id,
1826
+ file_path: f.file_path,
1827
+ file_metadata: f.file_metadata || {},
1828
+ organisation_id: f.organisation_id,
1829
+ app_id: f.app_id,
1830
+ is_public: f.is_public ?? false,
1831
+ created_at: f.created_at,
1832
+ updated_at: f.updated_at
1833
+ }));
1834
+ }
1835
+ } catch (err) {
1836
+ }
1837
+ }
1690
1838
  } else {
1691
- files = await service.listFileReferences(
1692
- table_name,
1693
- record_id,
1694
- organisation_id
1695
- );
1839
+ if (category) {
1840
+ logger.debug("useFileDisplay", "Using RPC function for category filtering:", {
1841
+ table_name,
1842
+ record_id,
1843
+ category,
1844
+ organisation_id
1845
+ });
1846
+ files = await service.getFilesByCategory(
1847
+ table_name,
1848
+ record_id,
1849
+ category,
1850
+ organisation_id
1851
+ );
1852
+ } else {
1853
+ files = await service.listFileReferences(
1854
+ table_name,
1855
+ record_id,
1856
+ organisation_id
1857
+ );
1858
+ }
1859
+ if (files.length === 0 && (!organisation_id || organisation_id === "")) {
1860
+ let userScopedFiles = [];
1861
+ let orgScopedFiles = [];
1862
+ try {
1863
+ if (category) {
1864
+ userScopedFiles = await service.getFilesByCategory(
1865
+ table_name,
1866
+ record_id,
1867
+ category,
1868
+ void 0
1869
+ );
1870
+ } else {
1871
+ userScopedFiles = await service.listFileReferences(
1872
+ table_name,
1873
+ record_id,
1874
+ void 0
1875
+ );
1876
+ }
1877
+ } catch (err) {
1878
+ }
1879
+ try {
1880
+ const { data: { user } } = await supabase.auth.getUser();
1881
+ if (user) {
1882
+ const { data: memberships } = await supabase.from("organisation_memberships").select("organisation_id").eq("user_id", user.id).or("status.is.null,status.eq.active");
1883
+ if (memberships && memberships.length > 0) {
1884
+ const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
1885
+ const orgQueries = orgIds.map(async (orgId) => {
1886
+ try {
1887
+ if (category) {
1888
+ return await service.getFilesByCategory(table_name, record_id, category, orgId);
1889
+ } else {
1890
+ return await service.listFileReferences(table_name, record_id, orgId);
1891
+ }
1892
+ } catch (err) {
1893
+ return [];
1894
+ }
1895
+ });
1896
+ const orgResults = await Promise.all(orgQueries);
1897
+ orgScopedFiles = orgResults.flat();
1898
+ }
1899
+ }
1900
+ } catch (err) {
1901
+ }
1902
+ const allFiles = [...userScopedFiles, ...orgScopedFiles];
1903
+ allFiles.sort((a, b) => {
1904
+ const aTime = new Date(a.created_at).getTime();
1905
+ const bTime = new Date(b.created_at).getTime();
1906
+ return bTime - aTime;
1907
+ });
1908
+ if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
1909
+ files = allFiles.filter((f) => f.organisation_id !== null);
1910
+ } else {
1911
+ files = allFiles;
1912
+ }
1913
+ }
1696
1914
  }
1697
1915
  if (files.length === 0) {
1698
1916
  setFileUrl(null);
@@ -1730,6 +1948,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1730
1948
  const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {
1731
1949
  appName: "pace-core",
1732
1950
  orgId: organisation_id,
1951
+ userId: organisation_id ? void 0 : record_id,
1733
1952
  expiresIn: 3600
1734
1953
  });
1735
1954
  url = signedUrlResult?.url || null;
@@ -1741,6 +1960,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1741
1960
  const urlMap = await generateFileUrlsBatch(supabase, files, {
1742
1961
  appName: "pace-core",
1743
1962
  orgId: organisation_id,
1963
+ userId: organisation_id ? void 0 : record_id,
1744
1964
  expiresIn: 3600
1745
1965
  });
1746
1966
  setFileUrls(urlMap);
@@ -1793,8 +2013,8 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1793
2013
  setIsLoading(false);
1794
2014
  }
1795
2015
  }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
1796
- useEffect6(() => {
1797
- if (table_name && record_id && organisation_id && supabase) {
2016
+ useEffect5(() => {
2017
+ if (table_name && record_id && supabase) {
1798
2018
  fetchFiles();
1799
2019
  } else {
1800
2020
  setFileUrl(null);
@@ -1805,11 +2025,11 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1805
2025
  setIsLoading(false);
1806
2026
  setError(null);
1807
2027
  }
1808
- }, [fetchFiles, table_name, record_id, organisation_id, supabase]);
1809
- const refetch = useCallback3(async () => {
1810
- if (!table_name || !record_id || !organisation_id || !supabase) return;
2028
+ }, [table_name, record_id, organisation_id, supabase]);
2029
+ const refetch = useCallback4(async () => {
2030
+ if (!table_name || !record_id || !supabase) return;
1811
2031
  if (enableCache) {
1812
- const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
2032
+ const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
1813
2033
  authenticatedFileCache.delete(cacheKey);
1814
2034
  }
1815
2035
  await fetchFiles();
@@ -1840,284 +2060,109 @@ function getFileDisplayCacheStats() {
1840
2060
  };
1841
2061
  }
1842
2062
  function invalidateFileDisplayCache(table_name, record_id, organisation_id, category) {
1843
- const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
2063
+ const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
1844
2064
  authenticatedFileCache.delete(cacheKey);
1845
2065
  if (category) {
1846
- const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id}_all`;
2066
+ const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_all`;
1847
2067
  authenticatedFileCache.delete(allCategoryKey);
1848
2068
  }
1849
2069
  }
1850
2070
 
1851
- // src/hooks/public/usePublicFileDisplay.ts
1852
- import { useState as useState4, useEffect as useEffect7, useCallback as useCallback4 } from "react";
1853
- var publicFileCache = /* @__PURE__ */ new Map();
1854
- function usePublicFileDisplay(table_name, record_id, organisation_id, category, options) {
1855
- const {
1856
- cacheTtl = 30 * 60 * 1e3,
1857
- // 30 minutes
1858
- enableCache = true,
1859
- supabase
1860
- } = options;
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 () => {
1869
- if (!table_name || !record_id || !organisation_id || !supabase) {
1870
- setFileUrl(null);
1871
- setFileReference(null);
1872
- setFileReferences([]);
1873
- setFileUrls(/* @__PURE__ */ new Map());
1874
- setFileCount(0);
1875
- setIsLoading(false);
2071
+ // src/hooks/useEventTheme.ts
2072
+ import { useEffect as useEffect6 } from "react";
2073
+ import { useLocation } from "react-router-dom";
2074
+ var log4 = createLogger("useEventTheme");
2075
+ function useEventTheme(event) {
2076
+ const location = useLocation();
2077
+ let selectedEvent;
2078
+ try {
2079
+ if (event === void 0) {
2080
+ const eventsContext = useEvents();
2081
+ selectedEvent = eventsContext.selectedEvent;
2082
+ } else {
2083
+ selectedEvent = event;
2084
+ }
2085
+ } catch (error) {
2086
+ if (event !== void 0) {
2087
+ selectedEvent = event;
2088
+ } else {
2089
+ selectedEvent = null;
2090
+ }
2091
+ }
2092
+ useEffect6(() => {
2093
+ const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login");
2094
+ if (isOnLoginRoute) {
2095
+ clearPalette();
1876
2096
  return;
1877
2097
  }
1878
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1879
- if (!uuidRegex.test(organisation_id)) {
1880
- logger.warn("usePublicFileDisplay", "Invalid organisationId format (not a valid UUID)", { organisation_id });
2098
+ if (!selectedEvent) {
2099
+ clearPalette();
2100
+ return;
1881
2101
  }
1882
- const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
1883
- if (enableCache) {
1884
- const cached = publicFileCache.get(cacheKey);
1885
- if (cached && Date.now() - cached.timestamp < cached.ttl) {
1886
- const cachedData = cached.data;
1887
- setFileUrl(cachedData.fileUrl || null);
1888
- setFileReference(cachedData.fileReference || null);
1889
- setFileReferences(cachedData.fileReferences || []);
1890
- setFileUrls(cachedData.fileUrls || /* @__PURE__ */ new Map());
1891
- setFileCount(cachedData.fileCount || 0);
1892
- setIsLoading(false);
1893
- setError(null);
1894
- return;
1895
- }
2102
+ const eventColours = selectedEvent.event_colours;
2103
+ const normalized = parseAndNormalizeEventColours(eventColours);
2104
+ if (!normalized) {
2105
+ clearPalette();
2106
+ return;
1896
2107
  }
1897
2108
  try {
1898
- setIsLoading(true);
1899
- setError(null);
1900
- let files = [];
1901
- if (category) {
1902
- const rpcParams = {
1903
- p_table_name: table_name,
1904
- p_record_id: record_id,
1905
- p_category: category,
1906
- p_organisation_id: organisation_id
1907
- };
1908
- const { data, error: rpcError } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
1909
- if (rpcError) {
1910
- logger.error("usePublicFileDisplay", "RPC function error", {
1911
- function: "data_file_reference_by_category_list",
1912
- table_name,
1913
- record_id,
1914
- category,
1915
- organisation_id,
1916
- error: rpcError.message,
1917
- errorCode: rpcError.code,
1918
- errorDetails: rpcError.details,
1919
- errorHint: rpcError.hint
1920
- });
1921
- throw new Error(rpcError.message || "Failed to fetch file reference");
1922
- }
1923
- if (!data || data.length === 0) {
1924
- files = [];
1925
- } else {
1926
- files = data.filter((item) => {
1927
- return item.is_public === true && item.id && item.file_path && item.file_metadata;
1928
- }).map((item) => {
1929
- return {
1930
- id: item.id,
1931
- table_name,
1932
- record_id,
1933
- file_path: item.file_path,
1934
- file_metadata: item.file_metadata || {},
1935
- organisation_id,
1936
- app_id: item.file_metadata?.app_id || null,
1937
- is_public: true,
1938
- // RPC already filtered for public files
1939
- created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1940
- updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
1941
- };
1942
- });
1943
- }
1944
- } else {
1945
- const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
1946
- p_table_name: table_name,
1947
- p_record_id: record_id,
1948
- p_organisation_id: organisation_id
1949
- });
1950
- if (rpcError) {
1951
- logger.error("usePublicFileDisplay", "RPC function error", {
1952
- function: "data_file_reference_list",
1953
- table_name,
1954
- record_id,
1955
- organisation_id,
1956
- error: rpcError.message,
1957
- errorCode: rpcError.code,
1958
- errorDetails: rpcError.details,
1959
- errorHint: rpcError.hint
1960
- });
1961
- throw new Error(rpcError.message || "Failed to fetch file references");
1962
- }
1963
- if (!fileIds || fileIds.length === 0) {
1964
- files = [];
1965
- } else {
1966
- const ids = fileIds.map((item) => item.id);
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);
1968
- if (fetchError) {
1969
- throw new Error(fetchError.message || "Failed to fetch file references");
1970
- }
1971
- files = fullData || [];
2109
+ applyPalette(normalized);
2110
+ } catch (error) {
2111
+ log4.error("Failed to apply event palette:", error);
2112
+ }
2113
+ return () => {
2114
+ };
2115
+ }, [selectedEvent, location.pathname]);
2116
+ }
2117
+
2118
+ // src/hooks/usePreventTabReload.ts
2119
+ import { useEffect as useEffect7, useRef as useRef3 } from "react";
2120
+ function usePreventTabReload(options = {}) {
2121
+ const { enabled = true, gracePeriodMs = 2e3 } = options;
2122
+ const isRestoringFromCacheRef = useRef3(false);
2123
+ const gracePeriodTimeoutRef = useRef3(null);
2124
+ useEffect7(() => {
2125
+ if (!enabled || typeof window === "undefined") return;
2126
+ const handlePageShow = (event) => {
2127
+ if (event.persisted) {
2128
+ isRestoringFromCacheRef.current = true;
2129
+ if (gracePeriodTimeoutRef.current) {
2130
+ clearTimeout(gracePeriodTimeoutRef.current);
1972
2131
  }
2132
+ gracePeriodTimeoutRef.current = setTimeout(() => {
2133
+ isRestoringFromCacheRef.current = false;
2134
+ }, gracePeriodMs);
1973
2135
  }
1974
- const publicFiles = files.filter((f) => f.is_public === true);
1975
- if (publicFiles.length === 0) {
1976
- setFileUrl(null);
1977
- setFileReference(null);
1978
- setFileReferences([]);
1979
- setFileUrls(/* @__PURE__ */ new Map());
1980
- setFileCount(0);
1981
- if (enableCache) {
1982
- publicFileCache.set(cacheKey, {
1983
- data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: /* @__PURE__ */ new Map(), fileCount: 0 },
1984
- timestamp: Date.now(),
1985
- ttl: cacheTtl
1986
- });
2136
+ };
2137
+ const handleVisibilityChange = () => {
2138
+ if (!document.hidden) {
2139
+ isRestoringFromCacheRef.current = true;
2140
+ if (gracePeriodTimeoutRef.current) {
2141
+ clearTimeout(gracePeriodTimeoutRef.current);
1987
2142
  }
1988
- return;
1989
- }
1990
- const fileRefs = publicFiles.map((f) => ({
1991
- id: f.id,
1992
- table_name: f.table_name,
1993
- record_id: f.record_id,
1994
- file_path: f.file_path,
1995
- file_metadata: f.file_metadata || {},
1996
- organisation_id: f.organisation_id,
1997
- app_id: f.app_id,
1998
- is_public: f.is_public ?? true,
1999
- created_at: f.created_at,
2000
- updated_at: f.updated_at
2001
- }));
2002
- setFileReferences(fileRefs);
2003
- setFileCount(fileRefs.length);
2004
- if (category && fileRefs.length > 0) {
2005
- const firstFile = fileRefs[0];
2006
- setFileReference(firstFile);
2007
- const url = getPublicUrl(supabase, firstFile.file_path, true);
2008
- setFileUrl(url);
2009
- } else {
2010
- const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
2011
- appName: "pace-core",
2012
- orgId: organisation_id,
2013
- expiresIn: 3600
2014
- });
2015
- setFileUrls(urlMap);
2016
- setFileReference(null);
2017
- setFileUrl(null);
2143
+ gracePeriodTimeoutRef.current = setTimeout(() => {
2144
+ isRestoringFromCacheRef.current = false;
2145
+ }, gracePeriodMs);
2018
2146
  }
2019
- if (enableCache) {
2020
- publicFileCache.set(cacheKey, {
2021
- data: {
2022
- fileUrl: category ? fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null : null,
2023
- fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,
2024
- fileReferences: fileRefs,
2025
- fileUrls: category ? /* @__PURE__ */ new Map() : (() => {
2026
- const urlMap = /* @__PURE__ */ new Map();
2027
- for (const fileRef of fileRefs) {
2028
- const url = getPublicUrl(supabase, fileRef.file_path, true);
2029
- if (url) {
2030
- urlMap.set(fileRef.id, url);
2031
- }
2032
- }
2033
- return urlMap;
2034
- })(),
2035
- fileCount: fileRefs.length
2036
- },
2037
- timestamp: Date.now(),
2038
- ttl: cacheTtl
2039
- });
2147
+ };
2148
+ window.addEventListener("pageshow", handlePageShow);
2149
+ document.addEventListener("visibilitychange", handleVisibilityChange);
2150
+ return () => {
2151
+ window.removeEventListener("pageshow", handlePageShow);
2152
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
2153
+ if (gracePeriodTimeoutRef.current) {
2154
+ clearTimeout(gracePeriodTimeoutRef.current);
2040
2155
  }
2041
- } catch (err) {
2042
- logger.error("usePublicFileDisplay", "Error fetching files", {
2043
- table_name,
2044
- record_id,
2045
- organisation_id,
2046
- category,
2047
- error: err instanceof Error ? err.message : "Unknown error",
2048
- errorDetails: err instanceof Error ? err.stack : String(err)
2049
- });
2050
- const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
2051
- setError(error2);
2052
- setFileUrl(null);
2053
- setFileReference(null);
2054
- setFileReferences([]);
2055
- setFileUrls(/* @__PURE__ */ new Map());
2056
- setFileCount(0);
2057
- } finally {
2058
- setIsLoading(false);
2059
- }
2060
- }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
2061
- useEffect7(() => {
2062
- if (table_name && record_id && organisation_id) {
2063
- fetchFiles();
2064
- } else {
2065
- setFileUrl(null);
2066
- setFileReference(null);
2067
- setFileReferences([]);
2068
- setFileUrls(/* @__PURE__ */ new Map());
2069
- setFileCount(0);
2070
- setIsLoading(false);
2071
- setError(null);
2072
- }
2073
- }, [fetchFiles, table_name, record_id, organisation_id]);
2074
- const refetch = useCallback4(async () => {
2075
- if (!table_name || !record_id || !organisation_id) return;
2076
- if (enableCache) {
2077
- const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
2078
- publicFileCache.delete(cacheKey);
2079
- }
2080
- await fetchFiles();
2081
- }, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);
2082
- return {
2083
- fileUrl,
2084
- fileReference,
2085
- fileReferences,
2086
- fileUrls,
2087
- fileCount,
2088
- isLoading,
2089
- error,
2090
- refetch
2091
- };
2092
- }
2093
- function clearPublicFileDisplayCache() {
2094
- for (const [key] of publicFileCache) {
2095
- if (key.startsWith("public_file_")) {
2096
- publicFileCache.delete(key);
2097
- }
2098
- }
2099
- }
2100
- function getPublicFileDisplayCacheStats() {
2101
- const keys = Array.from(publicFileCache.keys()).filter((key) => key.startsWith("public_file_"));
2102
- return {
2103
- size: keys.length,
2104
- keys
2105
- };
2156
+ };
2157
+ }, [enabled, gracePeriodMs]);
2106
2158
  }
2107
2159
 
2108
2160
  export {
2109
2161
  useDebounce,
2162
+ cleanupQueryCache,
2110
2163
  useQueryCache,
2111
2164
  queryCacheHelpers,
2112
2165
  useAddressAutocomplete,
2113
- useEventTheme,
2114
- usePreventTabReload,
2115
- ErrorBoundary,
2116
- PublicPageContext,
2117
- PublicPageProvider,
2118
- usePublicPageContext,
2119
- useIsPublicPage,
2120
- useAppConfig,
2121
2166
  FILE_SIZE_LIMITS,
2122
2167
  DEFAULT_FILE_SIZE_LIMIT,
2123
2168
  APP_PATH_MAPPING,
@@ -2136,14 +2181,16 @@ export {
2136
2181
  listFiles,
2137
2182
  downloadFile,
2138
2183
  archiveFile,
2184
+ usePublicFileDisplay,
2185
+ clearPublicFileDisplayCache,
2186
+ getPublicFileDisplayCacheStats,
2187
+ createFileReferenceService,
2188
+ uploadFileWithReference,
2139
2189
  useFileDisplay,
2140
2190
  clearFileDisplayCache,
2141
2191
  getFileDisplayCacheStats,
2142
2192
  invalidateFileDisplayCache,
2143
- createFileReferenceService,
2144
- uploadFileWithReference,
2145
- usePublicFileDisplay,
2146
- clearPublicFileDisplayCache,
2147
- getPublicFileDisplayCacheStats
2193
+ useEventTheme,
2194
+ usePreventTabReload
2148
2195
  };
2149
- //# sourceMappingURL=chunk-UNOTYLQF.js.map
2196
+ //# sourceMappingURL=chunk-NIU6J6OX.js.map