@jmruthers/pace-core 0.6.10 → 0.6.11

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 (726) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/audit-tool/00-dependencies.cjs +46 -13
  3. package/audit-tool/audits/01-pace-core-compliance.cjs +96 -21
  4. package/audit-tool/audits/02-project-structure.cjs +13 -3
  5. package/audit-tool/audits/03-architecture.cjs +78 -4
  6. package/audit-tool/audits/04-code-quality.cjs +9 -2
  7. package/audit-tool/audits/05-styling.cjs +19 -7
  8. package/audit-tool/audits/06-security-rbac.cjs +105 -14
  9. package/audit-tool/audits/07-api-tech-stack.cjs +31 -15
  10. package/audit-tool/audits/08-testing-documentation.cjs +11 -3
  11. package/audit-tool/audits/09-operations.cjs +19 -7
  12. package/audit-tool/index.cjs +22 -11
  13. package/audit-tool/utils/report-utils.cjs +4 -0
  14. package/cursor-rules/01-pace-core-compliance.mdc +1 -0
  15. package/cursor-rules/02-project-structure.mdc +1 -0
  16. package/cursor-rules/03-architecture.mdc +3 -1
  17. package/cursor-rules/04-code-quality.mdc +1 -0
  18. package/cursor-rules/05-styling.mdc +41 -7
  19. package/cursor-rules/06-security-rbac.mdc +2 -1
  20. package/cursor-rules/07-api-tech-stack.mdc +1 -0
  21. package/cursor-rules/08-testing-documentation.mdc +1 -0
  22. package/cursor-rules/09-operations.mdc +1 -0
  23. package/dist/{DataTable-SAXFG4XI.js → DataTable-EFYP2QLE.js} +10 -7
  24. package/dist/{InactivityServiceProvider-DHryoh6K.d.ts → InactivityServiceProvider-BbxwwDz1.d.ts} +10 -1
  25. package/dist/{UnifiedAuthProvider-CiBAl9-s.d.ts → UnifiedAuthProvider-Bkt_tzdS.d.ts} +56 -24
  26. package/dist/{api-F47QJ7FX.js → api-BZR2CYXL.js} +3 -2
  27. package/dist/api-result-USV1Czr-.d.ts +51 -0
  28. package/dist/{audit-Z6ZZBWLU.js → audit-HI2DHUVU.js} +2 -1
  29. package/dist/{auth-BZOJqrdd.d.ts → auth-JvdRVaud.d.ts} +1 -1
  30. package/dist/{chunk-KSNLMI7N.js → chunk-2DL2WSOE.js} +1 -155
  31. package/dist/{chunk-MPY44PWB.js → chunk-2OEVOGGR.js} +4648 -3560
  32. package/dist/chunk-44CNXN4P.js +15 -0
  33. package/dist/{chunk-Y4PF6HIM.js → chunk-4R3T5ENU.js} +867 -786
  34. package/dist/{chunk-LNHFAF4X.js → chunk-7A6IMHH2.js} +289 -247
  35. package/dist/chunk-CU2BU2MQ.js +2 -0
  36. package/dist/{chunk-JJEYZ3DX.js → chunk-D6BMFMQZ.js} +37 -2
  37. package/dist/{chunk-BCTXBU6U.js → chunk-ENLXB7GP.js} +88 -71
  38. package/dist/{chunk-FBZ7U3ID.js → chunk-J2KQK6DG.js} +937 -987
  39. package/dist/{chunk-TFIPNIPE.js → chunk-KJXRL3XE.js} +3300 -2245
  40. package/dist/{chunk-3GWSPISD.js → chunk-L5LFKKLJ.js} +1 -1
  41. package/dist/{chunk-X5EAU5G7.js → chunk-PCSHBLPB.js} +132 -114
  42. package/dist/{chunk-NIU6DPQV.js → chunk-QRYSEPHB.js} +2 -0
  43. package/dist/{chunk-KYURMOQM.js → chunk-V7FTM2LU.js} +423 -320
  44. package/dist/chunk-WY6Y7KC3.js +264 -0
  45. package/dist/{chunk-FN52B75D.js → chunk-XOJME5T7.js} +176 -15
  46. package/dist/{chunk-7YDC7LMU.js → chunk-XPFVT3GN.js} +71 -66
  47. package/dist/{chunk-66R6RLUZ.js → chunk-YFTFFJIV.js} +3 -3
  48. package/dist/{chunk-W46INAVW.js → chunk-YYTWKVHO.js} +688 -570
  49. package/dist/components.d.ts +8 -7
  50. package/dist/components.js +17 -15
  51. package/dist/{database.generated-DT8JTZiP.d.ts → database.generated-qkdoiVrJ.d.ts} +45 -10
  52. package/dist/eslint-rules/index.cjs +3 -0
  53. package/dist/eslint-rules/rules/03-architecture.cjs +74 -0
  54. package/dist/eslint-rules/rules/06-security-rbac.cjs +74 -0
  55. package/dist/{event-WTAQuGcq.d.ts → event-BfCox3N2.d.ts} +36 -10
  56. package/dist/{file-reference-BavO2eQj.d.ts → file-reference-DU1hcawx.d.ts} +29 -13
  57. package/dist/hooks.d.ts +22 -9
  58. package/dist/hooks.js +34 -25
  59. package/dist/icons/index.d.ts +1 -0
  60. package/dist/icons/index.js +1 -0
  61. package/dist/index.d.ts +66 -177
  62. package/dist/index.js +316 -340
  63. package/dist/pagination-BW1mqywp.d.ts +201 -0
  64. package/dist/providers.d.ts +6 -5
  65. package/dist/providers.js +5 -3
  66. package/dist/rbac/index.d.ts +123 -138
  67. package/dist/rbac/index.js +10 -8
  68. package/dist/theming/runtime.d.ts +19 -2
  69. package/dist/theming/runtime.js +1 -1
  70. package/dist/{timezone-K-ptz3HO.d.ts → timezone-BTWWXKVY.d.ts} +1 -1
  71. package/dist/types.d.ts +17 -10
  72. package/dist/types.js +1 -0
  73. package/dist/{usePublicPageContext-vxBlEHO9.d.ts → usePublicPageContext-B91dGYW1.d.ts} +433 -356
  74. package/dist/{usePublicRouteParams-G3Ks53mk.d.ts → usePublicRouteParams-BgV6VhMi.d.ts} +73 -4
  75. package/dist/utils.d.ts +163 -145
  76. package/dist/utils.js +42 -25
  77. package/docs/api/modules.md +782 -643
  78. package/docs/api-reference/rpc-functions.md +12 -3
  79. package/docs/core-concepts/rbac-system.md +8 -0
  80. package/docs/getting-started/cursor-rules.md +17 -20
  81. package/docs/getting-started/dependencies.md +1 -1
  82. package/docs/getting-started/setup.md +235 -0
  83. package/docs/implementation-guides/authentication.md +27 -0
  84. package/docs/implementation-guides/data-tables.md +176 -3
  85. package/docs/migration/ApiResult-migration.md +25 -0
  86. package/docs/rbac/api-reference.md +33 -31
  87. package/docs/standards/0-standards-overview.md +50 -15
  88. package/docs/standards/1-pace-core-compliance-standards.md +62 -57
  89. package/docs/standards/2-project-structure-standards.md +33 -16
  90. package/docs/standards/3-architecture-standards.md +41 -1
  91. package/docs/standards/4-code-quality-standards.md +26 -6
  92. package/docs/standards/5-styling-standards.md +35 -1
  93. package/docs/standards/6-security-rbac-standards.md +66 -0
  94. package/docs/standards/7-api-tech-stack-standards.md +25 -14
  95. package/docs/standards/8-testing-documentation-standards.md +31 -0
  96. package/docs/standards/9-operations-standards.md +19 -0
  97. package/docs/standards/README.md +20 -201
  98. package/docs/testing/test-setup-for-consumers.md +2 -0
  99. package/docs/troubleshooting/common-issues.md +17 -1
  100. package/docs/troubleshooting/organisation-context-setup.md +8 -0
  101. package/docs/troubleshooting/print-event-name-css-variable-analysis.md +217 -0
  102. package/eslint-config-pace-core.cjs +20 -0
  103. package/package.json +14 -20
  104. package/scripts/{build-docs-incremental.js → build-docs.js} +3 -2
  105. package/scripts/setup.cjs +536 -0
  106. package/scripts/validate.cjs +480 -0
  107. package/src/__tests__/helpers/{__tests__/component-test-utils.test.tsx → component-test-utils.test.tsx} +3 -3
  108. package/src/__tests__/helpers/{__tests__/optimized-test-setup.test.ts → optimized-test-setup.test.ts} +2 -2
  109. package/src/__tests__/helpers/{__tests__/supabaseMock.test.ts → supabaseMock.test.ts} +2 -2
  110. package/src/__tests__/helpers/{__tests__/test-providers.test.tsx → test-providers.test.tsx} +1 -1
  111. package/src/__tests__/helpers/test-providers.tsx +37 -39
  112. package/src/__tests__/helpers/{__tests__/test-utils.test.tsx → test-utils.test.tsx} +4 -3
  113. package/src/__tests__/helpers/{__tests__/timer-utils.test.ts → timer-utils.test.ts} +2 -2
  114. package/src/assets/app-icons/index.test.ts +304 -0
  115. package/src/components/AddressField/AddressField.test.tsx +1 -1
  116. package/src/components/AddressField/AddressField.tsx +238 -212
  117. package/src/components/Button/Button.tsx +1 -1
  118. package/src/components/Card/Card.test.tsx +172 -17
  119. package/src/components/Card/Card.tsx +19 -10
  120. package/src/components/ContextSelector/ContextSelector.internals.tsx +204 -0
  121. package/src/components/ContextSelector/{__tests__/ContextSelector.test.tsx → ContextSelector.test.tsx} +6 -6
  122. package/src/components/ContextSelector/ContextSelector.tsx +66 -280
  123. package/src/components/ContextSelector/ContextSelector.types.ts +35 -0
  124. package/src/components/ContextSelector/useContextSelectorState.tsx +195 -0
  125. package/src/components/DataTable/AUDIT_REPORT.md +59 -44
  126. package/src/components/DataTable/{__tests__/DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx} +6 -6
  127. package/src/components/DataTable/{__tests__/DataTable.default-state.test.tsx → DataTable.default-state.test.tsx} +5 -5
  128. package/src/components/DataTable/{__tests__/DataTable.export.test.tsx → DataTable.export.test.tsx} +10 -10
  129. package/src/components/DataTable/{__tests__/DataTable.grouping-aggregation.test.tsx → DataTable.grouping-aggregation.test.tsx} +6 -6
  130. package/src/components/DataTable/{__tests__/DataTable.hooks.test.tsx → DataTable.hooks.test.tsx} +6 -6
  131. package/src/components/DataTable/{__tests__/DataTable.select-label-display.test.tsx → DataTable.select-label-display.test.tsx} +6 -6
  132. package/src/components/DataTable/DataTable.test.tsx +787 -416
  133. package/src/components/DataTable/DataTable.tsx +12 -12
  134. package/src/components/DataTable/DataTableCore.integration.test.tsx +458 -0
  135. package/src/components/DataTable/{__tests__/DataTableCore.test-setup.ts → DataTableCore.test-setup.ts} +10 -9
  136. package/src/components/DataTable/{__tests__/DataTableCore.test.tsx → DataTableCore.test.tsx} +8 -8
  137. package/src/components/DataTable/{__tests__/README.md → README.md} +17 -7
  138. package/src/components/DataTable/TESTING.md +101 -0
  139. package/src/components/DataTable/{__tests__/a11y.basic.test.tsx → a11y.basic.test.tsx} +34 -34
  140. package/src/components/DataTable/components/DataTableCore.tsx +104 -864
  141. package/src/components/DataTable/components/{__tests__/GroupingDropdown.test.tsx → GroupingDropdown.test.tsx} +17 -8
  142. package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +61 -559
  144. package/src/components/DataTable/components/ImportModalFileSection.tsx +148 -0
  145. package/src/components/DataTable/context/{__tests__/DataTableContext.test.tsx → DataTableContext.test.tsx} +2 -2
  146. package/src/components/DataTable/context/DataTableContext.tsx +7 -6
  147. package/src/components/DataTable/core/{__tests__/ColumnFactory.test.ts → ColumnFactory.test.ts} +2 -2
  148. package/src/components/DataTable/hooks/{__tests__/useColumnOrderPersistence.test.ts → useColumnOrderPersistence.test.ts} +2 -2
  149. package/src/components/DataTable/hooks/{__tests__/useColumnVisibilityPersistence.test.ts → useColumnVisibilityPersistence.test.ts} +2 -2
  150. package/src/components/DataTable/hooks/{__tests__/useDataTableConfiguration.test.ts → useDataTableConfiguration.test.ts} +3 -3
  151. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +14 -2
  152. package/src/components/DataTable/hooks/{__tests__/useDataTableDataPipeline.test.ts → useDataTableDataPipeline.test.ts} +6 -6
  153. package/src/components/DataTable/hooks/useDataTableDeletionBatching.test.ts +127 -0
  154. package/src/components/DataTable/hooks/useDataTableDeletionBatching.ts +106 -0
  155. package/src/components/DataTable/hooks/useDataTableEffectiveActions.test.ts +461 -0
  156. package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +238 -0
  157. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.test.ts +296 -0
  158. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.ts +175 -0
  159. package/src/components/DataTable/hooks/useDataTablePaginationSync.test.ts +203 -0
  160. package/src/components/DataTable/hooks/useDataTablePaginationSync.ts +109 -0
  161. package/src/components/DataTable/hooks/{__tests__/useDataTablePermissions.test.ts → useDataTablePermissions.test.ts} +11 -11
  162. package/src/components/DataTable/hooks/useDataTablePermissions.ts +79 -247
  163. package/src/components/DataTable/hooks/useDataTablePipeline.test.tsx +219 -0
  164. package/src/components/DataTable/hooks/useDataTablePipeline.tsx +239 -0
  165. package/src/components/DataTable/hooks/useDataTableRenderGuard.test.tsx +316 -0
  166. package/src/components/DataTable/hooks/useDataTableRenderGuard.tsx +195 -0
  167. package/src/components/DataTable/hooks/useDataTableScope.test.ts +110 -0
  168. package/src/components/DataTable/hooks/useDataTableScope.ts +123 -0
  169. package/src/components/DataTable/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +47 -5
  170. package/src/components/DataTable/hooks/useDataTableState.ts +145 -94
  171. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.test.ts +277 -0
  172. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.ts +222 -0
  173. package/src/components/DataTable/hooks/useDataTableSuperAdmin.test.ts +93 -0
  174. package/src/components/DataTable/hooks/useDataTableSuperAdmin.ts +86 -0
  175. package/src/components/DataTable/hooks/useDataTableTableInstance.test.ts +185 -0
  176. package/src/components/DataTable/hooks/useDataTableTableInstance.ts +178 -0
  177. package/src/components/DataTable/hooks/{__tests__/useEffectiveColumnOrder.test.ts → useEffectiveColumnOrder.test.ts} +2 -2
  178. package/src/components/DataTable/hooks/{__tests__/useHierarchicalState.test.ts → useHierarchicalState.test.ts} +2 -2
  179. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.test.ts +3 -3
  180. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.ts +2 -2
  181. package/src/components/DataTable/hooks/useImportModalState.test.ts +390 -0
  182. package/src/components/DataTable/hooks/useImportModalState.ts +345 -0
  183. package/src/components/DataTable/hooks/{__tests__/useKeyboardNavigation.test.ts → useKeyboardNavigation.test.ts} +3 -3
  184. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +309 -269
  185. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.test.ts +3 -3
  186. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.ts +3 -3
  187. package/src/components/DataTable/hooks/{__tests__/useServerSideDataEffect.test.ts → useServerSideDataEffect.test.ts} +2 -2
  188. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +14 -3
  189. package/src/components/DataTable/hooks/{__tests__/useTableColumns.test.ts → useTableColumns.test.ts} +2 -2
  190. package/src/components/DataTable/hooks/{__tests__/useTableHandlers.test.ts → useTableHandlers.test.ts} +25 -4
  191. package/src/components/DataTable/hooks/useTableHandlers.ts +5 -2
  192. package/src/components/DataTable/index.ts +18 -17
  193. package/src/components/DataTable/{__tests__/keyboard.test.tsx → keyboard.test.tsx} +41 -63
  194. package/src/components/DataTable/{__tests__/mocks → mocks}/MockRBACProvider.tsx +1 -1
  195. package/src/components/DataTable/{__tests__/pagination.modes.test.tsx → pagination.modes.test.tsx} +6 -6
  196. package/src/components/DataTable/{__tests__/ssr.strict-mode.test.tsx → ssr.strict-mode.test.tsx} +2 -2
  197. package/src/components/DataTable/{__tests__/styles.test.ts → styles.test.ts} +1 -4
  198. package/src/components/DataTable/styles.ts +0 -1
  199. package/src/components/DataTable/test-utils/MockDataTableComponents.tsx +55 -0
  200. package/src/components/DataTable/{__tests__/test-utils → test-utils}/dataFactories.ts +2 -2
  201. package/src/components/DataTable/test-utils/featureConfig.ts +10 -0
  202. package/src/components/DataTable/{__tests__/test-utils/sharedTestUtils.tsx → test-utils/sharedTestUtils.ts} +97 -66
  203. package/src/components/DataTable/{__tests__/test-utils.ts → test-utils.ts} +1 -1
  204. package/src/components/DataTable/types/actions.ts +71 -0
  205. package/src/components/DataTable/types/base.ts +39 -0
  206. package/src/components/DataTable/types/columns.ts +125 -0
  207. package/src/components/DataTable/types/export.ts +32 -0
  208. package/src/components/DataTable/types/features.ts +81 -0
  209. package/src/components/DataTable/types/hierarchical.ts +44 -0
  210. package/src/components/DataTable/types/index.ts +43 -0
  211. package/src/components/DataTable/types/pagination.ts +85 -0
  212. package/src/components/DataTable/types/performance.ts +47 -0
  213. package/src/components/DataTable/types/props.ts +62 -0
  214. package/src/components/DataTable/types/rbac.ts +45 -0
  215. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableCore.test.tsx +430 -28
  216. package/src/components/DataTable/ui/layout/DataTableCore.tsx +345 -0
  217. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableErrorBoundary.test.tsx +4 -4
  218. package/src/components/DataTable/{components → ui/layout}/DataTableErrorBoundary.tsx +7 -7
  219. package/src/components/DataTable/ui/layout/DataTableLayout.test.tsx +1352 -0
  220. package/src/components/DataTable/ui/layout/DataTableLayout.tsx +661 -0
  221. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.test.tsx +91 -0
  222. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.tsx +43 -0
  223. package/src/components/DataTable/ui/modals/DataTableModals.test.tsx +749 -0
  224. package/src/components/DataTable/{components → ui/modals}/DataTableModals.tsx +36 -28
  225. package/src/components/DataTable/ui/modals/ImportModal.test.tsx +1834 -0
  226. package/src/components/DataTable/ui/modals/ImportModal.tsx +197 -0
  227. package/src/components/DataTable/ui/modals/ImportModalFailedRowsSection.tsx +60 -0
  228. package/src/components/DataTable/ui/modals/ImportModalFileSection.tsx +148 -0
  229. package/src/components/DataTable/ui/modals/ImportModalPreviewSection.tsx +60 -0
  230. package/src/components/DataTable/ui/modals/ImportModalSummarySection.tsx +59 -0
  231. package/src/components/DataTable/ui/modals/importModalPersistence.ts +73 -0
  232. package/src/components/DataTable/{components/__tests__ → ui/shared}/AccessDeniedPage.test.tsx +2 -2
  233. package/src/components/DataTable/{components → ui/shared}/AccessDeniedPage.tsx +2 -2
  234. package/src/components/DataTable/{components/__tests__ → ui/shared}/ActionButtons.test.tsx +6 -4
  235. package/src/components/DataTable/{components → ui/shared}/ActionButtons.tsx +4 -4
  236. package/src/components/DataTable/{components/__tests__ → ui/shared}/ColumnFilter.test.tsx +29 -16
  237. package/src/components/DataTable/{components → ui/shared}/ColumnFilter.tsx +4 -4
  238. package/src/components/DataTable/{components/__tests__ → ui/shared}/PaginationControls.test.tsx +38 -16
  239. package/src/components/DataTable/{components → ui/shared}/PaginationControls.tsx +21 -15
  240. package/src/components/DataTable/{components/__tests__ → ui/shared}/SortIndicator.test.tsx +2 -2
  241. package/src/components/DataTable/{components → ui/shared}/SortIndicator.tsx +1 -1
  242. package/src/components/DataTable/{components/__tests__ → ui/table}/EditFields.test.tsx +3 -3
  243. package/src/components/DataTable/{components → ui/table}/EditFields.tsx +138 -69
  244. package/src/components/DataTable/{components/__tests__ → ui/table}/EditableRow.test.tsx +36 -27
  245. package/src/components/DataTable/{components → ui/table}/EditableRow.tsx +86 -104
  246. package/src/components/DataTable/{components/__tests__ → ui/table}/EmptyState.test.tsx +2 -62
  247. package/src/components/DataTable/{components → ui/table}/EmptyState.tsx +7 -15
  248. package/src/components/DataTable/{components/__tests__ → ui/table}/FilterRow.test.tsx +5 -4
  249. package/src/components/DataTable/{components → ui/table}/FilterRow.tsx +3 -3
  250. package/src/components/DataTable/{components/__tests__ → ui/table}/LoadingState.test.tsx +6 -10
  251. package/src/components/DataTable/{components → ui/table}/LoadingState.tsx +4 -4
  252. package/src/components/DataTable/{components/__tests__ → ui/table}/RowComponent.test.tsx +412 -17
  253. package/src/components/DataTable/{components → ui/table}/RowComponent.tsx +183 -177
  254. package/src/components/DataTable/{components/__tests__ → ui/table}/UnifiedTableBody.test.tsx +425 -16
  255. package/src/components/DataTable/ui/table/UnifiedTableBody.tsx +440 -0
  256. package/src/components/DataTable/{components/__tests__ → ui/table}/cellValueUtils.test.ts +2 -2
  257. package/src/components/DataTable/{components → ui/table}/cellValueUtils.ts +1 -1
  258. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/BulkOperationsDropdown.test.tsx +12 -5
  259. package/src/components/DataTable/{components → ui/toolbar}/BulkOperationsDropdown.tsx +3 -3
  260. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/ColumnVisibilityDropdown.test.tsx +7 -4
  261. package/src/components/DataTable/{components → ui/toolbar}/ColumnVisibilityDropdown.tsx +7 -7
  262. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/DataTableToolbar.test.tsx +4 -4
  263. package/src/components/DataTable/{components → ui/toolbar}/DataTableToolbar.tsx +4 -4
  264. package/src/components/DataTable/ui/toolbar/GroupingDropdown.test.tsx +621 -0
  265. package/src/components/DataTable/ui/toolbar/GroupingDropdown.tsx +107 -0
  266. package/src/components/DataTable/utils/{__tests__/a11yUtils.test.ts → a11yUtils.test.ts} +2 -2
  267. package/src/components/DataTable/utils/{__tests__/aggregationUtils.test.ts → aggregationUtils.test.ts} +3 -3
  268. package/src/components/DataTable/utils/{__tests__/columnUtils.test.ts → columnUtils.test.ts} +2 -2
  269. package/src/components/DataTable/utils/csvParse.test.ts +74 -0
  270. package/src/components/DataTable/utils/csvParse.ts +65 -0
  271. package/src/components/DataTable/utils/{__tests__/errorHandling.test.ts → errorHandling.test.ts} +2 -2
  272. package/src/components/DataTable/utils/{__tests__/exportUtils.test.ts → exportUtils.test.ts} +3 -3
  273. package/src/components/DataTable/utils/{__tests__/flexibleImport.test.ts → flexibleImport.test.ts} +2 -2
  274. package/src/components/DataTable/utils/flexibleImport.ts +3 -186
  275. package/src/components/DataTable/utils/{__tests__/hierarchicalSorting.test.ts → hierarchicalSorting.test.ts} +3 -3
  276. package/src/components/DataTable/utils/{__tests__/hierarchicalUtils.test.ts → hierarchicalUtils.test.ts} +3 -3
  277. package/src/components/DataTable/utils/importDateParser.test.ts +162 -0
  278. package/src/components/DataTable/utils/importDateParser.ts +114 -0
  279. package/src/components/DataTable/utils/importValueParser.test.ts +138 -0
  280. package/src/components/DataTable/utils/importValueParser.ts +91 -0
  281. package/src/components/DataTable/utils/{__tests__/paginationUtils.test.ts → paginationUtils.test.ts} +2 -2
  282. package/src/components/DataTable/utils/paginationUtils.ts +6 -3
  283. package/src/components/DataTable/utils/{__tests__/performanceUtils.test.ts → performanceUtils.test.ts} +3 -3
  284. package/src/components/DataTable/utils/{__tests__/rowUtils.test.ts → rowUtils.test.ts} +3 -3
  285. package/src/components/DataTable/utils/{__tests__/selectFieldUtils.test.ts → selectFieldUtils.test.ts} +66 -3
  286. package/src/components/DataTable/utils/selectFieldUtils.ts +97 -60
  287. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -1
  288. package/src/components/DateTimeField/DateTimeField.test.tsx +1 -1
  289. package/src/components/Dialog/Dialog.test-utils.ts +49 -0
  290. package/src/components/Dialog/Dialog.test.tsx +896 -89
  291. package/src/components/Dialog/Dialog.tsx +174 -882
  292. package/src/components/Dialog/dialogLock.test.ts +238 -0
  293. package/src/components/Dialog/dialogLock.ts +98 -0
  294. package/src/components/Dialog/index.ts +2 -0
  295. package/src/components/Dialog/useDialogDimensions.test.ts +163 -0
  296. package/src/components/Dialog/useDialogDimensions.ts +140 -0
  297. package/src/components/Dialog/useDialogLifecycle.test.ts +358 -0
  298. package/src/components/Dialog/useDialogLifecycle.ts +135 -0
  299. package/src/components/Dialog/useDialogPersistence.test.ts +381 -0
  300. package/src/components/Dialog/useDialogPersistence.ts +357 -0
  301. package/src/components/FileDisplay/FileDisplay.test.tsx +40 -40
  302. package/src/components/FileDisplay/FileDisplay.tsx +24 -656
  303. package/src/components/FileDisplay/FileDisplayContent.test.tsx +395 -0
  304. package/src/components/FileDisplay/FileDisplayContent.tsx +242 -0
  305. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.test.tsx +74 -0
  306. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.tsx +38 -0
  307. package/src/components/FileDisplay/FileDisplayEmptyView.test.tsx +33 -0
  308. package/src/components/FileDisplay/FileDisplayEmptyView.tsx +33 -0
  309. package/src/components/FileDisplay/FileDisplayErrorView.test.tsx +71 -0
  310. package/src/components/FileDisplay/FileDisplayErrorView.tsx +50 -0
  311. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.test.tsx +22 -0
  312. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.tsx +22 -0
  313. package/src/components/FileDisplay/FileDisplayLoadingView.test.tsx +21 -0
  314. package/src/components/FileDisplay/FileDisplayLoadingView.tsx +23 -0
  315. package/src/components/FileDisplay/FileDisplayMultipleFilesView.test.tsx +101 -0
  316. package/src/components/FileDisplay/FileDisplayMultipleFilesView.tsx +109 -0
  317. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.test.tsx +58 -0
  318. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.tsx +48 -0
  319. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.test.tsx +111 -0
  320. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.tsx +270 -0
  321. package/src/components/FileDisplay/FileDisplaySingleImageView.test.tsx +78 -0
  322. package/src/components/FileDisplay/FileDisplaySingleImageView.tsx +67 -0
  323. package/src/components/FileDisplay/fallbackUtils.test.ts +50 -0
  324. package/src/components/FileDisplay/fallbackUtils.ts +44 -0
  325. package/src/components/FileDisplay/fetchFileDisplayData.ts +24 -0
  326. package/src/components/FileDisplay/fetchFileDisplayData.unit.test.ts +183 -0
  327. package/src/components/FileDisplay/fileDisplayUtils.test.ts +58 -0
  328. package/src/components/FileDisplay/fileDisplayUtils.ts +24 -0
  329. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.test.ts +40 -42
  330. package/src/components/FileDisplay/useFileDisplay.ts +515 -0
  331. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.unit.test.ts +406 -77
  332. package/src/components/FileDisplay/useFileDisplayData.ts +126 -0
  333. package/src/{hooks/public → components/FileDisplay}/usePublicFileDisplay.test.ts +94 -88
  334. package/src/components/FileDisplay/usePublicFileDisplay.ts +579 -0
  335. package/src/components/FileUpload/FileUpload.test.tsx +16 -10
  336. package/src/components/FileUpload/FileUpload.tsx +107 -525
  337. package/src/components/FileUpload/FileUploadDropZone.tsx +112 -0
  338. package/src/components/FileUpload/FileUploadProgressItem.tsx +86 -0
  339. package/src/components/FileUpload/FileUploadProgressList.tsx +40 -0
  340. package/src/components/FileUpload/useFileUploadManager.test.ts +308 -0
  341. package/src/components/FileUpload/useFileUploadManager.ts +454 -0
  342. package/src/components/FileUpload/useResolvedAppId.test.ts +102 -0
  343. package/src/components/FileUpload/useResolvedAppId.ts +77 -0
  344. package/src/components/Footer/Footer.test.tsx +6 -292
  345. package/src/components/Footer/Footer.tsx +8 -125
  346. package/src/components/Form/Form.test.tsx +44 -27
  347. package/src/components/Form/Form.tsx +64 -287
  348. package/src/components/Form/useFormPersistence.ts +257 -0
  349. package/src/components/Header/Header.test.tsx +17 -18
  350. package/src/components/Header/Header.tsx +10 -1
  351. package/src/components/Input/Input.tsx +1 -1
  352. package/src/components/Label/Label.test.tsx +1 -1
  353. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +1 -1
  354. package/src/components/NavigationMenu/HierarchicalNavItem.tsx +104 -0
  355. package/src/components/NavigationMenu/NavigationMenu.test.tsx +1029 -26
  356. package/src/components/NavigationMenu/NavigationMenu.tsx +61 -361
  357. package/src/components/NavigationMenu/index.ts +6 -1
  358. package/src/components/NavigationMenu/navigationPermissionHelper.ts +188 -0
  359. package/src/components/NavigationMenu/{__tests__/useNavigationFiltering.test.ts → useNavigationFiltering.test.ts} +68 -53
  360. package/src/components/NavigationMenu/useNavigationFiltering.ts +197 -296
  361. package/src/components/NavigationMenu/useNavigationScope.ts +125 -0
  362. package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +77 -62
  363. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +3 -3
  364. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +16 -19
  365. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +529 -5
  366. package/src/components/PaceAppLayout/PaceAppLayout.tsx +280 -756
  367. package/src/components/PaceAppLayout/useFilteredNavItems.ts +304 -0
  368. package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +142 -0
  369. package/src/components/PaceAppLayout/usePaceAppLayoutGate.tsx +150 -0
  370. package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +162 -0
  371. package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +79 -0
  372. package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +157 -0
  373. package/src/components/PaceAppLayout/useSuperAdminFallback.ts +58 -0
  374. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +31 -25
  375. package/src/components/PaceLoginPage/PaceLoginPage.tsx +31 -122
  376. package/src/components/PaceLoginPage/useLoginAppAccess.ts +153 -0
  377. package/src/components/Progress/Progress.tsx +1 -2
  378. package/src/components/ProtectedRoute/ProtectedRoute.tsx +29 -235
  379. package/src/components/ProtectedRoute/useProtectedRouteState.ts +128 -0
  380. package/src/components/ProtectedRoute/useVisibilityRedirectGrace.ts +89 -0
  381. package/src/components/PublicLayout/PublicLayout.test.tsx +217 -36
  382. package/src/components/PublicLayout/PublicPageLayout.tsx +132 -73
  383. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -1
  384. package/src/components/Select/Select.test.tsx +1 -1
  385. package/src/components/Select/Select.tsx +28 -18
  386. package/src/components/Select/{__tests__/context.test.tsx → context.test.tsx} +3 -3
  387. package/src/components/Select/{utils/__tests__/text.test.tsx → text.test.tsx} +2 -2
  388. package/src/components/Select/{utils/text.ts → text.ts} +1 -1
  389. package/src/components/Select/{hooks/__tests__/useSelectEvents.test.ts → useSelectEvents.test.ts} +5 -5
  390. package/src/components/Select/{hooks/useSelectEvents.ts → useSelectEvents.ts} +2 -2
  391. package/src/components/Select/{hooks/__tests__/useSelectSearch.test.tsx → useSelectSearch.test.tsx} +7 -7
  392. package/src/components/Select/{hooks/useSelectSearch.ts → useSelectSearch.ts} +2 -2
  393. package/src/components/Select/{hooks/__tests__/useSelectState.test.ts → useSelectState.test.ts} +16 -2
  394. package/src/components/Select/{hooks/useSelectState.ts → useSelectState.ts} +3 -3
  395. package/src/components/Table/Table.test.tsx +348 -0
  396. package/src/components/Tabs/Tabs.test.tsx +270 -0
  397. package/src/components/Tabs/Tabs.tsx +1 -1
  398. package/src/components/Toast/Toast.test.tsx +420 -0
  399. package/src/components/{__tests__/index.test.ts → index.test.ts} +2 -2
  400. package/src/constants/{__tests__/performance.test.ts → performance.test.ts} +2 -2
  401. package/src/hooks/{__tests__/ServiceHooks.test.tsx → ServiceHooks.test.tsx} +8 -8
  402. package/src/hooks/{__tests__/hooks.integration.test.tsx → hooks.integration.test.tsx} +11 -11
  403. package/src/hooks/index.ts +7 -4
  404. package/src/hooks/{__tests__/index.unit.test.ts → index.unit.test.ts} +2 -2
  405. package/src/hooks/public/usePublicEvent.test.ts +1 -1
  406. package/src/hooks/public/usePublicEventLogo.test.ts +1 -1
  407. package/src/hooks/public/usePublicRouteParams.test.ts +1 -1
  408. package/src/hooks/services/useAuth.ts +9 -7
  409. package/src/hooks/useAddressAutocomplete.test.ts +22 -22
  410. package/src/hooks/useAddressAutocomplete.ts +90 -75
  411. package/src/hooks/{__tests__/useAppConfig.unit.test.ts → useAppConfig.unit.test.ts} +328 -22
  412. package/src/hooks/{__tests__/useComponentPerformance.unit.test.tsx → useComponentPerformance.unit.test.tsx} +27 -41
  413. package/src/hooks/useDataTablePerformance.ts +100 -120
  414. package/src/hooks/{__tests__/useDataTablePerformance.unit.test.ts → useDataTablePerformance.unit.test.ts} +5 -5
  415. package/src/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +2 -2
  416. package/src/hooks/{__tests__/useDebounce.unit.test.ts → useDebounce.unit.test.ts} +2 -2
  417. package/src/hooks/useEventTheme.test.ts +4 -1
  418. package/src/hooks/useEventTheme.ts +49 -21
  419. package/src/hooks/useEvents.ts +41 -1
  420. package/src/hooks/{__tests__/useEvents.unit.test.ts → useEvents.unit.test.ts} +5 -5
  421. package/src/hooks/useFileReference.test.ts +44 -41
  422. package/src/hooks/useFileReference.ts +182 -173
  423. package/src/hooks/useFileUrl.ts +1 -1
  424. package/src/hooks/{__tests__/useFileUrl.unit.test.ts → useFileUrl.unit.test.ts} +26 -36
  425. package/src/hooks/{__tests__/useFileUrlCache.test.ts → useFileUrlCache.test.ts} +8 -8
  426. package/src/hooks/useFileUrlCache.ts +1 -1
  427. package/src/hooks/{__tests__/useFocusManagement.unit.test.ts → useFocusManagement.unit.test.ts} +2 -2
  428. package/src/hooks/{__tests__/useFocusTrap.unit.test.tsx → useFocusTrap.unit.test.tsx} +2 -2
  429. package/src/hooks/{__tests__/useFormDialog.test.ts → useFormDialog.test.ts} +2 -2
  430. package/src/hooks/useInactivityTracker.ts +138 -131
  431. package/src/hooks/{__tests__/useInactivityTracker.unit.test.ts → useInactivityTracker.unit.test.ts} +3 -3
  432. package/src/hooks/{__tests__/useIsMobile.unit.test.ts → useIsMobile.unit.test.ts} +2 -2
  433. package/src/hooks/useIsPrint.ts +62 -0
  434. package/src/hooks/useIsPrint.unit.test.ts +545 -0
  435. package/src/hooks/{__tests__/useKeyboardShortcuts.unit.test.ts → useKeyboardShortcuts.unit.test.ts} +2 -2
  436. package/src/hooks/{__tests__/useOrganisationPermissions.unit.test.tsx → useOrganisationPermissions.unit.test.tsx} +4 -4
  437. package/src/hooks/useOrganisationSecurity.test.ts +3 -3
  438. package/src/hooks/useOrganisationSecurity.ts +190 -201
  439. package/src/hooks/{__tests__/useOrganisationSecurity.unit.test.tsx → useOrganisationSecurity.unit.test.tsx} +61 -63
  440. package/src/hooks/{__tests__/useOrganisations.unit.test.ts → useOrganisations.unit.test.ts} +5 -5
  441. package/src/hooks/{__tests__/usePerformanceMonitor.unit.test.ts → usePerformanceMonitor.unit.test.ts} +13 -14
  442. package/src/hooks/{__tests__/usePermissionCache.test.ts → usePermissionCache.test.ts} +26 -27
  443. package/src/hooks/usePermissionCache.ts +276 -271
  444. package/src/hooks/{__tests__/usePreventTabReload.test.ts → usePreventTabReload.test.ts} +2 -2
  445. package/src/hooks/{__tests__/usePublicEvent.simple.test.ts → usePublicEvent.simple.test.ts} +4 -4
  446. package/src/hooks/{__tests__/usePublicEvent.test.ts → usePublicEvent.test.ts} +4 -4
  447. package/src/hooks/{__tests__/usePublicEvent.unit.test.ts → usePublicEvent.unit.test.ts} +4 -4
  448. package/src/hooks/{__tests__/usePublicFileDisplay.test.ts → usePublicFileDisplay.test.ts} +12 -12
  449. package/src/hooks/{__tests__/usePublicRouteParams.unit.test.ts → usePublicRouteParams.unit.test.ts} +3 -3
  450. package/src/hooks/{__tests__/useQueryCache.test.ts → useQueryCache.test.ts} +2 -2
  451. package/src/hooks/useQueryCache.ts +0 -2
  452. package/src/hooks/{__tests__/useRBAC.unit.test.ts → useRBAC.unit.test.ts} +55 -38
  453. package/src/hooks/{__tests__/useSessionDraft.test.ts → useSessionDraft.test.ts} +2 -2
  454. package/src/hooks/{__tests__/useSessionRestoration.unit.test.tsx → useSessionRestoration.unit.test.tsx} +10 -19
  455. package/src/hooks/useStorage.ts +21 -16
  456. package/src/hooks/{__tests__/useStorage.unit.test.ts → useStorage.unit.test.ts} +38 -75
  457. package/src/hooks/{__tests__/useToast.test.ts → useToast.test.ts} +2 -2
  458. package/src/hooks/{__tests__/useToast.unit.test.tsx → useToast.unit.test.tsx} +2 -2
  459. package/src/hooks/{__tests__/useZodForm.unit.test.tsx → useZodForm.unit.test.tsx} +2 -2
  460. package/src/icons/{__tests__/index.test.ts → index.test.ts} +2 -2
  461. package/src/icons/index.ts +2 -0
  462. package/src/{__tests__/index.test.ts → index.test.ts} +3 -7
  463. package/src/index.ts +15 -7
  464. package/src/providers/{__tests__/AuthProvider.test.tsx → AuthProvider.test.tsx} +3 -3
  465. package/src/providers/{__tests__/EventProvider.test.tsx → EventProvider.test.tsx} +3 -3
  466. package/src/providers/InactivityProvider.test-helper.tsx +40 -0
  467. package/src/providers/{__tests__/InactivityProvider.test.tsx → InactivityProvider.test.tsx} +14 -21
  468. package/src/providers/{__tests__/ProviderLifecycle.test.tsx → ProviderLifecycle.test.tsx} +4 -4
  469. package/src/providers/{__tests__/UnifiedAuthProvider.test.tsx → UnifiedAuthProvider.test.tsx} +1 -1
  470. package/src/providers/{__tests__/index.test.ts → index.test.ts} +2 -2
  471. package/src/providers/services/{__tests__/AuthServiceProvider.integration.test.tsx → AuthServiceProvider.integration.test.tsx} +4 -4
  472. package/src/providers/services/{__tests__/AuthServiceProvider.test.tsx → AuthServiceProvider.test.tsx} +7 -7
  473. package/src/providers/services/{__tests__/EventServiceProvider.test.tsx → EventServiceProvider.test.tsx} +7 -7
  474. package/src/providers/services/{__tests__/InactivityServiceProvider.test.tsx → InactivityServiceProvider.test.tsx} +5 -5
  475. package/src/providers/services/{__tests__/OrganisationServiceProvider.test.tsx → OrganisationServiceProvider.test.tsx} +6 -6
  476. package/src/providers/services/UnifiedAuthContext.ts +30 -27
  477. package/src/providers/services/{__tests__/UnifiedAuthProvider.advanced.test.tsx → UnifiedAuthProvider.advanced.test.tsx} +8 -9
  478. package/src/providers/services/{__tests__/UnifiedAuthProvider.appId.test.tsx → UnifiedAuthProvider.appId.test.tsx} +25 -25
  479. package/src/providers/services/{__tests__/UnifiedAuthProvider.integration.test.tsx → UnifiedAuthProvider.integration.test.tsx} +14 -11
  480. package/src/providers/services/UnifiedAuthProvider.tsx +115 -360
  481. package/src/providers/services/{__tests__/contexts.test.tsx → contexts.test.tsx} +6 -6
  482. package/src/providers/services/{__tests__/useUnifiedAuth.test.tsx → useUnifiedAuth.test.tsx} +6 -6
  483. package/src/providers/services/useUnifiedAuthContextValue.ts +279 -0
  484. package/src/providers/useInactivity.test-helper.ts +27 -0
  485. package/src/rbac/{__tests__/adapters.comprehensive.test.tsx → adapters.comprehensive.test.tsx} +24 -24
  486. package/src/rbac/adapters.test.tsx +22 -22
  487. package/src/rbac/adapters.tsx +29 -29
  488. package/src/rbac/api.test.ts +973 -42
  489. package/src/rbac/api.ts +228 -253
  490. package/src/rbac/{__tests__/audit-batched.test.ts → audit-batched.test.ts} +6 -6
  491. package/src/rbac/audit.ts +4 -1
  492. package/src/rbac/{__tests__/auth-rbac-security.integration.test.tsx → auth-rbac-security.integration.test.tsx} +1 -1
  493. package/src/rbac/{__tests__/auth-rbac.e2e.test.tsx → auth-rbac.e2e.test.tsx} +27 -34
  494. package/src/rbac/cache-invalidation.test.ts +715 -0
  495. package/src/rbac/components/{__tests__/AccessDenied.test.tsx → AccessDenied.test.tsx} +3 -3
  496. package/src/rbac/components/{__tests__/NavigationGuard.test.tsx → NavigationGuard.test.tsx} +13 -11
  497. package/src/{__tests__/rbac/PagePermissionGuard.test.tsx → rbac/components/PagePermissionGuard.guard.test.tsx} +33 -19
  498. package/src/rbac/components/{__tests__/PagePermissionGuard.performance.test.tsx → PagePermissionGuard.performance.test.tsx} +30 -9
  499. package/src/rbac/components/{__tests__/PagePermissionGuard.race-condition.test.tsx → PagePermissionGuard.race-condition.test.tsx} +7 -7
  500. package/src/rbac/components/{__tests__/PagePermissionGuard.test.tsx → PagePermissionGuard.test.tsx} +10 -10
  501. package/src/rbac/components/PagePermissionGuard.tsx +177 -372
  502. package/src/rbac/components/{__tests__/PagePermissionGuard.verification.test.tsx → PagePermissionGuard.verification.test.tsx} +7 -7
  503. package/src/rbac/config.ts +58 -18
  504. package/src/rbac/{__tests__/engine.comprehensive.test.ts → engine.comprehensive.test.ts} +3 -3
  505. package/src/rbac/engine.test.ts +494 -0
  506. package/src/rbac/errors.ts +89 -55
  507. package/src/rbac/hooks/permissions/runPermissionCheck.ts +77 -0
  508. package/src/rbac/hooks/permissions/{__tests__/useAccessLevel.test.ts → useAccessLevel.test.ts} +40 -40
  509. package/src/rbac/hooks/permissions/useAccessLevel.ts +16 -6
  510. package/src/rbac/hooks/permissions/{__tests__/useCan.test.ts → useCan.test.ts} +41 -41
  511. package/src/rbac/hooks/permissions/useCan.ts +170 -252
  512. package/src/rbac/hooks/permissions/{__tests__/useMultiplePermissions.test.ts → useMultiplePermissions.test.ts} +49 -49
  513. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +6 -2
  514. package/src/rbac/hooks/permissions/{__tests__/usePermissions.test.ts → usePermissions.test.ts} +10 -12
  515. package/src/rbac/hooks/permissions/usePermissions.ts +36 -65
  516. package/src/rbac/hooks/useCan.test.ts +42 -42
  517. package/src/rbac/hooks/usePageAccessLogging.ts +160 -0
  518. package/src/rbac/hooks/usePageGuardScope.ts +117 -0
  519. package/src/rbac/hooks/usePagePermissionCheck.ts +67 -0
  520. package/src/rbac/hooks/{__tests__/usePermissions.integration.test.ts → usePermissions.integration.test.ts} +9 -9
  521. package/src/{__tests__/hooks/usePermissions.test.ts → rbac/hooks/usePermissions.stability.test.ts} +18 -18
  522. package/src/rbac/hooks/usePermissions.test.ts +54 -54
  523. package/src/rbac/hooks/useRBAC.test.ts +313 -217
  524. package/src/rbac/hooks/useRBAC.ts +145 -81
  525. package/src/rbac/hooks/useResourcePermissions.test.ts +25 -25
  526. package/src/rbac/hooks/useResourcePermissions.ts +68 -134
  527. package/src/rbac/hooks/useResourcePermissionsSuperAdmin.ts +67 -0
  528. package/src/rbac/hooks/useRoleManagement.test.ts +27 -112
  529. package/src/rbac/hooks/useRoleManagement.ts +153 -585
  530. package/src/rbac/hooks/{__tests__/useSecureSupabase.test.ts → useSecureSupabase.test.ts} +17 -17
  531. package/src/rbac/hooks/useSecureSupabase.ts +10 -2
  532. package/src/rbac/hooks/useSuperAdminCheck.ts +80 -0
  533. package/src/rbac/{__tests__/performance.test.ts → performance.test.ts} +1 -1
  534. package/src/rbac/{__tests__/rbac-core.test.tsx → rbac-core.test.tsx} +3 -3
  535. package/src/rbac/{__tests__/rbac-engine-core-logic.test.ts → rbac-engine-core-logic.test.ts} +2 -2
  536. package/src/rbac/{__tests__/rbac-engine-simplified.test.ts → rbac-engine-simplified.test.ts} +3 -3
  537. package/src/rbac/{__tests__/rbac-functions.test.ts → rbac-functions.test.ts} +57 -0
  538. package/src/rbac/{__tests__/rbac-role-isolation.test.ts → rbac-role-isolation.test.ts} +2 -2
  539. package/src/rbac/request-deduplication.test.ts +14 -9
  540. package/src/rbac/request-deduplication.ts +5 -4
  541. package/src/rbac/{__tests__/scenarios.user-role.test.tsx → scenarios.user-role.test.tsx} +23 -23
  542. package/src/rbac/secureClient.test.ts +514 -83
  543. package/src/rbac/secureClient.ts +8 -2
  544. package/src/rbac/security.test.ts +323 -0
  545. package/src/rbac/types/roleManagement.ts +66 -0
  546. package/src/rbac/utils/{__tests__/clientSecurity.test.ts → clientSecurity.test.ts} +4 -4
  547. package/src/rbac/utils/{__tests__/contextValidator.test.ts → contextValidator.test.ts} +4 -4
  548. package/src/rbac/utils/contextValidator.ts +5 -1
  549. package/src/rbac/utils/{__tests__/deep-equal.test.ts → deep-equal.test.ts} +1 -1
  550. package/src/rbac/utils/{__tests__/eventContext.test.ts → eventContext.test.ts} +36 -21
  551. package/src/rbac/utils/eventContext.ts +37 -33
  552. package/src/rbac/utils/fetchPermissionMap.ts +13 -0
  553. package/src/rbac/utils/permissionMapHelpers.ts +34 -0
  554. package/src/rbac/utils/roleManagementRpc.ts +303 -0
  555. package/src/services/{__tests__/AuthService.edge-cases.test.ts → AuthService.edge-cases.test.ts} +19 -19
  556. package/src/services/{__tests__/AuthService.restoreSession.test.ts → AuthService.restoreSession.test.ts} +2 -2
  557. package/src/services/{__tests__/AuthService.test.ts → AuthService.test.ts} +89 -55
  558. package/src/services/AuthService.ts +184 -205
  559. package/src/services/{__tests__/BaseService.edge-cases.test.ts → BaseService.edge-cases.test.ts} +3 -3
  560. package/src/services/{__tests__/BaseService.test.ts → BaseService.test.ts} +2 -2
  561. package/src/services/{__tests__/EventService.edge-cases.test.ts → EventService.edge-cases.test.ts} +27 -24
  562. package/src/services/{__tests__/EventService.eventColours.test.ts → EventService.eventColours.test.ts} +1 -1
  563. package/src/services/{__tests__/EventService.test.ts → EventService.test.ts} +256 -24
  564. package/src/services/EventService.ts +242 -312
  565. package/src/services/{__tests__/InactivityService.edge-cases.test.ts → InactivityService.edge-cases.test.ts} +3 -3
  566. package/src/services/{__tests__/InactivityService.lifecycle.test.ts → InactivityService.lifecycle.test.ts} +2 -2
  567. package/src/services/{__tests__/InactivityService.test.ts → InactivityService.test.ts} +179 -4
  568. package/src/services/InactivityService.ts +172 -213
  569. package/src/services/{__tests__/OrganisationService.edge-cases.test.ts → OrganisationService.edge-cases.test.ts} +5 -5
  570. package/src/services/{__tests__/OrganisationService.pagination.test.ts → OrganisationService.pagination.test.ts} +4 -4
  571. package/src/services/{__tests__/OrganisationService.test.ts → OrganisationService.test.ts} +410 -7
  572. package/src/services/OrganisationService.ts +184 -238
  573. package/src/services/base/BaseService.test.ts +1 -1
  574. package/src/services/interfaces/{__tests__/IAuthService.test.ts → IAuthService.test.ts} +21 -27
  575. package/src/services/interfaces/IAuthService.ts +10 -9
  576. package/src/services/interfaces/{__tests__/IEventService.test.ts → IEventService.test.ts} +4 -4
  577. package/src/services/interfaces/{__tests__/IInactivityService.test.ts → IInactivityService.test.ts} +3 -3
  578. package/src/services/interfaces/{__tests__/IOrganisationService.test.ts → IOrganisationService.test.ts} +3 -3
  579. package/src/styles/core.css +243 -12
  580. package/src/theming/{__tests__/parseEventColours.test.ts → parseEventColours.test.ts} +1 -1
  581. package/src/theming/{__tests__/runtime.test.ts → runtime.test.ts} +8 -17
  582. package/src/theming/runtime.ts +71 -2
  583. package/src/types/api-result.ts +53 -0
  584. package/src/types/{__tests__/core.test.ts → core.test.ts} +2 -2
  585. package/src/types/{__tests__/database-generated.test.ts → database-generated.test.ts} +3 -3
  586. package/src/types/database.generated.ts +45 -10
  587. package/src/types/event.ts +38 -18
  588. package/src/types/{__tests__/file-reference.test.ts → file-reference.test.ts} +13 -13
  589. package/src/types/file-reference.ts +37 -12
  590. package/src/types/{__tests__/guards.test.ts → guards.test.ts} +2 -2
  591. package/src/types/{__tests__/index.test.ts → index.test.ts} +2 -2
  592. package/src/types/index.ts +3 -0
  593. package/src/types/{__tests__/organisation.roles.test.ts → organisation.roles.test.ts} +1 -1
  594. package/src/types/{__tests__/organisation.test.ts → organisation.test.ts} +3 -31
  595. package/src/types/organisation.ts +15 -15
  596. package/src/types/supabase.ts +13 -4
  597. package/src/types/{__tests__/theme.test.ts → theme.test.ts} +1 -1
  598. package/src/types/{__tests__/type-validation.test.ts → type-validation.test.ts} +1 -1
  599. package/src/types/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  600. package/src/utils/app/appIdResolver.test.ts +98 -71
  601. package/src/utils/app/appIdResolver.ts +31 -20
  602. package/src/utils/{__tests__/appConfig.unit.test.ts → appConfig.unit.test.ts} +1 -1
  603. package/src/utils/{__tests__/audit.unit.test.ts → audit.unit.test.ts} +1 -1
  604. package/src/utils/{__tests__/auth-utils.unit.test.ts → auth-utils.unit.test.ts} +16 -17
  605. package/src/utils/{__tests__/bundleAnalysis.unit.test.ts → bundleAnalysis.unit.test.ts} +35 -35
  606. package/src/utils/{__tests__/cn.unit.test.ts → cn.unit.test.ts} +1 -1
  607. package/src/utils/context/organisationContext.test.ts +105 -91
  608. package/src/utils/context/organisationContext.ts +29 -40
  609. package/src/utils/core/{__tests__/cn.test.ts → cn.test.ts} +3 -3
  610. package/src/utils/core/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +2 -2
  611. package/src/utils/core/{__tests__/logger.test.ts → logger.test.ts} +2 -2
  612. package/src/utils/core/mergeRefs.ts +24 -0
  613. package/src/utils/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +1 -1
  614. package/src/utils/{__tests__/deviceFingerprint.unit.test.ts → deviceFingerprint.unit.test.ts} +1 -1
  615. package/src/utils/dynamic/createLazyComponent.tsx +9 -1
  616. package/src/utils/dynamic/{__tests__/dynamicUtils.test.ts → dynamicUtils.test.ts} +2 -2
  617. package/src/utils/dynamic/{__tests__/lazyLoad.test.tsx → lazyLoad.test.tsx} +2 -2
  618. package/src/utils/{__tests__/dynamicUtils.unit.test.ts → dynamicUtils.unit.test.ts} +1 -1
  619. package/src/utils/file-reference/{__tests__/file-reference.test.ts → file-reference.test.ts} +214 -289
  620. package/src/utils/file-reference/index.ts +330 -347
  621. package/src/utils/{__tests__/formatDate.unit.test.ts → formatDate.unit.test.ts} +2 -2
  622. package/src/utils/formatting/formatDateTimeTimezone.test.ts +1 -1
  623. package/src/utils/formatting/formatNumber.test.ts +1 -1
  624. package/src/utils/{__tests__/formatting.unit.test.ts → formatting.unit.test.ts} +1 -1
  625. package/src/utils/google-places/googlePlacesUtils.test.ts +70 -48
  626. package/src/utils/google-places/googlePlacesUtils.ts +67 -99
  627. package/src/utils/google-places/loadGoogleMapsScript.test.ts +25 -22
  628. package/src/utils/google-places/loadGoogleMapsScript.ts +138 -117
  629. package/src/utils/{__tests__/index.unit.test.ts → index.unit.test.ts} +1 -1
  630. package/src/utils/{__tests__/lazyLoad.unit.test.tsx → lazyLoad.unit.test.tsx} +13 -14
  631. package/src/utils/location/location.test.ts +1 -1
  632. package/src/utils/{__tests__/logger.unit.test.ts → logger.unit.test.ts} +1 -1
  633. package/src/utils/{__tests__/organisationContext.unit.test.ts → organisationContext.unit.test.ts} +37 -48
  634. package/src/utils/performance/{__tests__/bundleAnalysis.test.ts → bundleAnalysis.test.ts} +2 -2
  635. package/src/utils/performance/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  636. package/src/utils/performance/{__tests__/performanceBudgets.test.ts → performanceBudgets.test.ts} +2 -2
  637. package/src/utils/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  638. package/src/utils/{__tests__/performanceBudgets.unit.test.ts → performanceBudgets.unit.test.ts} +2 -2
  639. package/src/utils/{__tests__/permissionTypes.unit.test.ts → permissionTypes.unit.test.ts} +1 -1
  640. package/src/utils/{__tests__/permissionUtils.unit.test.ts → permissionUtils.unit.test.ts} +1 -1
  641. package/src/utils/permissions/{__tests__/permissionTypes.test.ts → permissionTypes.test.ts} +2 -2
  642. package/src/utils/persistence/{__tests__/keyDerivation.test.ts → keyDerivation.test.ts} +2 -2
  643. package/src/utils/persistence/{__tests__/sensitiveFieldDetection.test.ts → sensitiveFieldDetection.test.ts} +2 -2
  644. package/src/utils/{__tests__/request-deduplication.test.ts → request-deduplication.test.ts} +2 -2
  645. package/src/utils/{__tests__/sanitization.unit.test.ts → sanitization.unit.test.ts} +1 -1
  646. package/src/utils/{__tests__/schemaUtils.unit.test.ts → schemaUtils.unit.test.ts} +1 -1
  647. package/src/utils/{__tests__/secureDataAccess.unit.test.ts → secureDataAccess.unit.test.ts} +2 -2
  648. package/src/utils/{__tests__/secureErrors.unit.test.ts → secureErrors.unit.test.ts} +4 -4
  649. package/src/utils/{__tests__/secureStorage.unit.test.ts → secureStorage.unit.test.ts} +1 -1
  650. package/src/utils/security/auth-utils.ts +34 -23
  651. package/src/utils/security/secureDataAccess.ts +241 -281
  652. package/src/utils/security/secureErrors.test.ts +1 -1
  653. package/src/utils/security/secureStorage.test.ts +1 -1
  654. package/src/utils/security/security.test.ts +25 -17
  655. package/src/utils/security/security.ts +15 -18
  656. package/src/utils/security/securityMonitor.test.ts +1 -1
  657. package/src/utils/{__tests__/security.unit.test.ts → security.unit.test.ts} +21 -15
  658. package/src/utils/{__tests__/securityMonitor.unit.test.ts → securityMonitor.unit.test.ts} +1 -1
  659. package/src/utils/{__tests__/sessionTracking.unit.test.ts → sessionTracking.unit.test.ts} +12 -12
  660. package/src/utils/storage/{__tests__/config.unit.test.ts → config.unit.test.ts} +2 -2
  661. package/src/utils/storage/helpers.test.ts +88 -102
  662. package/src/utils/storage/helpers.ts +173 -251
  663. package/src/utils/storage/{__tests__/index.unit.test.ts → index.unit.test.ts} +3 -3
  664. package/src/utils/storage/types.ts +7 -0
  665. package/src/utils/supabase/createBaseClient.test.ts +1 -1
  666. package/src/utils/timezone/timezone.test.ts +1 -1
  667. package/src/utils/{__tests__/timezone.test.ts → timezone.test.ts} +2 -2
  668. package/src/utils/validation/{__tests__/common.test.ts → common.test.ts} +2 -2
  669. package/src/utils/validation/{__tests__/csrf.test.ts → csrf.test.ts} +56 -28
  670. package/src/utils/validation/csrf.ts +42 -41
  671. package/src/utils/validation/{__tests__/htmlSanitization.unit.test.ts → htmlSanitization.unit.test.ts} +2 -2
  672. package/src/utils/validation/{__tests__/passwordSchema.test.ts → passwordSchema.test.ts} +2 -2
  673. package/src/utils/validation/{__tests__/schema.test.ts → schema.test.ts} +2 -2
  674. package/src/utils/validation/{__tests__/sqlInjectionProtection.test.ts → sqlInjectionProtection.test.ts} +2 -2
  675. package/src/utils/validation/{__tests__/user.test.ts → user.test.ts} +2 -2
  676. package/src/utils/validation/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  677. package/src/utils/validation/{__tests__/validationUtils.test.ts → validationUtils.test.ts} +2 -2
  678. package/src/utils/{__tests__/validation.unit.test.ts → validation.unit.test.ts} +1 -1
  679. package/src/utils/{__tests__/validationUtils.unit.test.ts → validationUtils.unit.test.ts} +5 -2
  680. package/dist/UnifiedAuthProvider-BBD2PS3Q.js +0 -7
  681. package/dist/chunk-KPYQWGFQ.js +0 -183
  682. package/dist/types-D05dCGma.d.ts +0 -521
  683. package/scripts/eslint-audit.cjs +0 -222
  684. package/scripts/generate-docs.js +0 -157
  685. package/scripts/install-cursor-rules.cjs +0 -255
  686. package/scripts/install-eslint-config.cjs +0 -349
  687. package/scripts/setup-build-cache.js +0 -73
  688. package/scripts/validate-pre-publish.js +0 -145
  689. package/src/__tests__/integration/UserProfile.test.tsx +0 -124
  690. package/src/__tests__/public-recipe-view.test.ts +0 -228
  691. package/src/__tests__/rls-policies.test.ts +0 -472
  692. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -876
  693. package/src/components/DataTable/components/DataTableLayout.tsx +0 -584
  694. package/src/components/DataTable/components/UnifiedTableBody.tsx +0 -395
  695. package/src/components/DataTable/components/__tests__/DataTableLayout.test.tsx +0 -467
  696. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +0 -358
  697. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +0 -957
  698. package/src/components/DataTable/core/ActionManager.ts +0 -235
  699. package/src/components/DataTable/core/ColumnManager.ts +0 -204
  700. package/src/components/DataTable/core/DataManager.ts +0 -190
  701. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -274
  702. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  703. package/src/components/DataTable/core/StateManager.ts +0 -312
  704. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -235
  705. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -141
  706. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -178
  707. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +0 -133
  708. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +0 -142
  709. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -158
  710. package/src/components/DataTable/core/interfaces.ts +0 -338
  711. package/src/components/DataTable/types.ts +0 -764
  712. package/src/hooks/public/usePublicFileDisplay.ts +0 -534
  713. package/src/hooks/useFileDisplay.ts +0 -748
  714. package/src/providers/OrganisationProvider.test.tsx +0 -40
  715. package/src/providers/OrganisationProvider.tsx +0 -92
  716. package/src/providers/__tests__/InactivityProvider.test-helper.tsx +0 -65
  717. package/src/providers/__tests__/OrganisationProvider.test.tsx +0 -616
  718. package/src/providers/__tests__/OrganisationProvider.wrapper.test.tsx +0 -591
  719. package/src/rbac/__tests__/cache-invalidation.test.ts +0 -393
  720. /package/src/components/DataTable/{components/__tests__ → ui}/COVERAGE_NOTE.md +0 -0
  721. /package/src/components/DataTable/utils/{__tests__/COVERAGE_NOTE.md → COVERAGE_NOTE.md} +0 -0
  722. /package/src/hooks/{__tests__/useApiFetch.unit.test.ts → useApiFetch.unit.test.ts} +0 -0
  723. /package/src/providers/{__tests__/README.md → README.md} +0 -0
  724. /package/src/rbac/{__tests__/index.test.ts → index.test.ts} +0 -0
  725. /package/src/rbac/{__tests__/rbac-integration.test.ts → rbac-integration.test.ts} +0 -0
  726. /package/src/types/{__tests__/README.md → README.md} +0 -0
