@jmruthers/pace-core 0.5.191 → 0.6.1

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 (380) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +372 -0
  4. package/cursor-rules/01-standards-compliance.mdc +275 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +341 -0
  7. package/cursor-rules/04-testing-standards.mdc +315 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +392 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
  11. package/cursor-rules/CHANGELOG.md +101 -0
  12. package/cursor-rules/README.md +191 -0
  13. package/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
  14. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
  15. package/dist/{DataTable-WKRZD47S.js → DataTable-DQ7RSOHE.js} +8 -7
  16. package/dist/{PublicPageProvider-ULXC_u6U.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +37 -156
  17. package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
  18. package/dist/{UnifiedAuthProvider-FTSG5XH7.js → UnifiedAuthProvider-ATAP5UTR.js} +3 -3
  19. package/dist/{api-IHKALJZD.js → api-N774RPUA.js} +2 -2
  20. package/dist/{chunk-6C4YBBJM.js → chunk-3QRJFVBR.js} +1 -1
  21. package/dist/chunk-3QRJFVBR.js.map +1 -0
  22. package/dist/{chunk-OETXORNB.js → chunk-3XTALGJF.js} +211 -136
  23. package/dist/chunk-3XTALGJF.js.map +1 -0
  24. package/dist/{chunk-6TQDD426.js → chunk-4N5C5XZU.js} +47 -228
  25. package/dist/chunk-4N5C5XZU.js.map +1 -0
  26. package/dist/{chunk-LOMZXPSN.js → chunk-4ZC4GX36.js} +47 -74
  27. package/dist/chunk-4ZC4GX36.js.map +1 -0
  28. package/dist/{chunk-6LTQQAT6.js → chunk-BYFSK72L.js} +357 -158
  29. package/dist/chunk-BYFSK72L.js.map +1 -0
  30. package/dist/{chunk-XYXSXPUK.js → chunk-EXUD6RNJ.js} +50 -10
  31. package/dist/chunk-EXUD6RNJ.js.map +1 -0
  32. package/dist/{chunk-VKB2CO4Z.js → chunk-GLK6VM3F.js} +244 -249
  33. package/dist/chunk-GLK6VM3F.js.map +1 -0
  34. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  35. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  36. package/dist/{chunk-XNYQOL3Z.js → chunk-JBKQ3SAO.js} +9 -18
  37. package/dist/chunk-JBKQ3SAO.js.map +1 -0
  38. package/dist/{chunk-ROXMHMY2.js → chunk-KNC55RTG.js} +13 -3
  39. package/dist/{chunk-ROXMHMY2.js.map → chunk-KNC55RTG.js.map} +1 -1
  40. package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
  41. package/dist/{chunk-ULHIJK66.js → chunk-T33XF5ZC.js} +255 -140
  42. package/dist/chunk-T33XF5ZC.js.map +1 -0
  43. package/dist/{chunk-VRGWKHDB.js → chunk-XM25TVIE.js} +100 -33
  44. package/dist/chunk-XM25TVIE.js.map +1 -0
  45. package/dist/components.d.ts +4 -4
  46. package/dist/components.js +9 -9
  47. package/dist/hooks.d.ts +6 -6
  48. package/dist/hooks.js +20 -25
  49. package/dist/hooks.js.map +1 -1
  50. package/dist/index.d.ts +11 -11
  51. package/dist/index.js +18 -21
  52. package/dist/index.js.map +1 -1
  53. package/dist/providers.d.ts +3 -3
  54. package/dist/providers.js +2 -2
  55. package/dist/rbac/index.d.ts +2 -20
  56. package/dist/rbac/index.js +7 -9
  57. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
  58. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  59. package/dist/utils.d.ts +1 -1
  60. package/dist/utils.js +3 -3
  61. package/docs/api/classes/ColumnFactory.md +1 -1
  62. package/docs/api/classes/ErrorBoundary.md +1 -1
  63. package/docs/api/classes/InvalidScopeError.md +1 -1
  64. package/docs/api/classes/Logger.md +1 -1
  65. package/docs/api/classes/MissingUserContextError.md +1 -1
  66. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  67. package/docs/api/classes/PermissionDeniedError.md +2 -2
  68. package/docs/api/classes/RBACAuditManager.md +2 -2
  69. package/docs/api/classes/RBACCache.md +1 -1
  70. package/docs/api/classes/RBACEngine.md +2 -2
  71. package/docs/api/classes/RBACError.md +1 -1
  72. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  73. package/docs/api/classes/SecureSupabaseClient.md +10 -10
  74. package/docs/api/classes/StorageUtils.md +1 -1
  75. package/docs/api/enums/FileCategory.md +1 -1
  76. package/docs/api/enums/LogLevel.md +1 -1
  77. package/docs/api/enums/RBACErrorCode.md +1 -1
  78. package/docs/api/enums/RPCFunction.md +1 -1
  79. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  80. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +1 -1
  82. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  83. package/docs/api/interfaces/AvatarProps.md +1 -1
  84. package/docs/api/interfaces/BadgeProps.md +1 -1
  85. package/docs/api/interfaces/ButtonProps.md +1 -1
  86. package/docs/api/interfaces/CalendarProps.md +1 -1
  87. package/docs/api/interfaces/CardProps.md +1 -1
  88. package/docs/api/interfaces/ColorPalette.md +1 -1
  89. package/docs/api/interfaces/ColorShade.md +1 -1
  90. package/docs/api/interfaces/ComplianceResult.md +1 -1
  91. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  92. package/docs/api/interfaces/DataRecord.md +1 -1
  93. package/docs/api/interfaces/DataTableAction.md +1 -1
  94. package/docs/api/interfaces/DataTableColumn.md +1 -1
  95. package/docs/api/interfaces/DataTableProps.md +1 -1
  96. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  97. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  98. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  99. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  100. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  101. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  102. package/docs/api/interfaces/ExportColumn.md +1 -1
  103. package/docs/api/interfaces/ExportOptions.md +1 -1
  104. package/docs/api/interfaces/FileDisplayProps.md +24 -11
  105. package/docs/api/interfaces/FileMetadata.md +1 -1
  106. package/docs/api/interfaces/FileReference.md +1 -1
  107. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  108. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  109. package/docs/api/interfaces/FileUploadProps.md +1 -1
  110. package/docs/api/interfaces/FooterProps.md +1 -1
  111. package/docs/api/interfaces/FormFieldProps.md +1 -1
  112. package/docs/api/interfaces/FormProps.md +1 -1
  113. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  114. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  115. package/docs/api/interfaces/InputProps.md +1 -1
  116. package/docs/api/interfaces/LabelProps.md +1 -1
  117. package/docs/api/interfaces/LoggerConfig.md +1 -1
  118. package/docs/api/interfaces/LoginFormProps.md +1 -1
  119. package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
  120. package/docs/api/interfaces/NavigationContextType.md +1 -1
  121. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  122. package/docs/api/interfaces/NavigationItem.md +1 -1
  123. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  124. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  125. package/docs/api/interfaces/Organisation.md +1 -1
  126. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  127. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  128. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  129. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  130. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  131. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  132. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  133. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  134. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  135. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  136. package/docs/api/interfaces/PaletteData.md +1 -1
  137. package/docs/api/interfaces/ParsedAddress.md +1 -1
  138. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  139. package/docs/api/interfaces/ProgressProps.md +1 -1
  140. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  141. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  142. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  143. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  144. package/docs/api/interfaces/QuickFix.md +1 -1
  145. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  146. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  147. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  148. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  149. package/docs/api/interfaces/RBACConfig.md +2 -2
  150. package/docs/api/interfaces/RBACContext.md +1 -1
  151. package/docs/api/interfaces/RBACLogger.md +1 -1
  152. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  153. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  154. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  155. package/docs/api/interfaces/RBACPermissionCheckResult.md +2 -2
  156. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  157. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  158. package/docs/api/interfaces/RBACResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleGrantParams.md +2 -2
  160. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  161. package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
  162. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  163. package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
  164. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  165. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  166. package/docs/api/interfaces/RBACRolesListResult.md +2 -2
  167. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  168. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  169. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  170. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  171. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  172. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  173. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  174. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  175. package/docs/api/interfaces/RouteConfig.md +2 -2
  176. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  177. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  178. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  179. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  180. package/docs/api/interfaces/SetupIssue.md +1 -1
  181. package/docs/api/interfaces/StorageConfig.md +1 -1
  182. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  183. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  184. package/docs/api/interfaces/StorageListOptions.md +1 -1
  185. package/docs/api/interfaces/StorageListResult.md +1 -1
  186. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  187. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  188. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  189. package/docs/api/interfaces/StyleImport.md +1 -1
  190. package/docs/api/interfaces/SwitchProps.md +1 -1
  191. package/docs/api/interfaces/TabsContentProps.md +1 -1
  192. package/docs/api/interfaces/TabsListProps.md +1 -1
  193. package/docs/api/interfaces/TabsProps.md +1 -1
  194. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  195. package/docs/api/interfaces/TextareaProps.md +1 -1
  196. package/docs/api/interfaces/ToastActionElement.md +1 -1
  197. package/docs/api/interfaces/ToastProps.md +1 -1
  198. package/docs/api/interfaces/UnifiedAuthContextType.md +60 -38
  199. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  200. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  201. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  202. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  203. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  205. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  207. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  208. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  209. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  210. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  211. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  212. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  213. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  214. package/docs/api/interfaces/UserEventAccess.md +1 -1
  215. package/docs/api/interfaces/UserMenuProps.md +1 -1
  216. package/docs/api/interfaces/UserProfile.md +1 -1
  217. package/docs/api/modules.md +194 -209
  218. package/docs/getting-started/cursor-rules.md +262 -0
  219. package/docs/getting-started/installation-guide.md +6 -1
  220. package/docs/getting-started/quick-start.md +6 -1
  221. package/docs/migration/MIGRATION_GUIDE.md +4 -4
  222. package/docs/migration/REACT_19_MIGRATION.md +227 -0
  223. package/docs/migration/database-changes-december-2025.md +2 -1
  224. package/docs/rbac/event-based-apps.md +124 -6
  225. package/docs/standards/README.md +39 -0
  226. package/docs/troubleshooting/migration.md +4 -4
  227. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  228. package/package.json +11 -6
  229. package/scripts/audit-consuming-app.cjs +961 -0
  230. package/scripts/check-pace-core-compliance.cjs +315 -61
  231. package/scripts/install-cursor-rules.cjs +236 -0
  232. package/src/__tests__/helpers/test-providers.tsx +1 -1
  233. package/src/__tests__/helpers/test-utils.tsx +1 -1
  234. package/src/__tests__/rls-policies.test.ts +3 -1
  235. package/src/components/Badge/Badge.tsx +2 -4
  236. package/src/components/Button/Button.tsx +5 -4
  237. package/src/components/Calendar/Calendar.tsx +1 -1
  238. package/src/components/DataTable/DataTable.test.tsx +57 -93
  239. package/src/components/DataTable/DataTable.tsx +2 -2
  240. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
  241. package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
  242. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
  243. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
  244. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
  245. package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
  246. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +88 -16
  247. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  248. package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
  249. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  250. package/src/components/DataTable/components/DataTableCore.tsx +4 -7
  251. package/src/components/DataTable/components/DataTableModals.tsx +1 -1
  252. package/src/components/DataTable/components/EditableRow.tsx +1 -1
  253. package/src/components/DataTable/components/UnifiedTableBody.tsx +86 -17
  254. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  255. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  256. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  257. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  258. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  259. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  260. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  261. package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
  262. package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
  263. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  264. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  265. package/src/components/Dialog/Dialog.tsx +6 -5
  266. package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
  267. package/src/components/EventSelector/EventSelector.tsx +1 -1
  268. package/src/components/FileDisplay/FileDisplay.test.tsx +4 -3
  269. package/src/components/FileDisplay/FileDisplay.tsx +16 -4
  270. package/src/components/Footer/Footer.tsx +1 -1
  271. package/src/components/Form/Form.test.tsx +36 -15
  272. package/src/components/Form/Form.tsx +30 -26
  273. package/src/components/Header/Header.tsx +1 -1
  274. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  275. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  276. package/src/components/Input/Input.tsx +28 -30
  277. package/src/components/Label/Label.tsx +1 -1
  278. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  279. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  280. package/src/components/LoginForm/LoginForm.tsx +8 -8
  281. package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
  282. package/src/components/NavigationMenu/NavigationMenu.tsx +2 -11
  283. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -1
  284. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  285. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +75 -52
  286. package/src/components/PaceAppLayout/PaceAppLayout.tsx +98 -69
  287. package/src/components/PaceAppLayout/README.md +1 -1
  288. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -8
  289. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  290. package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
  291. package/src/components/Progress/Progress.tsx +1 -1
  292. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
  293. package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
  294. package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
  295. package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
  296. package/src/components/Select/Select.tsx +33 -22
  297. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
  298. package/src/components/Table/Table.tsx +1 -1
  299. package/src/components/Textarea/Textarea.tsx +27 -29
  300. package/src/components/Toast/Toast.tsx +1 -1
  301. package/src/components/Tooltip/Tooltip.tsx +1 -1
  302. package/src/components/UserMenu/UserMenu.tsx +1 -1
  303. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  304. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +14 -7
  305. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  306. package/src/hooks/public/usePublicEvent.ts +1 -1
  307. package/src/hooks/public/usePublicEventLogo.ts +1 -1
  308. package/src/hooks/public/usePublicRouteParams.ts +1 -1
  309. package/src/hooks/services/useAuthService.ts +21 -3
  310. package/src/hooks/services/useEventService.ts +21 -3
  311. package/src/hooks/services/useInactivityService.ts +21 -3
  312. package/src/hooks/services/useOrganisationService.ts +21 -3
  313. package/src/hooks/useDataTableState.ts +8 -18
  314. package/src/hooks/useFileDisplay.ts +10 -17
  315. package/src/hooks/useFocusManagement.ts +2 -2
  316. package/src/hooks/useFocusTrap.ts +4 -4
  317. package/src/hooks/useFormDialog.ts +8 -7
  318. package/src/hooks/useInactivityTracker.ts +1 -1
  319. package/src/hooks/usePermissionCache.ts +1 -1
  320. package/src/hooks/useSecureDataAccess.test.ts +16 -9
  321. package/src/hooks/useSecureDataAccess.ts +22 -6
  322. package/src/hooks/useToast.ts +2 -2
  323. package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
  324. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  325. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  326. package/src/providers/services/EventServiceProvider.tsx +0 -8
  327. package/src/providers/services/UnifiedAuthProvider.tsx +196 -46
  328. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
  329. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +34 -40
  330. package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
  331. package/src/rbac/adapters.tsx +3 -22
  332. package/src/rbac/api.test.ts +2 -2
  333. package/src/rbac/api.ts +7 -1
  334. package/src/rbac/components/EnhancedNavigationMenu.tsx +3 -16
  335. package/src/rbac/components/NavigationGuard.tsx +2 -11
  336. package/src/rbac/components/NavigationProvider.tsx +1 -2
  337. package/src/rbac/components/PagePermissionGuard.tsx +1 -1
  338. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  339. package/src/rbac/components/PermissionEnforcer.tsx +46 -13
  340. package/src/rbac/components/RoleBasedRouter.tsx +1 -1
  341. package/src/rbac/components/SecureDataProvider.tsx +1 -2
  342. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
  343. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
  344. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
  345. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
  346. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
  347. package/src/rbac/engine.ts +14 -2
  348. package/src/rbac/hooks/index.ts +0 -3
  349. package/src/rbac/hooks/usePermissions.ts +51 -11
  350. package/src/rbac/hooks/useRBAC.ts +3 -13
  351. package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
  352. package/src/rbac/hooks/useResolvedScope.ts +58 -33
  353. package/src/rbac/hooks/useSecureSupabase.ts +4 -9
  354. package/src/rbac/secureClient.ts +43 -0
  355. package/src/services/EventService.ts +4 -57
  356. package/src/services/InactivityService.ts +127 -34
  357. package/src/services/OrganisationService.ts +68 -10
  358. package/src/utils/security/secureDataAccess.test.ts +31 -20
  359. package/src/utils/security/secureDataAccess.ts +4 -3
  360. package/dist/chunk-6C4YBBJM.js.map +0 -1
  361. package/dist/chunk-6LTQQAT6.js.map +0 -1
  362. package/dist/chunk-6TQDD426.js.map +0 -1
  363. package/dist/chunk-LOMZXPSN.js.map +0 -1
  364. package/dist/chunk-OETXORNB.js.map +0 -1
  365. package/dist/chunk-ULHIJK66.js.map +0 -1
  366. package/dist/chunk-VKB2CO4Z.js.map +0 -1
  367. package/dist/chunk-VRGWKHDB.js.map +0 -1
  368. package/dist/chunk-XNYQOL3Z.js.map +0 -1
  369. package/dist/chunk-XYXSXPUK.js.map +0 -1
  370. package/scripts/check-pace-core-compliance.js +0 -512
  371. package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
  372. package/src/utils/context/superAdminOverride.ts +0 -58
  373. /package/dist/{DataTable-WKRZD47S.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
  374. /package/dist/{UnifiedAuthProvider-FTSG5XH7.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
  375. /package/dist/{api-IHKALJZD.js.map → api-N774RPUA.js.map} +0 -0
  376. /package/dist/{chunk-QWWZ5CAQ.js.map → chunk-LXQLPRQ2.js.map} +0 -0
  377. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  378. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  379. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  380. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -212,4 +212,4 @@ export {
212
212
  CachedAppIdResolver,
213
213
  cachedAppIdResolver
214
214
  };
215
- //# sourceMappingURL=chunk-HW3OVDUF.js.map
215
+ //# sourceMappingURL=chunk-J36DSWQK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/LoadingSpinner/LoadingSpinner.tsx","../src/utils/timezone/timezone.ts","../src/utils/app/appIdResolver.ts"],"sourcesContent":["/**\n * @file LoadingSpinner Component\n * @package @jmruthers/pace-core\n * @module Components/LoadingSpinner\n * @since 0.1.0\n *\n * A simple, accessible loading spinner component for indicating loading states.\n * Provides smooth animations with reduced motion support for accessibility.\n *\n * Features:\n * - Multiple size variants (sm, md, lg)\n * - Smooth CSS animations\n * - Reduced motion support for accessibility\n * - Screen reader friendly with proper ARIA attributes\n * - Customizable styling\n * - Lightweight and performant\n *\n * @example\n * ```tsx\n * // Basic loading spinner\n * <LoadingSpinner />\n * \n * // Different sizes\n * <LoadingSpinner size=\"sm\" />\n * <LoadingSpinner size=\"md\" />\n * <LoadingSpinner size=\"lg\" />\n * \n * // With custom styling\n * <LoadingSpinner \n * size=\"lg\" \n * className=\"text-main-500\" \n * />\n * \n * // In a button\n * <Button disabled>\n * <LoadingSpinner size=\"sm\" className=\"mr-2\" />\n * Loading...\n * </Button>\n * ```\n *\n * @accessibility\n * - WCAG 2.1 AA compliant\n * - Proper ARIA role=\"status\"\n * - Screen reader announcement with \"Loading...\" text\n * - Reduced motion support for users with vestibular disorders\n * - High contrast support\n *\n * @performance\n * - CSS-only animations for optimal performance\n * - No JavaScript dependencies\n * - Minimal DOM structure\n * - Efficient rendering\n *\n * @dependencies\n * - React 18+ - Component framework\n * - Tailwind CSS - Styling and animations\n */\n\nimport React from 'react';\n\n/**\n * Props for the LoadingSpinner component\n */\nexport interface LoadingSpinnerProps {\n /** Size variant of the spinner */\n size?: 'sm' | 'md' | 'lg';\n /** Additional CSS classes for styling */\n className?: string;\n}\n\n/**\n * LoadingSpinner component\n * A simple, accessible loading spinner for indicating loading states\n * \n * @param props - Spinner configuration and styling\n * @returns JSX.Element - The rendered loading spinner\n * \n * @example\n * ```tsx\n * <LoadingSpinner size=\"lg\" className=\"text-main-500\" />\n * ```\n */\nexport const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({ \n size = 'md', \n className = '' \n}) => {\n const sizeClasses: Record<'sm' | 'md' | 'lg', string> = {\n sm: 'size-4',\n md: 'size-6',\n lg: 'size-8'\n };\n\n // Ensure we always have a valid size class, defaulting to 'md' if invalid\n const validSize = size && size in sizeClasses ? size : 'md';\n const sizeClass = sizeClasses[validSize];\n\n return (\n <canvas className={`inline-block animate-spin rounded-full border-2 border-solid border-current border-r-transparent motion-reduce:animate-[spin_1.5s_linear_infinite] ${sizeClass} ${className}`.trim()} role=\"status\">\n <span className=\"sr-only\">Loading...</span>\n </canvas>\n );\n};\n","/**\n * @file Timezone Utilities\n * @package @jmruthers/pace-core\n * @module Utils/Timezone\n * @since 0.1.0\n *\n * Comprehensive timezone conversion and formatting utilities using date-fns-tz and native Intl APIs.\n * Provides functions for timezone-aware date operations, conversions, and formatting.\n *\n * Features:\n * - Format dates in specific timezones\n * - Get timezone abbreviations\n * - Convert between UTC and timezone local times\n * - Round dates to nearest minutes\n * - Calculate timezone differences\n * - Get user's browser timezone\n *\n * @example\n * ```ts\n * import { toZonedTime, fromZonedTime, formatInTimeZone, getUserTimeZone } from '@jmruthers/pace-core/utils/timezone';\n *\n * // Convert UTC to local timezone\n * const utcDate = new Date('2024-01-15T10:00:00Z');\n * const localDate = toZonedTime(utcDate, 'America/New_York');\n *\n * // Convert local time to UTC\n * const localInput = new Date(2024, 0, 15, 10, 0);\n * const utcDate = fromZonedTime(localInput, 'America/New_York');\n *\n * // Format with timezone\n * const formatted = formatInTimeZone(utcDate, 'America/New_York', 'MMM dd, yyyy HH:mm');\n * ```\n */\n\nimport { format, parseISO, addMinutes, differenceInHours, isValid } from 'date-fns';\nimport { formatInTimeZone as fnsFormatInTimeZone, toZonedTime as fnsToZonedTime, fromZonedTime as fnsFromZonedTime } from 'date-fns-tz';\n\n/**\n * Format a date in a specific timezone using date-fns format strings\n *\n * @param date - Date to format (Date object, ISO string, or timestamp)\n * @param timeZone - IANA timezone string (e.g., 'America/New_York')\n * @param formatStr - date-fns format string (e.g., 'MMM dd, yyyy HH:mm')\n * @returns Formatted date string or 'Invalid date' on error\n *\n * @example\n * ```ts\n * formatInTimeZone(new Date('2024-01-15T10:00:00Z'), 'America/New_York', 'MMM dd, yyyy HH:mm');\n * // \"Jan 15, 2024 05:00\"\n * ```\n */\nexport function formatInTimeZone(\n date: Date | string | number,\n timeZone: string,\n formatStr: string\n): string {\n try {\n if (!timeZone || typeof timeZone !== 'string') {\n return 'Invalid date';\n }\n\n let dateObj: Date;\n if (typeof date === 'string') {\n dateObj = parseISO(date);\n } else if (typeof date === 'number') {\n dateObj = new Date(date);\n } else {\n dateObj = date;\n }\n\n if (!isValid(dateObj)) {\n return 'Invalid date';\n }\n\n return fnsFormatInTimeZone(dateObj, timeZone, formatStr);\n } catch {\n return 'Invalid date';\n }\n}\n\n/**\n * Get the timezone abbreviation (EST, PST, etc.) for a date in a specific timezone\n *\n * @param date - Date to get abbreviation for\n * @param timeZone - IANA timezone string\n * @returns Timezone abbreviation or timezone name on error\n *\n * @example\n * ```ts\n * getTimezoneAbbreviation(new Date(), 'America/New_York');\n * // \"EST\" or \"EDT\" depending on date\n * ```\n */\nexport function getTimezoneAbbreviation(date: Date, timeZone: string): string {\n try {\n if (!timeZone || typeof timeZone !== 'string') {\n return timeZone || 'UTC';\n }\n\n if (!isValid(date)) {\n return timeZone;\n }\n\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone,\n timeZoneName: 'short'\n });\n\n const parts = formatter.formatToParts(date);\n const timeZoneName = parts.find(part => part.type === 'timeZoneName');\n\n return timeZoneName?.value || timeZone;\n } catch {\n return timeZone || 'UTC';\n }\n}\n\n/**\n * Format time only in a specific timezone\n *\n * @param date - Date to format\n * @param timeZone - IANA timezone string\n * @returns Formatted time string (HH:mm format) or 'Invalid date' on error\n *\n * @example\n * ```ts\n * formatTimeInTimeZone(new Date('2024-01-15T10:00:00Z'), 'America/New_York');\n * // \"05:00\"\n * ```\n */\nexport function formatTimeInTimeZone(date: Date | string, timeZone: string): string {\n return formatInTimeZone(date, timeZone, 'HH:mm');\n}\n\n/**\n * Get the user's local timezone from the browser\n *\n * @returns IANA timezone string (e.g., 'America/New_York')\n *\n * @example\n * ```ts\n * const userTz = getUserTimeZone();\n * // \"America/New_York\" or user's actual timezone\n * ```\n */\nexport function getUserTimeZone(): string {\n try {\n if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {\n return Intl.DateTimeFormat().resolvedOptions().timeZone;\n }\n return 'UTC';\n } catch {\n return 'UTC';\n }\n}\n\n/**\n * Convert a UTC date to a specific timezone's local time representation\n *\n * @param date - UTC date to convert\n * @param timezone - IANA timezone string\n * @returns Date object representing local time in the timezone\n *\n * @example\n * ```ts\n * const utcDate = new Date('2024-01-15T10:00:00Z');\n * const localDate = toZonedTime(utcDate, 'America/New_York');\n * // Returns Date object representing 5:00 AM in New York\n * ```\n */\nexport function toZonedTime(date: Date, timezone: string): Date {\n try {\n if (!timezone || typeof timezone !== 'string') {\n return date;\n }\n\n if (!isValid(date)) {\n return date;\n }\n\n return fnsToZonedTime(date, timezone);\n } catch {\n return date;\n }\n}\n\n/**\n * Convert a local time in a specific timezone to UTC\n *\n * @param localDate - Local date in the timezone\n * @param timezone - IANA timezone string\n * @returns Date object in UTC\n *\n * @example\n * ```ts\n * const localDate = new Date(2024, 0, 15, 10, 0); // Jan 15, 2024 10:00 AM local\n * const utcDate = fromZonedTime(localDate, 'America/New_York');\n * // Returns Date object representing 3:00 PM UTC (if EST) or 2:00 PM UTC (if EDT)\n * ```\n */\nexport function fromZonedTime(localDate: Date, timezone: string): Date {\n try {\n if (!timezone || typeof timezone !== 'string') {\n return localDate;\n }\n\n if (!isValid(localDate)) {\n return localDate;\n }\n\n return fnsFromZonedTime(localDate, timezone);\n } catch {\n return localDate;\n }\n}\n\n/**\n * Round a date to the nearest X minutes\n *\n * @param date - Date to round\n * @param minutesStep - Number of minutes to round to (default: 5)\n * @returns Rounded date\n *\n * @example\n * ```ts\n * const date = new Date('2024-01-15T10:23:00Z');\n * roundToNearestMinutes(date, 5);\n * // Returns Date object for 10:25:00\n * ```\n */\nexport function roundToNearestMinutes(date: Date, minutesStep: number = 5): Date {\n try {\n if (!isValid(date)) {\n return date;\n }\n\n if (minutesStep <= 0 || !Number.isInteger(minutesStep)) {\n return date;\n }\n\n const minutes = date.getMinutes();\n const roundedMinutes = Math.round(minutes / minutesStep) * minutesStep;\n const diff = roundedMinutes - minutes;\n\n return addMinutes(date, diff);\n } catch {\n return date;\n }\n}\n\n/**\n * Calculate the time difference between two timezones in hours\n *\n * @param fromTimeZone - Source timezone (IANA string)\n * @param toTimeZone - Target timezone (IANA string)\n * @returns Difference in hours (positive if toTimeZone is ahead)\n *\n * @example\n * ```ts\n * getTimeZoneDifference('America/New_York', 'America/Los_Angeles');\n * // -3 (Los Angeles is 3 hours behind New York)\n * ```\n */\nexport function getTimeZoneDifference(fromTimeZone: string, toTimeZone: string): number {\n try {\n if (!fromTimeZone || !toTimeZone || typeof fromTimeZone !== 'string' || typeof toTimeZone !== 'string') {\n return 0;\n }\n\n const now = new Date();\n const fromDate = toZonedTime(now, fromTimeZone);\n const toDate = toZonedTime(now, toTimeZone);\n\n const diff = differenceInHours(toDate, fromDate);\n // Handle NaN case (differenceInHours can return NaN for invalid dates)\n return isNaN(diff) ? 0 : diff;\n } catch {\n return 0;\n }\n}\n\n","/**\n * App ID Resolution Utility\n * @package @jmruthers/pace-core\n * @module Utils/AppIdResolver\n * @since 1.0.0\n * \n * This module provides utilities to resolve app names to app IDs for database operations.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { Database } from '../../types/database';\nimport { createLogger } from '../core/logger';\n\nconst log = createLogger('AppIdResolver');\n\n/**\n * Resolves an app name to its corresponding app ID\n * \n * @param supabase - Supabase client instance\n * @param appName - The app name to resolve\n * @returns Promise resolving to the app ID or null if not found\n */\nexport async function getAppId(\n supabase: SupabaseClient<Database>,\n appName: string\n): Promise<string | null> {\n try {\n const { data, error } = await supabase\n .from('rbac_apps')\n .select('id')\n .ilike('name', appName)\n .eq('is_active', true)\n .single();\n\n if (error) {\n log.error('Failed to resolve app ID for app name:', appName, error);\n return null;\n }\n\n return (data as { id: string } | null)?.id || null;\n } catch (error) {\n log.error('Error resolving app ID for app name:', appName, error);\n return null;\n }\n}\n\n/**\n * Resolves multiple app names to their corresponding app IDs\n * \n * @param supabase - Supabase client instance\n * @param appNames - Array of app names to resolve\n * @returns Promise resolving to a map of app names to app IDs\n */\nexport async function getAppIds(\n supabase: SupabaseClient<Database>,\n appNames: string[]\n): Promise<Record<string, string | null>> {\n try {\n // For case-insensitive matching with multiple values, we need to use OR conditions\n // since PostgreSQL doesn't support case-insensitive IN with ILIKE\n const orConditions = appNames.map(name => `name.ilike.${name}`).join(',');\n \n const { data, error } = await supabase\n .from('rbac_apps')\n .select('id, name')\n .or(orConditions)\n .eq('is_active', true);\n\n if (error) {\n log.error('Failed to resolve app IDs for app names:', appNames, error);\n return {};\n }\n\n const result: Record<string, string | null> = {};\n \n // Initialize all app names with null\n appNames.forEach(name => {\n result[name] = null;\n });\n\n // Set resolved app IDs - match case-insensitively\n (data as { id: string; name: string }[] | null)?.forEach(app => {\n // Find the original app name that matches (case-insensitive)\n const originalName = appNames.find(name => \n name.toLowerCase() === app.name.toLowerCase()\n );\n if (originalName) {\n result[originalName] = app.id;\n }\n });\n\n return result;\n } catch (error) {\n log.error('Error resolving app IDs for app names:', appNames, error);\n return {};\n }\n}\n\n/**\n * Cached app ID resolver with TTL\n */\nexport class CachedAppIdResolver {\n private cache = new Map<string, { id: string | null; expires: number }>();\n private ttl = 5 * 60 * 1000; // 5 minutes\n\n async getAppId(\n supabase: SupabaseClient<Database>,\n appName: string\n ): Promise<string | null> {\n const now = Date.now();\n const cached = this.cache.get(appName);\n\n if (cached && cached.expires > now) {\n return cached.id;\n }\n\n const id = await getAppId(supabase, appName);\n this.cache.set(appName, { id, expires: now + this.ttl });\n\n return id;\n }\n\n clearCache(): void {\n this.cache.clear();\n }\n\n clearCacheForApp(appName: string): void {\n this.cache.delete(appName);\n }\n}\n\n// Export singleton instance\nexport const cachedAppIdResolver = new CachedAppIdResolver();\n"],"mappings":";;;;;AAkGM;AAhBC,IAAM,iBAAgD,CAAC;AAAA,EAC5D,OAAO;AAAA,EACP,YAAY;AACd,MAAM;AACJ,QAAM,cAAkD;AAAA,IACtD,IAAI;AAAA,IACJ,IAAI;AAAA,IACJ,IAAI;AAAA,EACN;AAGA,QAAM,YAAY,QAAQ,QAAQ,cAAc,OAAO;AACvD,QAAM,YAAY,YAAY,SAAS;AAEvC,SACE,oBAAC,YAAO,WAAW,sJAAsJ,SAAS,IAAI,SAAS,GAAG,KAAK,GAAG,MAAK,UAC7M,8BAAC,UAAK,WAAU,WAAU,wBAAU,GACtC;AAEJ;;;ACnEA,SAAiB,UAAU,YAAY,mBAAmB,eAAe;AACzE,SAAS,oBAAoB,qBAAqB,eAAe,gBAAgB,iBAAiB,wBAAwB;AAgBnH,SAAS,iBACd,MACA,UACA,WACQ;AACR,MAAI;AACF,QAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,aAAO;AAAA,IACT;AAEA,QAAI;AACJ,QAAI,OAAO,SAAS,UAAU;AAC5B,gBAAU,SAAS,IAAI;AAAA,IACzB,WAAW,OAAO,SAAS,UAAU;AACnC,gBAAU,IAAI,KAAK,IAAI;AAAA,IACzB,OAAO;AACL,gBAAU;AAAA,IACZ;AAEA,QAAI,CAAC,QAAQ,OAAO,GAAG;AACrB,aAAO;AAAA,IACT;AAEA,WAAO,oBAAoB,SAAS,UAAU,SAAS;AAAA,EACzD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAeO,SAAS,wBAAwB,MAAY,UAA0B;AAC5E,MAAI;AACF,QAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,aAAO,YAAY;AAAA,IACrB;AAEA,QAAI,CAAC,QAAQ,IAAI,GAAG;AAClB,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,IAAI,KAAK,eAAe,SAAS;AAAA,MACjD;AAAA,MACA,cAAc;AAAA,IAChB,CAAC;AAED,UAAM,QAAQ,UAAU,cAAc,IAAI;AAC1C,UAAM,eAAe,MAAM,KAAK,UAAQ,KAAK,SAAS,cAAc;AAEpE,WAAO,cAAc,SAAS;AAAA,EAChC,QAAQ;AACN,WAAO,YAAY;AAAA,EACrB;AACF;AAeO,SAAS,qBAAqB,MAAqB,UAA0B;AAClF,SAAO,iBAAiB,MAAM,UAAU,OAAO;AACjD;AAaO,SAAS,kBAA0B;AACxC,MAAI;AACF,QAAI,OAAO,SAAS,eAAe,KAAK,gBAAgB;AACtD,aAAO,KAAK,eAAe,EAAE,gBAAgB,EAAE;AAAA,IACjD;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBO,SAAS,YAAY,MAAY,UAAwB;AAC9D,MAAI;AACF,QAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,QAAQ,IAAI,GAAG;AAClB,aAAO;AAAA,IACT;AAEA,WAAO,eAAe,MAAM,QAAQ;AAAA,EACtC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBO,SAAS,cAAc,WAAiB,UAAwB;AACrE,MAAI;AACF,QAAI,CAAC,YAAY,OAAO,aAAa,UAAU;AAC7C,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,QAAQ,SAAS,GAAG;AACvB,aAAO;AAAA,IACT;AAEA,WAAO,iBAAiB,WAAW,QAAQ;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBO,SAAS,sBAAsB,MAAY,cAAsB,GAAS;AAC/E,MAAI;AACF,QAAI,CAAC,QAAQ,IAAI,GAAG;AAClB,aAAO;AAAA,IACT;AAEA,QAAI,eAAe,KAAK,CAAC,OAAO,UAAU,WAAW,GAAG;AACtD,aAAO;AAAA,IACT;AAEA,UAAM,UAAU,KAAK,WAAW;AAChC,UAAM,iBAAiB,KAAK,MAAM,UAAU,WAAW,IAAI;AAC3D,UAAM,OAAO,iBAAiB;AAE9B,WAAO,WAAW,MAAM,IAAI;AAAA,EAC9B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAeO,SAAS,sBAAsB,cAAsB,YAA4B;AACtF,MAAI;AACF,QAAI,CAAC,gBAAgB,CAAC,cAAc,OAAO,iBAAiB,YAAY,OAAO,eAAe,UAAU;AACtG,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,WAAW,YAAY,KAAK,YAAY;AAC9C,UAAM,SAAS,YAAY,KAAK,UAAU;AAE1C,UAAM,OAAO,kBAAkB,QAAQ,QAAQ;AAE/C,WAAO,MAAM,IAAI,IAAI,IAAI;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AC1QA,IAAM,MAAM,aAAa,eAAe;AASxC,eAAsB,SACpB,UACA,SACwB;AACxB,MAAI;AACF,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,WAAW,EAChB,OAAO,IAAI,EACX,MAAM,QAAQ,OAAO,EACrB,GAAG,aAAa,IAAI,EACpB,OAAO;AAEV,QAAI,OAAO;AACT,UAAI,MAAM,0CAA0C,SAAS,KAAK;AAClE,aAAO;AAAA,IACT;AAEA,WAAQ,MAAgC,MAAM;AAAA,EAChD,SAAS,OAAO;AACd,QAAI,MAAM,wCAAwC,SAAS,KAAK;AAChE,WAAO;AAAA,EACT;AACF;AASA,eAAsB,UACpB,UACA,UACwC;AACxC,MAAI;AAGF,UAAM,eAAe,SAAS,IAAI,UAAQ,cAAc,IAAI,EAAE,EAAE,KAAK,GAAG;AAExE,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAC3B,KAAK,WAAW,EAChB,OAAO,UAAU,EACjB,GAAG,YAAY,EACf,GAAG,aAAa,IAAI;AAEvB,QAAI,OAAO;AACT,UAAI,MAAM,4CAA4C,UAAU,KAAK;AACrE,aAAO,CAAC;AAAA,IACV;AAEA,UAAM,SAAwC,CAAC;AAG/C,aAAS,QAAQ,UAAQ;AACvB,aAAO,IAAI,IAAI;AAAA,IACjB,CAAC;AAGD,IAAC,MAAgD,QAAQ,SAAO;AAE9D,YAAM,eAAe,SAAS;AAAA,QAAK,UACjC,KAAK,YAAY,MAAM,IAAI,KAAK,YAAY;AAAA,MAC9C;AACA,UAAI,cAAc;AAChB,eAAO,YAAY,IAAI,IAAI;AAAA,MAC7B;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT,SAAS,OAAO;AACd,QAAI,MAAM,0CAA0C,UAAU,KAAK;AACnE,WAAO,CAAC;AAAA,EACV;AACF;AAKO,IAAM,sBAAN,MAA0B;AAAA,EAA1B;AACL,SAAQ,QAAQ,oBAAI,IAAoD;AACxE,SAAQ,MAAM,IAAI,KAAK;AAAA;AAAA;AAAA,EAEvB,MAAM,SACJ,UACA,SACwB;AACxB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,SAAS,KAAK,MAAM,IAAI,OAAO;AAErC,QAAI,UAAU,OAAO,UAAU,KAAK;AAClC,aAAO,OAAO;AAAA,IAChB;AAEA,UAAM,KAAK,MAAM,SAAS,UAAU,OAAO;AAC3C,SAAK,MAAM,IAAI,SAAS,EAAE,IAAI,SAAS,MAAM,KAAK,IAAI,CAAC;AAEvD,WAAO;AAAA,EACT;AAAA,EAEA,aAAmB;AACjB,SAAK,MAAM,MAAM;AAAA,EACnB;AAAA,EAEA,iBAAiB,SAAuB;AACtC,SAAK,MAAM,OAAO,OAAO;AAAA,EAC3B;AACF;AAGO,IAAM,sBAAsB,IAAI,oBAAoB;","names":[]}
1
+ {"version":3,"sources":["../src/components/LoadingSpinner/LoadingSpinner.tsx","../src/utils/timezone/timezone.ts","../src/utils/app/appIdResolver.ts"],"sourcesContent":["/**\n * @file LoadingSpinner Component\n * @package @jmruthers/pace-core\n * @module Components/LoadingSpinner\n * @since 0.1.0\n *\n * A simple, accessible loading spinner component for indicating loading states.\n * Provides smooth animations with reduced motion support for accessibility.\n *\n * Features:\n * - Multiple size variants (sm, md, lg)\n * - Smooth CSS animations\n * - Reduced motion support for accessibility\n * - Screen reader friendly with proper ARIA attributes\n * - Customizable styling\n * - Lightweight and performant\n *\n * @example\n * ```tsx\n * // Basic loading spinner\n * <LoadingSpinner />\n * \n * // Different sizes\n * <LoadingSpinner size=\"sm\" />\n * <LoadingSpinner size=\"md\" />\n * <LoadingSpinner size=\"lg\" />\n * \n * // With custom styling\n * <LoadingSpinner \n * size=\"lg\" \n * className=\"text-main-500\" \n * />\n * \n * // In a button\n * <Button disabled>\n * <LoadingSpinner size=\"sm\" className=\"mr-2\" />\n * Loading...\n * </Button>\n * ```\n *\n * @accessibility\n * - WCAG 2.1 AA compliant\n * - Proper ARIA role=\"status\"\n * - Screen reader announcement with \"Loading...\" text\n * - Reduced motion support for users with vestibular disorders\n * - High contrast support\n *\n * @performance\n * - CSS-only animations for optimal performance\n * - No JavaScript dependencies\n * - Minimal DOM structure\n * - Efficient rendering\n *\n * @dependencies\n * - React 19+ - Component framework\n * - Tailwind CSS - Styling and animations\n */\n\nimport React from 'react';\n\n/**\n * Props for the LoadingSpinner component\n */\nexport interface LoadingSpinnerProps {\n /** Size variant of the spinner */\n size?: 'sm' | 'md' | 'lg';\n /** Additional CSS classes for styling */\n className?: string;\n}\n\n/**\n * LoadingSpinner component\n * A simple, accessible loading spinner for indicating loading states\n * \n * @param props - Spinner configuration and styling\n * @returns JSX.Element - The rendered loading spinner\n * \n * @example\n * ```tsx\n * <LoadingSpinner size=\"lg\" className=\"text-main-500\" />\n * ```\n */\nexport const LoadingSpinner: React.FC<LoadingSpinnerProps> = ({ \n size = 'md', \n className = '' \n}) => {\n const sizeClasses: Record<'sm' | 'md' | 'lg', string> = {\n sm: 'size-4',\n md: 'size-6',\n lg: 'size-8'\n };\n\n // Ensure we always have a valid size class, defaulting to 'md' if invalid\n const validSize = size && size in sizeClasses ? size : 'md';\n const sizeClass = sizeClasses[validSize];\n\n return (\n <canvas className={`inline-block animate-spin rounded-full border-2 border-solid border-current border-r-transparent motion-reduce:animate-[spin_1.5s_linear_infinite] ${sizeClass} ${className}`.trim()} role=\"status\">\n <span className=\"sr-only\">Loading...</span>\n </canvas>\n );\n};\n","/**\n * @file Timezone Utilities\n * @package @jmruthers/pace-core\n * @module Utils/Timezone\n * @since 0.1.0\n *\n * Comprehensive timezone conversion and formatting utilities using date-fns-tz and native Intl APIs.\n * Provides functions for timezone-aware date operations, conversions, and formatting.\n *\n * Features:\n * - Format dates in specific timezones\n * - Get timezone abbreviations\n * - Convert between UTC and timezone local times\n * - Round dates to nearest minutes\n * - Calculate timezone differences\n * - Get user's browser timezone\n *\n * @example\n * ```ts\n * import { toZonedTime, fromZonedTime, formatInTimeZone, getUserTimeZone } from '@jmruthers/pace-core/utils/timezone';\n *\n * // Convert UTC to local timezone\n * const utcDate = new Date('2024-01-15T10:00:00Z');\n * const localDate = toZonedTime(utcDate, 'America/New_York');\n *\n * // Convert local time to UTC\n * const localInput = new Date(2024, 0, 15, 10, 0);\n * const utcDate = fromZonedTime(localInput, 'America/New_York');\n *\n * // Format with timezone\n * const formatted = formatInTimeZone(utcDate, 'America/New_York', 'MMM dd, yyyy HH:mm');\n * ```\n */\n\nimport { format, parseISO, addMinutes, differenceInHours, isValid } from 'date-fns';\nimport { formatInTimeZone as fnsFormatInTimeZone, toZonedTime as fnsToZonedTime, fromZonedTime as fnsFromZonedTime } from 'date-fns-tz';\n\n/**\n * Format a date in a specific timezone using date-fns format strings\n *\n * @param date - Date to format (Date object, ISO string, or timestamp)\n * @param timeZone - IANA timezone string (e.g., 'America/New_York')\n * @param formatStr - date-fns format string (e.g., 'MMM dd, yyyy HH:mm')\n * @returns Formatted date string or 'Invalid date' on error\n *\n * @example\n * ```ts\n * formatInTimeZone(new Date('2024-01-15T10:00:00Z'), 'America/New_York', 'MMM dd, yyyy HH:mm');\n * // \"Jan 15, 2024 05:00\"\n * ```\n */\nexport function formatInTimeZone(\n date: Date | string | number,\n timeZone: string,\n formatStr: string\n): string {\n try {\n if (!timeZone || typeof timeZone !== 'string') {\n return 'Invalid date';\n }\n\n let dateObj: Date;\n if (typeof date === 'string') {\n dateObj = parseISO(date);\n } else if (typeof date === 'number') {\n dateObj = new Date(date);\n } else {\n dateObj = date;\n }\n\n if (!isValid(dateObj)) {\n return 'Invalid date';\n }\n\n return fnsFormatInTimeZone(dateObj, timeZone, formatStr);\n } catch {\n return 'Invalid date';\n }\n}\n\n/**\n * Get the timezone abbreviation (EST, PST, etc.) for a date in a specific timezone\n *\n * @param date - Date to get abbreviation for\n * @param timeZone - IANA timezone string\n * @returns Timezone abbreviation or timezone name on error\n *\n * @example\n * ```ts\n * getTimezoneAbbreviation(new Date(), 'America/New_York');\n * // \"EST\" or \"EDT\" depending on date\n * ```\n */\nexport function getTimezoneAbbreviation(date: Date, timeZone: string): string {\n try {\n if (!timeZone || typeof timeZone !== 'string') {\n return timeZone || 'UTC';\n }\n\n if (!isValid(date)) {\n return timeZone;\n }\n\n const formatter = new Intl.DateTimeFormat('en-US', {\n timeZone,\n timeZoneName: 'short'\n });\n\n const parts = formatter.formatToParts(date);\n const timeZoneName = parts.find(part => part.type === 'timeZoneName');\n\n return timeZoneName?.value || timeZone;\n } catch {\n return timeZone || 'UTC';\n }\n}\n\n/**\n * Format time only in a specific timezone\n *\n * @param date - Date to format\n * @param timeZone - IANA timezone string\n * @returns Formatted time string (HH:mm format) or 'Invalid date' on error\n *\n * @example\n * ```ts\n * formatTimeInTimeZone(new Date('2024-01-15T10:00:00Z'), 'America/New_York');\n * // \"05:00\"\n * ```\n */\nexport function formatTimeInTimeZone(date: Date | string, timeZone: string): string {\n return formatInTimeZone(date, timeZone, 'HH:mm');\n}\n\n/**\n * Get the user's local timezone from the browser\n *\n * @returns IANA timezone string (e.g., 'America/New_York')\n *\n * @example\n * ```ts\n * const userTz = getUserTimeZone();\n * // \"America/New_York\" or user's actual timezone\n * ```\n */\nexport function getUserTimeZone(): string {\n try {\n if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {\n return Intl.DateTimeFormat().resolvedOptions().timeZone;\n }\n return 'UTC';\n } catch {\n return 'UTC';\n }\n}\n\n/**\n * Convert a UTC date to a specific timezone's local time representation\n *\n * @param date - UTC date to convert\n * @param timezone - IANA timezone string\n * @returns Date object representing local time in the timezone\n *\n * @example\n * ```ts\n * const utcDate = new Date('2024-01-15T10:00:00Z');\n * const localDate = toZonedTime(utcDate, 'America/New_York');\n * // Returns Date object representing 5:00 AM in New York\n * ```\n */\nexport function toZonedTime(date: Date, timezone: string): Date {\n try {\n if (!timezone || typeof timezone !== 'string') {\n return date;\n }\n\n if (!isValid(date)) {\n return date;\n }\n\n return fnsToZonedTime(date, timezone);\n } catch {\n return date;\n }\n}\n\n/**\n * Convert a local time in a specific timezone to UTC\n *\n * @param localDate - Local date in the timezone\n * @param timezone - IANA timezone string\n * @returns Date object in UTC\n *\n * @example\n * ```ts\n * const localDate = new Date(2024, 0, 15, 10, 0); // Jan 15, 2024 10:00 AM local\n * const utcDate = fromZonedTime(localDate, 'America/New_York');\n * // Returns Date object representing 3:00 PM UTC (if EST) or 2:00 PM UTC (if EDT)\n * ```\n */\nexport function fromZonedTime(localDate: Date, timezone: string): Date {\n try {\n if (!timezone || typeof timezone !== 'string') {\n return localDate;\n }\n\n if (!isValid(localDate)) {\n return localDate;\n }\n\n return fnsFromZonedTime(localDate, timezone);\n } catch {\n return localDate;\n }\n}\n\n/**\n * Round a date to the nearest X minutes\n *\n * @param date - Date to round\n * @param minutesStep - Number of minutes to round to (default: 5)\n * @returns Rounded date\n *\n * @example\n * ```ts\n * const date = new Date('2024-01-15T10:23:00Z');\n * roundToNearestMinutes(date, 5);\n * // Returns Date object for 10:25:00\n * ```\n */\nexport function roundToNearestMinutes(date: Date, minutesStep: number = 5): Date {\n try {\n if (!isValid(date)) {\n return date;\n }\n\n if (minutesStep <= 0 || !Number.isInteger(minutesStep)) {\n return date;\n }\n\n const minutes = date.getMinutes();\n const roundedMinutes = Math.round(minutes / minutesStep) * minutesStep;\n const diff = roundedMinutes - minutes;\n\n return addMinutes(date, diff);\n } catch {\n return date;\n }\n}\n\n/**\n * Calculate the time difference between two timezones in hours\n *\n * @param fromTimeZone - Source timezone (IANA string)\n * @param toTimeZone - Target timezone (IANA string)\n * @returns Difference in hours (positive if toTimeZone is ahead)\n *\n * @example\n * ```ts\n * getTimeZoneDifference('America/New_York', 'America/Los_Angeles');\n * // -3 (Los Angeles is 3 hours behind New York)\n * ```\n */\nexport function getTimeZoneDifference(fromTimeZone: string, toTimeZone: string): number {\n try {\n if (!fromTimeZone || !toTimeZone || typeof fromTimeZone !== 'string' || typeof toTimeZone !== 'string') {\n return 0;\n }\n\n const now = new Date();\n const fromDate = toZonedTime(now, fromTimeZone);\n const toDate = toZonedTime(now, toTimeZone);\n\n const diff = differenceInHours(toDate, fromDate);\n // Handle NaN case (differenceInHours can return NaN for invalid dates)\n return isNaN(diff) ? 0 : diff;\n } catch {\n return 0;\n }\n}\n\n","/**\n * App ID Resolution Utility\n * @package @jmruthers/pace-core\n * @module Utils/AppIdResolver\n * @since 1.0.0\n * \n * This module provides utilities to resolve app names to app IDs for database operations.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { Database } from '../../types/database';\nimport { createLogger } from '../core/logger';\n\nconst log = createLogger('AppIdResolver');\n\n/**\n * Resolves an app name to its corresponding app ID\n * \n * @param supabase - Supabase client instance\n * @param appName - The app name to resolve\n * @returns Promise resolving to the app ID or null if not found\n */\nexport async function getAppId(\n supabase: SupabaseClient<Database>,\n appName: string\n): Promise<string | null> {\n try {\n const { data, error } = await supabase\n .from('rbac_apps')\n .select('id')\n .ilike('name', appName)\n .eq('is_active', true)\n .single();\n\n if (error) {\n log.error('Failed to resolve app ID for app name:', appName, error);\n return null;\n }\n\n return (data as { id: string } | null)?.id || null;\n } catch (error) {\n log.error('Error resolving app ID for app name:', appName, error);\n return null;\n }\n}\n\n/**\n * Resolves multiple app names to their corresponding app IDs\n * \n * @param supabase - Supabase client instance\n * @param appNames - Array of app names to resolve\n * @returns Promise resolving to a map of app names to app IDs\n */\nexport async function getAppIds(\n supabase: SupabaseClient<Database>,\n appNames: string[]\n): Promise<Record<string, string | null>> {\n try {\n // For case-insensitive matching with multiple values, we need to use OR conditions\n // since PostgreSQL doesn't support case-insensitive IN with ILIKE\n const orConditions = appNames.map(name => `name.ilike.${name}`).join(',');\n \n const { data, error } = await supabase\n .from('rbac_apps')\n .select('id, name')\n .or(orConditions)\n .eq('is_active', true);\n\n if (error) {\n log.error('Failed to resolve app IDs for app names:', appNames, error);\n return {};\n }\n\n const result: Record<string, string | null> = {};\n \n // Initialize all app names with null\n appNames.forEach(name => {\n result[name] = null;\n });\n\n // Set resolved app IDs - match case-insensitively\n (data as { id: string; name: string }[] | null)?.forEach(app => {\n // Find the original app name that matches (case-insensitive)\n const originalName = appNames.find(name => \n name.toLowerCase() === app.name.toLowerCase()\n );\n if (originalName) {\n result[originalName] = app.id;\n }\n });\n\n return result;\n } catch (error) {\n log.error('Error resolving app IDs for app names:', appNames, error);\n return {};\n }\n}\n\n/**\n * Cached app ID resolver with TTL\n */\nexport class CachedAppIdResolver {\n private cache = new Map<string, { id: string | null; expires: number }>();\n private ttl = 5 * 60 * 1000; // 5 minutes\n\n async getAppId(\n supabase: SupabaseClient<Database>,\n appName: string\n ): Promise<string | null> {\n const now = Date.now();\n const cached = this.cache.get(appName);\n\n if (cached && cached.expires > now) {\n return cached.id;\n }\n\n const id = await getAppId(supabase, appName);\n this.cache.set(appName, { id, expires: now + this.ttl });\n\n return id;\n }\n\n clearCache(): void {\n this.cache.clear();\n }\n\n clearCacheForApp(appName: string): void {\n this.cache.delete(appName);\n }\n}\n\n// Export singleton instance\nexport const cachedAppIdResolver = new CachedAppIdResolver();\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useEvents
3
- } from "./chunk-OETXORNB.js";
3
+ } from "./chunk-3XTALGJF.js";
4
4
  import {
5
5
  assertAppId
6
6
  } from "./chunk-QXHPKYJV.js";
@@ -1837,12 +1837,6 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1837
1837
  }
1838
1838
  } else {
1839
1839
  if (category) {
1840
- logger.debug("useFileDisplay", "Using RPC function for category filtering:", {
1841
- table_name,
1842
- record_id,
1843
- category,
1844
- organisation_id
1845
- });
1846
1840
  files = await service.getFilesByCategory(
1847
1841
  table_name,
1848
1842
  record_id,
@@ -1932,18 +1926,10 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1932
1926
  setFileCount(files.length);
1933
1927
  if (category && files.length > 0) {
1934
1928
  const firstFile = files[0];
1935
- logger.debug("useFileDisplay", "Processing category files, first file:", {
1936
- id: firstFile.id,
1937
- file_path: firstFile.file_path,
1938
- is_public: firstFile.is_public,
1939
- has_file_metadata: !!firstFile.file_metadata,
1940
- category_in_metadata: firstFile.file_metadata?.category
1941
- });
1942
1929
  setFileReference(firstFile);
1943
1930
  let url = null;
1944
1931
  if (firstFile.is_public) {
1945
1932
  url = getPublicUrl(supabase, firstFile.file_path, true);
1946
- logger.debug("useFileDisplay", "Generated public URL:", url);
1947
1933
  } else {
1948
1934
  const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {
1949
1935
  appName: "pace-core",
@@ -1952,9 +1938,14 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
1952
1938
  expiresIn: 3600
1953
1939
  });
1954
1940
  url = signedUrlResult?.url || null;
1955
- logger.debug("useFileDisplay", "Generated signed URL:", url ? "URL generated" : "URL generation failed");
1941
+ if (!url) {
1942
+ logger.warn("useFileDisplay", "Failed to generate signed URL for file:", {
1943
+ file_path: firstFile.file_path,
1944
+ record_id,
1945
+ table_name
1946
+ });
1947
+ }
1956
1948
  }
1957
- logger.debug("useFileDisplay", "Setting file URL:", url ? "URL set" : "URL is null");
1958
1949
  setFileUrl(url);
1959
1950
  } else {
1960
1951
  const urlMap = await generateFileUrlsBatch(supabase, files, {
@@ -2193,4 +2184,4 @@ export {
2193
2184
  useEventTheme,
2194
2185
  usePreventTabReload
2195
2186
  };
2196
- //# sourceMappingURL=chunk-XNYQOL3Z.js.map
2187
+ //# sourceMappingURL=chunk-JBKQ3SAO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/hooks/useDebounce.ts","../src/hooks/useQueryCache.ts","../src/hooks/useAddressAutocomplete.ts","../src/utils/storage/config.ts","../src/utils/storage/helpers.ts","../src/hooks/public/usePublicFileDisplay.ts","../src/hooks/useFileDisplay.ts","../src/utils/file-reference/index.ts","../src/hooks/useEventTheme.ts","../src/hooks/usePreventTabReload.ts"],"sourcesContent":["\nimport { useState, useEffect } from 'react';\n\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n clearTimeout(handler);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n","/**\n * Query Result Caching Hook\n * @package @jmruthers/pace-core\n * @module Hooks/QueryCache\n * @since 2.0.0\n * \n * Provides in-memory caching for frequently accessed data to eliminate duplicate queries.\n * Useful for caching user profiles, app pages, and other relatively static data.\n */\n\nimport { useCallback, useRef, useEffect } from 'react';\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../types/database';\nimport { createLogger } from '../utils/core/logger';\n\nconst log = createLogger('useQueryCache');\n\ninterface CachedQueryEntry<T> {\n data: T;\n expiresAt: number; // Unix timestamp\n promise?: Promise<T>; // For in-flight requests\n}\n\n/**\n * In-memory cache for query results\n * Key format: `table:filterKey:filterValue`\n */\nconst queryCache = new Map<string, CachedQueryEntry<any>>();\n\n// Cleanup interval (every 5 minutes)\nconst CLEANUP_INTERVAL_MS = 5 * 60 * 1000;\nlet cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\nfunction runCacheCleanup() {\n const now = Date.now();\n const expiredKeys: string[] = [];\n \n queryCache.forEach((entry, key) => {\n if (entry.expiresAt <= now) {\n expiredKeys.push(key);\n }\n });\n \n expiredKeys.forEach(key => {\n queryCache.delete(key);\n log.debug(`Removed expired query from cache: ${key}`);\n });\n}\n\n// Initialize cleanup timer once\nif (typeof window !== 'undefined' && !cleanupTimer) {\n cleanupTimer = setInterval(runCacheCleanup, CLEANUP_INTERVAL_MS);\n log.debug('Query cache cleanup initialized.');\n \n // Cleanup on page unload to prevent memory leaks\n window.addEventListener('beforeunload', () => {\n if (cleanupTimer) {\n clearInterval(cleanupTimer);\n cleanupTimer = null;\n log.debug('Query cache cleanup timer cleared on page unload.');\n }\n });\n}\n\n/**\n * Manually cleanup the query cache cleanup timer\n * Useful for testing or when the module is reloaded\n */\nexport function cleanupQueryCache(): void {\n if (cleanupTimer) {\n clearInterval(cleanupTimer);\n cleanupTimer = null;\n log.debug('Query cache cleanup timer cleared.');\n }\n}\n\nexport interface UseQueryCacheOptions {\n /** Time to live in seconds (default: 300 = 5 minutes) */\n ttl?: number;\n /** Whether to enable caching (default: true) */\n enabled?: boolean;\n}\n\nexport interface UseQueryCacheReturn {\n /** Get cached query result or fetch if not cached */\n getCachedQuery: <T>(\n table: string,\n filterKey: string,\n filterValue: string,\n fetchFn: () => Promise<T>,\n options?: UseQueryCacheOptions\n ) => Promise<T>;\n /** Invalidate a specific cached query */\n invalidateQuery: (table: string, filterKey: string, filterValue: string) => void;\n /** Clear all cached queries */\n clearCache: () => void;\n /** Get cache statistics */\n getCacheStats: () => { size: number; keys: string[] };\n}\n\n/**\n * Hook for query result caching\n * \n * Provides caching for frequently accessed data to eliminate duplicate queries.\n * Automatically handles cache expiration and cleanup.\n * \n * @param supabase - Supabase client (optional, can be passed in fetchFn)\n * @returns Query cache utilities\n * \n * @example\n * ```tsx\n * const { getCachedQuery } = useQueryCache(supabase);\n * \n * const person = await getCachedQuery(\n * 'core_person',\n * 'user_id',\n * userId,\n * async () => {\n * const { data } = await supabase\n * .from('core_person')\n * .select('id, first_name, last_name, email')\n * .eq('user_id', userId)\n * .single();\n * return data;\n * },\n * { ttl: 300 } // 5 minutes\n * );\n * ```\n */\nexport function useQueryCache(supabase?: SupabaseClient<Database>): UseQueryCacheReturn {\n const getCachedQuery = useCallback(async <T,>(\n table: string,\n filterKey: string,\n filterValue: string,\n fetchFn: () => Promise<T>,\n options: UseQueryCacheOptions = {}\n ): Promise<T> => {\n const { ttl = 300, enabled = true } = options; // Default 5 minutes\n const cacheKey = `${table}:${filterKey}:${filterValue}`;\n const now = Date.now();\n \n if (!enabled) {\n return fetchFn();\n }\n \n // Check cache\n const cached = queryCache.get(cacheKey);\n if (cached) {\n // If data is still valid, return it\n if (cached.expiresAt > now && cached.data !== undefined) {\n log.debug(`Cache hit for query: ${cacheKey}`);\n return cached.data as T;\n }\n \n // If there's an in-flight request, wait for it\n if (cached.promise) {\n log.debug(`Waiting for in-flight request: ${cacheKey}`);\n return cached.promise as Promise<T>;\n }\n }\n \n // Fetch data\n log.debug(`Cache miss for query: ${cacheKey}, fetching...`);\n const fetchPromise = fetchFn();\n \n // Store promise for in-flight request deduplication\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + (ttl * 1000),\n promise: fetchPromise,\n });\n \n try {\n const data = await fetchPromise;\n \n // Update cache with actual data\n queryCache.set(cacheKey, {\n data,\n expiresAt: now + (ttl * 1000),\n });\n \n log.debug(`Cached query result: ${cacheKey}, expires in ${ttl}s`);\n return data;\n } catch (error) {\n // Remove failed request from cache\n queryCache.delete(cacheKey);\n log.error(`Query failed for ${cacheKey}:`, error);\n throw error;\n }\n }, []);\n \n const invalidateQuery = useCallback((table: string, filterKey: string, filterValue: string) => {\n const cacheKey = `${table}:${filterKey}:${filterValue}`;\n queryCache.delete(cacheKey);\n log.debug(`Invalidated query cache: ${cacheKey}`);\n }, []);\n \n const clearCache = useCallback(() => {\n queryCache.clear();\n log.debug('Cleared all query cache entries.');\n }, []);\n \n const getCacheStats = useCallback(() => {\n return {\n size: queryCache.size,\n keys: Array.from(queryCache.keys()),\n };\n }, []);\n \n return {\n getCachedQuery,\n invalidateQuery,\n clearCache,\n getCacheStats,\n };\n}\n\n/**\n * Pre-configured cache helpers for common queries\n */\nexport const queryCacheHelpers = {\n /**\n * Cache core_person queries by user_id\n * TTL: 5 minutes\n */\n pacePersonByUserId: <T>(\n supabase: SupabaseClient<Database>,\n userId: string,\n fetchFn: () => Promise<T>\n ): Promise<T> => {\n const cacheKey = `core_person:user_id:${userId}`;\n const now = Date.now();\n const ttl = 300 * 1000; // 5 minutes\n \n const cached = queryCache.get(cacheKey);\n if (cached && cached.expiresAt > now && cached.data !== undefined) {\n return Promise.resolve(cached.data as T);\n }\n \n if (cached?.promise) {\n return cached.promise as Promise<T>;\n }\n \n const promise = fetchFn();\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + ttl,\n promise,\n });\n \n promise.then(data => {\n queryCache.set(cacheKey, { data, expiresAt: now + ttl });\n }).catch(() => {\n queryCache.delete(cacheKey);\n });\n \n return promise;\n },\n \n /**\n * Cache core_member queries by person_id\n * TTL: 5 minutes\n */\n paceMemberByPersonId: <T>(\n supabase: SupabaseClient<Database>,\n personId: string,\n fetchFn: () => Promise<T>\n ): Promise<T> => {\n const cacheKey = `core_member:person_id:${personId}`;\n const now = Date.now();\n const ttl = 300 * 1000; // 5 minutes\n \n const cached = queryCache.get(cacheKey);\n if (cached && cached.expiresAt > now && cached.data !== undefined) {\n return Promise.resolve(cached.data as T);\n }\n \n if (cached?.promise) {\n return cached.promise as Promise<T>;\n }\n \n const promise = fetchFn();\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + ttl,\n promise,\n });\n \n promise.then(data => {\n queryCache.set(cacheKey, { data, expiresAt: now + ttl });\n }).catch(() => {\n queryCache.delete(cacheKey);\n });\n \n return promise;\n },\n \n /**\n * Cache rbac_app_pages queries by app_id\n * TTL: 15 minutes (app pages are relatively static)\n */\n rbacAppPagesByAppId: <T>(\n supabase: SupabaseClient<Database>,\n appId: string,\n fetchFn: () => Promise<T>\n ): Promise<T> => {\n const cacheKey = `rbac_app_pages:app_id:${appId}`;\n const now = Date.now();\n const ttl = 15 * 60 * 1000; // 15 minutes\n \n const cached = queryCache.get(cacheKey);\n if (cached && cached.expiresAt > now && cached.data !== undefined) {\n return Promise.resolve(cached.data as T);\n }\n \n if (cached?.promise) {\n return cached.promise as Promise<T>;\n }\n \n const promise = fetchFn();\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + ttl,\n promise,\n });\n \n promise.then(data => {\n queryCache.set(cacheKey, { data, expiresAt: now + ttl });\n }).catch(() => {\n queryCache.delete(cacheKey);\n });\n \n return promise;\n },\n};\n\n","/**\n * @file Address Autocomplete Hook\n * @package @jmruthers/pace-core\n * @module Hooks/AddressAutocomplete\n * @since 0.1.0\n *\n * Hook for managing Google Places API autocomplete functionality.\n * Provides debounced input, caching, and address selection.\n */\n\nimport { useState, useEffect, useCallback, useRef, useMemo } from 'react';\nimport { useDebounce } from './useDebounce';\nimport { useQueryCache } from './useQueryCache';\nimport {\n fetchPlaceAutocomplete,\n fetchPlaceDetails,\n createAddressFromPlaceResult,\n getAddressByPlaceId,\n} from '../utils/google-places';\nimport type {\n GooglePlaceAutocompletePrediction,\n ParsedAddress,\n AutocompleteOptions,\n} from '../utils/google-places';\n\nexport interface UseAddressAutocompleteOptions {\n /** Google Places API options */\n autocompleteOptions?: AutocompleteOptions;\n /** Debounce delay in milliseconds (default: 300) */\n debounceDelay?: number;\n /** Enable caching (default: true) */\n cacheEnabled?: boolean;\n /** Cache TTL configuration */\n cacheTTL?: {\n /** Autocomplete cache TTL in seconds (default: 3600 = 1 hour) */\n autocomplete?: number;\n /** Place details cache TTL in seconds (default: 86400 = 24 hours) */\n placeDetails?: number;\n };\n}\n\nexport interface UseAddressAutocompleteReturn {\n /** Array of autocomplete suggestions */\n suggestions: GooglePlaceAutocompletePrediction[];\n /** Loading state */\n isLoading: boolean;\n /** Error state */\n error: Error | null;\n /** Select an address by place_id */\n selectAddress: (placeId: string) => Promise<ParsedAddress | null>;\n /** Get address by place_id (uses cache) */\n getAddressByPlaceId: (placeId: string) => Promise<ParsedAddress | null>;\n /** Clear suggestions */\n clearSuggestions: () => void;\n}\n\n/**\n * Hook for Google Places API autocomplete with caching\n *\n * @param apiKey - Google Places API key\n * @param inputValue - Current input value\n * @param options - Hook configuration options\n * @returns Autocomplete state and functions\n *\n * @example\n * ```tsx\n * const { suggestions, isLoading, selectAddress } = useAddressAutocomplete(\n * apiKey,\n * inputValue,\n * { debounceDelay: 300, cacheEnabled: true }\n * );\n * ```\n */\nexport function useAddressAutocomplete(\n apiKey: string,\n inputValue: string,\n options: UseAddressAutocompleteOptions = {}\n): UseAddressAutocompleteReturn {\n const {\n debounceDelay = 300,\n cacheEnabled = true,\n cacheTTL = {\n autocomplete: 3600, // 1 hour\n placeDetails: 86400, // 24 hours\n },\n autocompleteOptions,\n } = options;\n\n const [suggestions, setSuggestions] = useState<GooglePlaceAutocompletePrediction[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const debouncedInput = useDebounce(inputValue, debounceDelay);\n const { getCachedQuery } = useQueryCache();\n const abortControllerRef = useRef<AbortController | null>(null);\n\n // Memoize autocompleteOptions to prevent unnecessary re-renders\n const memoizedAutocompleteOptions = useMemo(\n () => autocompleteOptions,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(autocompleteOptions)]\n );\n\n // Fetch autocomplete suggestions\n useEffect(() => {\n // Cancel previous request if still in flight\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n // Reset state if input is empty\n if (!debouncedInput.trim()) {\n setSuggestions([]);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n // Don't fetch if no API key\n if (!apiKey) {\n setError(new Error('Google Places API key is required'));\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n const fetchSuggestions = async () => {\n try {\n let predictions: GooglePlaceAutocompletePrediction[];\n\n if (cacheEnabled) {\n // Use cached query with TTL\n predictions = await getCachedQuery(\n 'google-places-autocomplete',\n 'query',\n debouncedInput,\n async () => {\n return fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);\n },\n { ttl: cacheTTL.autocomplete, enabled: true }\n );\n } else {\n // Direct fetch without cache\n predictions = await fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);\n }\n\n setSuggestions(predictions);\n setIsLoading(false);\n } catch (err) {\n // Ignore aborted requests\n if (err instanceof Error && err.name === 'AbortError') {\n return;\n }\n\n const error = err instanceof Error ? err : new Error('Failed to fetch autocomplete suggestions');\n setError(error);\n setSuggestions([]);\n setIsLoading(false);\n }\n };\n\n fetchSuggestions();\n\n // Cleanup function to cancel in-flight requests\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debouncedInput, apiKey, cacheEnabled, cacheTTL.autocomplete]);\n\n // Select address by place_id\n const selectAddress = useCallback(\n async (placeId: string): Promise<ParsedAddress | null> => {\n if (!placeId || !apiKey) {\n return null;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n let placeDetails;\n\n if (cacheEnabled) {\n // Use cached query with TTL\n placeDetails = await getCachedQuery(\n 'google-places-details',\n 'place_id',\n placeId,\n async () => {\n return fetchPlaceDetails(placeId, apiKey);\n },\n { ttl: cacheTTL.placeDetails, enabled: true }\n );\n } else {\n // Direct fetch without cache\n placeDetails = await fetchPlaceDetails(placeId, apiKey);\n }\n\n const parsedAddress = createAddressFromPlaceResult(placeDetails);\n setIsLoading(false);\n return parsedAddress;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch place details');\n setError(error);\n setIsLoading(false);\n return null;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [apiKey, cacheEnabled, cacheTTL.placeDetails]\n );\n\n // Get address by place_id (utility function)\n const getAddressByPlaceIdFn = useCallback(\n async (placeId: string): Promise<ParsedAddress | null> => {\n if (!placeId || !apiKey) {\n return null;\n }\n\n try {\n if (cacheEnabled) {\n // Use cached query with TTL\n return await getCachedQuery(\n 'google-places-details',\n 'place_id',\n placeId,\n async () => {\n const result = await getAddressByPlaceId(placeId, apiKey);\n if (!result) {\n throw new Error('Failed to fetch address');\n }\n return result;\n },\n { ttl: cacheTTL.placeDetails, enabled: true }\n );\n } else {\n return await getAddressByPlaceId(placeId, apiKey);\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to get address by place_id');\n setError(error);\n return null;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [apiKey, cacheEnabled, cacheTTL.placeDetails]\n );\n\n // Clear suggestions\n const clearSuggestions = useCallback(() => {\n setSuggestions([]);\n setError(null);\n }, []);\n\n return {\n suggestions,\n isLoading,\n error,\n selectAddress,\n getAddressByPlaceId: getAddressByPlaceIdFn,\n clearSuggestions,\n };\n}\n\n","/**\n * Storage configuration for pace-core\n */\n\nimport { FileSizeLimits, StorageConfig } from './types';\n\n/**\n * File size limits by MIME type (in bytes)\n */\nexport const FILE_SIZE_LIMITS: FileSizeLimits = {\n // Images\n 'image/jpeg': 5 * 1024 * 1024, // 5MB\n 'image/png': 5 * 1024 * 1024, // 5MB\n 'image/gif': 10 * 1024 * 1024, // 10MB (for animations)\n 'image/webp': 5 * 1024 * 1024, // 5MB\n 'image/svg+xml': 1 * 1024 * 1024, // 1MB (vector graphics)\n \n // Documents\n 'application/pdf': 50 * 1024 * 1024, // 50MB\n 'application/msword': 25 * 1024 * 1024, // 25MB\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 25 * 1024 * 1024, // 25MB\n 'application/vnd.ms-excel': 25 * 1024 * 1024, // 25MB\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 25 * 1024 * 1024, // 25MB\n \n // Archives\n 'application/zip': 100 * 1024 * 1024, // 100MB\n 'application/x-rar-compressed': 100 * 1024 * 1024, // 100MB\n \n // Text files\n 'text/plain': 1 * 1024 * 1024, // 1MB\n 'text/csv': 10 * 1024 * 1024, // 10MB\n 'application/json': 10 * 1024 * 1024, // 10MB\n};\n\n/**\n * Default file size limit for unknown MIME types\n */\nexport const DEFAULT_FILE_SIZE_LIMIT = 10 * 1024 * 1024; // 10MB\n\n/**\n * App-specific path mappings for file organization\n * Maps app names to their primary category in the organization-first structure\n */\nexport const APP_PATH_MAPPING: Record<string, string> = {\n 'PACE': 'event_logos',\n 'TRAC': 'trac_accommodation', // Default category for TRAC files\n 'MEDI': 'documents',\n 'CAKE': 'documents'\n};\n\n/**\n * Storage configuration\n */\nexport const STORAGE_CONFIG: StorageConfig = {\n bucketName: 'files',\n fileSizeLimits: FILE_SIZE_LIMITS,\n defaultFileSizeLimit: DEFAULT_FILE_SIZE_LIMIT,\n};\n\n/**\n * Get the file size limit for a given MIME type\n */\nexport function getFileSizeLimit(mimeType: string): number {\n return STORAGE_CONFIG.fileSizeLimits[mimeType] || STORAGE_CONFIG.defaultFileSizeLimit;\n}\n\n/**\n * Get the bucket name based on whether the file is public or private\n * @param isPublic - Whether the file should be publicly accessible\n * @returns The bucket name: 'public-files' for public files, 'files' for private files\n */\nexport function getBucketName(isPublic: boolean): 'files' | 'public-files' {\n return isPublic ? 'public-files' : 'files';\n}\n\n/**\n * Validate file size against limits\n */\nexport function validateFileSize(file: File): { isValid: boolean; error?: string } {\n const limit = getFileSizeLimit(file.type);\n \n if (file.size > limit) {\n const limitMB = Math.round(limit / (1024 * 1024));\n const fileMB = Math.round(file.size / (1024 * 1024));\n return {\n isValid: false,\n error: `File size (${fileMB}MB) exceeds limit (${limitMB}MB) for ${file.type}`\n };\n }\n \n return { isValid: true };\n}\n\n/**\n * Get human-readable file size\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 Bytes';\n if (bytes < 0) return `${bytes} Bytes`; // Handle negative numbers\n \n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n // Ensure we don't exceed the available size units\n const sizeIndex = Math.min(Math.max(i, 0), sizes.length - 1);\n \n return parseFloat((bytes / Math.pow(k, sizeIndex)).toFixed(2)) + ' ' + sizes[sizeIndex];\n}\n","/**\n * Storage helper functions for pace-core\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { \n StorageUploadOptions, \n StorageUploadResult, \n StorageFileMetadata,\n StorageUrlOptions,\n StorageListOptions,\n StorageListResult,\n StorageFileInfo\n} from './types';\nimport { validateFileSize, STORAGE_CONFIG, getBucketName } from './config';\nimport { createLogger } from '../core/logger';\n\nconst log = createLogger('StorageHelpers');\n\n/**\n * Generate a file path based on organization-first or user-first structure\n * - If orgId is provided: {orgId}/{folder}/filename\n * - If userId is provided (and orgId is not): users/{userId}/{folder}/filename\n */\nexport function generateFilePath(options: StorageUploadOptions, fileName: string): string {\n const { orgId, userId, isPublic = false, customPath } = options;\n \n // Validate that either orgId or userId is provided\n if (!orgId && !userId) {\n throw new Error('Either orgId or userId is required for file path generation');\n }\n \n // Determine base path: organisation-based or user-based\n const basePath = orgId ? orgId : `users/${userId}`;\n \n if (isPublic) {\n // Public files go to {basePath}/{folder}/filename\n if (customPath) {\n return `${basePath}/${customPath}/${fileName}`;\n }\n return `${basePath}/public/${fileName}`;\n }\n \n // Organization-first or user-first structure: {basePath}/{folder}/filename\n if (customPath) {\n return `${basePath}/${customPath}/${fileName}`;\n }\n \n // Use customPath if available, otherwise default to files\n const pathFolder = customPath || 'files';\n return `${basePath}/${pathFolder}/${fileName}`;\n}\n\n/**\n * Generate a unique filename with timestamp and UUID\n * Sanitizes the original filename to remove spaces and invalid characters for storage compatibility\n */\nexport function generateUniqueFileName(originalName: string): string {\n const timestamp = Date.now();\n const uuid = crypto.randomUUID();\n const extension = originalName.split('.').pop() || '';\n let baseName = originalName.replace(/\\.[^/.]+$/, '');\n \n // Sanitize the base name for storage compatibility\n // Replace spaces with underscores and remove invalid characters\n baseName = baseName\n .trim()\n .replace(/\\s+/g, '_') // Replace spaces with underscores\n .replace(/[<>:\"/\\\\|?*]/g, '') // Remove invalid file name characters\n .replace(/\\.\\./g, '') // Remove directory traversal attempts\n .replace(/^\\.+|\\.+$/g, '') // Remove leading/trailing dots\n .substring(0, 200); // Limit length to leave room for timestamp and UUID\n \n // If there's no extension, don't add one\n if (!extension || extension === originalName) {\n return `${timestamp}-${uuid}-${baseName}`;\n }\n \n return `${timestamp}-${uuid}-${baseName}.${extension}`;\n}\n\n/**\n * Extract file metadata from a File object\n */\nexport async function extractFileMetadata(\n file: File, \n options: StorageUploadOptions,\n uploadedBy: string\n): Promise<StorageFileMetadata> {\n const metadata: StorageFileMetadata = {\n mimeType: file.type,\n size: file.size,\n ...(options.orgId && { orgId: options.orgId }),\n ...(options.userId && { userId: options.userId }),\n appName: options.appName || 'pace-core',\n uploadedBy,\n uploadedAt: new Date().toISOString(),\n tags: options.tags || [],\n isPublic: options.isPublic || false,\n customMetadata: options.metadata || {}\n };\n\n // Extract image dimensions if it's an image\n if (file.type.startsWith('image/')) {\n try {\n const dimensions = await getImageDimensions(file);\n metadata.width = dimensions.width;\n metadata.height = dimensions.height;\n } catch (error) {\n // Non-critical error - image dimensions are optional metadata\n // Using Logger would be better, but this is in a utility function\n // For now, silently continue - dimensions are optional\n }\n }\n\n // Generate file hash if possible\n try {\n metadata.hash = await generateFileHash(file);\n } catch (error) {\n // Non-critical error - file hash is optional metadata\n // Using Logger would be better, but this is in a utility function\n // For now, silently continue - hash is optional\n }\n\n return metadata;\n}\n\n/**\n * Get image dimensions from a File object\n */\nasync function getImageDimensions(file: File): Promise<{ width: number; height: number }> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n const url = URL.createObjectURL(file);\n \n img.onload = () => {\n URL.revokeObjectURL(url);\n resolve({ width: img.width, height: img.height });\n };\n \n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error('Could not load image'));\n };\n \n img.src = url;\n });\n}\n\n/**\n * Generate a hash for a file\n */\nasync function generateFileHash(file: File): Promise<string> {\n const buffer = await file.arrayBuffer();\n const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n return `sha256:${hashHex}`;\n}\n\n/**\n * Ensure a folder exists in the storage bucket\n * In Supabase storage, folders are created automatically when files are uploaded,\n * but this function explicitly creates the folder structure by uploading a placeholder\n * file if the folder doesn't exist\n * @param supabase - Supabase client instance\n * @param folderPath - Folder path to ensure exists (e.g., 'orgId/folder')\n * @param bucketName - Bucket name\n * @returns True if folder exists or was created, false on error\n */\nasync function ensureFolderExists(\n supabase: SupabaseClient,\n folderPath: string,\n bucketName: string\n): Promise<boolean> {\n try {\n // Check if folder exists by trying to list it\n const { data, error } = await supabase.storage\n .from(bucketName)\n .list(folderPath, {\n limit: 1\n });\n\n // If listing succeeds (even with empty data), the folder exists\n if (!error) {\n return true;\n }\n\n // If we get a \"not found\" error, the folder doesn't exist yet\n // Create it by uploading a placeholder file\n // Supabase storage doesn't support empty folders, so we create a .keep file\n const placeholderPath = `${folderPath}/.keep`;\n const placeholderBlob = new Blob([''], { type: 'text/plain' });\n const placeholderFile = new File([placeholderBlob], '.keep', { type: 'text/plain' });\n\n const { error: uploadError } = await supabase.storage\n .from(bucketName)\n .upload(placeholderPath, placeholderFile, {\n cacheControl: '3600',\n upsert: true, // Use upsert to avoid errors if file already exists\n contentType: 'text/plain'\n });\n\n if (uploadError) {\n // If we can't create the placeholder, log it but don't fail\n // The folder will be created automatically when we upload the actual file\n log.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);\n return true; // Still return true - folder will be created on actual file upload\n }\n\n // Folder structure now exists\n return true;\n } catch (error) {\n // If there's an exception, log it but proceed anyway\n // The folder structure will be created when we upload the actual file\n log.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : 'Unknown error'}`);\n return true; // Return true to proceed - folder will be created on actual file upload\n }\n}\n\n/**\n * Upload a file to Supabase storage with app segregation\n */\nexport async function uploadFile(\n supabase: SupabaseClient,\n file: File,\n options: StorageUploadOptions\n): Promise<StorageUploadResult> {\n try {\n // Validate file size\n const sizeValidation = validateFileSize(file);\n if (!sizeValidation.isValid) {\n return {\n success: false,\n error: sizeValidation.error\n };\n }\n\n // Generate unique filename and path\n const uniqueFileName = generateUniqueFileName(file.name);\n const filePath = generateFilePath(options, uniqueFileName);\n\n // Extract folder path from file path (everything except the filename)\n const folderPath = filePath.substring(0, filePath.lastIndexOf('/'));\n\n // Extract metadata\n const metadata = await extractFileMetadata(file, options, 'current-user'); // TODO: Get actual user ID\n\n // Select bucket based on isPublic flag\n const bucketName = getBucketName(options.isPublic || false);\n\n // Ensure folder exists (Supabase creates folders automatically on upload,\n // but we verify the path is accessible)\n await ensureFolderExists(supabase, folderPath, bucketName);\n\n // Upload file to Supabase\n // Note: Supabase will automatically create the folder structure if it doesn't exist\n const { data, error } = await supabase.storage\n .from(bucketName)\n .upload(filePath, file, {\n cacheControl: '3600',\n upsert: false,\n contentType: file.type\n });\n\n if (error) {\n return {\n success: false,\n error: `Upload failed: ${error.message}`\n };\n }\n\n // Generate public URL if file is public\n let publicUrl: string | undefined;\n if (options.isPublic) {\n const { data: urlData } = supabase.storage\n .from(bucketName)\n .getPublicUrl(filePath);\n publicUrl = urlData.publicUrl;\n }\n\n return {\n success: true,\n path: filePath,\n publicUrl,\n metadata\n };\n\n } catch (error) {\n return {\n success: false,\n error: `Upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n}\n\n/**\n * Get a public URL for a file\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param isPublic - Whether the file is in the public-files bucket (default: true)\n * @returns Public URL for the file\n */\nexport function getPublicUrl(\n supabase: SupabaseClient,\n path: string,\n isPublic: boolean = true\n): string {\n if (!supabase) {\n throw new Error('Supabase client is required to generate a public URL');\n }\n\n if (!path || typeof path !== 'string') {\n throw new Error('A valid storage path is required to generate a public URL');\n }\n\n // If the path is already an absolute URL, return it directly\n if (/^https?:\\/\\//i.test(path)) {\n return path;\n }\n\n // Normalise path by trimming whitespace and leading slashes\n let normalisedPath = path.trim().replace(/^\\/+/, '');\n\n if (!normalisedPath) {\n throw new Error('Storage path cannot be empty after normalisation');\n }\n\n const { bucketNameFromPath, storagePath } = resolveBucketHint(normalisedPath);\n\n const bucketName = bucketNameFromPath || getBucketName(isPublic);\n\n const { data } = supabase.storage\n .from(bucketName)\n .getPublicUrl(storagePath);\n\n return data.publicUrl;\n}\n\nfunction resolveBucketHint(pathWithPotentialBucket: string): { bucketNameFromPath: string | null; storagePath: string } {\n const BUCKET_NAME_PATTERN = /^[a-z0-9][a-z0-9-_\\.]{1,62}$/i;\n const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n const KNOWN_BUCKET_NAMES = new Set(['files', 'public-files']);\n\n const trimmedPath = pathWithPotentialBucket.trim();\n\n if (!trimmedPath) {\n throw new Error('Storage path cannot be empty after normalisation');\n }\n\n // Support \"bucket::path\" notation to remove ambiguity with colon usage in file names.\n const doubleColonIndex = trimmedPath.indexOf('::');\n if (doubleColonIndex > 0) {\n const potentialBucket = trimmedPath.slice(0, doubleColonIndex).trim();\n const remainingPath = trimmedPath.slice(doubleColonIndex + 2).replace(/^\\/+/, '');\n\n if (potentialBucket && remainingPath && BUCKET_NAME_PATTERN.test(potentialBucket)) {\n return {\n bucketNameFromPath: potentialBucket,\n storagePath: remainingPath\n };\n }\n }\n\n // Support \"bucket/path\" hints, provided the first segment looks like a bucket name and not a directory prefix.\n const firstSlashIndex = trimmedPath.indexOf('/');\n if (firstSlashIndex > 0) {\n const potentialBucket = trimmedPath.slice(0, firstSlashIndex).trim();\n const remainingPath = trimmedPath.slice(firstSlashIndex + 1).replace(/^\\/+/, '');\n\n if (\n potentialBucket &&\n remainingPath &&\n BUCKET_NAME_PATTERN.test(potentialBucket) &&\n !UUID_PATTERN.test(potentialBucket) &&\n (KNOWN_BUCKET_NAMES.has(potentialBucket) || !potentialBucket.includes('-')) &&\n !/^\\d+$/.test(potentialBucket)\n ) {\n return {\n bucketNameFromPath: potentialBucket,\n storagePath: remainingPath\n };\n }\n }\n\n return {\n bucketNameFromPath: null,\n storagePath: trimmedPath\n };\n}\n\n/**\n * Get a signed URL for a protected file\n * Private files are always in the 'files' bucket, so this always uses 'files' bucket\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param options - URL options including expiry time\n * @returns Signed URL with expiration timestamp, or null if failed\n */\nexport async function getSignedUrl(\n supabase: SupabaseClient,\n path: string,\n options: StorageUrlOptions\n): Promise<{ url: string; expiresAt: string } | null> {\n try {\n // Signed URLs are only for private files, which are always in the 'files' bucket\n const bucketName = getBucketName(false);\n \n const { data, error } = await supabase.storage\n .from(bucketName)\n .createSignedUrl(path, options.expiresIn || 3600);\n\n if (error) {\n log.error('Failed to create signed URL:', error);\n return null;\n }\n\n return {\n url: data.signedUrl,\n expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1000).toISOString()\n };\n } catch (error) {\n log.error('Failed to create signed URL:', error);\n return null;\n }\n}\n\n// Global URL cache for batch operations (shared with useFileUrlCache)\nconst globalUrlCache = new Map<string, { url: string; expiresAt: number }>();\nconst MAX_CACHE_SIZE = 500;\nconst DEFAULT_TTL_MS = 3600 * 1000;\n\nfunction getCacheKey(fileId: string, filePath: string, isPublic: boolean): string {\n return `file-url:${fileId}:${isPublic ? 'public' : 'private'}`;\n}\n\nfunction cleanupUrlCache(): void {\n const now = Date.now();\n \n // Remove expired entries\n for (const [key, value] of globalUrlCache.entries()) {\n if (value.expiresAt < now) {\n globalUrlCache.delete(key);\n }\n }\n \n // Enforce size limit by removing oldest entries\n if (globalUrlCache.size > MAX_CACHE_SIZE) {\n const entries = Array.from(globalUrlCache.entries());\n entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);\n \n const toRemove = Math.floor(MAX_CACHE_SIZE * 0.2);\n for (let i = 0; i < toRemove && i < entries.length; i++) {\n globalUrlCache.delete(entries[i][0]);\n }\n }\n}\n\n/**\n * Generate URLs for multiple file references in parallel\n * This batches URL generation to reduce sequential requests and uses caching\n * @param supabase - Supabase client instance\n * @param fileReferences - Array of file references to generate URLs for\n * @param options - URL options including expiry time and organisation ID\n * @returns Map of file ID to URL string (only includes successful URL generations)\n */\nexport async function generateFileUrlsBatch(\n supabase: SupabaseClient,\n fileReferences: Array<{ id: string; file_path: string; is_public: boolean }>,\n options: StorageUrlOptions & { orgId?: string }\n): Promise<Map<string, string>> {\n const urlMap = new Map<string, string>();\n \n if (fileReferences.length === 0) {\n return urlMap;\n }\n\n const now = Date.now();\n const ttl = (options.expiresIn || 3600) * 1000; // Convert seconds to milliseconds\n \n // Separate files into cached, public, and private\n const publicFiles: Array<{ id: string; file_path: string }> = [];\n const privateFiles: Array<{ id: string; file_path: string }> = [];\n const uncachedFiles: Array<{ id: string; file_path: string; is_public: boolean }> = [];\n\n for (const fileRef of fileReferences) {\n const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);\n const cached = globalUrlCache.get(cacheKey);\n \n // Use cached URL if still valid\n if (cached && cached.expiresAt > now) {\n urlMap.set(fileRef.id, cached.url);\n continue;\n }\n \n // Add to processing queue\n if (fileRef.is_public) {\n publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });\n } else {\n privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });\n }\n uncachedFiles.push(fileRef);\n }\n\n // Generate public URLs synchronously (they're just string concatenation)\n for (const file of publicFiles) {\n try {\n const url = getPublicUrl(supabase, file.file_path, true);\n if (url) {\n urlMap.set(file.id, url);\n // Cache the URL\n const cacheKey = getCacheKey(file.id, file.file_path, true);\n globalUrlCache.set(cacheKey, {\n url,\n expiresAt: now + ttl\n });\n }\n } catch (err) {\n log.error(`Failed to generate public URL for file ${file.id}:`, err);\n }\n }\n\n // Generate signed URLs in parallel using Promise.all\n if (privateFiles.length > 0) {\n const signedUrlPromises = privateFiles.map(async (file) => {\n try {\n const signedUrlResult = await getSignedUrl(supabase, file.file_path, {\n appName: options.appName || 'pace-core',\n orgId: options.orgId,\n expiresIn: options.expiresIn || 3600\n });\n const url = signedUrlResult?.url || null;\n \n // Cache the URL if generated successfully\n if (url) {\n const cacheKey = getCacheKey(file.id, file.file_path, false);\n globalUrlCache.set(cacheKey, {\n url,\n expiresAt: now + ttl\n });\n }\n \n return { id: file.id, url };\n } catch (err) {\n log.error(`Failed to generate signed URL for file ${file.id}:`, err);\n return { id: file.id, url: null };\n }\n });\n\n const signedUrlResults = await Promise.all(signedUrlPromises);\n \n for (const result of signedUrlResults) {\n if (result.url) {\n urlMap.set(result.id, result.url);\n }\n }\n }\n\n // Clean up cache after adding new entries\n if (uncachedFiles.length > 0) {\n cleanupUrlCache();\n }\n\n return urlMap;\n}\n\n/**\n * Delete a file from storage\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param isPublic - Whether the file is in the public-files bucket (default: false)\n * @returns Success status and optional error message\n */\nexport async function deleteFile(\n supabase: SupabaseClient,\n path: string,\n isPublic: boolean = false\n): Promise<{ success: boolean; error?: string }> {\n try {\n const bucketName = getBucketName(isPublic);\n \n const { error } = await supabase.storage\n .from(bucketName)\n .remove([path]);\n\n if (error) {\n return {\n success: false,\n error: `Delete failed: ${error.message}`\n };\n }\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: `Delete failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n}\n\n/**\n * List files in an organization scope\n * @param supabase - Supabase client instance\n * @param options - List options including bucket selection via isPublic\n * @returns List of files with metadata\n */\nexport async function listFiles(\n supabase: SupabaseClient,\n options: StorageListOptions & { isPublic?: boolean }\n): Promise<StorageListResult> {\n try {\n // Select bucket based on isPublic flag (default to private files bucket)\n const bucketName = getBucketName(options.isPublic || false);\n \n // Validate that either orgId or userId is provided\n if (!options.orgId && !options.userId) {\n throw new Error('Either orgId or userId is required for listing files');\n }\n \n // Organization-first or user-first structure\n const basePath = options.orgId ? options.orgId : `users/${options.userId}`;\n const pathPrefix = `${basePath}/`;\n const searchPath = options.pathPrefix ? `${pathPrefix}${options.pathPrefix}` : pathPrefix;\n\n const { data, error } = await supabase.storage\n .from(bucketName)\n .list(searchPath, {\n limit: options.limit || 100,\n offset: options.offset || 0,\n sortBy: { column: 'created_at', order: 'desc' }\n });\n\n if (error) {\n log.error('Failed to list files:', error);\n return { files: [], totalCount: 0, hasMore: false };\n }\n\n const files: StorageFileInfo[] = (data || []).map(item => ({\n name: item.name,\n path: `${searchPath}${item.name}`,\n size: item.metadata?.size || 0,\n mimeType: item.metadata?.mimetype || 'application/octet-stream',\n lastModified: item.updated_at || item.created_at || new Date().toISOString(),\n metadata: {\n mimeType: item.metadata?.mimetype || 'application/octet-stream',\n size: item.metadata?.size || 0,\n ...(options.orgId && { orgId: options.orgId }),\n ...(options.userId && { userId: options.userId }),\n appName: options.appName,\n uploadedBy: 'unknown',\n uploadedAt: item.created_at || new Date().toISOString(),\n isPublic: options.isPublic || false\n }\n }));\n\n return {\n files,\n totalCount: files.length,\n hasMore: files.length >= (options.limit || 100)\n };\n } catch (error) {\n log.error('Failed to list files:', error);\n return { files: [], totalCount: 0, hasMore: false };\n }\n}\n\n/**\n * Download a file from storage\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param isPublic - Whether the file is in the public-files bucket (default: false)\n * @returns File blob with metadata, or null if failed\n */\nexport async function downloadFile(\n supabase: SupabaseClient,\n path: string,\n isPublic: boolean = false\n): Promise<{ blob: Blob; metadata: { name: string; size: number; type: string } } | null> {\n try {\n const bucketName = getBucketName(isPublic);\n \n const { data, error } = await supabase.storage\n .from(bucketName)\n .download(path);\n\n if (error) {\n log.error('Failed to download file:', error);\n return null;\n }\n\n if (!data) {\n return null;\n }\n\n // Extract file name from path\n const fileName = path.split('/').pop() || 'download';\n \n // Get file metadata\n const { data: fileInfo } = await supabase.storage\n .from(bucketName)\n .list(path.split('/').slice(0, -1).join('/'), {\n search: fileName\n });\n\n const metadata = fileInfo?.[0]?.metadata || {};\n \n return {\n blob: data,\n metadata: {\n name: fileName,\n size: metadata.size || data.size,\n type: metadata.mimetype || 'application/octet-stream'\n }\n };\n } catch (error) {\n log.error('Failed to download file:', error);\n return null;\n }\n}\n\n/**\n * Move a file to archived location (soft delete)\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param options - Archive options including bucket selection via isPublic\n */\nexport async function archiveFile(\n supabase: SupabaseClient,\n path: string,\n options: { appName: string; orgId?: string; userId?: string; isPublic?: boolean }\n): Promise<{ success: boolean; error?: string }> {\n try {\n const bucketName = getBucketName(options.isPublic || false);\n \n // Generate archived path for organization-first or user-first structure\n let archivedPath: string;\n if (options.orgId) {\n archivedPath = path.replace(`${options.orgId}/`, `archived/${options.orgId}/`);\n } else if (options.userId) {\n archivedPath = path.replace(`users/${options.userId}/`, `archived/users/${options.userId}/`);\n } else {\n throw new Error('Either orgId or userId is required for archiving files');\n }\n \n // Copy file to archived location\n const { error: copyError } = await supabase.storage\n .from(bucketName)\n .copy(path, archivedPath);\n\n if (copyError) {\n return {\n success: false,\n error: `Archive failed: ${copyError.message}`\n };\n }\n\n // Delete original file\n const deleteResult = await deleteFile(supabase, path, options.isPublic || false);\n if (!deleteResult.success) {\n return deleteResult;\n }\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: `Archive failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n}\n","/**\n * @file Public File Display Hook\n * @package @jmruthers/pace-core\n * @module Hooks/Public\n * @since 1.0.0\n *\n * A React hook for accessing file references in public contexts without authentication.\n * Provides file URLs and metadata for public pages.\n *\n * Features:\n * - No authentication required\n * - Only returns public files (is_public = true)\n * - Caching for performance\n * - Error handling and loading states\n * - TypeScript support\n * - Supports both single file (category) and multiple files\n *\n * @example\n * ```tsx\n * import { usePublicFileDisplay } from '@jmruthers/pace-core';\n *\n * function PublicFileView() {\n * const { fileUrl, fileReference, isLoading, error } = usePublicFileDisplay(\n * 'event',\n * eventId,\n * organisationId,\n * FileCategory.EVENT_LOGOS,\n * { supabase }\n * );\n *\n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n *\n * return fileUrl ? <img src={fileUrl} alt=\"File\" /> : null;\n * }\n * ```\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { Database } from '../../types/database';\nimport { FileReference, FileCategory } from '../../types/file-reference';\nimport { getPublicUrl, generateFileUrlsBatch } from '../../utils/storage/helpers';\nimport { logger } from '../../utils/core/logger';\n\n// Simple in-memory cache for public file data\nconst publicFileCache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n\nexport interface UsePublicFileDisplayReturn {\n /** Single file URL if category is provided and file found, null otherwise */\n fileUrl: string | null;\n /** Single file reference if category is provided and file found, null otherwise */\n fileReference: FileReference | null;\n /** Array of all file references for the record (when category not provided or for multiple files) */\n fileReferences: FileReference[];\n /** Map of file IDs to URLs for multiple files */\n fileUrls: Map<string, string>;\n /** Total count of files for the record */\n fileCount: number;\n /** Whether the data is currently loading */\n isLoading: boolean;\n /** Any error that occurred during loading */\n error: Error | null;\n /** Function to manually refetch the data */\n refetch: () => Promise<void>;\n}\n\nexport interface UsePublicFileDisplayOptions {\n /** Cache TTL in milliseconds (default: 30 minutes) */\n cacheTtl?: number;\n /** Whether to enable caching (default: true) */\n enableCache?: boolean;\n /** Supabase client instance (required) */\n supabase: SupabaseClient<Database>;\n}\n\n/**\n * Hook for accessing public file references\n * \n * This hook provides access to file references without requiring\n * authentication. It only returns public files and generates public URLs.\n * \n * @param table_name - The table name containing the file reference\n * @param record_id - The record ID that owns the file(s)\n * @param organisation_id - The organisation ID for storage path\n * @param category - Optional file category to filter by (for single file mode)\n * @param options - Configuration options for caching and behavior\n * @returns Object containing file data, loading state, error, and refetch function\n */\nexport function usePublicFileDisplay(\n table_name: string | undefined,\n record_id: string | undefined,\n organisation_id: string | undefined,\n category: FileCategory | undefined,\n options: UsePublicFileDisplayOptions\n): UsePublicFileDisplayReturn {\n const {\n cacheTtl = 30 * 60 * 1000, // 30 minutes\n enableCache = true,\n supabase\n } = options;\n\n const [fileUrl, setFileUrl] = useState<string | null>(null);\n const [fileReference, setFileReference] = useState<FileReference | null>(null);\n const [fileReferences, setFileReferences] = useState<FileReference[]>([]);\n const [fileUrls, setFileUrls] = useState<Map<string, string>>(new Map());\n const [fileCount, setFileCount] = useState<number>(0);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchFiles = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id || !supabase) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n return;\n }\n\n // Validate UUID format for organisationId to prevent database errors (only if provided)\n if (organisation_id) {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(organisation_id)) {\n logger.warn('usePublicFileDisplay', 'Invalid organisationId format (not a valid UUID)', { organisation_id });\n }\n }\n\n // Check cache first\n // When organisation_id is undefined, use 'undefined' in cache key to distinguish from explicit null\n const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n if (enableCache) {\n const cached = publicFileCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < cached.ttl) {\n const cachedData = cached.data;\n setFileUrl(cachedData.fileUrl || null);\n setFileReference(cachedData.fileReference || null);\n setFileReferences(cachedData.fileReferences || []);\n setFileUrls(cachedData.fileUrls || new Map());\n setFileCount(cachedData.fileCount || 0);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n try {\n setIsLoading(true);\n setError(null);\n\n let files: any[] = [];\n\n // When organisation_id is undefined, search both user-scoped (null) and organisation-scoped files\n // This allows FileDisplay to work without requiring the organisation_id prop\n if (organisation_id === undefined) {\n logger.debug('usePublicFileDisplay', 'organisation_id is undefined, searching both user-scoped and organisation-scoped files:', {\n table_name,\n record_id,\n category\n });\n\n // First, try user-scoped files (organisation_id = null)\n let userScopedFiles: any[] = [];\n let orgScopedFiles: any[] = [];\n\n // Query user-scoped files\n if (category) {\n const { data: userData, error: userRpcError } = await (supabase as any)\n .rpc('data_file_reference_by_category_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_category: category,\n p_organisation_id: null\n });\n\n if (!userRpcError && userData) {\n userScopedFiles = userData\n .filter((item: any) => item.is_public === true && item.id && item.file_path && item.file_metadata)\n .map((item: any) => ({\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: item.file_metadata || {},\n organisation_id: null,\n app_id: item.file_metadata?.app_id || null,\n is_public: true,\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString()\n }));\n }\n } else {\n const { data: userData, error: userRpcError } = await (supabase as any)\n .rpc('data_file_reference_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: null\n });\n\n if (!userRpcError && userData) {\n const ids = userData.map((item: any) => item.id);\n if (ids.length > 0) {\n const { data: fullData } = await supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .in('id', ids)\n .eq('is_public', true);\n userScopedFiles = fullData || [];\n }\n }\n }\n\n // For public pages, we can't query user's organisations (user is anonymous)\n // But we can try to find organisation-scoped files by querying the record's context\n // For now, we'll just use user-scoped files. If needed, the page context can provide organisation_id\n // Note: This is a limitation of public pages - they should provide organisation_id if needed\n\n // Merge results\n const allFiles = [...userScopedFiles, ...orgScopedFiles];\n allFiles.sort((a, b) => {\n const aTime = new Date(a.created_at).getTime();\n const bTime = new Date(b.created_at).getTime();\n return bTime - aTime;\n });\n\n files = allFiles;\n\n logger.debug('usePublicFileDisplay', 'Found files with undefined organisation_id:', {\n userScopedCount: userScopedFiles.length,\n orgScopedCount: orgScopedFiles.length,\n totalCount: files.length\n });\n } else {\n // organisation_id is provided (or explicitly null) - use normal query\n // CRITICAL: When category is provided, MUST use RPC function, not direct queries\n // Category is stored in file_metadata JSONB field, not a direct column\n if (category) {\n // Single file mode - use RPC to get files by category\n const rpcParams = {\n p_table_name: table_name,\n p_record_id: record_id,\n p_category: category,\n p_organisation_id: organisation_id ?? null\n };\n \n const { data, error: rpcError } = await (supabase as any)\n .rpc('data_file_reference_by_category_list', rpcParams);\n\n if (rpcError) {\n logger.error('usePublicFileDisplay', 'RPC function error', {\n function: 'data_file_reference_by_category_list',\n table_name,\n record_id,\n category,\n organisation_id,\n error: rpcError.message,\n errorCode: rpcError.code,\n errorDetails: rpcError.details,\n errorHint: rpcError.hint\n });\n throw new Error(rpcError.message || 'Failed to fetch file reference');\n }\n\n // RPC returns partial data with: id, file_path, file_metadata, is_public, created_at\n // We have table_name, record_id, organisation_id from function parameters\n // We can construct FileReference objects directly without another query (avoiding RLS issues)\n if (!data || data.length === 0) {\n files = [];\n } else {\n // Construct file reference objects from RPC response\n // This avoids RLS issues - the RPC already validated permissions and filtered public files\n files = data\n .filter((item: any) => {\n // RPC should only return public files for public context, but verify\n return item.is_public === true && item.id && item.file_path && item.file_metadata;\n })\n .map((item: any) => {\n // Construct complete file reference from RPC response + function parameters\n return {\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: item.file_metadata || {},\n organisation_id: organisation_id ?? null,\n app_id: item.file_metadata?.app_id || null,\n is_public: true, // RPC already filtered for public files\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString()\n };\n });\n }\n } else {\n // Multiple files mode - use RPC to get all files\n const { data: fileIds, error: rpcError } = await (supabase as any)\n .rpc('data_file_reference_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (rpcError) {\n logger.error('usePublicFileDisplay', 'RPC function error', {\n function: 'data_file_reference_list',\n table_name,\n record_id,\n organisation_id,\n error: rpcError.message,\n errorCode: rpcError.code,\n errorDetails: rpcError.details,\n errorHint: rpcError.hint\n });\n throw new Error(rpcError.message || 'Failed to fetch file references');\n }\n\n if (!fileIds || fileIds.length === 0) {\n files = [];\n } else {\n // Fetch full file reference data for each ID, but only public files\n const ids = fileIds.map((item: any) => item.id);\n const { data: fullData, error: fetchError } = await supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .in('id', ids)\n .eq('is_public', true); // Only public files in public context\n\n if (fetchError) {\n throw new Error(fetchError.message || 'Failed to fetch file references');\n }\n\n files = fullData || [];\n }\n }\n }\n\n // Ensure all files are public (category RPC might return both)\n const publicFiles = files.filter((f: any) => f.is_public === true);\n\n if (publicFiles.length === 0) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n \n // Cache empty result\n if (enableCache) {\n publicFileCache.set(cacheKey, {\n data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: new Map(), fileCount: 0 },\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n }\n return;\n }\n\n // Convert to FileReference format\n const fileRefs: FileReference[] = publicFiles.map((f: any) => ({\n id: f.id,\n table_name: f.table_name,\n record_id: f.record_id,\n file_path: f.file_path,\n file_metadata: f.file_metadata || {},\n organisation_id: f.organisation_id,\n app_id: f.app_id,\n is_public: f.is_public ?? true,\n created_at: f.created_at,\n updated_at: f.updated_at\n }));\n\n setFileReferences(fileRefs);\n setFileCount(fileRefs.length);\n\n if (category && fileRefs.length > 0) {\n // Single file mode - get first file\n const firstFile = fileRefs[0];\n setFileReference(firstFile);\n \n // Generate public URL\n const url = getPublicUrl(supabase, firstFile.file_path, true);\n setFileUrl(url);\n } else {\n // Multiple files mode - generate URLs for all files in batch\n const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {\n appName: 'pace-core',\n orgId: organisation_id,\n expiresIn: 3600\n });\n setFileUrls(urlMap);\n setFileReference(null);\n setFileUrl(null);\n }\n\n // Cache the result\n if (enableCache) {\n publicFileCache.set(cacheKey, {\n data: {\n fileUrl: category ? (fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null) : null,\n fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,\n fileReferences: fileRefs,\n fileUrls: category ? new Map() : (() => {\n const urlMap = new Map<string, string>();\n for (const fileRef of fileRefs) {\n const url = getPublicUrl(supabase, fileRef.file_path, true);\n if (url) {\n urlMap.set(fileRef.id, url);\n }\n }\n return urlMap;\n })(),\n fileCount: fileRefs.length\n },\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n }\n\n } catch (err) {\n logger.error('usePublicFileDisplay', 'Error fetching files', {\n table_name,\n record_id,\n organisation_id,\n category,\n error: err instanceof Error ? err.message : 'Unknown error',\n errorDetails: err instanceof Error ? err.stack : String(err)\n });\n const error = err instanceof Error ? err : new Error('Unknown error occurred');\n setError(error);\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n } finally {\n setIsLoading(false);\n }\n }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);\n\n // Fetch files when parameters change\n useEffect(() => {\n if (table_name && record_id) {\n fetchFiles();\n } else {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n setError(null);\n }\n }, [fetchFiles, table_name, record_id, organisation_id]);\n\n const refetch = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id) return;\n \n // Clear cache for this file\n if (enableCache) {\n const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n publicFileCache.delete(cacheKey);\n }\n await fetchFiles();\n }, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);\n\n return {\n fileUrl,\n fileReference,\n fileReferences,\n fileUrls,\n fileCount,\n isLoading,\n error,\n refetch\n };\n}\n\n/**\n * Clear all cached public file data\n * Useful for testing or when you need to force refresh all data\n */\nexport function clearPublicFileDisplayCache(): void {\n for (const [key] of publicFileCache) {\n if (key.startsWith('public_file_')) {\n publicFileCache.delete(key);\n }\n }\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getPublicFileDisplayCacheStats(): { size: number; keys: string[] } {\n const keys = Array.from(publicFileCache.keys()).filter(key => key.startsWith('public_file_'));\n return {\n size: keys.length,\n keys\n };\n}\n\n","/**\n * @file File Display Hook (Authenticated)\n * @package @jmruthers/pace-core\n * @module Hooks\n *\n * A React hook for accessing file references in authenticated contexts.\n * Can handle both public and private files using the core_file_references system.\n *\n * Features:\n * - Works in authenticated contexts\n * - Supports both public and private files\n * - Automatic signed URL generation for private files\n * - Caching for performance\n * - Error handling and loading states\n * - Supports both single file (category) and multiple files\n *\n * @example\n * ```tsx\n * import { useFileDisplay } from '@jmruthers/pace-core';\n *\n * function FileView() {\n * const { fileUrl, fileReference, isLoading, error } = useFileDisplay(\n * 'core_events',\n * eventId,\n * organisationId,\n * FileCategory.EVENT_LOGOS\n * );\n *\n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n *\n * return fileUrl ? <img src={fileUrl} alt=\"File\" /> : null;\n * }\n * ```\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport { FileReference, FileCategory } from '../types/file-reference';\nimport { getPublicUrl, getSignedUrl, generateFileUrlsBatch } from '../utils/storage/helpers';\nimport { createFileReferenceService } from '../utils/file-reference';\nimport { logger } from '../utils/core/logger';\n\n// Simple in-memory cache for authenticated file data\nconst authenticatedFileCache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n\n// Cache size limit to prevent memory leaks\nconst MAX_CACHE_SIZE = 100;\n\n// Helper function to clean up expired entries and enforce size limit\nfunction cleanupCache() {\n const now = Date.now();\n const entries = Array.from(authenticatedFileCache.entries());\n \n // Remove expired entries\n const expiredKeys: string[] = [];\n entries.forEach(([key, value]) => {\n if (now - value.timestamp >= value.ttl) {\n expiredKeys.push(key);\n }\n });\n expiredKeys.forEach(key => authenticatedFileCache.delete(key));\n \n // If still over limit, remove oldest entries\n if (authenticatedFileCache.size > MAX_CACHE_SIZE) {\n const sorted = entries\n .filter(([key]) => !expiredKeys.includes(key))\n .sort((a, b) => a[1].timestamp - b[1].timestamp);\n const toRemove = sorted.slice(0, authenticatedFileCache.size - MAX_CACHE_SIZE);\n toRemove.forEach(([key]) => authenticatedFileCache.delete(key));\n }\n}\n\nexport interface UseFileDisplayReturn {\n /** Single file URL if category is provided and file found, null otherwise */\n fileUrl: string | null;\n /** Single file reference if category is provided and file found, null otherwise */\n fileReference: FileReference | null;\n /** Array of all file references for the record (when category not provided or for multiple files) */\n fileReferences: FileReference[];\n /** Map of file IDs to URLs for multiple files */\n fileUrls: Map<string, string>;\n /** Total count of files for the record */\n fileCount: number;\n /** Whether the data is currently loading */\n isLoading: boolean;\n /** Any error that occurred during loading */\n error: Error | null;\n /** Function to manually refetch the data */\n refetch: () => Promise<void>;\n}\n\nexport interface UseFileDisplayOptions {\n /** Cache TTL in milliseconds (default: 30 minutes) */\n cacheTtl?: number;\n /** Whether to enable caching (default: true) */\n enableCache?: boolean;\n /** Supabase client instance (required) */\n supabase: SupabaseClient | null;\n}\n\n/**\n * Hook for accessing file references in authenticated contexts\n * \n * This hook provides access to file references for authenticated users.\n * It supports both public and private files, generating appropriate URLs\n * (public URLs for public files, signed URLs for private files).\n * \n * @param table_name - The table name containing the file reference\n * @param record_id - The record ID that owns the file(s)\n * @param organisation_id - The organisation ID for storage path\n * @param category - Optional file category to filter by (for single file mode)\n * @param options - Configuration options for caching and behavior\n * @returns Object containing file data, loading state, error, and refetch function\n */\nexport function useFileDisplay(\n table_name: string | undefined,\n record_id: string | undefined,\n organisation_id: string | undefined,\n category: FileCategory | undefined,\n options: UseFileDisplayOptions\n): UseFileDisplayReturn {\n const {\n cacheTtl = 30 * 60 * 1000, // 30 minutes\n enableCache = true,\n supabase\n } = options;\n\n const [fileUrl, setFileUrl] = useState<string | null>(null);\n const [fileReference, setFileReference] = useState<FileReference | null>(null);\n const [fileReferences, setFileReferences] = useState<FileReference[]>([]);\n const [fileUrls, setFileUrls] = useState<Map<string, string>>(new Map());\n const [fileCount, setFileCount] = useState<number>(0);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchFiles = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id || !supabase) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n return;\n }\n\n // Validate UUID format for organisationId to prevent database errors (only if provided)\n if (organisation_id) {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(organisation_id)) {\n logger.warn('useFileDisplay', 'Invalid organisationId format (not a valid UUID):', organisation_id);\n }\n }\n\n // Check cache first\n // When organisation_id is undefined, use 'undefined' in cache key to distinguish from explicit null\n const cacheKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n if (enableCache) {\n const cached = authenticatedFileCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < cached.ttl) {\n const cachedData = cached.data;\n \n // FIX: Regenerate signed URL for private files if missing\n if (cachedData.fileReference && !cachedData.fileUrl && \n cachedData.fileReference.is_public === false && supabase) {\n // Regenerate signed URL without fetching from database\n try {\n const signedUrlResult = await getSignedUrl(supabase, cachedData.fileReference.file_path, {\n appName: 'pace-core',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: 3600\n });\n const regeneratedUrl = signedUrlResult?.url || null;\n setFileUrl(regeneratedUrl);\n setFileReference(cachedData.fileReference);\n setFileReferences(cachedData.fileReferences || []);\n setFileUrls(cachedData.fileUrls || new Map());\n setFileCount(cachedData.fileCount || 0);\n setIsLoading(false);\n setError(null);\n return;\n } catch (err) {\n // If signed URL regeneration fails, fall through to normal fetch\n logger.warn('useFileDisplay', 'Failed to regenerate signed URL from cache, falling back to fetch:', err);\n }\n }\n \n // Normal cache hit for public files or files with URLs\n setFileUrl(cachedData.fileUrl || null);\n setFileReference(cachedData.fileReference || null);\n setFileReferences(cachedData.fileReferences || []);\n setFileUrls(cachedData.fileUrls || new Map());\n setFileCount(cachedData.fileCount || 0);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n try {\n setIsLoading(true);\n setError(null);\n\n const service = createFileReferenceService(supabase);\n let files: FileReference[] = [];\n\n // When organisation_id is undefined (not provided), search both user-scoped (null) and organisation-scoped files\n // This allows FileDisplay to work without requiring the organisation_id prop\n // Note: Explicitly passing null or empty string should only search user-scoped files\n const shouldSearchBothScopes = organisation_id === undefined;\n \n if (shouldSearchBothScopes) {\n // First, try user-scoped files (organisation_id = null)\n let userScopedFiles: FileReference[] = [];\n let orgScopedFiles: FileReference[] = [];\n\n try {\n if (category) {\n userScopedFiles = await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n undefined // Explicitly pass undefined for user-scoped files (service converts to null for RPC)\n );\n } else {\n userScopedFiles = await service.listFileReferences(\n table_name,\n record_id,\n undefined // Explicitly pass undefined for user-scoped files (service converts to null for RPC)\n );\n }\n } catch (err) {\n logger.warn('useFileDisplay', 'Error querying user-scoped files:', err);\n userScopedFiles = [];\n }\n\n // For organisation-scoped files, we need to query the user's organisations\n // Get user's organisations from the authenticated session\n try {\n const { data: { user }, error: userError } = await supabase.auth.getUser();\n if (userError) {\n logger.warn('useFileDisplay', 'Error getting user:', userError);\n }\n \n if (user) {\n // Query user's active organisation memberships\n const { data: memberships, error: membershipError } = await supabase\n .from('core_organisation_memberships')\n .select('organisation_id')\n .eq('user_id', user.id);\n\n if (membershipError) {\n logger.warn('useFileDisplay', 'Error querying organisation memberships:', membershipError);\n }\n\n if (memberships && memberships.length > 0) {\n // Try each organisation the user belongs to\n const orgIds = memberships.map(m => m.organisation_id).filter(Boolean) as string[];\n \n // Query each organisation in parallel\n const orgQueries = orgIds.map(async (orgId) => {\n try {\n if (category) {\n return await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n orgId\n );\n } else {\n return await service.listFileReferences(\n table_name,\n record_id,\n orgId\n );\n }\n } catch (err) {\n // Silently fail for individual org queries - user might not have access\n return [];\n }\n });\n\n const orgResults = await Promise.all(orgQueries);\n orgScopedFiles = orgResults.flat();\n } else {\n // When user has no organisation memberships, try querying files with any organisation_id\n // as a fallback - the file might be organisation-scoped but accessible via RLS\n // This handles edge cases where RLS allows access but we can't enumerate organisations\n try {\n // Try querying without organisation_id filter - let RLS handle security\n let fallbackQuery = supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('table_name', table_name)\n .eq('record_id', record_id)\n .order('created_at', { ascending: false });\n\n if (category) {\n fallbackQuery = fallbackQuery.eq('file_metadata->>category', category);\n }\n\n const { data: fallbackFiles } = await fallbackQuery;\n \n if (fallbackFiles && fallbackFiles.length > 0) {\n // Convert to FileReference format\n orgScopedFiles = fallbackFiles.map((f: any) => ({\n id: f.id,\n table_name: f.table_name,\n record_id: f.record_id,\n file_path: f.file_path,\n file_metadata: f.file_metadata || {},\n organisation_id: f.organisation_id,\n app_id: f.app_id,\n is_public: f.is_public ?? false,\n created_at: f.created_at,\n updated_at: f.updated_at\n })) as FileReference[];\n }\n } catch (err) {\n // Silently fail - RLS may block or other error\n }\n }\n }\n } catch (err) {\n logger.warn('useFileDisplay', 'Error querying organisation-scoped files:', err);\n orgScopedFiles = [];\n }\n\n // Merge results: prefer organisation-scoped files if both exist, otherwise use user-scoped\n // Sort by created_at DESC to get most recent first\n const allFiles = [...userScopedFiles, ...orgScopedFiles];\n allFiles.sort((a, b) => {\n const aTime = new Date(a.created_at).getTime();\n const bTime = new Date(b.created_at).getTime();\n return bTime - aTime;\n });\n\n // If we have both types, prefer organisation-scoped (non-null organisation_id)\n // Otherwise, use whatever we found\n if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {\n // Prefer organisation-scoped files - filter to only those with organisation_id\n files = allFiles.filter(f => f.organisation_id !== null);\n } else {\n // Use all files found (either user-scoped or org-scoped, but not both)\n files = allFiles;\n }\n\n // If no files found through RPC, try a direct query as fallback\n // This handles cases where RLS policy allows access but RPC security check is too strict\n // (e.g., core_person files where user owns the person record but record_id != user_id)\n if (files.length === 0) {\n try {\n let directQuery = supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('table_name', table_name)\n .eq('record_id', record_id)\n .order('created_at', { ascending: false });\n\n // If category is provided, filter by category in metadata\n if (category) {\n directQuery = directQuery.eq('file_metadata->>category', category);\n }\n\n const { data: directFiles } = await directQuery;\n\n if (directFiles && directFiles.length > 0) {\n // Convert to FileReference format\n files = directFiles.map((f: any) => ({\n id: f.id,\n table_name: f.table_name,\n record_id: f.record_id,\n file_path: f.file_path,\n file_metadata: f.file_metadata || {},\n organisation_id: f.organisation_id,\n app_id: f.app_id,\n is_public: f.is_public ?? false,\n created_at: f.created_at,\n updated_at: f.updated_at\n })) as FileReference[];\n }\n } catch (err) {\n // Silently fail - RLS may block or other error\n }\n }\n } else {\n // organisation_id is provided (or explicitly null) - use normal query\n // CRITICAL: When category is provided, MUST use RPC function, not direct queries\n // Category is stored in file_metadata JSONB field, not a direct column\n if (category) {\n // Single file mode - get files by category using RPC\n // Removed verbose debug log - only log on errors\n files = await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n organisation_id\n );\n } else {\n // Multiple files mode - get all files using RPC\n files = await service.listFileReferences(\n table_name,\n record_id,\n organisation_id\n );\n }\n\n // Fallback: If no files found and organisation_id is falsy (undefined, null, empty string),\n // try searching both scopes as a fallback\n // This handles cases where the prop might be passed as empty string or the check above didn't catch it\n if (files.length === 0 && (!organisation_id || organisation_id === '')) {\n // Try the dual-scope search logic\n let userScopedFiles: FileReference[] = [];\n let orgScopedFiles: FileReference[] = [];\n\n try {\n if (category) {\n userScopedFiles = await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n undefined\n );\n } else {\n userScopedFiles = await service.listFileReferences(\n table_name,\n record_id,\n undefined\n );\n }\n } catch (err) {\n // Silently fail\n }\n\n try {\n const { data: { user } } = await supabase.auth.getUser();\n if (user) {\n const { data: memberships } = await supabase\n .from('core_organisation_memberships')\n .select('organisation_id')\n .eq('user_id', user.id)\n .or('status.is.null,status.eq.active');\n\n if (memberships && memberships.length > 0) {\n const orgIds = memberships.map(m => m.organisation_id).filter(Boolean) as string[];\n const orgQueries = orgIds.map(async (orgId) => {\n try {\n if (category) {\n return await service.getFilesByCategory(table_name, record_id, category, orgId);\n } else {\n return await service.listFileReferences(table_name, record_id, orgId);\n }\n } catch (err) {\n return [];\n }\n });\n const orgResults = await Promise.all(orgQueries);\n orgScopedFiles = orgResults.flat();\n }\n }\n } catch (err) {\n // Silently fail\n }\n\n // Merge results\n const allFiles = [...userScopedFiles, ...orgScopedFiles];\n allFiles.sort((a, b) => {\n const aTime = new Date(a.created_at).getTime();\n const bTime = new Date(b.created_at).getTime();\n return bTime - aTime;\n });\n\n if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {\n files = allFiles.filter(f => f.organisation_id !== null);\n } else {\n files = allFiles;\n }\n }\n }\n\n if (files.length === 0) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n \n // Cache empty result\n if (enableCache) {\n authenticatedFileCache.set(cacheKey, {\n data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: new Map(), fileCount: 0 },\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n cleanupCache();\n }\n return;\n }\n\n setFileReferences(files);\n setFileCount(files.length);\n\n if (category && files.length > 0) {\n // Single file mode - get first file\n const firstFile = files[0];\n // Removed verbose debug logs - only log on errors\n setFileReference(firstFile);\n \n // Generate URL based on file visibility\n let url: string | null = null;\n if (firstFile.is_public) {\n url = getPublicUrl(supabase, firstFile.file_path, true);\n } else {\n const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {\n appName: 'pace-core',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: 3600\n });\n url = signedUrlResult?.url || null;\n // Only log if URL generation fails\n if (!url) {\n logger.warn('useFileDisplay', 'Failed to generate signed URL for file:', {\n file_path: firstFile.file_path,\n record_id,\n table_name\n });\n }\n }\n setFileUrl(url);\n } else {\n // Multiple files mode - generate URLs for all files in batch\n const urlMap = await generateFileUrlsBatch(supabase, files, {\n appName: 'pace-core',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: 3600\n });\n setFileUrls(urlMap);\n setFileReference(null);\n setFileUrl(null);\n }\n\n // Cache the result\n if (enableCache) {\n // Prepare cache data\n let cacheData: any = {\n fileReference: category && files.length > 0 ? files[0] : null,\n fileReferences: files,\n fileUrls: new Map(),\n fileCount: files.length\n };\n\n if (category && files.length > 0) {\n const firstFile = files[0];\n let url: string | null = null;\n if (firstFile.is_public) {\n url = getPublicUrl(supabase, firstFile.file_path, true);\n }\n cacheData.fileUrl = url;\n } else {\n const urlMap = new Map<string, string>();\n for (const fileRef of files) {\n if (fileRef.is_public) {\n const url = getPublicUrl(supabase, fileRef.file_path, true);\n if (url) {\n urlMap.set(fileRef.id, url);\n }\n }\n }\n cacheData.fileUrls = urlMap;\n }\n\n authenticatedFileCache.set(cacheKey, {\n data: cacheData,\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n cleanupCache();\n }\n\n } catch (err) {\n logger.error('useFileDisplay', 'Error fetching files:', err);\n const error = err instanceof Error ? err : new Error('Unknown error occurred');\n setError(error);\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n } finally {\n setIsLoading(false);\n }\n }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);\n\n // Fetch files when parameters change\n useEffect(() => {\n if (table_name && record_id && supabase) {\n fetchFiles();\n } else {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n setError(null);\n }\n // fetchFiles is memoized; we only need to re-run when parameters change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [table_name, record_id, organisation_id, supabase]);\n\n const refetch = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id || !supabase) return;\n \n // Clear cache for this file\n if (enableCache) {\n const cacheKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n authenticatedFileCache.delete(cacheKey);\n }\n await fetchFiles();\n }, [fetchFiles, table_name, record_id, organisation_id, category, supabase, enableCache]);\n\n return {\n fileUrl,\n fileReference,\n fileReferences,\n fileUrls,\n fileCount,\n isLoading,\n error,\n refetch\n };\n}\n\n/**\n * Clear all cached authenticated file data\n * Useful for testing or when you need to force refresh all data\n */\nexport function clearFileDisplayCache(): void {\n for (const [key] of authenticatedFileCache) {\n if (key.startsWith('file_')) {\n authenticatedFileCache.delete(key);\n }\n }\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getFileDisplayCacheStats(): { size: number; keys: string[] } {\n const keys = Array.from(authenticatedFileCache.keys()).filter(key => key.startsWith('file_'));\n return {\n size: keys.length,\n keys\n };\n}\n\n/**\n * Invalidate cache for a specific file display entry\n * Useful for clearing cache after file uploads or updates\n * \n * @param table_name - The table name containing the file reference\n * @param record_id - The record ID that owns the file(s)\n * @param organisation_id - The organisation ID for storage path\n * @param category - Optional file category to invalidate (if provided, also invalidates 'all' category)\n */\nexport function invalidateFileDisplayCache(\n table_name: string,\n record_id: string,\n organisation_id: string | null | undefined,\n category?: FileCategory\n): void {\n const cacheKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n authenticatedFileCache.delete(cacheKey);\n // Also invalidate 'all' category if specific category invalidated\n if (category) {\n const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_all`;\n authenticatedFileCache.delete(allCategoryKey);\n }\n}\n\n","// File Reference Service\n// Provides CRUD operations for the centralized file reference system\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { \n FileReference, \n FileUploadOptions, \n FileReferenceService, \n FileUploadResult,\n FileCategory,\n FileMetadata\n} from '../../types/file-reference';\nimport { uploadFile, getPublicUrl, getSignedUrl, deleteFile, extractFileMetadata } from '../storage/helpers';\nimport { setOrganisationContext } from '../context/organisationContext';\nimport { invalidateFileDisplayCache } from '../../hooks/useFileDisplay';\nimport { createLogger } from '../core/logger';\nimport { assertAppId } from '../../types/core';\n\nconst log = createLogger('FileReferenceService');\n\nexport class FileReferenceServiceImpl implements FileReferenceService {\n constructor(private supabase: SupabaseClient) {}\n\n /**\n * Creates a file reference by uploading a file to storage and linking it in the database.\n * \n * Storage Flow:\n * 1. Upload file to storage bucket first (files or public-files based on is_public flag)\n * - Path format: {orgId}/{folder}/{timestamp-uuid-filename}\n * - Bucket selection: 'files' (private) or 'public-files' (public)\n * 2. Extract file metadata (dimensions, hash, etc.)\n * 3. Set organisation context for RLS policies\n * 4. Create database reference via RPC function\n * 5. If DB insert fails, rollback by deleting uploaded file\n * \n * This ensures atomicity: either both storage and DB succeed, or both are cleaned up.\n */\n async createFileReference(options: FileUploadOptions, file: File): Promise<FileReference> {\n try {\n\n // organisation_id is optional for user-scoped files (e.g., profile photos)\n const isUserScoped = !options.organisation_id && options.userId;\n if (!isUserScoped && !options.organisation_id) {\n throw new Error('organisation_id is required for file upload, or userId must be provided for user-scoped files');\n }\n if (!options.table_name) {\n throw new Error('table_name is required for file upload');\n }\n if (!options.record_id) {\n throw new Error('record_id is required for file upload');\n }\n if (!options.folder) {\n throw new Error('folder is required for file upload. The folder prop determines the storage path.');\n }\n\n // For user-scoped files, we MUST use auth.uid() for the path to match RLS policies\n // Get the authenticated user ID from the Supabase session\n let authenticatedUserId: string | undefined = undefined;\n if (isUserScoped) {\n const { data: { user: authUser }, error: authError } = await this.supabase.auth.getUser();\n if (authError || !authUser) {\n throw new Error('User must be authenticated to upload user-scoped files');\n }\n authenticatedUserId = authUser.id;\n log.debug('Using authenticated user ID for user-scoped file upload', { userId: authenticatedUserId });\n }\n\n // Step 1: Upload file to storage bucket first\n // This generates a unique path: {orgId}/{folder}/{timestamp-uuid-filename} or users/{auth.uid()}/{folder}/{timestamp-uuid-filename}\n // Bucket is automatically selected based on is_public flag\n const uploadResult = await uploadFile(this.supabase, file, {\n appName: 'file-reference',\n orgId: options.organisation_id || undefined,\n userId: authenticatedUserId || (isUserScoped ? undefined : options.userId), // Use auth.uid() for user-scoped files\n isPublic: options.is_public || false,\n customPath: options.folder // Use folder prop as the custom path segment\n });\n if (!uploadResult.success) {\n throw new Error(`Failed to upload file: ${uploadResult.error}`);\n }\n\n if (!uploadResult.path) {\n throw new Error('File upload did not return a path');\n }\n\n const filePath = uploadResult.path;\n\n // Step 2: Extract file metadata (dimensions, hash, etc.)\n const metadata = await extractFileMetadata(file, {\n appName: 'file-reference',\n orgId: options.organisation_id || undefined,\n userId: authenticatedUserId || (isUserScoped ? undefined : options.userId), // Use auth.uid() for user-scoped files\n isPublic: options.is_public || false\n }, 'system');\n\n // Step 3: Set organisation context in database session before creating file reference\n // Skip for user-scoped files (no org context needed)\n if (!isUserScoped && options.organisation_id) {\n await setOrganisationContext(this.supabase, options.organisation_id);\n }\n\n // Step 4: Create file reference in database using RPC function\n // This links the storage path to the record in core_file_references table\n const { data, error } = await this.supabase\n .rpc('data_file_reference_create', {\n p_table_name: options.table_name,\n p_record_id: options.record_id,\n p_file_path: filePath, // Storage path from step 1\n p_organisation_id: options.organisation_id ?? null,\n p_app_id: options.app_id,\n p_page_context: options.pageContext,\n p_event_id: options.event_id || null, // Pass event_id for event-based apps\n p_file_metadata: {\n fileName: file.name,\n fileType: file.type,\n fileSize: file.size,\n category: options.category,\n ...metadata,\n ...options.custom_metadata\n },\n p_is_public: options.is_public || false,\n p_user_id: authenticatedUserId || options.userId || null // Pass authenticated user ID for user-scoped files\n });\n\n // Step 5: Rollback - if database insert fails, clean up uploaded file\n if (error) {\n await deleteFile(this.supabase, filePath, options.is_public || false);\n throw new Error(`Failed to create file reference: ${error.message}`);\n }\n\n // Check if RPC returned null (permission denied or other failure)\n if (!data || data === null) {\n // Clean up the uploaded file since DB insert failed\n await deleteFile(this.supabase, filePath, options.is_public || false);\n throw new Error(`File upload denied: insufficient permissions. You need 'create:page.${options.pageContext}' or 'update:page.${options.pageContext}' permission for the '${options.pageContext}' page. Make sure the page exists in rbac_app_pages table.`);\n }\n\n // Get the created file reference\n const { data: fileRef, error: fetchError } = await this.supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('id', data)\n .single();\n\n if (fetchError || !fileRef) {\n // Clean up uploaded file if we can't fetch the reference\n await deleteFile(this.supabase, filePath, options.is_public || false);\n throw new Error(`Failed to fetch created file reference: ${fetchError?.message}`);\n }\n\n // Invalidate cache for this file display entry so newly uploaded files appear immediately\n // For user-scoped files, pass null for organisation_id\n invalidateFileDisplayCache(\n options.table_name,\n options.record_id,\n options.organisation_id || null,\n options.category\n );\n\n return fileRef as FileReference;\n } catch (error) {\n log.error('Error creating file reference:', error);\n throw error;\n }\n }\n\n async getFileReference(table_name: string, record_id: string, organisation_id?: string): Promise<FileReference | null> {\n try {\n let query = this.supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('table_name', table_name)\n .eq('record_id', record_id);\n \n // Handle NULL organisation_id for user-owned files\n if (organisation_id === null || organisation_id === undefined) {\n query = query.is('organisation_id', null);\n } else {\n query = query.eq('organisation_id', organisation_id);\n }\n \n const { data, error } = await query.single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n return null; // No rows found\n }\n throw new Error(`Failed to get file reference: ${error.message}`);\n }\n\n return data as FileReference;\n } catch (error) {\n log.error('Error getting file reference:', error);\n throw error;\n }\n }\n\n async getFileUrl(table_name: string, record_id: string, organisation_id?: string): Promise<string | null> {\n try {\n // Get file reference to check if it's public\n const fileRef = await this.getFileReference(table_name, record_id, organisation_id);\n if (!fileRef) {\n return null;\n }\n\n // For public files, RPC returns file path - generate public URL client-side\n if (fileRef.is_public) {\n const { data: pathData } = await this.supabase\n .rpc('data_file_reference_url_get', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id\n });\n\n if (!pathData) {\n return null;\n }\n\n // Generate public URL using bucket-aware helper\n return getPublicUrl(this.supabase, pathData, true);\n } else {\n // For private files, use signed URL\n return await this.getSignedUrl(table_name, record_id, organisation_id);\n }\n } catch (error) {\n log.error('Error getting file URL:', error);\n throw error;\n }\n }\n\n async getSignedUrl(table_name: string, record_id: string, organisation_id?: string, expires_in: number = 3600): Promise<string | null> {\n try {\n // Get file path from RPC function\n const { data: filePath, error } = await this.supabase\n .rpc('data_file_reference_signed_url_get', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null,\n p_expires_in: expires_in\n });\n\n if (error) {\n throw new Error(`Failed to get signed URL: ${error.message}`);\n }\n\n if (!filePath) {\n return null;\n }\n\n // Generate signed URL client-side using bucket-aware helper (files bucket for private files)\n const signedUrlResult = await getSignedUrl(this.supabase, filePath, {\n appName: 'file-reference',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: expires_in\n });\n\n return signedUrlResult?.url || null;\n } catch (error) {\n log.error('Error getting signed URL:', error);\n throw error;\n }\n }\n\n async updateFileReference(id: string, updates: Partial<FileReference>): Promise<FileReference> {\n try {\n const { data, error } = await this.supabase\n .from('core_file_references')\n .update(updates)\n .eq('id', id)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to update file reference: ${error.message}`);\n }\n\n return data as FileReference;\n } catch (error) {\n log.error('Error updating file reference:', error);\n throw error;\n }\n }\n\n async deleteFileReference(table_name: string, record_id: string, organisation_id?: string, delete_file: boolean = false): Promise<boolean> {\n try {\n // Get file reference first to determine bucket\n const fileRef = await this.getFileReference(table_name, record_id, organisation_id);\n \n const { error } = await this.supabase\n .rpc('data_file_reference_delete', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null,\n p_delete_file: delete_file\n });\n\n if (error) {\n throw new Error(`Failed to delete file reference: ${error.message}`);\n }\n\n // If delete_file is true and we have the file reference, delete from storage\n if (delete_file && fileRef) {\n await deleteFile(this.supabase, fileRef.file_path, fileRef.is_public || false);\n }\n\n return true;\n } catch (error) {\n log.error('Error deleting file reference:', error);\n throw error;\n }\n }\n\n async listFileReferences(table_name: string, record_id: string, organisation_id?: string): Promise<FileReference[]> {\n try {\n const { data, error } = await this.supabase\n .rpc('data_file_reference_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n throw new Error(`Failed to list file references: ${error.message}`);\n }\n\n // RPC returns: id, file_path, file_metadata, is_public, created_at\n // We can construct FileReference objects directly from RPC response + function parameters\n // This avoids a second query and reduces network requests\n if (!data || data.length === 0) {\n return [];\n }\n\n // Construct FileReference objects from RPC response\n // This avoids RLS issues with direct queries - the RPC already validated permissions\n interface RpcFileItem {\n id: string;\n file_path: string;\n file_metadata: { app_id?: string; [key: string]: unknown };\n is_public?: boolean;\n created_at?: string;\n }\n const fileReferences: FileReference[] = data\n .filter((item: RpcFileItem) => item.id && item.file_path && item.file_metadata)\n .map((item: RpcFileItem) => {\n // Extract file name and type from file_path\n const fileName = item.file_path.split('/').pop() || 'unknown';\n const fileType = fileName.split('.').pop() || 'unknown';\n \n // Construct complete FileReference from RPC response + function parameters\n const fileRef: FileReference = {\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: {\n fileName,\n fileType,\n ...(item.file_metadata || {}),\n } as FileMetadata,\n organisation_id: organisation_id ?? null,\n app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(''), // May not be in metadata, use empty string\n is_public: item.is_public ?? false,\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString() // RPC doesn't return updated_at, use created_at\n };\n return fileRef;\n });\n\n return fileReferences;\n } catch (error) {\n log.error('Error listing file references:', error);\n throw error;\n }\n }\n\n async getFileCount(table_name: string, record_id: string, organisation_id?: string): Promise<number> {\n try {\n const { data, error } = await this.supabase\n .rpc('data_file_reference_count_get', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n throw new Error(`Failed to get file count: ${error.message}`);\n }\n\n return data || 0;\n } catch (error) {\n log.error('Error getting file count:', error);\n throw error;\n }\n }\n\n async getFileReferenceById(id: string, organisation_id?: string): Promise<FileReference | null> {\n try {\n const { data, error } = await this.supabase\n .rpc('data_file_reference_get', {\n p_file_reference_id: id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n throw new Error(`Failed to get file reference by ID: ${error.message}`);\n }\n\n if (!data || data.length === 0) {\n return null;\n }\n\n return data[0] as FileReference;\n } catch (error) {\n log.error('Error getting file reference by ID:', error);\n throw error;\n }\n }\n\n async getFilesByCategory(\n table_name: string, \n record_id: string, \n category: FileCategory, \n organisation_id?: string\n ): Promise<FileReference[]> {\n try {\n // CRITICAL: Use RPC function to get files by category - this correctly filters on file_metadata->>'category'\n // NOTE: We MUST use RPC function. Direct queries with .eq('category', ...) will FAIL with HTTP 406\n // because there is NO 'category' column. The category is stored in the JSONB file_metadata field.\n const { data, error } = await this.supabase\n .rpc('data_file_reference_by_category_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_category: category,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n // Provide clear error message about category filtering\n log.error('RPC ERROR getting files by category:', {\n error,\n errorCode: error.code,\n errorMessage: error.message,\n errorDetails: error.details,\n table_name,\n record_id,\n category,\n organisation_id,\n 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.'\n });\n 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.`);\n }\n\n // RPC returns partial data with: id, file_path, file_metadata, is_public, created_at\n // We have table_name, record_id, organisation_id from function parameters\n // We can construct FileReference objects directly without another query (avoiding RLS issues)\n if (!data || data.length === 0) {\n return [];\n }\n\n // Construct FileReference objects from RPC response\n // This avoids RLS issues with direct queries - the RPC already validated permissions\n interface RpcFileItem {\n id: string;\n file_path: string;\n file_metadata: { category?: string; app_id?: string; [key: string]: unknown };\n is_public?: boolean;\n created_at?: string;\n }\n const fileReferences: FileReference[] = data\n .filter((item: RpcFileItem) => {\n // Verify category matches (defensive check)\n const fileCategory = item.file_metadata?.category;\n const matches = fileCategory === category;\n if (!matches) {\n log.warn('File category mismatch in RPC response:', {\n fileId: item.id,\n expectedCategory: category,\n actualCategory: fileCategory\n });\n }\n return matches && item.id && item.file_path && item.file_metadata;\n })\n .map((item: RpcFileItem) => {\n // Extract file name and type from file_path\n const fileName = item.file_path.split('/').pop() || 'unknown';\n const fileType = fileName.split('.').pop() || 'unknown';\n \n // Construct complete FileReference from RPC response + function parameters\n const fileRef: FileReference = {\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: {\n fileName,\n fileType,\n category: (item.file_metadata?.category as FileCategory) || FileCategory.GENERAL_DOCUMENTS,\n ...(item.file_metadata || {}),\n } as FileMetadata,\n organisation_id: organisation_id ?? null,\n app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(''), // May not be in metadata, use empty string\n is_public: item.is_public ?? false,\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString() // RPC doesn't return updated_at, use created_at\n };\n return fileRef;\n });\n\n return fileReferences;\n } catch (error) {\n log.error('Error getting files by category:', error);\n throw error;\n }\n }\n\n async uploadMultipleFiles(\n options: FileUploadOptions,\n files: File[]\n ): Promise<import('../../types/file-reference').BulkUploadResult> {\n const success: FileReference[] = [];\n const failed: { file: File; error: string }[] = [];\n const results: Array<{ file: File; result: FileUploadResult | null; error?: string }> = [];\n\n // Upload files sequentially to avoid overwhelming the server\n for (const file of files) {\n try {\n const fileReference = await this.createFileReference(options, file);\n\n success.push(fileReference);\n results.push({\n file,\n result: {\n file_reference: fileReference,\n file_url: fileReference.is_public\n ? getPublicUrl(this.supabase, fileReference.file_path, true)\n : '',\n },\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n failed.push({ file, error: message });\n results.push({ file, result: null, error: message });\n }\n }\n\n return {\n success,\n failed,\n total: results.length,\n successful: success.length,\n results,\n };\n }\n}\n\n// Factory function to create file reference service\nexport function createFileReferenceService(supabase: SupabaseClient): FileReferenceService {\n return new FileReferenceServiceImpl(supabase);\n}\n\n// Helper function to upload file and create reference in one operation\nexport async function uploadFileWithReference(\n supabase: SupabaseClient,\n options: FileUploadOptions,\n file: File\n): Promise<FileUploadResult> {\n \n const service = createFileReferenceService(supabase);\n const fileReference = await service.createFileReference(options, file);\n \n const fileUrl = options.is_public \n ? getPublicUrl(supabase, fileReference.file_path, true)\n : await getSignedUrl(supabase, fileReference.file_path, { \n appName: 'file-reference',\n orgId: options.organisation_id || undefined,\n userId: options.userId || undefined,\n expiresIn: 3600 \n });\n\n const urlString = typeof fileUrl === 'string' ? fileUrl : fileUrl?.url || '';\n\n return {\n file_reference: fileReference,\n file_url: urlString,\n signed_url: options.is_public ? undefined : urlString || undefined\n };\n}\n","/**\n * @file Event Theme Hook\n * @package @jmruthers/pace-core\n * @module Hooks/EventTheme\n * @since 2.0.0\n * \n * Hook that automatically applies event-specific theming when an event is selected.\n * This ensures consistent UX across all apps in the pace suite.\n * \n * Works in two modes:\n * 1. Authenticated mode: Uses EventProvider context via useEvents() hook\n * 2. Public page mode: Accepts event prop directly (no EventProvider required)\n * \n * @example\n * ```tsx\n * // Authenticated pages (with EventProvider)\n * import { useEventTheme } from '@jmruthers/pace-core/hooks';\n * \n * function MyApp() {\n * // Automatically applies event colors when event is selected via EventProvider\n * useEventTheme();\n * \n * return <div>Your app content</div>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Public pages (without EventProvider)\n * import { useEventTheme } from '@jmruthers/pace-core/hooks';\n * \n * function PublicPageLayout({ event }) {\n * // Applies event colors directly from event prop\n * useEventTheme(event);\n * \n * return <div>Public page content</div>;\n * }\n * ```\n */\n\nimport { useEffect } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { useEvents } from './useEvents';\nimport { applyPalette, clearPalette } from '../theming/runtime';\nimport { parseAndNormalizeEventColours } from '../theming/parseEventColours';\nimport type { Event } from '../types/event';\nimport { createLogger } from '../utils/core/logger';\n\nconst log = createLogger('useEventTheme');\n\n/**\n * Hook that automatically applies event-specific theming\n * \n * This hook applies event colors using the theming system. It works in two modes:\n * \n * **Authenticated Mode (default):**\n * - Uses EventProvider context via useEvents() hook\n * - Automatically watches selectedEvent changes\n * - Used in authenticated pages with EventProvider\n * \n * **Public Page Mode:**\n * - Accepts event prop directly\n * - No EventProvider required\n * - Used in public pages without authentication context\n * \n * Behavior:\n * - Applies event colors when an event with `event_colours` is provided\n * - Clears theming when no event is provided\n * - Skips theme application when on the login route to prevent login screen styling\n * - Handles cleanup and error cases gracefully\n * \n * @param event - Optional event object. If provided, uses this event directly (public page mode).\n * If not provided, uses EventProvider context via useEvents() (authenticated mode).\n * \n * @returns void - This is an effect hook with no return value\n * \n * @example\n * ```tsx\n * // Authenticated mode - uses EventProvider\n * function MyApp() {\n * useEventTheme(); // Watches selectedEvent from EventProvider\n * return <div>App content</div>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Public page mode - uses event prop\n * function PublicPageLayout({ event }) {\n * useEventTheme(event); // Uses event prop directly\n * return <div>Public content</div>;\n * }\n * ```\n */\nexport function useEventTheme(event?: Event | null): void {\n const location = useLocation();\n \n // Try to get event from EventProvider context (authenticated mode)\n // Only use useEvents() if event prop is not provided\n let selectedEvent: Event | null | undefined;\n try {\n if (event === undefined) {\n // No event prop provided, try to use EventProvider context\n const eventsContext = useEvents();\n selectedEvent = eventsContext.selectedEvent;\n } else {\n // Event prop provided, use it directly (public page mode)\n selectedEvent = event;\n }\n } catch (error) {\n // useEvents() throws if EventProvider is not available\n // This is expected for public pages - use the event prop if provided\n if (event !== undefined) {\n selectedEvent = event;\n } else {\n // No event prop and no EventProvider - can't apply theme\n selectedEvent = null;\n }\n }\n\n useEffect(() => {\n // Skip theme application when on login route\n // This prevents event colors from styling the login screen\n const isOnLoginRoute = location.pathname === '/login' || location.pathname.startsWith('/login');\n \n if (isOnLoginRoute) {\n // Clear any active theme when on login route\n clearPalette();\n return;\n }\n\n // If there's no selected event, clear any dynamic theming\n if (!selectedEvent) {\n clearPalette();\n return;\n }\n\n // Check if the event has theme colors\n const eventColours = selectedEvent.event_colours;\n\n // Parse and normalize event_colours using shared utility\n const normalized = parseAndNormalizeEventColours(eventColours);\n \n if (!normalized) {\n clearPalette();\n return;\n }\n\n // Apply the normalized palette\n try {\n applyPalette(normalized);\n } catch (error) {\n log.error('Failed to apply event palette:', error);\n }\n\n // Cleanup function to clear palette when component unmounts or event changes\n return () => {\n // Don't clear on unmount since we want the theme to persist\n // The next event selection will update it\n };\n }, [selectedEvent, location.pathname]);\n}\n","/**\n * @file usePreventTabReload Hook\n * @package @jmruthers/pace-core\n * @module Hooks\n * @since 0.6.0\n *\n * Prevents full page reloads when switching browser tabs.\n * Handles browser back-forward cache (bfcache) restoration and tab visibility changes.\n */\n\nimport { useEffect, useRef } from 'react';\n\nexport interface UsePreventTabReloadOptions {\n /**\n * Whether to enable the prevention logic\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Grace period in milliseconds to wait after tab becomes visible\n * before allowing any potential reloads\n * @default 2000\n */\n gracePeriodMs?: number;\n}\n\n/**\n * Hook to prevent full page reloads when switching browser tabs.\n * \n * This hook handles:\n * - Browser back-forward cache (bfcache) restoration\n * - Tab visibility changes\n * - Prevents unwanted page reloads when returning to a tab\n * \n * @param options - Configuration options\n * \n * @example\n * ```tsx\n * import { usePreventTabReload } from '@jmruthers/pace-core';\n * \n * function MyComponent() {\n * usePreventTabReload();\n * // Component code...\n * }\n * ```\n */\nexport function usePreventTabReload(options: UsePreventTabReloadOptions = {}): void {\n const { enabled = true, gracePeriodMs = 2000 } = options;\n const isRestoringFromCacheRef = useRef(false);\n const gracePeriodTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (!enabled || typeof window === 'undefined') return;\n\n // Handle pageshow event - detects when page is restored from bfcache\n const handlePageShow = (event: PageTransitionEvent) => {\n // If page was restored from bfcache, prevent any reload behavior\n if (event.persisted) {\n isRestoringFromCacheRef.current = true;\n \n // Clear any existing timeout\n if (gracePeriodTimeoutRef.current) {\n clearTimeout(gracePeriodTimeoutRef.current);\n }\n \n // Set grace period to prevent reloads\n gracePeriodTimeoutRef.current = setTimeout(() => {\n isRestoringFromCacheRef.current = false;\n }, gracePeriodMs);\n }\n };\n\n // Handle visibility changes - when tab becomes visible\n const handleVisibilityChange = () => {\n if (!document.hidden) {\n // Tab just became visible - set flag to prevent reloads\n isRestoringFromCacheRef.current = true;\n \n // Clear any existing timeout\n if (gracePeriodTimeoutRef.current) {\n clearTimeout(gracePeriodTimeoutRef.current);\n }\n \n // Set grace period\n gracePeriodTimeoutRef.current = setTimeout(() => {\n isRestoringFromCacheRef.current = false;\n }, gracePeriodMs);\n }\n };\n\n // Add event listeners\n window.addEventListener('pageshow', handlePageShow);\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n return () => {\n window.removeEventListener('pageshow', handlePageShow);\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n \n if (gracePeriodTimeoutRef.current) {\n clearTimeout(gracePeriodTimeoutRef.current);\n }\n };\n }, [enabled, gracePeriodMs]);\n}\n\n"],"mappings":"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","names":["useState","useEffect","useCallback","useRef","useState","useRef","useEffect","error","useCallback","log","useState","useEffect","useCallback","useState","useCallback","error","useEffect","useState","useEffect","useCallback","log","MAX_CACHE_SIZE","useState","useCallback","error","useEffect","useEffect","log","useEffect","useEffect","useRef"]}