@@ -1,8 +1,9 @@
1
- import { clearPalette, parseAndNormalizeEventColours, applyPalette } from './chunk-JJEYZ3DX.js';
2
- import { EventServiceContext } from './chunk-Y4PF6HIM.js';
1
+ import { EventServiceContext } from './chunk-4R3T5ENU.js';
2
+ import { clearPalette, parseAndNormalizeEventColours, applyPalette } from './chunk-D6BMFMQZ.js';
3
3
  import { assertAppId } from './chunk-4SXLQIZO.js';
4
- import { fetchPlaceDetails, createAddressFromPlaceResult, getAddressByPlaceId, fetchPlaceAutocomplete, setOrganisationContext } from './chunk-7YDC7LMU.js';
4
+ import { fetchPlaceDetails, createAddressFromPlaceResult, getAddressByPlaceId, fetchPlaceAutocomplete, setOrganisationContext } from './chunk-XPFVT3GN.js';
5
5
  import { createLogger, logger } from './chunk-BTHN5MKC.js';
6
+ import { ok, err } from './chunk-44CNXN4P.js';
6
7
  import { useState, useEffect, useCallback, useMemo, useRef, useContext } from 'react';
7
8
  import { useLocation } from 'react-router-dom';
8
9
 
@@ -41,7 +42,6 @@ if (typeof window !== "undefined" && !cleanupTimer) {
41
42
  if (cleanupTimer) {
42
43
  clearInterval(cleanupTimer);
43
44
  cleanupTimer = null;
44
- log.debug("Query cache cleanup timer cleared on page unload.");
45
45
  }
46
46
  });
47
47
  }
@@ -49,7 +49,6 @@ function cleanupQueryCache() {
49
49
  if (cleanupTimer) {
50
50
  clearInterval(cleanupTimer);
51
51
  cleanupTimer = null;
52
- log.debug("Query cache cleanup timer cleared.");
53
52
  }
54
53
  }
55
54
  function useQueryCache(_supabase) {
@@ -200,6 +199,44 @@ var queryCacheHelpers = {
200
199
  return promise;
201
200
  }
202
201
  };
202
+ async function fetchSuggestionsForInput(debouncedInput, apiKey, memoizedAutocompleteOptions, cacheEnabled, cacheTTLAutocomplete, getCachedQuery, isCancelledRef, setters) {
203
+ try {
204
+ let result;
205
+ if (cacheEnabled) {
206
+ result = await getCachedQuery(
207
+ "google-places-autocomplete",
208
+ "query",
209
+ debouncedInput,
210
+ async () => {
211
+ if (isCancelledRef.current) {
212
+ throw new Error("Request cancelled");
213
+ }
214
+ return fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
215
+ },
216
+ { ttl: cacheTTLAutocomplete, enabled: true }
217
+ );
218
+ } else {
219
+ result = await fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
220
+ }
221
+ if (isCancelledRef.current) return;
222
+ if (!result.ok) {
223
+ setters.setError(new Error(result.error.message));
224
+ setters.setSuggestions([]);
225
+ setters.setIsLoading(false);
226
+ return;
227
+ }
228
+ setters.setSuggestions(result.data);
229
+ setters.setIsLoading(false);
230
+ } catch (err2) {
231
+ if (isCancelledRef.current || err2 instanceof Error && err2.message === "Request cancelled") {
232
+ return;
233
+ }
234
+ const error = err2 instanceof Error ? err2 : new Error("Failed to fetch autocomplete suggestions");
235
+ setters.setError(error);
236
+ setters.setSuggestions([]);
237
+ setters.setIsLoading(false);
238
+ }
239
+ }
203
240
  function useAddressAutocomplete(apiKey, inputValue, options = {}) {
204
241
  const {
205
242
  debounceDelay = 300,
@@ -244,45 +281,19 @@ function useAddressAutocomplete(apiKey, inputValue, options = {}) {
244
281
  }
245
282
  setIsLoading(true);
246
283
  setError(null);
247
- let isCancelled = false;
248
- const fetchSuggestions = async () => {
249
- try {
250
- let predictions;
251
- if (cacheEnabled) {
252
- predictions = await getCachedQuery(
253
- "google-places-autocomplete",
254
- "query",
255
- debouncedInput,
256
- async () => {
257
- if (isCancelled) {
258
- throw new Error("Request cancelled");
259
- }
260
- return fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
261
- },
262
- { ttl: cacheTTL.autocomplete, enabled: true }
263
- );
264
- } else {
265
- predictions = await fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);
266
- }
267
- if (!isCancelled) {
268
- setSuggestions(predictions);
269
- setIsLoading(false);
270
- }
271
- } catch (err) {
272
- if (isCancelled || err instanceof Error && err.message === "Request cancelled") {
273
- return;
274
- }
275
- if (!isCancelled) {
276
- const error2 = err instanceof Error ? err : new Error("Failed to fetch autocomplete suggestions");
277
- setError(error2);
278
- setSuggestions([]);
279
- setIsLoading(false);
280
- }
281
- }
282
- };
283
- fetchSuggestions();
284
+ const isCancelledRef = { current: false };
285
+ fetchSuggestionsForInput(
286
+ debouncedInput,
287
+ apiKey,
288
+ memoizedAutocompleteOptions,
289
+ cacheEnabled,
290
+ cacheTTL.autocomplete ?? 3600,
291
+ getCachedQuery,
292
+ isCancelledRef,
293
+ { setSuggestions, setError, setIsLoading }
294
+ );
284
295
  return () => {
285
- isCancelled = true;
296
+ isCancelledRef.current = true;
286
297
  };
287
298
  }, [debouncedInput, apiKey, cacheEnabled, cacheTTL.autocomplete, memoizedAutocompleteOptions, getCachedQuery]);
288
299
  const selectAddress = useCallback(
@@ -293,25 +304,28 @@ function useAddressAutocomplete(apiKey, inputValue, options = {}) {
293
304
  setIsLoading(true);
294
305
  setError(null);
295
306
  try {
296
- let placeDetails;
307
+ let placeResult;
297
308
  if (cacheEnabled) {
298
- placeDetails = await getCachedQuery(
309
+ placeResult = await getCachedQuery(
299
310
  "google-places-details",
300
311
  "place_id",
301
312
  placeId,
302
- async () => {
303
- return fetchPlaceDetails(placeId, apiKey);
304
- },
313
+ async () => fetchPlaceDetails(placeId, apiKey),
305
314
  { ttl: cacheTTL.placeDetails, enabled: true }
306
315
  );
307
316
  } else {
308
- placeDetails = await fetchPlaceDetails(placeId, apiKey);
317
+ placeResult = await fetchPlaceDetails(placeId, apiKey);
318
+ }
319
+ if (!placeResult.ok) {
320
+ setError(new Error(placeResult.error.message));
321
+ setIsLoading(false);
322
+ return null;
309
323
  }
310
- const parsedAddress = createAddressFromPlaceResult(placeDetails);
324
+ const parsedAddress = createAddressFromPlaceResult(placeResult.data);
311
325
  setIsLoading(false);
312
326
  return parsedAddress;
313
- } catch (err) {
314
- const error2 = err instanceof Error ? err : new Error("Failed to fetch place details");
327
+ } catch (err2) {
328
+ const error2 = err2 instanceof Error ? err2 : new Error("Failed to fetch place details");
315
329
  setError(error2);
316
330
  setIsLoading(false);
317
331
  return null;
@@ -326,25 +340,25 @@ function useAddressAutocomplete(apiKey, inputValue, options = {}) {
326
340
  return null;
327
341
  }
328
342
  try {
343
+ let result;
329
344
  if (cacheEnabled) {
330
- return await getCachedQuery(
345
+ result = await getCachedQuery(
331
346
  "google-places-details",
332
347
  "place_id",
333
348
  placeId,
334
- async () => {
335
- const result = await getAddressByPlaceId(placeId, apiKey);
336
- if (!result) {
337
- throw new Error("Failed to fetch address");
338
- }
339
- return result;
340
- },
349
+ async () => getAddressByPlaceId(placeId, apiKey),
341
350
  { ttl: cacheTTL.placeDetails, enabled: true }
342
351
  );
343
352
  } else {
344
- return await getAddressByPlaceId(placeId, apiKey);
353
+ result = await getAddressByPlaceId(placeId, apiKey);
345
354
  }
346
- } catch (err) {
347
- const error2 = err instanceof Error ? err : new Error("Failed to get address by place_id");
355
+ if (!result.ok) {
356
+ setError(new Error(result.error.message));
357
+ return null;
358
+ }
359
+ return result.data;
360
+ } catch (err2) {
361
+ const error2 = err2 instanceof Error ? err2 : new Error("Failed to get address by place_id");
348
362
  setError(error2);
349
363
  return null;
350
364
  }
@@ -467,31 +481,38 @@ function generateUniqueFileName(originalName) {
467
481
  return `${timestamp}-${uuid}-${baseName}.${extension}`;
468
482
  }
469
483
  async function extractFileMetadata(file, options, uploadedBy) {
470
- const metadata = {
471
- mimeType: file.type,
472
- size: file.size,
473
- ...options.orgId && { orgId: options.orgId },
474
- ...options.userId && { userId: options.userId },
475
- appName: options.appName || "pace-core",
476
- uploadedBy,
477
- uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
478
- tags: options.tags || [],
479
- isPublic: options.isPublic || false,
480
- customMetadata: options.metadata || {}
481
- };
482
- if (file.type.startsWith("image/")) {
484
+ try {
485
+ const metadata = {
486
+ mimeType: file.type,
487
+ size: file.size,
488
+ ...options.orgId && { orgId: options.orgId },
489
+ ...options.userId && { userId: options.userId },
490
+ appName: options.appName || "pace-core",
491
+ uploadedBy,
492
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
493
+ tags: options.tags || [],
494
+ isPublic: options.isPublic || false,
495
+ customMetadata: options.metadata || {}
496
+ };
497
+ if (file.type.startsWith("image/")) {
498
+ try {
499
+ const dimensions = await getImageDimensions(file);
500
+ metadata.width = dimensions.width;
501
+ metadata.height = dimensions.height;
502
+ } catch (_error) {
503
+ }
504
+ }
483
505
  try {
484
- const dimensions = await getImageDimensions(file);
485
- metadata.width = dimensions.width;
486
- metadata.height = dimensions.height;
506
+ metadata.hash = await generateFileHash(file);
487
507
  } catch (_error) {
488
508
  }
509
+ return ok(metadata);
510
+ } catch (error) {
511
+ return err({
512
+ code: "EXTRACT_METADATA_FAILED",
513
+ message: error instanceof Error ? error.message : "Unknown error"
514
+ });
489
515
  }
490
- try {
491
- metadata.hash = await generateFileHash(file);
492
- } catch (_error) {
493
- }
494
- return metadata;
495
516
  }
496
517
  async function getImageDimensions(file) {
497
518
  return new Promise((resolve, reject) => {
@@ -546,44 +567,37 @@ async function uploadFile(supabase, file, options) {
546
567
  try {
547
568
  const sizeValidation = validateFileSize(file);
548
569
  if (!sizeValidation.isValid) {
549
- return {
550
- success: false,
551
- error: sizeValidation.error
552
- };
570
+ return err({ code: "FILE_SIZE_INVALID", message: sizeValidation.error ?? "File size validation failed" });
553
571
  }
554
572
  const uniqueFileName = generateUniqueFileName(file.name);
555
573
  const filePath = generateFilePath(options, uniqueFileName);
556
574
  const folderPath = filePath.substring(0, filePath.lastIndexOf("/"));
557
- const metadata = await extractFileMetadata(file, options, "current-user");
575
+ const metadataResult = await extractFileMetadata(file, options, "current-user");
576
+ if (!metadataResult.ok) {
577
+ return err(metadataResult.error);
578
+ }
579
+ const metadata = metadataResult.data;
558
580
  const bucketName = getBucketName(options.isPublic || false);
559
581
  await ensureFolderExists(supabase, folderPath, bucketName);
560
- const { data, error } = await supabase.storage.from(bucketName).upload(filePath, file, {
582
+ const { error } = await supabase.storage.from(bucketName).upload(filePath, file, {
561
583
  cacheControl: "3600",
562
584
  upsert: false,
563
585
  contentType: file.type
564
586
  });
565
587
  if (error) {
566
- return {
567
- success: false,
568
- error: `Upload failed: ${error.message}`
569
- };
588
+ return err({ code: "UPLOAD_FAILED", message: error.message });
570
589
  }
571
590
  let publicUrl;
572
591
  if (options.isPublic) {
573
592
  const { data: urlData } = supabase.storage.from(bucketName).getPublicUrl(filePath);
574
593
  publicUrl = urlData.publicUrl;
575
594
  }
576
- return {
577
- success: true,
578
- path: filePath,
579
- publicUrl,
580
- metadata
581
- };
595
+ return ok({ path: filePath, publicUrl, metadata });
582
596
  } catch (error) {
583
- return {
584
- success: false,
585
- error: `Upload failed: ${error instanceof Error ? error.message : "Unknown error"}`
586
- };
597
+ return err({
598
+ code: "UPLOAD_FAILED",
599
+ message: error instanceof Error ? error.message : "Unknown error"
600
+ });
587
601
  }
588
602
  }
589
603
  function getPublicUrl(supabase, path, isPublic = true) {
@@ -646,15 +660,18 @@ async function getSignedUrl(supabase, path, options) {
646
660
  const { data, error } = await supabase.storage.from(bucketName).createSignedUrl(path, options.expiresIn || 3600);
647
661
  if (error) {
648
662
  log2.error("Failed to create signed URL:", error);
649
- return null;
663
+ return err({ code: "SIGNED_URL_FAILED", message: error.message });
650
664
  }
651
- return {
665
+ return ok({
652
666
  url: data.signedUrl,
653
667
  expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1e3).toISOString()
654
- };
668
+ });
655
669
  } catch (error) {
656
670
  log2.error("Failed to create signed URL:", error);
657
- return null;
671
+ return err({
672
+ code: "SIGNED_URL_FAILED",
673
+ message: error instanceof Error ? error.message : "Unknown error"
674
+ });
658
675
  }
659
676
  }
660
677
  var globalUrlCache = /* @__PURE__ */ new Map();
@@ -679,101 +696,94 @@ function cleanupUrlCache() {
679
696
  }
680
697
  }
681
698
  async function generateFileUrlsBatch(supabase, fileReferences, options) {
682
- const urlMap = /* @__PURE__ */ new Map();
683
- if (fileReferences.length === 0) {
684
- return urlMap;
685
- }
686
- const now = Date.now();
687
- const ttl = (options.expiresIn || 3600) * 1e3;
688
- const publicFiles = [];
689
- const privateFiles = [];
690
- const uncachedFiles = [];
691
- for (const fileRef of fileReferences) {
692
- const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);
693
- const cached = globalUrlCache.get(cacheKey);
694
- if (cached && cached.expiresAt > now) {
695
- urlMap.set(fileRef.id, cached.url);
696
- continue;
697
- }
698
- if (fileRef.is_public) {
699
- publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
700
- } else {
701
- privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
699
+ try {
700
+ const urlMap = /* @__PURE__ */ new Map();
701
+ if (fileReferences.length === 0) {
702
+ return ok(urlMap);
702
703
  }
703
- uncachedFiles.push(fileRef);
704
- }
705
- for (const file of publicFiles) {
706
- try {
707
- const url = getPublicUrl(supabase, file.file_path, true);
708
- if (url) {
709
- urlMap.set(file.id, url);
710
- const cacheKey = getCacheKey(file.id, file.file_path, true);
711
- globalUrlCache.set(cacheKey, {
712
- url,
713
- expiresAt: now + ttl
714
- });
704
+ const now = Date.now();
705
+ const ttl = (options.expiresIn || 3600) * 1e3;
706
+ const publicFiles = [];
707
+ const privateFiles = [];
708
+ const uncachedFiles = [];
709
+ for (const fileRef of fileReferences) {
710
+ const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);
711
+ const cached = globalUrlCache.get(cacheKey);
712
+ if (cached && cached.expiresAt > now) {
713
+ urlMap.set(fileRef.id, cached.url);
714
+ continue;
715
+ }
716
+ if (fileRef.is_public) {
717
+ publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
718
+ } else {
719
+ privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });
715
720
  }
716
- } catch (err) {
717
- log2.error(`Failed to generate public URL for file ${file.id}:`, err);
721
+ uncachedFiles.push(fileRef);
718
722
  }
719
- }
720
- if (privateFiles.length > 0) {
721
- const signedUrlPromises = privateFiles.map(async (file) => {
723
+ for (const file of publicFiles) {
722
724
  try {
725
+ const url = getPublicUrl(supabase, file.file_path, true);
726
+ if (url) {
727
+ urlMap.set(file.id, url);
728
+ const cacheKey = getCacheKey(file.id, file.file_path, true);
729
+ globalUrlCache.set(cacheKey, { url, expiresAt: now + ttl });
730
+ }
731
+ } catch (e) {
732
+ log2.error(`Failed to generate public URL for file ${file.id}:`, e);
733
+ }
734
+ }
735
+ if (privateFiles.length > 0) {
736
+ const signedUrlPromises = privateFiles.map(async (file) => {
723
737
  const signedUrlResult = await getSignedUrl(supabase, file.file_path, {
724
738
  appName: options.appName || "pace-core",
725
739
  orgId: options.orgId,
726
740
  expiresIn: options.expiresIn || 3600
727
741
  });
728
- const url = signedUrlResult?.url || null;
742
+ const url = signedUrlResult.ok ? signedUrlResult.data.url : null;
729
743
  if (url) {
730
744
  const cacheKey = getCacheKey(file.id, file.file_path, false);
731
- globalUrlCache.set(cacheKey, {
732
- url,
733
- expiresAt: now + ttl
734
- });
745
+ globalUrlCache.set(cacheKey, { url, expiresAt: now + ttl });
735
746
  }
736
747
  return { id: file.id, url };
737
- } catch (err) {
738
- log2.error(`Failed to generate signed URL for file ${file.id}:`, err);
739
- return { id: file.id, url: null };
740
- }
741
- });
742
- const signedUrlResults = await Promise.all(signedUrlPromises);
743
- for (const result of signedUrlResults) {
744
- if (result.url) {
745
- urlMap.set(result.id, result.url);
748
+ });
749
+ const signedUrlResults = await Promise.all(signedUrlPromises);
750
+ for (const result of signedUrlResults) {
751
+ if (result.url) {
752
+ urlMap.set(result.id, result.url);
753
+ }
746
754
  }
747
755
  }
756
+ if (uncachedFiles.length > 0) {
757
+ cleanupUrlCache();
758
+ }
759
+ return ok(urlMap);
760
+ } catch (error) {
761
+ return err({
762
+ code: "BATCH_URLS_FAILED",
763
+ message: error instanceof Error ? error.message : "Unknown error"
764
+ });
748
765
  }
749
- if (uncachedFiles.length > 0) {
750
- cleanupUrlCache();
751
- }
752
- return urlMap;
753
766
  }
754
767
  async function deleteFile(supabase, path, isPublic = false) {
755
768
  try {
756
769
  const bucketName = getBucketName(isPublic);
757
770
  const { error } = await supabase.storage.from(bucketName).remove([path]);
758
771
  if (error) {
759
- return {
760
- success: false,
761
- error: `Delete failed: ${error.message}`
762
- };
772
+ return err({ code: "DELETE_FAILED", message: error.message });
763
773
  }
764
- return { success: true };
774
+ return ok(void 0);
765
775
  } catch (error) {
766
- return {
767
- success: false,
768
- error: `Delete failed: ${error instanceof Error ? error.message : "Unknown error"}`
769
- };
776
+ return err({
777
+ code: "DELETE_FAILED",
778
+ message: error instanceof Error ? error.message : "Unknown error"
779
+ });
770
780
  }
771
781
  }
772
782
  async function listFiles(supabase, options) {
773
783
  try {
774
784
  const bucketName = getBucketName(options.isPublic || false);
775
785
  if (!options.orgId && !options.userId) {
776
- throw new Error("Either orgId or userId is required for listing files");
786
+ return err({ code: "LIST_FILES_INVALID_OPTIONS", message: "Either orgId or userId is required for listing files" });
777
787
  }
778
788
  const basePath = options.orgId ? options.orgId : `users/${options.userId}`;
779
789
  const pathPrefix = `${basePath}/`;
@@ -785,7 +795,7 @@ async function listFiles(supabase, options) {
785
795
  });
786
796
  if (error) {
787
797
  log2.error("Failed to list files:", error);
788
- return { files: [], totalCount: 0, hasMore: false };
798
+ return err({ code: "LIST_FILES_FAILED", message: error.message });
789
799
  }
790
800
  const files = (data || []).map((item) => ({
791
801
  name: item.name,
@@ -804,14 +814,17 @@ async function listFiles(supabase, options) {
804
814
  isPublic: options.isPublic || false
805
815
  }
806
816
  }));
807
- return {
817
+ return ok({
808
818
  files,
809
819
  totalCount: files.length,
810
820
  hasMore: files.length >= (options.limit || 100)
811
- };
821
+ });
812
822
  } catch (error) {
813
823
  log2.error("Failed to list files:", error);
814
- return { files: [], totalCount: 0, hasMore: false };
824
+ return err({
825
+ code: "LIST_FILES_FAILED",
826
+ message: error instanceof Error ? error.message : "Unknown error"
827
+ });
815
828
  }
816
829
  }
817
830
  async function downloadFile(supabase, path, isPublic = false) {
@@ -820,27 +833,28 @@ async function downloadFile(supabase, path, isPublic = false) {
820
833
  const { data, error } = await supabase.storage.from(bucketName).download(path);
821
834
  if (error) {
822
835
  log2.error("Failed to download file:", error);
823
- return null;
836
+ return err({ code: "DOWNLOAD_FAILED", message: error.message });
824
837
  }
825
838
  if (!data) {
826
- return null;
839
+ return err({ code: "DOWNLOAD_FAILED", message: "No data returned" });
827
840
  }
828
841
  const fileName = path.split("/").pop() || "download";
829
- const { data: fileInfo } = await supabase.storage.from(bucketName).list(path.split("/").slice(0, -1).join("/"), {
830
- search: fileName
831
- });
842
+ const { data: fileInfo } = await supabase.storage.from(bucketName).list(path.split("/").slice(0, -1).join("/"), { search: fileName });
832
843
  const metadata = fileInfo?.[0]?.metadata || {};
833
- return {
844
+ return ok({
834
845
  blob: data,
835
846
  metadata: {
836
847
  name: fileName,
837
848
  size: metadata.size || data.size,
838
849
  type: metadata.mimetype || "application/octet-stream"
839
850
  }
840
- };
851
+ });
841
852
  } catch (error) {
842
853
  log2.error("Failed to download file:", error);
843
- return null;
854
+ return err({
855
+ code: "DOWNLOAD_FAILED",
856
+ message: error instanceof Error ? error.message : "Unknown error"
857
+ });
844
858
  }
845
859
  }
846
860
  async function archiveFile(supabase, path, options) {
@@ -852,25 +866,22 @@ async function archiveFile(supabase, path, options) {
852
866
  } else if (options.userId) {
853
867
  archivedPath = path.replace(`users/${options.userId}/`, `archived/users/${options.userId}/`);
854
868
  } else {
855
- throw new Error("Either orgId or userId is required for archiving files");
869
+ return err({ code: "ARCHIVE_FAILED", message: "Either orgId or userId is required for archiving files" });
856
870
  }
857
871
  const { error: copyError } = await supabase.storage.from(bucketName).copy(path, archivedPath);
858
872
  if (copyError) {
859
- return {
860
- success: false,
861
- error: `Archive failed: ${copyError.message}`
862
- };
873
+ return err({ code: "ARCHIVE_FAILED", message: copyError.message });
863
874
  }
864
875
  const deleteResult = await deleteFile(supabase, path, options.isPublic || false);
865
- if (!deleteResult.success) {
866
- return deleteResult;
876
+ if (!deleteResult.ok) {
877
+ return err(deleteResult.error);
867
878
  }
868
- return { success: true };
879
+ return ok(void 0);
869
880
  } catch (error) {
870
- return {
871
- success: false,
872
- error: `Archive failed: ${error instanceof Error ? error.message : "Unknown error"}`
873
- };
881
+ return err({
882
+ code: "ARCHIVE_FAILED",
883
+ message: error instanceof Error ? error.message : "Unknown error"
884
+ });
874
885
  }
875
886
  }
876
887
  var publicFileCache = /* @__PURE__ */ new Map();
@@ -889,263 +900,80 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
889
900
  const [isLoading, setIsLoading] = useState(false);
890
901
  const [error, setError] = useState(null);
891
902
  const fetchFiles = useCallback(async () => {
903
+ const emptySetters = () => {
904
+ applyEmptyPublicFileState(
905
+ setFileUrl,
906
+ setFileReference,
907
+ setFileReferences,
908
+ setFileUrls,
909
+ setFileCount,
910
+ setIsLoading,
911
+ setError
912
+ );
913
+ };
892
914
  if (!table_name || !record_id || !supabase) {
893
- setFileUrl(null);
894
- setFileReference(null);
895
- setFileReferences([]);
896
- setFileUrls(/* @__PURE__ */ new Map());
897
- setFileCount(0);
898
- setIsLoading(false);
915
+ emptySetters();
899
916
  return;
900
917
  }
901
- if (organisation_id) {
902
- const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
903
- if (!uuidRegex.test(organisation_id)) {
904
- logger.warn("usePublicFileDisplay", "Invalid organisationId format (not a valid UUID)", { organisation_id });
905
- }
906
- }
907
- const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
908
- if (enableCache) {
909
- const cached = publicFileCache.get(cacheKey);
910
- if (cached && Date.now() - cached.timestamp < cached.ttl) {
911
- const cachedData = cached.data;
912
- setFileUrl(cachedData.fileUrl || null);
913
- setFileReference(cachedData.fileReference || null);
914
- setFileReferences(cachedData.fileReferences || []);
915
- setFileUrls(cachedData.fileUrls || /* @__PURE__ */ new Map());
916
- setFileCount(cachedData.fileCount || 0);
917
- setIsLoading(false);
918
- setError(null);
919
- return;
920
- }
918
+ warnInvalidOrganisationIdUuid(organisation_id ?? void 0);
919
+ const cacheKey = buildPublicFileCacheKey(table_name, record_id, organisation_id, category);
920
+ const cached = getCachedPublicFileData(cacheKey, enableCache);
921
+ if (cached) {
922
+ applyCachedPublicFileState(
923
+ cached,
924
+ setFileUrl,
925
+ setFileReference,
926
+ setFileReferences,
927
+ setFileUrls,
928
+ setFileCount,
929
+ setIsLoading,
930
+ setError
931
+ );
932
+ return;
921
933
  }
922
934
  try {
923
935
  setIsLoading(true);
924
936
  setError(null);
925
- let files = [];
926
- if (organisation_id === void 0) {
927
- logger.debug("usePublicFileDisplay", "organisation_id is undefined, searching both user-scoped and organisation-scoped files:", {
928
- table_name,
929
- record_id,
930
- category
931
- });
932
- let userScopedFiles = [];
933
- const orgScopedFiles = [];
934
- if (category) {
935
- const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_by_category_list", {
936
- p_table_name: table_name,
937
- p_record_id: record_id,
938
- p_category: category,
939
- p_organisation_id: void 0
940
- });
941
- if (!userRpcError && userData) {
942
- userScopedFiles = userData.filter((item) => item.is_public === true && item.id && item.file_path && item.file_metadata).map((item) => {
943
- const metadata = item.file_metadata;
944
- return {
945
- id: item.id,
946
- table_name,
947
- record_id,
948
- file_path: item.file_path,
949
- file_metadata: item.file_metadata || null,
950
- organisation_id: null,
951
- app_id: metadata?.app_id || "",
952
- is_public: true,
953
- created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
954
- updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
955
- created_by: null
956
- };
957
- });
958
- }
959
- } else {
960
- const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_list", {
961
- p_table_name: table_name,
962
- p_record_id: record_id,
963
- p_organisation_id: void 0
964
- });
965
- if (!userRpcError && userData) {
966
- const ids = userData.map((item) => item.id);
967
- if (ids.length > 0) {
968
- const { data: fullData } = await supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").in("id", ids).eq("is_public", true);
969
- userScopedFiles = fullData || [];
970
- }
971
- }
972
- }
973
- const allFiles = [...userScopedFiles, ...orgScopedFiles];
974
- allFiles.sort((a, b) => {
975
- const aTime = new Date(a.created_at || 0).getTime();
976
- const bTime = new Date(b.created_at || 0).getTime();
977
- return bTime - aTime;
978
- });
979
- files = allFiles;
980
- logger.debug("usePublicFileDisplay", "Found files with undefined organisation_id:", {
981
- userScopedCount: userScopedFiles.length,
982
- orgScopedCount: orgScopedFiles.length,
983
- totalCount: files.length
984
- });
985
- } else {
986
- if (category) {
987
- const rpcParams = {
988
- p_table_name: table_name,
989
- p_record_id: record_id,
990
- p_category: category,
991
- p_organisation_id: organisation_id ?? null
992
- };
993
- const { data, error: rpcError } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
994
- if (rpcError) {
995
- logger.error("usePublicFileDisplay", "RPC function error", {
996
- function: "data_file_reference_by_category_list",
997
- table_name,
998
- record_id,
999
- category,
1000
- organisation_id,
1001
- error: rpcError.message,
1002
- errorCode: rpcError.code,
1003
- errorDetails: rpcError.details,
1004
- errorHint: rpcError.hint
1005
- });
1006
- throw new Error(rpcError.message || "Failed to fetch file reference");
1007
- }
1008
- if (!data || data.length === 0) {
1009
- files = [];
1010
- } else {
1011
- files = data.filter((item) => {
1012
- return item.is_public === true && item.id && item.file_path && item.file_metadata;
1013
- }).map((item) => {
1014
- const metadata = item.file_metadata;
1015
- return {
1016
- id: item.id,
1017
- table_name,
1018
- record_id,
1019
- file_path: item.file_path,
1020
- file_metadata: item.file_metadata || null,
1021
- organisation_id: organisation_id ?? null,
1022
- app_id: metadata?.app_id || "",
1023
- is_public: true,
1024
- // RPC already filtered for public files
1025
- created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1026
- updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1027
- created_by: null
1028
- };
1029
- });
1030
- }
1031
- } else {
1032
- const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
1033
- p_table_name: table_name,
1034
- p_record_id: record_id,
1035
- p_organisation_id: organisation_id ?? null
1036
- });
1037
- if (rpcError) {
1038
- logger.error("usePublicFileDisplay", "RPC function error", {
1039
- function: "data_file_reference_list",
1040
- table_name,
1041
- record_id,
1042
- organisation_id,
1043
- error: rpcError.message,
1044
- errorCode: rpcError.code,
1045
- errorDetails: rpcError.details,
1046
- errorHint: rpcError.hint
1047
- });
1048
- throw new Error(rpcError.message || "Failed to fetch file references");
1049
- }
1050
- if (!fileIds || fileIds.length === 0) {
1051
- files = [];
1052
- } else {
1053
- const ids = fileIds.map((item) => item.id);
1054
- const { data: fullData, error: fetchError } = await supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").in("id", ids).eq("is_public", true);
1055
- if (fetchError) {
1056
- throw new Error(fetchError.message || "Failed to fetch file references");
1057
- }
1058
- files = fullData || [];
1059
- }
1060
- }
1061
- }
1062
- const publicFiles = files.filter((f) => f.is_public === true);
1063
- if (publicFiles.length === 0) {
1064
- setFileUrl(null);
1065
- setFileReference(null);
1066
- setFileReferences([]);
1067
- setFileUrls(/* @__PURE__ */ new Map());
1068
- setFileCount(0);
1069
- if (enableCache) {
1070
- publicFileCache.set(cacheKey, {
1071
- data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: /* @__PURE__ */ new Map(), fileCount: 0 },
1072
- timestamp: Date.now(),
1073
- ttl: cacheTtl
1074
- });
1075
- }
937
+ const rows = await fetchPublicFileRows(supabase, table_name, record_id, organisation_id, category);
938
+ const publicRows = rows.filter((f) => f.is_public === true);
939
+ if (publicRows.length === 0) {
940
+ emptySetters();
941
+ setCachedPublicFileData(cacheKey, emptyCacheData, enableCache, cacheTtl);
1076
942
  return;
1077
943
  }
1078
- const fileRefs = publicFiles.map((f) => {
1079
- const fileName = f.file_path.split("/").pop() || "unknown";
1080
- const fileType = fileName.split(".").pop() || "unknown";
1081
- const metadata = f.file_metadata;
1082
- return {
1083
- id: f.id,
1084
- table_name: f.table_name,
1085
- record_id: f.record_id,
1086
- file_path: f.file_path,
1087
- file_metadata: {
1088
- fileName: metadata?.fileName || fileName,
1089
- fileType: metadata?.fileType || fileType,
1090
- category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
1091
- ...metadata || {}
1092
- },
1093
- organisation_id: f.organisation_id,
1094
- app_id: f.app_id,
1095
- is_public: f.is_public ?? true,
1096
- created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1097
- updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
1098
- };
1099
- });
944
+ const fileRefs = mapDbRowsToFileReferences(publicRows);
1100
945
  setFileReferences(fileRefs);
1101
946
  setFileCount(fileRefs.length);
1102
947
  if (category && fileRefs.length > 0) {
1103
948
  const firstFile = fileRefs[0];
1104
949
  setFileReference(firstFile);
1105
- const url = getPublicUrl(supabase, firstFile.file_path, true);
1106
- setFileUrl(url);
950
+ setFileUrl(getPublicUrl(supabase, firstFile.file_path, true));
1107
951
  } else {
1108
- const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {
952
+ const urlResult = await generateFileUrlsBatch(supabase, fileRefs, {
1109
953
  appName: "pace-core",
1110
954
  orgId: organisation_id,
1111
955
  expiresIn: 3600
1112
956
  });
1113
- setFileUrls(urlMap);
957
+ setFileUrls(urlResult.ok ? urlResult.data : /* @__PURE__ */ new Map());
1114
958
  setFileReference(null);
1115
959
  setFileUrl(null);
1116
960
  }
1117
- if (enableCache) {
1118
- publicFileCache.set(cacheKey, {
1119
- data: {
1120
- fileUrl: category ? fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null : null,
1121
- fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,
1122
- fileReferences: fileRefs,
1123
- fileUrls: category ? /* @__PURE__ */ new Map() : (() => {
1124
- const urlMap = /* @__PURE__ */ new Map();
1125
- for (const fileRef of fileRefs) {
1126
- const url = getPublicUrl(supabase, fileRef.file_path, true);
1127
- if (url) {
1128
- urlMap.set(fileRef.id, url);
1129
- }
1130
- }
1131
- return urlMap;
1132
- })(),
1133
- fileCount: fileRefs.length
1134
- },
1135
- timestamp: Date.now(),
1136
- ttl: cacheTtl
1137
- });
1138
- }
1139
- } catch (err) {
961
+ setCachedPublicFileData(
962
+ cacheKey,
963
+ buildCacheDataFromRefs(fileRefs, category, supabase),
964
+ enableCache,
965
+ cacheTtl
966
+ );
967
+ } catch (err2) {
1140
968
  logger.error("usePublicFileDisplay", "Error fetching files", {
1141
969
  table_name,
1142
970
  record_id,
1143
971
  organisation_id,
1144
972
  category,
1145
- error: err instanceof Error ? err.message : "Unknown error",
1146
- errorDetails: err instanceof Error ? err.stack : String(err)
973
+ error: err2 instanceof Error ? err2.message : "Unknown error",
974
+ errorDetails: err2 instanceof Error ? err2.stack : String(err2)
1147
975
  });
1148
- const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
976
+ const error2 = err2 instanceof Error ? err2 : new Error("Unknown error occurred");
1149
977
  setError(error2);
1150
978
  setFileUrl(null);
1151
979
  setFileReference(null);
@@ -1172,7 +1000,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
1172
1000
  const refetch = useCallback(async () => {
1173
1001
  if (!table_name || !record_id) return;
1174
1002
  if (enableCache) {
1175
- const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
1003
+ const cacheKey = buildPublicFileCacheKey(table_name, record_id, organisation_id, category);
1176
1004
  publicFileCache.delete(cacheKey);
1177
1005
  }
1178
1006
  await fetchFiles();
@@ -1188,6 +1016,217 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
1188
1016
  refetch
1189
1017
  };
1190
1018
  }
1019
+ function buildPublicFileCacheKey(table_name, record_id, organisation_id, category) {
1020
+ return `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
1021
+ }
1022
+ function getCachedPublicFileData(cacheKey, enableCache) {
1023
+ if (!enableCache) return null;
1024
+ const cached = publicFileCache.get(cacheKey);
1025
+ if (!cached || Date.now() - cached.timestamp >= cached.ttl) return null;
1026
+ return cached.data;
1027
+ }
1028
+ function setCachedPublicFileData(cacheKey, data, enableCache, cacheTtl) {
1029
+ if (!enableCache) return;
1030
+ publicFileCache.set(cacheKey, {
1031
+ data,
1032
+ timestamp: Date.now(),
1033
+ ttl: cacheTtl
1034
+ });
1035
+ }
1036
+ function mapRpcItemToRow(item, table_name, record_id, organisation_id) {
1037
+ const metadata = item.file_metadata;
1038
+ return {
1039
+ id: item.id,
1040
+ table_name,
1041
+ record_id,
1042
+ file_path: item.file_path,
1043
+ file_metadata: item.file_metadata || null,
1044
+ organisation_id,
1045
+ app_id: metadata?.app_id || "",
1046
+ is_public: true,
1047
+ created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1048
+ updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1049
+ created_by: null
1050
+ };
1051
+ }
1052
+ async function fetchPublicFileRowsWhenOrgUndefined(supabase, table_name, record_id, category) {
1053
+ let userScopedFiles = [];
1054
+ const orgScopedFiles = [];
1055
+ logger.debug("usePublicFileDisplay", "organisation_id is undefined, searching both user-scoped and organisation-scoped files:", {
1056
+ table_name,
1057
+ record_id,
1058
+ category
1059
+ });
1060
+ if (category) {
1061
+ const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_by_category_list", {
1062
+ p_table_name: table_name,
1063
+ p_record_id: record_id,
1064
+ p_category: category,
1065
+ p_organisation_id: void 0
1066
+ });
1067
+ if (!userRpcError && userData) {
1068
+ userScopedFiles = userData.filter((item) => item.is_public === true && item.id && item.file_path && item.file_metadata).map((item) => mapRpcItemToRow(item, table_name, record_id, null));
1069
+ }
1070
+ } else {
1071
+ const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_list", {
1072
+ p_table_name: table_name,
1073
+ p_record_id: record_id,
1074
+ p_organisation_id: void 0
1075
+ });
1076
+ if (!userRpcError && userData) {
1077
+ const ids = userData.map((item) => item.id);
1078
+ if (ids.length > 0) {
1079
+ const { data: fullData } = await supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").in("id", ids).eq("is_public", true);
1080
+ userScopedFiles = fullData || [];
1081
+ }
1082
+ }
1083
+ }
1084
+ const allFiles = [...userScopedFiles, ...orgScopedFiles];
1085
+ allFiles.sort((a, b) => {
1086
+ const aTime = new Date(a.created_at || 0).getTime();
1087
+ const bTime = new Date(b.created_at || 0).getTime();
1088
+ return bTime - aTime;
1089
+ });
1090
+ logger.debug("usePublicFileDisplay", "Found files with undefined organisation_id:", {
1091
+ userScopedCount: userScopedFiles.length,
1092
+ orgScopedCount: orgScopedFiles.length,
1093
+ totalCount: allFiles.length
1094
+ });
1095
+ return allFiles;
1096
+ }
1097
+ async function fetchPublicFileRowsWhenOrgDefined(supabase, table_name, record_id, organisation_id, category) {
1098
+ if (category) {
1099
+ const rpcParams = {
1100
+ p_table_name: table_name,
1101
+ p_record_id: record_id,
1102
+ p_category: category,
1103
+ p_organisation_id: organisation_id ?? void 0
1104
+ };
1105
+ const { data, error: rpcError2 } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
1106
+ if (rpcError2) {
1107
+ logger.error("usePublicFileDisplay", "RPC function error", {
1108
+ function: "data_file_reference_by_category_list",
1109
+ table_name,
1110
+ record_id,
1111
+ category,
1112
+ organisation_id,
1113
+ error: rpcError2.message,
1114
+ errorCode: rpcError2.code,
1115
+ errorDetails: rpcError2.details,
1116
+ errorHint: rpcError2.hint
1117
+ });
1118
+ throw new Error(rpcError2.message || "Failed to fetch file reference");
1119
+ }
1120
+ if (!data || data.length === 0) return [];
1121
+ return data.filter((item) => item.is_public === true && item.id && item.file_path && item.file_metadata).map((item) => mapRpcItemToRow(item, table_name, record_id, organisation_id ?? null));
1122
+ }
1123
+ const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
1124
+ p_table_name: table_name,
1125
+ p_record_id: record_id,
1126
+ p_organisation_id: organisation_id ?? void 0
1127
+ });
1128
+ if (rpcError) {
1129
+ logger.error("usePublicFileDisplay", "RPC function error", {
1130
+ function: "data_file_reference_list",
1131
+ table_name,
1132
+ record_id,
1133
+ organisation_id,
1134
+ error: rpcError.message,
1135
+ errorCode: rpcError.code,
1136
+ errorDetails: rpcError.details,
1137
+ errorHint: rpcError.hint
1138
+ });
1139
+ throw new Error(rpcError.message || "Failed to fetch file references");
1140
+ }
1141
+ if (!fileIds || fileIds.length === 0) return [];
1142
+ const ids = fileIds.map((item) => item.id);
1143
+ const { data: fullData, error: fetchError } = await supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").in("id", ids).eq("is_public", true);
1144
+ if (fetchError) throw new Error(fetchError.message || "Failed to fetch file references");
1145
+ return fullData || [];
1146
+ }
1147
+ async function fetchPublicFileRows(supabase, table_name, record_id, organisation_id, category) {
1148
+ if (organisation_id === void 0) {
1149
+ return fetchPublicFileRowsWhenOrgUndefined(supabase, table_name, record_id, category);
1150
+ }
1151
+ return fetchPublicFileRowsWhenOrgDefined(supabase, table_name, record_id, organisation_id, category);
1152
+ }
1153
+ function mapDbRowsToFileReferences(rows) {
1154
+ return rows.map((f) => {
1155
+ const fileName = f.file_path.split("/").pop() || "unknown";
1156
+ const fileType = fileName.split(".").pop() || "unknown";
1157
+ const metadata = f.file_metadata;
1158
+ return {
1159
+ id: f.id,
1160
+ table_name: f.table_name,
1161
+ record_id: f.record_id,
1162
+ file_path: f.file_path,
1163
+ file_metadata: {
1164
+ fileName: metadata?.fileName || fileName,
1165
+ fileType: metadata?.fileType || fileType,
1166
+ category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
1167
+ ...metadata || {}
1168
+ },
1169
+ organisation_id: f.organisation_id,
1170
+ app_id: f.app_id,
1171
+ is_public: f.is_public ?? true,
1172
+ created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1173
+ updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
1174
+ };
1175
+ });
1176
+ }
1177
+ function buildCacheDataFromRefs(fileRefs, category, supabase) {
1178
+ const fileUrl = category && fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null;
1179
+ const fileReference = category && fileRefs.length > 0 ? fileRefs[0] : null;
1180
+ let fileUrls;
1181
+ if (category) {
1182
+ fileUrls = /* @__PURE__ */ new Map();
1183
+ } else {
1184
+ fileUrls = /* @__PURE__ */ new Map();
1185
+ for (const fileRef of fileRefs) {
1186
+ const url = getPublicUrl(supabase, fileRef.file_path, true);
1187
+ if (url) fileUrls.set(fileRef.id, url);
1188
+ }
1189
+ }
1190
+ return {
1191
+ fileUrl,
1192
+ fileReference,
1193
+ fileReferences: fileRefs,
1194
+ fileUrls,
1195
+ fileCount: fileRefs.length
1196
+ };
1197
+ }
1198
+ var emptyCacheData = {
1199
+ fileUrl: null,
1200
+ fileReference: null,
1201
+ fileReferences: [],
1202
+ fileUrls: /* @__PURE__ */ new Map(),
1203
+ fileCount: 0
1204
+ };
1205
+ function applyEmptyPublicFileState(setFileUrl, setFileReference, setFileReferences, setFileUrls, setFileCount, setIsLoading, setError) {
1206
+ setFileUrl(null);
1207
+ setFileReference(null);
1208
+ setFileReferences([]);
1209
+ setFileUrls(/* @__PURE__ */ new Map());
1210
+ setFileCount(0);
1211
+ setIsLoading(false);
1212
+ setError(null);
1213
+ }
1214
+ function applyCachedPublicFileState(cachedData, setFileUrl, setFileReference, setFileReferences, setFileUrls, setFileCount, setIsLoading, setError) {
1215
+ setFileUrl(cachedData.fileUrl || null);
1216
+ setFileReference(cachedData.fileReference || null);
1217
+ setFileReferences(cachedData.fileReferences || []);
1218
+ setFileUrls(cachedData.fileUrls || /* @__PURE__ */ new Map());
1219
+ setFileCount(cachedData.fileCount || 0);
1220
+ setIsLoading(false);
1221
+ setError(null);
1222
+ }
1223
+ function warnInvalidOrganisationIdUuid(organisation_id) {
1224
+ if (!organisation_id) return;
1225
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1226
+ if (!uuidRegex.test(organisation_id)) {
1227
+ logger.warn("usePublicFileDisplay", "Invalid organisationId format (not a valid UUID)", { organisation_id });
1228
+ }
1229
+ }
1191
1230
  function clearPublicFileDisplayCache() {
1192
1231
  for (const [key] of publicFileCache) {
1193
1232
  if (key.startsWith("public_file_")) {
@@ -1205,6 +1244,33 @@ function getPublicFileDisplayCacheStats() {
1205
1244
 
1206
1245
  // src/utils/file-reference/index.ts
1207
1246
  var log3 = createLogger("FileReferenceService");
1247
+ function toApiError(error) {
1248
+ const message = error instanceof Error ? error.message : "Unknown error";
1249
+ const code = error instanceof Error && error.code ? error.code : "FILE_REFERENCE_ERROR";
1250
+ return { code, message };
1251
+ }
1252
+ function mapFileReferenceRowToFileReference(f) {
1253
+ const fileName = f.file_path.split("/").pop() || "unknown";
1254
+ const fileType = fileName.split(".").pop() || "unknown";
1255
+ const metadata = f.file_metadata;
1256
+ return {
1257
+ id: f.id,
1258
+ table_name: f.table_name,
1259
+ record_id: f.record_id,
1260
+ file_path: f.file_path,
1261
+ file_metadata: {
1262
+ fileName: metadata?.fileName || fileName,
1263
+ fileType: metadata?.fileType || fileType,
1264
+ category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
1265
+ ...metadata || {}
1266
+ },
1267
+ organisation_id: f.organisation_id,
1268
+ app_id: f.app_id,
1269
+ is_public: f.is_public ?? false,
1270
+ created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1271
+ updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
1272
+ };
1273
+ }
1208
1274
  var FileReferenceServiceImpl = class {
1209
1275
  constructor(supabase) {
1210
1276
  this.supabase = supabase;
@@ -1223,6 +1289,114 @@ var FileReferenceServiceImpl = class {
1223
1289
  return "unknown";
1224
1290
  }
1225
1291
  }
1292
+ validateCreateOptions(options) {
1293
+ const isUserScoped = !options.organisation_id && !!options.userId;
1294
+ if (!isUserScoped && !options.organisation_id) {
1295
+ return err({ code: "VALIDATION_ERROR", message: "organisation_id is required for file upload, or userId must be provided for user-scoped files" });
1296
+ }
1297
+ if (!options.table_name) {
1298
+ return err({ code: "VALIDATION_ERROR", message: "table_name is required for file upload" });
1299
+ }
1300
+ if (!options.record_id) {
1301
+ return err({ code: "VALIDATION_ERROR", message: "record_id is required for file upload" });
1302
+ }
1303
+ if (!options.folder) {
1304
+ return err({ code: "VALIDATION_ERROR", message: "folder is required for file upload. The folder prop determines the storage path." });
1305
+ }
1306
+ return ok({ isUserScoped });
1307
+ }
1308
+ async resolveAuthenticatedUserIdForUserScoped() {
1309
+ const { data: { user: authUser }, error: authError } = await this.supabase.auth.getUser();
1310
+ if (authError || !authUser) {
1311
+ return err({ code: "UNAUTHORIZED", message: "User must be authenticated to upload user-scoped files" });
1312
+ }
1313
+ log3.debug("Using authenticated user ID for user-scoped file upload", { userId: authUser.id });
1314
+ return ok(authUser.id);
1315
+ }
1316
+ async checkSuperAdminUser(userId) {
1317
+ if (!userId) return false;
1318
+ try {
1319
+ const { isSuperAdmin } = await import('./api-BZR2CYXL.js');
1320
+ const superResult = await isSuperAdmin(userId);
1321
+ if (superResult.ok && superResult.data) {
1322
+ log3.debug("Super admin detected - bypassing permission checks", { userId });
1323
+ return true;
1324
+ }
1325
+ return false;
1326
+ } catch (superAdminCheckError) {
1327
+ log3.warn("Failed to check super-admin status, proceeding with normal permission checks", superAdminCheckError);
1328
+ return false;
1329
+ }
1330
+ }
1331
+ async uploadFileAndExtractMetadata(options, file, authenticatedUserId, isUserScoped) {
1332
+ const userId = authenticatedUserId || (isUserScoped ? void 0 : options.userId);
1333
+ const uploadResult = await uploadFile(this.supabase, file, {
1334
+ appName: "file-reference",
1335
+ orgId: options.organisation_id || void 0,
1336
+ userId,
1337
+ isPublic: options.is_public || false,
1338
+ customPath: options.folder
1339
+ });
1340
+ if (!uploadResult.ok) return err(uploadResult.error);
1341
+ const metadataResult = await extractFileMetadata(file, {
1342
+ appName: "file-reference",
1343
+ orgId: options.organisation_id || void 0,
1344
+ userId,
1345
+ isPublic: options.is_public || false
1346
+ }, "system");
1347
+ if (!metadataResult.ok) return err(metadataResult.error);
1348
+ return ok({ path: uploadResult.data.path, metadata: metadataResult.data });
1349
+ }
1350
+ async setOrgContextIfNeeded(isUserScoped, organisation_id) {
1351
+ if (isUserScoped || !organisation_id) return;
1352
+ const ctxResult = await setOrganisationContext(this.supabase, organisation_id);
1353
+ if (!ctxResult.ok) {
1354
+ log3.warn("setOrganisationContext failed (non-fatal):", ctxResult.error.message);
1355
+ }
1356
+ }
1357
+ async resolveRpcUserId(authenticatedUserId, options) {
1358
+ if (authenticatedUserId) return authenticatedUserId;
1359
+ if (options.userId) return options.userId;
1360
+ const { data: { user: authUser } } = await this.supabase.auth.getUser();
1361
+ return authUser?.id ?? null;
1362
+ }
1363
+ async buildPermissionDeniedMessage(options, isSuperAdminUser) {
1364
+ const appName = await this.getAppName(options.app_id).catch(() => "unknown");
1365
+ const pageContextDisplay = options.pageContext || "undefined";
1366
+ return isSuperAdminUser ? `File upload failed for super-admin user. Page context: '${pageContextDisplay}', App: '${appName}'.` : `File upload denied: insufficient permissions. You need 'create:page.${pageContextDisplay}' or 'update:page.${pageContextDisplay}' for the '${pageContextDisplay}' page.`;
1367
+ }
1368
+ async rollbackUploadAndErr(filePath, isPublic, code, message) {
1369
+ await deleteFile(this.supabase, filePath, isPublic);
1370
+ return err({ code, message });
1371
+ }
1372
+ async fetchCreatedFileReference(createdId) {
1373
+ const { data: fileRef, error: fetchError } = await this.supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("id", createdId).single();
1374
+ if (fetchError || !fileRef) {
1375
+ return err({ code: "FETCH_FAILED", message: fetchError?.message ?? "Failed to fetch created file reference" });
1376
+ }
1377
+ return ok(fileRef);
1378
+ }
1379
+ buildCreateRpcPayload(options, filePath, file, metadata, rpcUserId) {
1380
+ return {
1381
+ p_table_name: options.table_name,
1382
+ p_record_id: options.record_id,
1383
+ p_file_path: filePath,
1384
+ p_organisation_id: options.organisation_id ?? null,
1385
+ p_app_id: options.app_id,
1386
+ p_page_context: options.pageContext,
1387
+ p_event_id: options.event_id || null,
1388
+ p_file_metadata: {
1389
+ fileName: file.name,
1390
+ fileType: file.type,
1391
+ fileSize: file.size,
1392
+ category: options.category,
1393
+ ...metadata,
1394
+ ...options.custom_metadata
1395
+ },
1396
+ p_is_public: options.is_public || false,
1397
+ p_user_id: rpcUserId
1398
+ };
1399
+ }
1226
1400
  /**
1227
1401
  * Creates a file reference by uploading a file to storage and linking it in the database.
1228
1402
  *
@@ -1239,122 +1413,38 @@ var FileReferenceServiceImpl = class {
1239
1413
  */
1240
1414
  async createFileReference(options, file) {
1241
1415
  try {
1242
- const isUserScoped = !options.organisation_id && options.userId;
1243
- if (!isUserScoped && !options.organisation_id) {
1244
- throw new Error("organisation_id is required for file upload, or userId must be provided for user-scoped files");
1245
- }
1246
- if (!options.table_name) {
1247
- throw new Error("table_name is required for file upload");
1248
- }
1249
- if (!options.record_id) {
1250
- throw new Error("record_id is required for file upload");
1251
- }
1252
- if (!options.folder) {
1253
- throw new Error("folder is required for file upload. The folder prop determines the storage path.");
1254
- }
1255
- let authenticatedUserId = void 0;
1416
+ const validation = this.validateCreateOptions(options);
1417
+ if (!validation.ok) return validation;
1418
+ const { isUserScoped } = validation.data;
1419
+ let authenticatedUserId;
1256
1420
  if (isUserScoped) {
1257
- const { data: { user: authUser }, error: authError } = await this.supabase.auth.getUser();
1258
- if (authError || !authUser) {
1259
- throw new Error("User must be authenticated to upload user-scoped files");
1260
- }
1261
- authenticatedUserId = authUser.id;
1262
- log3.debug("Using authenticated user ID for user-scoped file upload", { userId: authenticatedUserId });
1421
+ const authResult = await this.resolveAuthenticatedUserIdForUserScoped();
1422
+ if (!authResult.ok) return authResult;
1423
+ authenticatedUserId = authResult.data;
1263
1424
  }
1264
- let isSuperAdminUser = false;
1265
- const userIdForCheck = authenticatedUserId || options.userId;
1266
- if (userIdForCheck) {
1267
- try {
1268
- const { isSuperAdmin } = await import('./api-F47QJ7FX.js');
1269
- isSuperAdminUser = await isSuperAdmin(userIdForCheck);
1270
- if (isSuperAdminUser) {
1271
- log3.debug("Super admin detected - bypassing permission checks", { userId: userIdForCheck });
1272
- }
1273
- } catch (superAdminCheckError) {
1274
- log3.warn("Failed to check super-admin status, proceeding with normal permission checks", superAdminCheckError);
1275
- }
1276
- }
1277
- const uploadResult = await uploadFile(this.supabase, file, {
1278
- appName: "file-reference",
1279
- orgId: options.organisation_id || void 0,
1280
- userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
1281
- // Use auth.uid() for user-scoped files
1282
- isPublic: options.is_public || false,
1283
- customPath: options.folder
1284
- // Use folder prop as the custom path segment
1285
- });
1286
- if (!uploadResult.success) {
1287
- throw new Error(`Failed to upload file: ${uploadResult.error}`);
1288
- }
1289
- if (!uploadResult.path) {
1290
- throw new Error("File upload did not return a path");
1291
- }
1292
- const filePath = uploadResult.path;
1293
- const metadata = await extractFileMetadata(file, {
1294
- appName: "file-reference",
1295
- orgId: options.organisation_id || void 0,
1296
- userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
1297
- // Use auth.uid() for user-scoped files
1298
- isPublic: options.is_public || false
1299
- }, "system");
1300
- if (!isUserScoped && options.organisation_id) {
1301
- await setOrganisationContext(this.supabase, options.organisation_id);
1302
- }
1303
- let rpcUserId = null;
1304
- if (authenticatedUserId) {
1305
- rpcUserId = authenticatedUserId;
1306
- } else if (options.userId) {
1307
- rpcUserId = options.userId;
1308
- } else {
1309
- const { data: { user: authUser } } = await this.supabase.auth.getUser();
1310
- if (authUser) {
1311
- rpcUserId = authUser.id;
1312
- }
1313
- }
1314
- const { data, error } = await this.supabase.rpc("data_file_reference_create", {
1315
- p_table_name: options.table_name,
1316
- p_record_id: options.record_id,
1317
- p_file_path: filePath,
1318
- // Storage path from step 1
1319
- p_organisation_id: options.organisation_id ?? null,
1320
- p_app_id: options.app_id,
1321
- p_page_context: options.pageContext,
1322
- p_event_id: options.event_id || null,
1323
- // Pass event_id for event-based apps
1324
- p_file_metadata: {
1325
- fileName: file.name,
1326
- fileType: file.type,
1327
- fileSize: file.size,
1328
- category: options.category,
1329
- ...metadata,
1330
- ...options.custom_metadata
1331
- },
1332
- p_is_public: options.is_public || false,
1333
- p_user_id: rpcUserId
1334
- // Always pass authenticated user ID for SECURITY DEFINER functions
1335
- });
1425
+ const isSuperAdminUser = await this.checkSuperAdminUser(authenticatedUserId || options.userId);
1426
+ const uploadResult = await this.uploadFileAndExtractMetadata(options, file, authenticatedUserId, isUserScoped);
1427
+ if (!uploadResult.ok) return uploadResult;
1428
+ const { path: filePath, metadata } = uploadResult.data;
1429
+ await this.setOrgContextIfNeeded(isUserScoped, options.organisation_id ?? void 0);
1430
+ const rpcUserId = await this.resolveRpcUserId(authenticatedUserId, options);
1431
+ const payload = this.buildCreateRpcPayload(options, filePath, file, metadata, rpcUserId);
1432
+ const { data, error } = await this.supabase.rpc("data_file_reference_create", payload);
1336
1433
  if (error) {
1337
- await deleteFile(this.supabase, filePath, options.is_public || false);
1338
- throw new Error(`Failed to create file reference: ${error.message}`);
1434
+ return await this.rollbackUploadAndErr(filePath, options.is_public ?? false, "CREATE_FAILED", error.message);
1339
1435
  }
1340
1436
  if (!data || data === null) {
1341
- await deleteFile(this.supabase, filePath, options.is_public || false);
1342
- const appName = await this.getAppName(options.app_id).catch(() => "unknown");
1343
- const pageContextDisplay = options.pageContext || "undefined";
1344
- if (isSuperAdminUser) {
1345
- throw new Error(
1346
- `File upload failed for super-admin user. This may indicate a database issue. Page context: '${pageContextDisplay}', App: '${appName}'. Please check that the page '${pageContextDisplay}' exists in rbac_app_pages table for app '${appName}'.`
1347
- );
1348
- } else {
1349
- throw new Error(
1350
- `File upload denied: insufficient permissions. You need 'create:page.${pageContextDisplay}' or 'update:page.${pageContextDisplay}' permission for the '${pageContextDisplay}' page. Make sure the page exists in rbac_app_pages table for app '${appName}'.`
1351
- );
1352
- }
1437
+ const message = await this.buildPermissionDeniedMessage(options, isSuperAdminUser);
1438
+ return await this.rollbackUploadAndErr(filePath, options.is_public ?? false, "PERMISSION_DENIED", message);
1353
1439
  }
1354
- const { data: fileRef, error: fetchError } = await this.supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("id", data).single();
1355
- if (fetchError || !fileRef) {
1356
- await deleteFile(this.supabase, filePath, options.is_public || false);
1357
- throw new Error(`Failed to fetch created file reference: ${fetchError?.message}`);
1440
+ const fetchResult = await this.fetchCreatedFileReference(data);
1441
+ if (!fetchResult.ok) {
1442
+ return await this.rollbackUploadAndErr(
1443
+ filePath,
1444
+ options.is_public ?? false,
1445
+ "FETCH_FAILED",
1446
+ fetchResult.error.message
1447
+ );
1358
1448
  }
1359
1449
  invalidateFileDisplayCache(
1360
1450
  options.table_name,
@@ -1362,10 +1452,10 @@ var FileReferenceServiceImpl = class {
1362
1452
  options.organisation_id || null,
1363
1453
  options.category
1364
1454
  );
1365
- return fileRef;
1455
+ return ok(fetchResult.data);
1366
1456
  } catch (error) {
1367
1457
  log3.error("Error creating file reference:", error);
1368
- throw error;
1458
+ return err(toApiError(error));
1369
1459
  }
1370
1460
  }
1371
1461
  async getFileReference(table_name, record_id, organisation_id) {
@@ -1379,21 +1469,25 @@ var FileReferenceServiceImpl = class {
1379
1469
  const { data, error } = await query.single();
1380
1470
  if (error) {
1381
1471
  if (error.code === "PGRST116") {
1382
- return null;
1472
+ return ok(null);
1383
1473
  }
1384
- throw new Error(`Failed to get file reference: ${error.message}`);
1474
+ return err({ code: "FETCH_FAILED", message: error.message });
1385
1475
  }
1386
- return data;
1476
+ return ok(data);
1387
1477
  } catch (error) {
1388
1478
  log3.error("Error getting file reference:", error);
1389
- throw error;
1479
+ return err(toApiError(error));
1390
1480
  }
1391
1481
  }
1392
1482
  async getFileUrl(table_name, record_id, organisation_id) {
1393
1483
  try {
1394
- const fileRef = await this.getFileReference(table_name, record_id, organisation_id);
1484
+ const refResult = await this.getFileReference(table_name, record_id, organisation_id);
1485
+ if (!refResult.ok) {
1486
+ return refResult;
1487
+ }
1488
+ const fileRef = refResult.data;
1395
1489
  if (!fileRef) {
1396
- return null;
1490
+ return ok(null);
1397
1491
  }
1398
1492
  if (fileRef.is_public) {
1399
1493
  const { data: pathData } = await this.supabase.rpc("data_file_reference_url_get", {
@@ -1402,15 +1496,14 @@ var FileReferenceServiceImpl = class {
1402
1496
  p_organisation_id: organisation_id
1403
1497
  });
1404
1498
  if (!pathData) {
1405
- return null;
1499
+ return ok(null);
1406
1500
  }
1407
- return getPublicUrl(this.supabase, pathData, true);
1408
- } else {
1409
- return await this.getSignedUrl(table_name, record_id, organisation_id);
1501
+ return ok(getPublicUrl(this.supabase, pathData, true));
1410
1502
  }
1503
+ return this.getSignedUrl(table_name, record_id, organisation_id);
1411
1504
  } catch (error) {
1412
1505
  log3.error("Error getting file URL:", error);
1413
- throw error;
1506
+ return err(toApiError(error));
1414
1507
  }
1415
1508
  }
1416
1509
  async getSignedUrl(table_name, record_id, organisation_id, expires_in = 3600) {
@@ -1422,10 +1515,10 @@ var FileReferenceServiceImpl = class {
1422
1515
  p_expires_in: expires_in
1423
1516
  });
1424
1517
  if (error) {
1425
- throw new Error(`Failed to get signed URL: ${error.message}`);
1518
+ return err({ code: "SIGNED_URL_FAILED", message: error.message });
1426
1519
  }
1427
1520
  if (!filePath) {
1428
- return null;
1521
+ return ok(null);
1429
1522
  }
1430
1523
  const signedUrlResult = await getSignedUrl(this.supabase, filePath, {
1431
1524
  appName: "file-reference",
@@ -1433,27 +1526,31 @@ var FileReferenceServiceImpl = class {
1433
1526
  userId: organisation_id ? void 0 : record_id,
1434
1527
  expiresIn: expires_in
1435
1528
  });
1436
- return signedUrlResult?.url || null;
1529
+ if (!signedUrlResult.ok) {
1530
+ return err(signedUrlResult.error);
1531
+ }
1532
+ return ok(signedUrlResult.data.url ?? null);
1437
1533
  } catch (error) {
1438
1534
  log3.error("Error getting signed URL:", error);
1439
- throw error;
1535
+ return err(toApiError(error));
1440
1536
  }
1441
1537
  }
1442
1538
  async updateFileReference(id, updates) {
1443
1539
  try {
1444
1540
  const { data, error } = await this.supabase.from("core_file_references").update(updates).eq("id", id).select().single();
1445
1541
  if (error) {
1446
- throw new Error(`Failed to update file reference: ${error.message}`);
1542
+ return err({ code: "UPDATE_FAILED", message: error.message });
1447
1543
  }
1448
- return data;
1544
+ return ok(data);
1449
1545
  } catch (error) {
1450
1546
  log3.error("Error updating file reference:", error);
1451
- throw error;
1547
+ return err(toApiError(error));
1452
1548
  }
1453
1549
  }
1454
1550
  async deleteFileReference(table_name, record_id, organisation_id, delete_file = false) {
1455
1551
  try {
1456
- const fileRef = await this.getFileReference(table_name, record_id, organisation_id);
1552
+ const refResult = await this.getFileReference(table_name, record_id, organisation_id);
1553
+ const fileRef = refResult.ok ? refResult.data : null;
1457
1554
  const { error } = await this.supabase.rpc("data_file_reference_delete", {
1458
1555
  p_table_name: table_name,
1459
1556
  p_record_id: record_id,
@@ -1461,15 +1558,15 @@ var FileReferenceServiceImpl = class {
1461
1558
  p_delete_file: delete_file
1462
1559
  });
1463
1560
  if (error) {
1464
- throw new Error(`Failed to delete file reference: ${error.message}`);
1561
+ return err({ code: "DELETE_FAILED", message: error.message });
1465
1562
  }
1466
1563
  if (delete_file && fileRef) {
1467
1564
  await deleteFile(this.supabase, fileRef.file_path, fileRef.is_public || false);
1468
1565
  }
1469
- return true;
1566
+ return ok(true);
1470
1567
  } catch (error) {
1471
1568
  log3.error("Error deleting file reference:", error);
1472
- throw error;
1569
+ return err(toApiError(error));
1473
1570
  }
1474
1571
  }
1475
1572
  async listFileReferences(table_name, record_id, organisation_id) {
@@ -1480,38 +1577,31 @@ var FileReferenceServiceImpl = class {
1480
1577
  p_organisation_id: organisation_id ?? null
1481
1578
  });
1482
1579
  if (error) {
1483
- throw new Error(`Failed to list file references: ${error.message}`);
1580
+ return err({ code: "LIST_FAILED", message: error.message });
1484
1581
  }
1485
1582
  if (!data || data.length === 0) {
1486
- return [];
1583
+ return ok([]);
1487
1584
  }
1488
1585
  const fileReferences = data.filter((item) => item.id && item.file_path && item.file_metadata).map((item) => {
1489
1586
  const fileName = item.file_path.split("/").pop() || "unknown";
1490
1587
  const fileType = fileName.split(".").pop() || "unknown";
1491
- const fileRef = {
1588
+ return {
1492
1589
  id: item.id,
1493
1590
  table_name,
1494
1591
  record_id,
1495
1592
  file_path: item.file_path,
1496
- file_metadata: {
1497
- fileName,
1498
- fileType,
1499
- ...item.file_metadata || {}
1500
- },
1593
+ file_metadata: { fileName, fileType, ...item.file_metadata || {} },
1501
1594
  organisation_id: organisation_id ?? null,
1502
1595
  app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
1503
- // May not be in metadata, use empty string
1504
1596
  is_public: item.is_public ?? false,
1505
1597
  created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1506
1598
  updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
1507
- // RPC doesn't return updated_at, use created_at
1508
1599
  };
1509
- return fileRef;
1510
1600
  });
1511
- return fileReferences;
1601
+ return ok(fileReferences);
1512
1602
  } catch (error) {
1513
1603
  log3.error("Error listing file references:", error);
1514
- throw error;
1604
+ return err(toApiError(error));
1515
1605
  }
1516
1606
  }
1517
1607
  async getFileCount(table_name, record_id, organisation_id) {
@@ -1522,12 +1612,12 @@ var FileReferenceServiceImpl = class {
1522
1612
  p_organisation_id: organisation_id ?? null
1523
1613
  });
1524
1614
  if (error) {
1525
- throw new Error(`Failed to get file count: ${error.message}`);
1615
+ return err({ code: "COUNT_FAILED", message: error.message });
1526
1616
  }
1527
- return data || 0;
1617
+ return ok(data ?? 0);
1528
1618
  } catch (error) {
1529
1619
  log3.error("Error getting file count:", error);
1530
- throw error;
1620
+ return err(toApiError(error));
1531
1621
  }
1532
1622
  }
1533
1623
  async getFileReferenceById(id, organisation_id) {
@@ -1537,15 +1627,15 @@ var FileReferenceServiceImpl = class {
1537
1627
  p_organisation_id: organisation_id ?? null
1538
1628
  });
1539
1629
  if (error) {
1540
- throw new Error(`Failed to get file reference by ID: ${error.message}`);
1630
+ return err({ code: "FETCH_FAILED", message: error.message });
1541
1631
  }
1542
1632
  if (!data || data.length === 0) {
1543
- return null;
1633
+ return ok(null);
1544
1634
  }
1545
- return data[0];
1635
+ return ok(data[0]);
1546
1636
  } catch (error) {
1547
1637
  log3.error("Error getting file reference by ID:", error);
1548
- throw error;
1638
+ return err(toApiError(error));
1549
1639
  }
1550
1640
  }
1551
1641
  async getFilesByCategory(table_name, record_id, category, organisation_id) {
@@ -1557,61 +1647,36 @@ var FileReferenceServiceImpl = class {
1557
1647
  p_organisation_id: organisation_id ?? null
1558
1648
  });
1559
1649
  if (error) {
1560
- log3.error("RPC ERROR getting files by category:", {
1561
- error,
1562
- errorCode: error.code,
1563
- errorMessage: error.message,
1564
- errorDetails: error.details,
1565
- table_name,
1566
- record_id,
1567
- category,
1568
- organisation_id,
1569
- message: "CRITICAL: Category filtering MUST use RPC function data_file_reference_by_category_list. Direct queries with .eq('category', ...) will FAIL with HTTP 406 because category is stored in file_metadata JSONB field, not a direct column."
1570
- });
1571
- throw new Error(`Failed to get files by category: ${error.message}. Category filtering uses file_metadata JSONB field, not a direct column. RPC function required.`);
1650
+ log3.error("RPC ERROR getting files by category:", { error, table_name, record_id, category });
1651
+ return err({ code: "LIST_FAILED", message: error.message });
1572
1652
  }
1573
1653
  if (!data || data.length === 0) {
1574
- return [];
1654
+ return ok([]);
1575
1655
  }
1576
1656
  const fileReferences = data.filter((item) => {
1577
1657
  const fileCategory = item.file_metadata?.category;
1578
- const matches = fileCategory === category;
1579
- if (!matches) {
1580
- log3.warn("File category mismatch in RPC response:", {
1581
- fileId: item.id,
1582
- expectedCategory: category,
1583
- actualCategory: fileCategory
1584
- });
1585
- }
1586
- return matches && item.id && item.file_path && item.file_metadata;
1587
- }).map((item) => {
1588
- const fileName = item.file_path.split("/").pop() || "unknown";
1589
- const fileType = fileName.split(".").pop() || "unknown";
1590
- const fileRef = {
1591
- id: item.id,
1592
- table_name,
1593
- record_id,
1594
- file_path: item.file_path,
1595
- file_metadata: {
1596
- fileName,
1597
- fileType,
1598
- category: item.file_metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
1599
- ...item.file_metadata || {}
1600
- },
1601
- organisation_id: organisation_id ?? null,
1602
- app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
1603
- // May not be in metadata, use empty string
1604
- is_public: item.is_public ?? false,
1605
- created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1606
- updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
1607
- // RPC doesn't return updated_at, use created_at
1608
- };
1609
- return fileRef;
1610
- });
1611
- return fileReferences;
1658
+ return fileCategory === category && item.id && item.file_path && item.file_metadata;
1659
+ }).map((item) => ({
1660
+ id: item.id,
1661
+ table_name,
1662
+ record_id,
1663
+ file_path: item.file_path,
1664
+ file_metadata: {
1665
+ fileName: item.file_path.split("/").pop() || "unknown",
1666
+ fileType: item.file_path.split(".").pop() || "unknown",
1667
+ category: item.file_metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
1668
+ ...item.file_metadata || {}
1669
+ },
1670
+ organisation_id: organisation_id ?? null,
1671
+ app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
1672
+ is_public: item.is_public ?? false,
1673
+ created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1674
+ updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
1675
+ }));
1676
+ return ok(fileReferences);
1612
1677
  } catch (error) {
1613
1678
  log3.error("Error getting files by category:", error);
1614
- throw error;
1679
+ return err(toApiError(error));
1615
1680
  }
1616
1681
  }
1617
1682
  async uploadMultipleFiles(options, files) {
@@ -1619,8 +1684,9 @@ var FileReferenceServiceImpl = class {
1619
1684
  const failed = [];
1620
1685
  const results = [];
1621
1686
  for (const file of files) {
1622
- try {
1623
- const fileReference = await this.createFileReference(options, file);
1687
+ const result = await this.createFileReference(options, file);
1688
+ if (result.ok) {
1689
+ const fileReference = result.data;
1624
1690
  success.push(fileReference);
1625
1691
  results.push({
1626
1692
  file,
@@ -1629,19 +1695,18 @@ var FileReferenceServiceImpl = class {
1629
1695
  file_url: fileReference.is_public ? getPublicUrl(this.supabase, fileReference.file_path, true) : ""
1630
1696
  }
1631
1697
  });
1632
- } catch (error) {
1633
- const message = error instanceof Error ? error.message : "Unknown error";
1634
- failed.push({ file, error: message });
1635
- results.push({ file, result: null, error: message });
1698
+ } else {
1699
+ failed.push({ file, error: result.error.message });
1700
+ results.push({ file, result: null, error: result.error.message });
1636
1701
  }
1637
1702
  }
1638
- return {
1703
+ return ok({
1639
1704
  success,
1640
1705
  failed,
1641
1706
  total: results.length,
1642
1707
  successful: success.length,
1643
1708
  results
1644
- };
1709
+ });
1645
1710
  }
1646
1711
  };
1647
1712
  function createFileReferenceService(supabase) {
@@ -1649,22 +1714,54 @@ function createFileReferenceService(supabase) {
1649
1714
  }
1650
1715
  async function uploadFileWithReference(supabase, options, file) {
1651
1716
  const service = createFileReferenceService(supabase);
1652
- const fileReference = await service.createFileReference(options, file);
1653
- const fileUrl = options.is_public ? getPublicUrl(supabase, fileReference.file_path, true) : await getSignedUrl(supabase, fileReference.file_path, {
1654
- appName: "file-reference",
1655
- orgId: options.organisation_id || void 0,
1656
- userId: options.userId || void 0,
1657
- expiresIn: 3600
1658
- });
1659
- const urlString = typeof fileUrl === "string" ? fileUrl : fileUrl?.url || "";
1660
- return {
1717
+ const result = await service.createFileReference(options, file);
1718
+ if (!result.ok) {
1719
+ return result;
1720
+ }
1721
+ const fileReference = result.data;
1722
+ let urlString;
1723
+ if (options.is_public) {
1724
+ urlString = getPublicUrl(supabase, fileReference.file_path, true);
1725
+ } else {
1726
+ const signedResult = await getSignedUrl(supabase, fileReference.file_path, {
1727
+ appName: "file-reference",
1728
+ orgId: options.organisation_id || void 0,
1729
+ userId: options.userId || void 0,
1730
+ expiresIn: 3600
1731
+ });
1732
+ urlString = signedResult.ok ? signedResult.data.url ?? "" : "";
1733
+ }
1734
+ return ok({
1661
1735
  file_reference: fileReference,
1662
1736
  file_url: urlString,
1663
1737
  signed_url: options.is_public ? void 0 : urlString || void 0
1664
- };
1738
+ });
1739
+ }
1740
+
1741
+ // src/components/FileDisplay/useFileDisplayData.ts
1742
+ async function fetchFileDisplayDataInternal(params) {
1743
+ const { table_name, record_id, organisation_id, category, supabase } = params;
1744
+ if (!supabase) {
1745
+ return [];
1746
+ }
1747
+ let query = supabase.from("core_file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at, created_by").eq("table_name", table_name).eq("record_id", record_id);
1748
+ if (organisation_id === void 0 || organisation_id === null) {
1749
+ query = query.is("organisation_id", null);
1750
+ } else {
1751
+ query = query.eq("organisation_id", organisation_id);
1752
+ }
1753
+ if (category !== void 0 && category !== null) {
1754
+ query = query.contains("file_metadata", { category });
1755
+ }
1756
+ const { data, error } = await query.order("created_at", { ascending: false });
1757
+ if (error) {
1758
+ throw new Error(error.message ?? "Failed to fetch file references");
1759
+ }
1760
+ const rows = data ?? [];
1761
+ return rows.map(mapFileReferenceRowToFileReference);
1665
1762
  }
1666
1763
 
1667
- // src/hooks/useFileDisplay.ts
1764
+ // src/components/FileDisplay/useFileDisplay.ts
1668
1765
  var authenticatedFileCache = /* @__PURE__ */ new Map();
1669
1766
  var MAX_CACHE_SIZE2 = 100;
1670
1767
  function cleanupCache() {
@@ -1683,6 +1780,111 @@ function cleanupCache() {
1683
1780
  toRemove.forEach(([key]) => authenticatedFileCache.delete(key));
1684
1781
  }
1685
1782
  }
1783
+ function getFileDisplayCacheKey(table_name, record_id, organisation_id, category) {
1784
+ return `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
1785
+ }
1786
+ async function tryApplyCacheHit(cacheKey, enableCache, cacheTtl, supabase, organisation_id, record_id, safeSetState, setters) {
1787
+ if (!enableCache || !supabase) return false;
1788
+ const cached = authenticatedFileCache.get(cacheKey);
1789
+ if (!cached || Date.now() - cached.timestamp >= cached.ttl) return false;
1790
+ const cachedData = cached.data;
1791
+ if (cachedData.fileReference && !cachedData.fileUrl && cachedData.fileReference.is_public === false && supabase) {
1792
+ try {
1793
+ const signedUrlResult = await getSignedUrl(supabase, cachedData.fileReference.file_path, {
1794
+ appName: "pace-core",
1795
+ orgId: organisation_id,
1796
+ userId: organisation_id ? void 0 : record_id,
1797
+ expiresIn: 3600
1798
+ });
1799
+ const regeneratedUrl = signedUrlResult.ok ? signedUrlResult.data.url : null;
1800
+ safeSetState(setters.setFileUrl, regeneratedUrl);
1801
+ safeSetState(setters.setFileReference, cachedData.fileReference);
1802
+ safeSetState(setters.setFileReferences, cachedData.fileReferences || []);
1803
+ safeSetState(setters.setFileUrls, cachedData.fileUrls || /* @__PURE__ */ new Map());
1804
+ safeSetState(setters.setFileCount, cachedData.fileCount || 0);
1805
+ safeSetState(setters.setIsLoading, false);
1806
+ safeSetState(setters.setError, null);
1807
+ return true;
1808
+ } catch (err2) {
1809
+ logger.warn("useFileDisplay", "Failed to regenerate signed URL from cache, falling back to fetch:", err2);
1810
+ }
1811
+ }
1812
+ safeSetState(setters.setFileUrl, cachedData.fileUrl || null);
1813
+ safeSetState(setters.setFileReference, cachedData.fileReference || null);
1814
+ safeSetState(setters.setFileReferences, cachedData.fileReferences || []);
1815
+ safeSetState(setters.setFileUrls, cachedData.fileUrls || /* @__PURE__ */ new Map());
1816
+ safeSetState(setters.setFileCount, cachedData.fileCount || 0);
1817
+ safeSetState(setters.setIsLoading, false);
1818
+ safeSetState(setters.setError, null);
1819
+ return true;
1820
+ }
1821
+ async function applyFetchedFilesAndCache(files, category, table_name, record_id, organisation_id, supabase, cacheKey, enableCache, cacheTtl, safeSetState, setters) {
1822
+ safeSetState(setters.setFileReferences, files);
1823
+ safeSetState(setters.setFileCount, files.length);
1824
+ if (category && files.length > 0) {
1825
+ const firstFile = files[0];
1826
+ safeSetState(setters.setFileReference, firstFile);
1827
+ let url = null;
1828
+ if (firstFile.is_public) {
1829
+ url = getPublicUrl(supabase, firstFile.file_path, true);
1830
+ } else {
1831
+ const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {
1832
+ appName: "pace-core",
1833
+ orgId: organisation_id,
1834
+ userId: organisation_id ? void 0 : record_id,
1835
+ expiresIn: 3600
1836
+ });
1837
+ url = signedUrlResult.ok ? signedUrlResult.data.url : null;
1838
+ if (!url) {
1839
+ logger.warn("useFileDisplay", "Failed to generate signed URL for file:", {
1840
+ file_path: firstFile.file_path,
1841
+ record_id,
1842
+ table_name
1843
+ });
1844
+ }
1845
+ }
1846
+ safeSetState(setters.setFileUrl, url);
1847
+ } else {
1848
+ const urlResult = await generateFileUrlsBatch(supabase, files, {
1849
+ appName: "pace-core",
1850
+ orgId: organisation_id,
1851
+ userId: organisation_id ? void 0 : record_id,
1852
+ expiresIn: 3600
1853
+ });
1854
+ const urlMap = urlResult.ok ? urlResult.data : /* @__PURE__ */ new Map();
1855
+ safeSetState(setters.setFileUrls, urlMap);
1856
+ safeSetState(setters.setFileReference, null);
1857
+ safeSetState(setters.setFileUrl, null);
1858
+ }
1859
+ if (enableCache) {
1860
+ const cacheData = {
1861
+ fileUrl: null,
1862
+ fileReference: category && files.length > 0 ? files[0] : null,
1863
+ fileReferences: files,
1864
+ fileUrls: /* @__PURE__ */ new Map(),
1865
+ fileCount: files.length
1866
+ };
1867
+ if (category && files.length > 0) {
1868
+ const firstFile = files[0];
1869
+ cacheData.fileUrl = firstFile.is_public ? getPublicUrl(supabase, firstFile.file_path, true) : null;
1870
+ } else {
1871
+ const urlMap = /* @__PURE__ */ new Map();
1872
+ for (const fileRef of files) {
1873
+ if (fileRef.is_public) {
1874
+ const url = getPublicUrl(supabase, fileRef.file_path, true);
1875
+ if (url) urlMap.set(fileRef.id, url);
1876
+ }
1877
+ }
1878
+ cacheData.fileUrls = urlMap;
1879
+ }
1880
+ authenticatedFileCache.set(cacheKey, {
1881
+ data: cacheData,
1882
+ timestamp: Date.now(),
1883
+ ttl: cacheTtl
1884
+ });
1885
+ cleanupCache();
1886
+ }
1887
+ }
1686
1888
  function useFileDisplay(table_name, record_id, organisation_id, category, options) {
1687
1889
  const {
1688
1890
  cacheTtl = 30 * 60 * 1e3,
@@ -1719,261 +1921,36 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1719
1921
  logger.warn("useFileDisplay", "Invalid organisationId format (not a valid UUID):", organisation_id);
1720
1922
  }
1721
1923
  }
1722
- const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
1723
- if (enableCache) {
1724
- const cached = authenticatedFileCache.get(cacheKey);
1725
- if (cached && Date.now() - cached.timestamp < cached.ttl) {
1726
- const cachedData = cached.data;
1727
- if (cachedData.fileReference && !cachedData.fileUrl && cachedData.fileReference.is_public === false && supabase) {
1728
- try {
1729
- const signedUrlResult = await getSignedUrl(supabase, cachedData.fileReference.file_path, {
1730
- appName: "pace-core",
1731
- orgId: organisation_id,
1732
- userId: organisation_id ? void 0 : record_id,
1733
- expiresIn: 3600
1734
- });
1735
- const regeneratedUrl = signedUrlResult?.url || null;
1736
- safeSetState(setFileUrl, regeneratedUrl);
1737
- safeSetState(setFileReference, cachedData.fileReference);
1738
- safeSetState(setFileReferences, cachedData.fileReferences || []);
1739
- safeSetState(setFileUrls, cachedData.fileUrls || /* @__PURE__ */ new Map());
1740
- safeSetState(setFileCount, cachedData.fileCount || 0);
1741
- safeSetState(setIsLoading, false);
1742
- safeSetState(setError, null);
1743
- return;
1744
- } catch (err) {
1745
- logger.warn("useFileDisplay", "Failed to regenerate signed URL from cache, falling back to fetch:", err);
1746
- }
1747
- }
1748
- safeSetState(setFileUrl, cachedData.fileUrl || null);
1749
- safeSetState(setFileReference, cachedData.fileReference || null);
1750
- safeSetState(setFileReferences, cachedData.fileReferences || []);
1751
- safeSetState(setFileUrls, cachedData.fileUrls || /* @__PURE__ */ new Map());
1752
- safeSetState(setFileCount, cachedData.fileCount || 0);
1753
- safeSetState(setIsLoading, false);
1754
- safeSetState(setError, null);
1755
- return;
1924
+ safeSetState(setIsLoading, true);
1925
+ safeSetState(setError, null);
1926
+ const cacheKey = getFileDisplayCacheKey(table_name, record_id, organisation_id, category);
1927
+ const applied = await tryApplyCacheHit(
1928
+ cacheKey,
1929
+ enableCache,
1930
+ cacheTtl,
1931
+ supabase,
1932
+ organisation_id,
1933
+ record_id,
1934
+ safeSetState,
1935
+ {
1936
+ setFileUrl,
1937
+ setFileReference,
1938
+ setFileReferences,
1939
+ setFileUrls,
1940
+ setFileCount,
1941
+ setIsLoading,
1942
+ setError
1756
1943
  }
1757
- }
1944
+ );
1945
+ if (applied) return;
1758
1946
  try {
1759
- safeSetState(setIsLoading, true);
1760
- safeSetState(setError, null);
1761
- const service = createFileReferenceService(supabase);
1762
- let files = [];
1763
- const shouldSearchBothScopes = organisation_id === void 0;
1764
- if (shouldSearchBothScopes) {
1765
- let userScopedFiles = [];
1766
- let orgScopedFiles = [];
1767
- try {
1768
- if (category) {
1769
- userScopedFiles = await service.getFilesByCategory(
1770
- table_name,
1771
- record_id,
1772
- category,
1773
- void 0
1774
- // Explicitly pass undefined for user-scoped files (service converts to null for RPC)
1775
- );
1776
- } else {
1777
- userScopedFiles = await service.listFileReferences(
1778
- table_name,
1779
- record_id,
1780
- void 0
1781
- // Explicitly pass undefined for user-scoped files (service converts to null for RPC)
1782
- );
1783
- }
1784
- } catch (err) {
1785
- logger.warn("useFileDisplay", "Error querying user-scoped files:", err);
1786
- userScopedFiles = [];
1787
- }
1788
- try {
1789
- const { data: { user }, error: userError } = await supabase.auth.getUser();
1790
- if (userError) {
1791
- logger.warn("useFileDisplay", "Error getting user:", userError);
1792
- }
1793
- if (user) {
1794
- const { data: memberships, error: membershipError } = await supabase.from("core_organisation_memberships").select("organisation_id").eq("user_id", user.id);
1795
- if (membershipError) {
1796
- logger.warn("useFileDisplay", "Error querying organisation memberships:", membershipError);
1797
- }
1798
- if (memberships && memberships.length > 0) {
1799
- const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
1800
- const orgQueries = orgIds.map(async (orgId) => {
1801
- try {
1802
- if (category) {
1803
- return await service.getFilesByCategory(
1804
- table_name,
1805
- record_id,
1806
- category,
1807
- orgId
1808
- );
1809
- } else {
1810
- return await service.listFileReferences(
1811
- table_name,
1812
- record_id,
1813
- orgId
1814
- );
1815
- }
1816
- } catch (_err) {
1817
- return [];
1818
- }
1819
- });
1820
- const orgResults = await Promise.all(orgQueries);
1821
- orgScopedFiles = orgResults.flat();
1822
- } else {
1823
- try {
1824
- let fallbackQuery = supabase.from("core_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 });
1825
- if (category) {
1826
- fallbackQuery = fallbackQuery.eq("file_metadata->>category", category);
1827
- }
1828
- const { data: fallbackFiles } = await fallbackQuery;
1829
- if (fallbackFiles && fallbackFiles.length > 0) {
1830
- orgScopedFiles = fallbackFiles.map((f) => {
1831
- const fileName = f.file_path.split("/").pop() || "unknown";
1832
- const fileType = fileName.split(".").pop() || "unknown";
1833
- const metadata = f.file_metadata;
1834
- return {
1835
- id: f.id,
1836
- table_name: f.table_name,
1837
- record_id: f.record_id,
1838
- file_path: f.file_path,
1839
- file_metadata: {
1840
- fileName: metadata?.fileName || fileName,
1841
- fileType: metadata?.fileType || fileType,
1842
- category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
1843
- ...metadata || {}
1844
- },
1845
- organisation_id: f.organisation_id,
1846
- app_id: f.app_id,
1847
- is_public: f.is_public ?? false,
1848
- created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1849
- updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
1850
- };
1851
- });
1852
- }
1853
- } catch (_err) {
1854
- }
1855
- }
1856
- }
1857
- } catch (err) {
1858
- logger.warn("useFileDisplay", "Error querying organisation-scoped files:", err);
1859
- orgScopedFiles = [];
1860
- }
1861
- const allFiles = [...userScopedFiles, ...orgScopedFiles];
1862
- allFiles.sort((a, b) => {
1863
- const aTime = new Date(a.created_at).getTime();
1864
- const bTime = new Date(b.created_at).getTime();
1865
- return bTime - aTime;
1866
- });
1867
- if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
1868
- files = allFiles.filter((f) => f.organisation_id !== null);
1869
- } else {
1870
- files = allFiles;
1871
- }
1872
- if (files.length === 0) {
1873
- try {
1874
- let directQuery = supabase.from("core_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 });
1875
- if (category) {
1876
- directQuery = directQuery.eq("file_metadata->>category", category);
1877
- }
1878
- const { data: directFiles } = await directQuery;
1879
- if (directFiles && directFiles.length > 0) {
1880
- files = directFiles.map((f) => {
1881
- const fileName = f.file_path.split("/").pop() || "unknown";
1882
- const fileType = fileName.split(".").pop() || "unknown";
1883
- const metadata = f.file_metadata;
1884
- return {
1885
- id: f.id,
1886
- table_name: f.table_name,
1887
- record_id: f.record_id,
1888
- file_path: f.file_path,
1889
- file_metadata: {
1890
- fileName: metadata?.fileName || fileName,
1891
- fileType: metadata?.fileType || fileType,
1892
- category: metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
1893
- ...metadata || {}
1894
- },
1895
- organisation_id: f.organisation_id,
1896
- app_id: f.app_id,
1897
- is_public: f.is_public ?? false,
1898
- created_at: f.created_at || (/* @__PURE__ */ new Date()).toISOString(),
1899
- updated_at: f.updated_at || (/* @__PURE__ */ new Date()).toISOString()
1900
- };
1901
- });
1902
- }
1903
- } catch (_err) {
1904
- }
1905
- }
1906
- } else {
1907
- if (category) {
1908
- files = await service.getFilesByCategory(
1909
- table_name,
1910
- record_id,
1911
- category,
1912
- organisation_id
1913
- );
1914
- } else {
1915
- files = await service.listFileReferences(
1916
- table_name,
1917
- record_id,
1918
- organisation_id
1919
- );
1920
- }
1921
- if (files.length === 0 && (!organisation_id || organisation_id === "")) {
1922
- let userScopedFiles = [];
1923
- let orgScopedFiles = [];
1924
- try {
1925
- if (category) {
1926
- userScopedFiles = await service.getFilesByCategory(
1927
- table_name,
1928
- record_id,
1929
- category,
1930
- void 0
1931
- );
1932
- } else {
1933
- userScopedFiles = await service.listFileReferences(
1934
- table_name,
1935
- record_id,
1936
- void 0
1937
- );
1938
- }
1939
- } catch (_err) {
1940
- }
1941
- try {
1942
- const { data: { user } } = await supabase.auth.getUser();
1943
- if (user) {
1944
- const { data: memberships } = await supabase.from("core_organisation_memberships").select("organisation_id").eq("user_id", user.id).or("status.is.null,status.eq.active");
1945
- if (memberships && memberships.length > 0) {
1946
- const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
1947
- const orgQueries = orgIds.map(async (orgId) => {
1948
- try {
1949
- if (category) {
1950
- return await service.getFilesByCategory(table_name, record_id, category, orgId);
1951
- } else {
1952
- return await service.listFileReferences(table_name, record_id, orgId);
1953
- }
1954
- } catch (_err) {
1955
- return [];
1956
- }
1957
- });
1958
- const orgResults = await Promise.all(orgQueries);
1959
- orgScopedFiles = orgResults.flat();
1960
- }
1961
- }
1962
- } catch (_err) {
1963
- }
1964
- const allFiles = [...userScopedFiles, ...orgScopedFiles];
1965
- allFiles.sort((a, b) => {
1966
- const aTime = new Date(a.created_at).getTime();
1967
- const bTime = new Date(b.created_at).getTime();
1968
- return bTime - aTime;
1969
- });
1970
- if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
1971
- files = allFiles.filter((f) => f.organisation_id !== null);
1972
- } else {
1973
- files = allFiles;
1974
- }
1975
- }
1976
- }
1947
+ const files = await fetchFileDisplayDataInternal({
1948
+ table_name,
1949
+ record_id,
1950
+ organisation_id,
1951
+ category,
1952
+ supabase
1953
+ });
1977
1954
  if (files.length === 0) {
1978
1955
  safeSetState(setFileUrl, null);
1979
1956
  safeSetState(setFileReference, null);
@@ -1990,79 +1967,22 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1990
1967
  }
1991
1968
  return;
1992
1969
  }
1993
- safeSetState(setFileReferences, files);
1994
- safeSetState(setFileCount, files.length);
1995
- if (category && files.length > 0) {
1996
- const firstFile = files[0];
1997
- safeSetState(setFileReference, firstFile);
1998
- let url = null;
1999
- if (firstFile.is_public) {
2000
- url = getPublicUrl(supabase, firstFile.file_path, true);
2001
- } else {
2002
- const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {
2003
- appName: "pace-core",
2004
- orgId: organisation_id,
2005
- userId: organisation_id ? void 0 : record_id,
2006
- expiresIn: 3600
2007
- });
2008
- url = signedUrlResult?.url || null;
2009
- if (!url) {
2010
- logger.warn("useFileDisplay", "Failed to generate signed URL for file:", {
2011
- file_path: firstFile.file_path,
2012
- record_id,
2013
- table_name
2014
- });
2015
- }
2016
- }
2017
- safeSetState(setFileUrl, url);
2018
- } else {
2019
- const urlMap = await generateFileUrlsBatch(supabase, files, {
2020
- appName: "pace-core",
2021
- orgId: organisation_id,
2022
- userId: organisation_id ? void 0 : record_id,
2023
- expiresIn: 3600
2024
- });
2025
- safeSetState(setFileUrls, urlMap);
2026
- safeSetState(setFileReference, null);
2027
- safeSetState(setFileUrl, null);
2028
- }
2029
- if (enableCache) {
2030
- const cacheData = {
2031
- fileUrl: null,
2032
- fileReference: category && files.length > 0 ? files[0] : null,
2033
- fileReferences: files,
2034
- fileUrls: /* @__PURE__ */ new Map(),
2035
- fileCount: files.length
2036
- };
2037
- if (category && files.length > 0) {
2038
- const firstFile = files[0];
2039
- let url = null;
2040
- if (firstFile.is_public) {
2041
- url = getPublicUrl(supabase, firstFile.file_path, true);
2042
- }
2043
- cacheData.fileUrl = url;
2044
- } else {
2045
- const urlMap = /* @__PURE__ */ new Map();
2046
- for (const fileRef of files) {
2047
- if (fileRef.is_public) {
2048
- const url = getPublicUrl(supabase, fileRef.file_path, true);
2049
- if (url) {
2050
- urlMap.set(fileRef.id, url);
2051
- }
2052
- }
2053
- }
2054
- cacheData.fileUrls = urlMap;
2055
- }
2056
- authenticatedFileCache.set(cacheKey, {
2057
- data: cacheData,
2058
- timestamp: Date.now(),
2059
- ttl: cacheTtl
2060
- });
2061
- cleanupCache();
2062
- }
2063
- } catch (err) {
2064
- logger.error("useFileDisplay", "Error fetching files:", err);
2065
- const error2 = err instanceof Error ? err : new Error("Unknown error occurred");
1970
+ await applyFetchedFilesAndCache(
1971
+ files,
1972
+ category,
1973
+ table_name,
1974
+ record_id,
1975
+ organisation_id,
1976
+ supabase,
1977
+ cacheKey,
1978
+ enableCache,
1979
+ cacheTtl,
1980
+ safeSetState,
1981
+ { setFileUrl, setFileReference, setFileReferences, setFileUrls, setFileCount }
1982
+ );
1983
+ } catch (err2) {
1984
+ logger.error("useFileDisplay", "Error fetching files:", err2);
1985
+ const error2 = err2 instanceof Error ? err2 : new Error("Unknown error occurred");
2066
1986
  safeSetState(setError, error2);
2067
1987
  safeSetState(setFileUrl, null);
2068
1988
  safeSetState(setFileReference, null);
@@ -2076,9 +1996,9 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
2076
1996
  useEffect(() => {
2077
1997
  isMountedRef.current = true;
2078
1998
  if (table_name && record_id && supabase) {
2079
- fetchFiles().catch((err) => {
1999
+ fetchFiles().catch((err2) => {
2080
2000
  if (isMountedRef.current) {
2081
- safeSetState(setError, err instanceof Error ? err : new Error("Unknown error"));
2001
+ safeSetState(setError, err2 instanceof Error ? err2 : new Error("Unknown error"));
2082
2002
  }
2083
2003
  });
2084
2004
  } else {
@@ -2097,8 +2017,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
2097
2017
  const refetch = useCallback(async () => {
2098
2018
  if (!table_name || !record_id || !supabase) return;
2099
2019
  if (enableCache) {
2100
- const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
2101
- authenticatedFileCache.delete(cacheKey);
2020
+ authenticatedFileCache.delete(getFileDisplayCacheKey(table_name, record_id, organisation_id, category));
2102
2021
  }
2103
2022
  await fetchFiles();
2104
2023
  }, [fetchFiles, table_name, record_id, organisation_id, category, supabase, enableCache]);
@@ -2128,18 +2047,48 @@ function getFileDisplayCacheStats() {
2128
2047
  };
2129
2048
  }
2130
2049
  function invalidateFileDisplayCache(table_name, record_id, organisation_id, category) {
2131
- const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
2132
- authenticatedFileCache.delete(cacheKey);
2050
+ const orgId = organisation_id ?? void 0;
2051
+ authenticatedFileCache.delete(getFileDisplayCacheKey(table_name, record_id, orgId, category));
2133
2052
  if (category) {
2134
- const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_all`;
2135
- authenticatedFileCache.delete(allCategoryKey);
2053
+ authenticatedFileCache.delete(getFileDisplayCacheKey(table_name, record_id, orgId, void 0));
2136
2054
  }
2137
2055
  }
2138
2056
  var log4 = createLogger("useEventTheme");
2139
2057
  function useEventTheme(event) {
2140
2058
  const location = useLocation();
2141
2059
  const eventServiceContext = useContext(EventServiceContext);
2142
- const eventsContextSelectedEvent = eventServiceContext?.eventService?.getSelectedEvent() ?? null;
2060
+ const eventService = eventServiceContext?.eventService ?? null;
2061
+ useEffect(() => {
2062
+ if (event !== void 0 || !eventService) return;
2063
+ const applyThemeFromService = () => {
2064
+ const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login/");
2065
+ if (isOnLoginRoute) {
2066
+ clearPalette();
2067
+ return;
2068
+ }
2069
+ const current = eventService.getSelectedEvent();
2070
+ if (!current?.event_colours) {
2071
+ clearPalette();
2072
+ return;
2073
+ }
2074
+ const normalized = parseAndNormalizeEventColours(current.event_colours);
2075
+ if (normalized) {
2076
+ try {
2077
+ applyPalette(normalized, current.event_name ?? "");
2078
+ } catch (err2) {
2079
+ log4.error("Failed to apply event palette (subscription):", err2);
2080
+ }
2081
+ } else {
2082
+ clearPalette();
2083
+ }
2084
+ };
2085
+ applyThemeFromService();
2086
+ const unsubscribe = eventService.subscribe(applyThemeFromService);
2087
+ return () => {
2088
+ unsubscribe();
2089
+ };
2090
+ }, [event, eventService, location.pathname]);
2091
+ const eventsContextSelectedEvent = eventService?.getSelectedEvent() ?? null;
2143
2092
  const selectedEvent = event !== void 0 ? event : eventsContextSelectedEvent;
2144
2093
  useEffect(() => {
2145
2094
  const isOnLoginRoute = location.pathname === "/login" || location.pathname.startsWith("/login");
@@ -2147,6 +2096,9 @@ function useEventTheme(event) {
2147
2096
  clearPalette();
2148
2097
  return;
2149
2098
  }
2099
+ if (event === void 0) {
2100
+ return;
2101
+ }
2150
2102
  if (!selectedEvent) {
2151
2103
  clearPalette();
2152
2104
  return;
@@ -2158,13 +2110,11 @@ function useEventTheme(event) {
2158
2110
  return;
2159
2111
  }
2160
2112
  try {
2161
- applyPalette(normalized);
2113
+ applyPalette(normalized, selectedEvent.event_name ?? "");
2162
2114
  } catch (error) {
2163
2115
  log4.error("Failed to apply event palette:", error);
2164
2116
  }
2165
- return () => {
2166
- };
2167
- }, [selectedEvent, location.pathname]);
2117
+ }, [selectedEvent, location.pathname, event]);
2168
2118
  }
2169
2119
  function usePreventTabReload(options = {}) {
2170
2120
  const { enabled = true, gracePeriodMs = 2e3 } = options;