@jmruthers/pace-core 0.5.188 → 0.5.190

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/core-usage-manifest.json +0 -4
  2. package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
  3. package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
  4. package/dist/{PublicPageProvider-DrLDztHt.d.ts → PublicPageProvider-C4uxosp6.d.ts} +129 -40
  5. package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
  6. package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
  7. package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
  8. package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
  9. package/dist/chunk-4QYC5L4K.js.map +1 -0
  10. package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
  11. package/dist/chunk-73HSNNOQ.js.map +1 -0
  12. package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
  13. package/dist/chunk-DZWK57KZ.js.map +1 -0
  14. package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
  15. package/dist/chunk-HQVPB5MZ.js.map +1 -0
  16. package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
  17. package/dist/chunk-HW3OVDUF.js.map +1 -0
  18. package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
  19. package/dist/chunk-I7PSE6JW.js.map +1 -0
  20. package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
  21. package/dist/chunk-J2XXC7R5.js.map +1 -0
  22. package/dist/{chunk-UNOTYLQF.js → chunk-NIU6J6OX.js} +772 -725
  23. package/dist/chunk-NIU6J6OX.js.map +1 -0
  24. package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
  25. package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
  26. package/dist/chunk-RUYZKXOD.js.map +1 -0
  27. package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
  28. package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
  29. package/dist/{chunk-IPCH26AG.js → chunk-STYK4OH2.js} +11 -11
  30. package/dist/chunk-STYK4OH2.js.map +1 -0
  31. package/dist/{chunk-EFCLXK7F.js → chunk-VVBAW5A5.js} +4201 -3809
  32. package/dist/chunk-VVBAW5A5.js.map +1 -0
  33. package/dist/chunk-Y4BUBBHD.js +614 -0
  34. package/dist/chunk-Y4BUBBHD.js.map +1 -0
  35. package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
  36. package/dist/chunk-ZSAAAMVR.js.map +1 -0
  37. package/dist/components.d.ts +3 -5
  38. package/dist/components.js +19 -23
  39. package/dist/components.js.map +1 -1
  40. package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
  41. package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
  42. package/dist/hooks.d.ts +10 -5
  43. package/dist/hooks.js +14 -8
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +13 -12
  46. package/dist/index.js +79 -73
  47. package/dist/index.js.map +1 -1
  48. package/dist/providers.d.ts +3 -3
  49. package/dist/providers.js +3 -1
  50. package/dist/rbac/index.d.ts +76 -12
  51. package/dist/rbac/index.js +12 -9
  52. package/dist/types.d.ts +1 -1
  53. package/dist/types.js +1 -1
  54. package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
  55. package/dist/utils.js +16 -16
  56. package/docs/README.md +2 -2
  57. package/docs/api/classes/ColumnFactory.md +1 -1
  58. package/docs/api/classes/ErrorBoundary.md +1 -1
  59. package/docs/api/classes/InvalidScopeError.md +2 -2
  60. package/docs/api/classes/Logger.md +1 -1
  61. package/docs/api/classes/MissingUserContextError.md +2 -2
  62. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  63. package/docs/api/classes/PermissionDeniedError.md +1 -1
  64. package/docs/api/classes/RBACAuditManager.md +1 -1
  65. package/docs/api/classes/RBACCache.md +1 -1
  66. package/docs/api/classes/RBACEngine.md +4 -4
  67. package/docs/api/classes/RBACError.md +1 -1
  68. package/docs/api/classes/RBACNotInitializedError.md +2 -2
  69. package/docs/api/classes/SecureSupabaseClient.md +21 -16
  70. package/docs/api/classes/StorageUtils.md +7 -4
  71. package/docs/api/enums/FileCategory.md +1 -1
  72. package/docs/api/enums/LogLevel.md +1 -1
  73. package/docs/api/enums/RBACErrorCode.md +1 -1
  74. package/docs/api/enums/RPCFunction.md +1 -1
  75. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  76. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  77. package/docs/api/interfaces/AggregateConfig.md +1 -1
  78. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  79. package/docs/api/interfaces/AvatarProps.md +128 -0
  80. package/docs/api/interfaces/BadgeProps.md +1 -1
  81. package/docs/api/interfaces/ButtonProps.md +1 -1
  82. package/docs/api/interfaces/CalendarProps.md +20 -6
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/ComplianceResult.md +1 -1
  87. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  88. package/docs/api/interfaces/DataRecord.md +1 -1
  89. package/docs/api/interfaces/DataTableAction.md +1 -1
  90. package/docs/api/interfaces/DataTableColumn.md +1 -1
  91. package/docs/api/interfaces/DataTableProps.md +1 -1
  92. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  93. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  94. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  95. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  96. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  97. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  98. package/docs/api/interfaces/ExportColumn.md +1 -1
  99. package/docs/api/interfaces/ExportOptions.md +1 -1
  100. package/docs/api/interfaces/FileDisplayProps.md +62 -16
  101. package/docs/api/interfaces/FileMetadata.md +1 -1
  102. package/docs/api/interfaces/FileReference.md +2 -2
  103. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  104. package/docs/api/interfaces/FileUploadOptions.md +26 -12
  105. package/docs/api/interfaces/FileUploadProps.md +30 -19
  106. package/docs/api/interfaces/FooterProps.md +1 -1
  107. package/docs/api/interfaces/FormFieldProps.md +1 -1
  108. package/docs/api/interfaces/FormProps.md +1 -1
  109. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  110. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  111. package/docs/api/interfaces/InputProps.md +1 -1
  112. package/docs/api/interfaces/LabelProps.md +1 -1
  113. package/docs/api/interfaces/LoggerConfig.md +1 -1
  114. package/docs/api/interfaces/LoginFormProps.md +1 -1
  115. package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
  116. package/docs/api/interfaces/NavigationContextType.md +9 -9
  117. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  118. package/docs/api/interfaces/NavigationItem.md +1 -1
  119. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  120. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  121. package/docs/api/interfaces/Organisation.md +1 -1
  122. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  123. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  124. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  125. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  126. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  127. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  128. package/docs/api/interfaces/PageAccessRecord.md +8 -8
  129. package/docs/api/interfaces/PagePermissionContextType.md +8 -8
  130. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  131. package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
  132. package/docs/api/interfaces/PaletteData.md +1 -1
  133. package/docs/api/interfaces/ParsedAddress.md +1 -1
  134. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  135. package/docs/api/interfaces/ProgressProps.md +3 -11
  136. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  137. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  138. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  139. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  140. package/docs/api/interfaces/QuickFix.md +1 -1
  141. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  142. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  143. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  144. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  145. package/docs/api/interfaces/RBACConfig.md +1 -1
  146. package/docs/api/interfaces/RBACContext.md +1 -1
  147. package/docs/api/interfaces/RBACLogger.md +1 -1
  148. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  149. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  150. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  151. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  152. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  153. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  154. package/docs/api/interfaces/RBACResult.md +1 -1
  155. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  156. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  157. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  158. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  160. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  161. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  162. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  163. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  164. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  165. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  166. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  167. package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
  168. package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
  169. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  170. package/docs/api/interfaces/RouteAccessRecord.md +10 -10
  171. package/docs/api/interfaces/RouteConfig.md +10 -10
  172. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  173. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  174. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  175. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  176. package/docs/api/interfaces/SetupIssue.md +1 -1
  177. package/docs/api/interfaces/StorageConfig.md +4 -4
  178. package/docs/api/interfaces/StorageFileInfo.md +7 -7
  179. package/docs/api/interfaces/StorageFileMetadata.md +25 -14
  180. package/docs/api/interfaces/StorageListOptions.md +22 -9
  181. package/docs/api/interfaces/StorageListResult.md +4 -4
  182. package/docs/api/interfaces/StorageUploadOptions.md +21 -8
  183. package/docs/api/interfaces/StorageUploadResult.md +6 -6
  184. package/docs/api/interfaces/StorageUrlOptions.md +19 -6
  185. package/docs/api/interfaces/StyleImport.md +1 -1
  186. package/docs/api/interfaces/SwitchProps.md +1 -1
  187. package/docs/api/interfaces/TabsContentProps.md +1 -1
  188. package/docs/api/interfaces/TabsListProps.md +1 -1
  189. package/docs/api/interfaces/TabsProps.md +1 -1
  190. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  191. package/docs/api/interfaces/TextareaProps.md +1 -1
  192. package/docs/api/interfaces/ToastActionElement.md +1 -1
  193. package/docs/api/interfaces/ToastProps.md +1 -1
  194. package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
  195. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  196. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  197. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  198. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  199. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  200. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  201. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  202. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  203. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  205. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  207. package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
  208. package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
  209. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  210. package/docs/api/interfaces/UserEventAccess.md +11 -11
  211. package/docs/api/interfaces/UserMenuProps.md +1 -1
  212. package/docs/api/interfaces/UserProfile.md +1 -1
  213. package/docs/api/modules.md +155 -135
  214. package/docs/api-reference/components.md +72 -29
  215. package/docs/api-reference/providers.md +2 -2
  216. package/docs/api-reference/rpc-functions.md +1 -0
  217. package/docs/best-practices/README.md +1 -1
  218. package/docs/best-practices/deployment.md +8 -8
  219. package/docs/getting-started/examples/README.md +2 -2
  220. package/docs/getting-started/installation-guide.md +4 -4
  221. package/docs/getting-started/quick-start.md +3 -3
  222. package/docs/migration/MIGRATION_GUIDE.md +3 -3
  223. package/docs/rbac/compliance/compliance-guide.md +2 -2
  224. package/docs/rbac/event-based-apps.md +2 -2
  225. package/docs/rbac/getting-started.md +2 -2
  226. package/docs/rbac/quick-start.md +2 -2
  227. package/docs/security/README.md +4 -4
  228. package/docs/standards/07-rbac-and-rls-standard.md +430 -7
  229. package/docs/troubleshooting/README.md +2 -2
  230. package/docs/troubleshooting/migration.md +3 -3
  231. package/package.json +1 -4
  232. package/scripts/check-pace-core-compliance.cjs +1 -1
  233. package/scripts/check-pace-core-compliance.js +1 -1
  234. package/src/__tests__/fixtures/supabase.ts +301 -0
  235. package/src/__tests__/public-recipe-view.test.ts +9 -9
  236. package/src/__tests__/rls-policies.test.ts +197 -61
  237. package/src/components/AddressField/AddressField.test.tsx +42 -0
  238. package/src/components/AddressField/AddressField.tsx +71 -60
  239. package/src/components/AddressField/README.md +1 -0
  240. package/src/components/Alert/Alert.test.tsx +50 -10
  241. package/src/components/Alert/Alert.tsx +5 -3
  242. package/src/components/Avatar/Avatar.test.tsx +252 -226
  243. package/src/components/Avatar/Avatar.tsx +179 -53
  244. package/src/components/Avatar/index.ts +1 -1
  245. package/src/components/Button/Button.test.tsx +2 -1
  246. package/src/components/Button/Button.tsx +3 -3
  247. package/src/components/Calendar/Calendar.test.tsx +53 -37
  248. package/src/components/Calendar/Calendar.tsx +409 -82
  249. package/src/components/Card/Card.test.tsx +7 -4
  250. package/src/components/Card/Card.tsx +3 -6
  251. package/src/components/Checkbox/Checkbox.tsx +2 -2
  252. package/src/components/DataTable/components/ActionButtons.tsx +5 -5
  253. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  254. package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
  255. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
  256. package/src/components/DataTable/components/DataTableBody.tsx +12 -12
  257. package/src/components/DataTable/components/DataTableCore.tsx +3 -3
  258. package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
  259. package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
  260. package/src/components/DataTable/components/EditableRow.tsx +2 -2
  261. package/src/components/DataTable/components/EmptyState.tsx +3 -3
  262. package/src/components/DataTable/components/GroupHeader.tsx +2 -2
  263. package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
  264. package/src/components/DataTable/components/ImportModal.tsx +4 -4
  265. package/src/components/DataTable/components/LoadingState.tsx +1 -1
  266. package/src/components/DataTable/components/PaginationControls.tsx +11 -11
  267. package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
  268. package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
  269. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
  270. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
  271. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
  272. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
  273. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
  274. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
  275. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
  276. package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
  277. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
  278. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
  279. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
  280. package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
  281. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
  282. package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
  283. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
  284. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
  285. package/src/components/Dialog/Dialog.tsx +2 -2
  286. package/src/components/EventSelector/EventSelector.tsx +7 -7
  287. package/src/components/FileDisplay/FileDisplay.tsx +291 -179
  288. package/src/components/FileUpload/FileUpload.tsx +7 -4
  289. package/src/components/Header/Header.test.tsx +28 -0
  290. package/src/components/Header/Header.tsx +22 -9
  291. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
  292. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
  293. package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
  294. package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
  295. package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
  296. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
  297. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
  298. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
  299. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
  300. package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
  301. package/src/components/PaceAppLayout/test-setup.tsx +1 -0
  302. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
  303. package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
  304. package/src/components/Progress/Progress.test.tsx +18 -19
  305. package/src/components/Progress/Progress.tsx +31 -32
  306. package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
  307. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
  308. package/src/components/Select/Select.tsx +5 -5
  309. package/src/components/Switch/Switch.test.tsx +2 -1
  310. package/src/components/Switch/Switch.tsx +1 -1
  311. package/src/components/Toast/Toast.tsx +1 -1
  312. package/src/components/Tooltip/Tooltip.test.tsx +8 -2
  313. package/src/components/UserMenu/UserMenu.test.tsx +7 -9
  314. package/src/components/UserMenu/UserMenu.tsx +10 -8
  315. package/src/components/index.ts +2 -1
  316. package/src/eslint-rules/pace-core-compliance.cjs +0 -2
  317. package/src/eslint-rules/pace-core-compliance.js +0 -2
  318. package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
  319. package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
  320. package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
  321. package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
  322. package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
  323. package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
  324. package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
  325. package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
  326. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
  327. package/src/hooks/index.ts +1 -1
  328. package/src/hooks/public/usePublicEvent.ts +2 -2
  329. package/src/hooks/public/usePublicFileDisplay.ts +173 -87
  330. package/src/hooks/useAppConfig.ts +24 -5
  331. package/src/hooks/useFileDisplay.ts +297 -34
  332. package/src/hooks/useFileReference.ts +56 -11
  333. package/src/hooks/useFileUrl.ts +1 -1
  334. package/src/hooks/useInactivityTracker.ts +16 -7
  335. package/src/hooks/usePermissionCache.test.ts +85 -8
  336. package/src/hooks/useQueryCache.ts +21 -0
  337. package/src/hooks/useSecureDataAccess.test.ts +80 -35
  338. package/src/hooks/useSecureDataAccess.ts +80 -37
  339. package/src/index.ts +2 -1
  340. package/src/providers/services/EventServiceProvider.tsx +37 -17
  341. package/src/providers/services/InactivityServiceProvider.tsx +4 -4
  342. package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
  343. package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
  344. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
  345. package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
  346. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
  347. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
  348. package/src/rbac/api.ts +240 -36
  349. package/src/rbac/cache-invalidation.ts +21 -7
  350. package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
  351. package/src/rbac/components/NavigationGuard.tsx +23 -63
  352. package/src/rbac/components/NavigationProvider.test.tsx +52 -23
  353. package/src/rbac/components/NavigationProvider.tsx +13 -11
  354. package/src/rbac/components/PagePermissionGuard.tsx +77 -203
  355. package/src/rbac/components/PagePermissionProvider.tsx +13 -11
  356. package/src/rbac/components/PermissionEnforcer.tsx +24 -62
  357. package/src/rbac/components/RoleBasedRouter.tsx +14 -12
  358. package/src/rbac/components/SecureDataProvider.tsx +13 -11
  359. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
  360. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
  361. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
  362. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
  363. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
  364. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
  365. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
  366. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
  367. package/src/rbac/engine.ts +4 -2
  368. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
  369. package/src/rbac/hooks/index.ts +3 -0
  370. package/src/rbac/hooks/useCan.test.ts +101 -53
  371. package/src/rbac/hooks/usePermissions.ts +108 -41
  372. package/src/rbac/hooks/useRBAC.test.ts +11 -3
  373. package/src/rbac/hooks/useRBAC.ts +83 -40
  374. package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
  375. package/src/rbac/hooks/useResolvedScope.ts +128 -70
  376. package/src/rbac/hooks/useSecureSupabase.ts +36 -19
  377. package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
  378. package/src/rbac/request-deduplication.ts +1 -1
  379. package/src/rbac/secureClient.ts +72 -12
  380. package/src/rbac/security.ts +29 -23
  381. package/src/rbac/types.ts +10 -0
  382. package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
  383. package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
  384. package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
  385. package/src/rbac/utils/contextValidator.ts +288 -0
  386. package/src/rbac/utils/eventContext.ts +48 -2
  387. package/src/services/EventService.ts +165 -21
  388. package/src/services/OrganisationService.ts +37 -2
  389. package/src/services/__tests__/EventService.test.ts +26 -21
  390. package/src/types/file-reference.ts +13 -10
  391. package/src/utils/app/appNameResolver.test.ts +346 -73
  392. package/src/utils/context/superAdminOverride.ts +58 -0
  393. package/src/utils/file-reference/index.ts +61 -33
  394. package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
  395. package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
  396. package/src/utils/storage/helpers.test.ts +1 -1
  397. package/src/utils/storage/helpers.ts +38 -19
  398. package/src/utils/storage/types.ts +15 -8
  399. package/src/utils/validation/__tests__/csrf.test.ts +105 -0
  400. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
  401. package/src/vite-env.d.ts +2 -2
  402. package/dist/chunk-3GOZZZYH.js.map +0 -1
  403. package/dist/chunk-DDM4CCYT.js.map +0 -1
  404. package/dist/chunk-E7UAOUMY.js +0 -75
  405. package/dist/chunk-E7UAOUMY.js.map +0 -1
  406. package/dist/chunk-EFCLXK7F.js.map +0 -1
  407. package/dist/chunk-F2IMUDXZ.js.map +0 -1
  408. package/dist/chunk-HEHYGYOX.js.map +0 -1
  409. package/dist/chunk-IM4QE42D.js.map +0 -1
  410. package/dist/chunk-IPCH26AG.js.map +0 -1
  411. package/dist/chunk-SAUPYVLF.js.map +0 -1
  412. package/dist/chunk-THRPYOFK.js.map +0 -1
  413. package/dist/chunk-UNOTYLQF.js.map +0 -1
  414. package/dist/chunk-VGZZXKBR.js.map +0 -1
  415. package/dist/chunk-YHCN776L.js.map +0 -1
  416. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
  417. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
  418. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
  419. package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
  420. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
  421. /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
  422. /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
  423. /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
  424. /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
@@ -8,20 +8,34 @@ import { PublicPageContext, useIsPublicPage } from '../PublicLayout/PublicPagePr
8
8
  import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
9
9
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogBody, DialogFooter } from '../Dialog/Dialog';
10
10
  import { Button } from '../Button/Button';
11
+ import { LoadingSpinner } from '../LoadingSpinner/LoadingSpinner';
11
12
  import { logger } from '../../utils/core/logger';
12
13
 
13
14
  /**
14
15
  * Size classes for fallback display
15
16
  */
16
17
  const fallbackSizeClasses = {
17
- xs: 'h-4 w-4 text-xs',
18
- sm: 'h-6 w-6 text-sm',
19
- md: 'h-8 w-8 text-base',
20
- lg: 'h-12 w-12 text-lg',
21
- xl: 'h-16 w-16 text-xl',
22
- '2xl': 'h-20 w-20 text-2xl'
18
+ xs: 'size-4 text-xs',
19
+ sm: 'size-6 text-sm',
20
+ md: 'size-8 text-base',
21
+ lg: 'size-12 text-lg',
22
+ xl: 'size-16 text-xl',
23
+ '2xl': 'size-20 text-2xl'
23
24
  };
24
25
 
26
+ /**
27
+ * Base classes for fallback display
28
+ */
29
+ const fallbackBaseClasses = 'size-full grid place-items-center text-center text-sec-600 font-semibold';
30
+
31
+ /**
32
+ * Helper function to compute fallback classes for a given size
33
+ */
34
+ function getFallbackClasses(size: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' = 'md'): string {
35
+ const sizeClass = fallbackSizeClasses[size];
36
+ return `${fallbackBaseClasses} ${sizeClass}`.trim();
37
+ }
38
+
25
39
  /**
26
40
  * Default fallback text generator - extracts initials from file name
27
41
  */
@@ -43,7 +57,13 @@ function defaultGenerateFallbackText(fileName?: string): string {
43
57
  export interface FileDisplayProps {
44
58
  table_name: string;
45
59
  record_id: string;
46
- organisation_id: string;
60
+ /**
61
+ * Optional organisation ID. When not provided (undefined), the component will automatically
62
+ * search for files in both user-scoped (organisation_id = null) and organisation-scoped contexts.
63
+ * If both types of files exist, organisation-scoped files are preferred.
64
+ * When explicitly set to null, only user-scoped files are searched.
65
+ */
66
+ organisation_id?: string;
47
67
  category?: FileCategory;
48
68
  /** Display only a single file instead of all files. Uses first file (prefers images) from all files, without category filtering */
49
69
  displayOnly?: boolean;
@@ -60,8 +80,18 @@ export interface FileDisplayProps {
60
80
  generateFallbackText?: (fileName?: string) => string;
61
81
  /** Explicit fallback text to display (overrides generateFallbackText) */
62
82
  fallbackText?: string;
83
+ /** Source text to use for generating fallback text (overrides filename) */
84
+ fallbackSourceText?: string;
63
85
  /** Size variant for fallback display (only applies when showFallback is true) */
64
86
  fallbackSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
87
+ /**
88
+ * Enable children rendering in displayOnly mode. When true, uses standard display path
89
+ * (with children support) even if showDelete is false. The delete button will only appear
90
+ * if showDelete is also true.
91
+ */
92
+ enableChildren?: boolean;
93
+ /** Whether to show metadata (filename, filesize, mimetype) in figcaption. Defaults to true. */
94
+ showMetadata?: boolean;
65
95
  }
66
96
 
67
97
  // Shared rendering logic for file display
@@ -80,13 +110,16 @@ interface FileDisplayContentProps {
80
110
  children?: React.ReactNode;
81
111
  onDelete?: () => Promise<void>;
82
112
  clearError?: () => void;
83
- organisation_id: string;
113
+ organisation_id: string | undefined;
84
114
  loadingComponent?: React.ComponentType;
85
115
  errorComponent?: React.ComponentType<{ error: Error | string | null; retry?: () => void }>;
86
116
  showFallback?: boolean;
87
117
  generateFallbackText?: (fileName?: string) => string;
88
118
  fallbackText?: string;
119
+ fallbackSourceText?: string;
89
120
  fallbackSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
121
+ enableChildren?: boolean;
122
+ showMetadata?: boolean;
90
123
  }
91
124
 
92
125
  function FileDisplayContent({
@@ -110,7 +143,10 @@ function FileDisplayContent({
110
143
  showFallback = false,
111
144
  generateFallbackText = defaultGenerateFallbackText,
112
145
  fallbackText,
113
- fallbackSize = 'md'
146
+ fallbackSourceText,
147
+ fallbackSize = 'md',
148
+ enableChildren = false,
149
+ showMetadata = true
114
150
  }: FileDisplayContentProps) {
115
151
  const [imageError, setImageError] = useState(false);
116
152
  const [internalFileUrls, setInternalFileUrls] = useState<Map<string, string>>(new Map(fileUrls));
@@ -120,16 +156,15 @@ function FileDisplayContent({
120
156
  // Compute fallback text
121
157
  const computedFallbackText = useMemo(() => {
122
158
  if (fallbackText) return fallbackText;
123
- const fileName = fileReference?.file_metadata?.fileName;
124
- return generateFallbackText(fileName);
125
- }, [fallbackText, fileReference, generateFallbackText]);
159
+ // Use fallbackSourceText if provided, otherwise fall back to filename
160
+ const sourceText = fallbackSourceText ?? fileReference?.file_metadata?.fileName;
161
+ return generateFallbackText(sourceText);
162
+ }, [fallbackText, fallbackSourceText, fileReference, generateFallbackText]);
126
163
 
127
164
  // Compute fallback classes
128
165
  const fallbackClasses = useMemo(() => {
129
- const sizeClass = fallbackSizeClasses[fallbackSize];
130
- const baseClasses = 'flex items-center justify-center bg-sec-100 text-sec-600 font-semibold rounded';
131
- return `${baseClasses} ${sizeClass} ${className}`.trim();
132
- }, [fallbackSize, className]);
166
+ return getFallbackClasses(fallbackSize);
167
+ }, [fallbackSize]);
133
168
 
134
169
  // Sync fileUrls prop with internal state
135
170
  // Track file references to detect when they change and sync URLs
@@ -159,28 +194,8 @@ function FileDisplayContent({
159
194
  setImageError(false);
160
195
  };
161
196
 
162
- const handleImageError = (e?: React.SyntheticEvent<HTMLImageElement>) => {
197
+ const handleImageError = () => {
163
198
  setImageError(true);
164
-
165
- // If fallback is enabled, show fallback UI when image fails to load
166
- if (showFallback && e) {
167
- const target = e.target as HTMLImageElement;
168
- target.style.display = 'none';
169
-
170
- // Check if fallback already exists
171
- if (target.nextSibling && (target.nextSibling as HTMLElement).className.includes('bg-sec-100')) {
172
- return; // Fallback already shown
173
- }
174
-
175
- // Create fallback element
176
- const fallback = document.createElement('div');
177
- fallback.className = fallbackClasses;
178
- fallback.textContent = computedFallbackText;
179
- fallback.title = fileReference?.file_metadata?.fileName || 'File';
180
-
181
- // Insert fallback after the image
182
- target.parentNode?.insertBefore(fallback, target.nextSibling);
183
- }
184
199
  };
185
200
 
186
201
  const getFileIcon = (fileType: string) => {
@@ -211,27 +226,29 @@ function FileDisplayContent({
211
226
  // Show fallback if enabled
212
227
  if (showFallback) {
213
228
  return (
214
- <div className={fallbackClasses} title="File unavailable">
215
- {computedFallbackText}
216
- </div>
229
+ <figure className={className} title="File unavailable">
230
+ <p className={fallbackClasses}>
231
+ {computedFallbackText}
232
+ </p>
233
+ </figure>
217
234
  );
218
235
  }
219
236
 
220
237
  return (
221
- <div className={`p-4 bg-acc-50 border border-acc-200 rounded-lg ${className}`}>
222
- <div className="text-acc-600">
238
+ <figure className={className} title="Error">
239
+ <p className={getFallbackClasses(fallbackSize || 'md')}>
223
240
  Error loading file: {error instanceof Error ? error.message : String(error)}
224
- </div>
241
+ </p>
225
242
  {clearError && (
226
- <button
243
+ <Button
227
244
  onClick={clearError}
228
- className="mt-2 text-sm text-acc-700 hover:text-acc-800 underline"
245
+ className="mt-2"
229
246
  aria-label="Retry loading file"
230
247
  >
231
248
  Try again
232
- </button>
249
+ </Button>
233
250
  )}
234
- </div>
251
+ </figure>
235
252
  );
236
253
  }
237
254
 
@@ -241,28 +258,32 @@ function FileDisplayContent({
241
258
  // Show fallback if enabled
242
259
  if (showFallback) {
243
260
  return (
244
- <div className={fallbackClasses} title="No file">
245
- {computedFallbackText}
261
+ <figure className={className} title="No file">
262
+ <p className={fallbackClasses}>
263
+ {computedFallbackText}
264
+ </p>
246
265
  {children}
247
- </div>
266
+ </figure>
248
267
  );
249
268
  }
250
269
 
251
270
  return (
252
- <div className={`text-sec-500 text-center p-4 ${className}`}>
253
- No files found
271
+ <figure className={`text-sec-500 text-center p-4 ${className}`}>
272
+ <p>No files found</p>
254
273
  {children}
255
- </div>
274
+ </figure>
256
275
  );
257
276
  }
258
277
 
259
278
  // During loading, show fallback if enabled (better UX than spinner for empty states)
260
279
  if (isLoading && showFallback && fileCount === 0) {
261
280
  return (
262
- <div className={fallbackClasses} title="Loading...">
263
- {computedFallbackText}
281
+ <figure className={className} title="Loading...">
282
+ <p className={fallbackClasses}>
283
+ {computedFallbackText}
284
+ </p>
264
285
  {children}
265
- </div>
286
+ </figure>
266
287
  );
267
288
  }
268
289
 
@@ -271,9 +292,11 @@ function FileDisplayContent({
271
292
  return <LoadingComponent />;
272
293
  }
273
294
  return (
274
- <div className={`flex items-center justify-center p-4 ${className}`}>
275
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-main-500"></div>
276
- </div>
295
+ <figure className={className} title="Loading">
296
+ <p className="flex items-center justify-center p-4">
297
+ <LoadingSpinner />
298
+ </p>
299
+ </figure>
277
300
  );
278
301
  }
279
302
 
@@ -282,34 +305,39 @@ function FileDisplayContent({
282
305
  const isImage = fileReference.file_metadata.fileType?.startsWith('image/');
283
306
 
284
307
  // Simplified image-only display when displayOnly is true and it's an image
285
- if (displayOnly && isImage && !showDelete) {
308
+ // Only use simplified path if enableChildren is not explicitly enabled
309
+ if (displayOnly && isImage && !showDelete && !enableChildren) {
286
310
  // Show fallback if image error occurred and fallback is enabled
287
311
  if (imageError && showFallback) {
288
312
  return (
289
- <div className={fallbackClasses} title={fileReference.file_metadata.fileName || 'File'}>
290
- {computedFallbackText}
291
- </div>
313
+ <figure className={className} title={fileReference.file_metadata.fileName || 'File'}>
314
+ <p className={fallbackClasses}>
315
+ {computedFallbackText}
316
+ </p>
317
+ </figure>
292
318
  );
293
319
  }
294
320
 
295
321
  // Show loading skeleton if URL is not available yet
296
322
  if (!fileUrl) {
297
323
  return (
298
- <div className={`bg-sec-100 rounded animate-pulse ${className || "max-w-full h-48"}`}>
299
- <div className="w-full h-full flex items-center justify-center">
300
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-main-500"></div>
301
- </div>
302
- </div>
324
+ <figure className={className || "max-w-full h-48"} title="Loading">
325
+ <p className={fallbackClasses}>
326
+ <LoadingSpinner />
327
+ </p>
328
+ </figure>
303
329
  );
304
330
  }
305
331
 
306
332
  return (
307
- <img
308
- src={fileUrl}
309
- alt={fileReference.file_metadata.fileName || 'File'}
310
- className={className || "max-w-full h-auto"}
311
- onError={handleImageError}
312
- />
333
+ <figure className={className || "max-w-full h-auto"}>
334
+ <img
335
+ src={fileUrl}
336
+ alt={fileReference.file_metadata.fileName || 'File'}
337
+ className="max-w-full h-auto"
338
+ onError={handleImageError}
339
+ />
340
+ </figure>
313
341
  );
314
342
  }
315
343
 
@@ -320,19 +348,29 @@ function FileDisplayContent({
320
348
  const ariaLabel = `Open ${fileName} in new tab`;
321
349
 
322
350
  return (
323
- <a
324
- href={fileUrl}
325
- target="_blank"
326
- rel="noopener noreferrer"
327
- aria-label={ariaLabel}
328
- className={`flex items-center gap-2 p-3 bg-sec-50 border border-sec-200 rounded-lg hover:bg-sec-100 transition-colors text-main-600 hover:text-main-700 focus:outline-none focus:ring-2 focus:ring-main-500 focus:ring-offset-2 ${className || ''}`.trim()}
329
- >
330
- <FileText className="h-5 w-5 shrink-0" aria-hidden="true" />
331
- <span className="flex-1 font-medium truncate">
332
- {fileName}
333
- </span>
334
- <ExternalLink className="h-5 w-5 shrink-0" aria-hidden="true" />
335
- </a>
351
+ <figure className={className}>
352
+ <a
353
+ href={fileUrl}
354
+ target="_blank"
355
+ rel="noopener noreferrer"
356
+ aria-label={ariaLabel}
357
+ className="flex items-center gap-2 p-3 bg-sec-50 border border-sec-200 rounded-lg hover:bg-sec-100 transition-colors text-main-600 hover:text-main-700 focus:outline-none focus:ring-2 focus:ring-main-500 focus:ring-offset-2"
358
+ >
359
+ <FileText className="size-5 shrink-0" aria-hidden="true" />
360
+ <span className="flex-1 font-medium truncate">
361
+ {fileName}
362
+ </span>
363
+ <ExternalLink className="size-5 shrink-0" aria-hidden="true" />
364
+ </a>
365
+ {showMetadata && (
366
+ <figcaption>
367
+ <p>{fileName}</p>
368
+ {fileReference.file_metadata.fileSize && (
369
+ <p>{formatFileSize(fileReference.file_metadata.fileSize)} • {fileReference.file_metadata.fileType}</p>
370
+ )}
371
+ </figcaption>
372
+ )}
373
+ </figure>
336
374
  );
337
375
  }
338
376
 
@@ -340,16 +378,18 @@ function FileDisplayContent({
340
378
  // For displayOnly mode, if fallback is enabled and there's no URL or image error, show fallback instead of folder icon
341
379
  if (displayOnly && showFallback && (!fileUrl || imageError || !isImage)) {
342
380
  return (
343
- <div className={fallbackClasses} title={fileReference.file_metadata.fileName || 'File'}>
344
- {computedFallbackText}
345
- </div>
381
+ <figure className={className} title={fileReference.file_metadata.fileName || 'File'}>
382
+ <p className={fallbackClasses}>
383
+ {computedFallbackText}
384
+ </p>
385
+ </figure>
346
386
  );
347
387
  }
348
388
 
349
389
  return (
350
- <div className={`space-y-2 ${className}`}>
390
+ <figure className={`relative ${className}`}>
351
391
  {isImage && fileUrl && !imageError ? (
352
- <div className="relative">
392
+ <>
353
393
  <img
354
394
  src={fileUrl}
355
395
  alt={fileReference.file_metadata.fileName || 'File'}
@@ -358,14 +398,16 @@ function FileDisplayContent({
358
398
  />
359
399
  {showDelete && (
360
400
  <>
361
- <button
401
+ <Button
402
+ variant="destructive"
403
+ size="icon"
362
404
  onClick={handleDeleteClick}
363
- className="absolute top-2 right-2 bg-acc-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-sm hover:bg-acc-600"
405
+ className="absolute top-2 right-2"
364
406
  title="Delete file"
365
407
  aria-label="Delete file"
366
408
  >
367
409
  ×
368
- </button>
410
+ </Button>
369
411
  <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
370
412
  <DialogContent size="sm">
371
413
  <DialogHeader>
@@ -386,73 +428,108 @@ function FileDisplayContent({
386
428
  </Dialog>
387
429
  </>
388
430
  )}
389
- </div>
431
+ {children}
432
+ {showMetadata && (
433
+ <figcaption>
434
+ <p>{fileReference.file_metadata.fileName || 'Unknown file'}</p>
435
+ {(fileReference.file_metadata.fileSize || fileReference.file_metadata.fileType) && (
436
+ <p>
437
+ {fileReference.file_metadata.fileSize && formatFileSize(fileReference.file_metadata.fileSize)}
438
+ {fileReference.file_metadata.fileSize && fileReference.file_metadata.fileType && ' • '}
439
+ {fileReference.file_metadata.fileType}
440
+ </p>
441
+ )}
442
+ </figcaption>
443
+ )}
444
+ </>
390
445
  ) : isImage && imageError && showFallback ? (
391
446
  // Show fallback when image fails to load and fallback is enabled
392
- <div className={fallbackClasses} title={fileReference.file_metadata.fileName || 'File'}>
393
- {computedFallbackText}
394
- </div>
447
+ <>
448
+ <p className={fallbackClasses} title={fileReference.file_metadata.fileName || 'File'}>
449
+ {computedFallbackText}
450
+ </p>
451
+ {children}
452
+ {showMetadata && (
453
+ <figcaption>
454
+ <p>{fileReference.file_metadata.fileName || 'Unknown file'}</p>
455
+ {(fileReference.file_metadata.fileSize || fileReference.file_metadata.fileType) && (
456
+ <p>
457
+ {fileReference.file_metadata.fileSize && formatFileSize(fileReference.file_metadata.fileSize)}
458
+ {fileReference.file_metadata.fileSize && fileReference.file_metadata.fileType && ' • '}
459
+ {fileReference.file_metadata.fileType}
460
+ </p>
461
+ )}
462
+ </figcaption>
463
+ )}
464
+ </>
395
465
  ) : (
396
- <div className="flex items-center space-x-3 p-3 bg-sec-50 rounded-lg border border-sec-200">
397
- <span className="text-2xl">
398
- {getFileIcon(fileReference.file_metadata.fileType || '')}
399
- </span>
400
- <div className="flex-1 min-w-0">
401
- <div className="font-medium text-sec-900 truncate">
402
- {fileReference.file_metadata.fileName || 'Unknown file'}
403
- </div>
404
- <div className="text-sm text-sec-500">
405
- {fileReference.file_metadata.fileSize && formatFileSize(fileReference.file_metadata.fileSize)}
406
- {fileReference.file_metadata.fileType && ` • ${fileReference.file_metadata.fileType}`}
407
- </div>
408
- </div>
409
- {showDelete && (
410
- <>
411
- <button
412
- onClick={handleDeleteClick}
413
- className="text-acc-500 hover:text-acc-700 p-1"
414
- title="Delete file"
415
- aria-label="Delete file"
416
- >
417
- ×
418
- </button>
419
- <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
420
- <DialogContent size="sm">
421
- <DialogHeader>
422
- <DialogTitle>Confirm Delete</DialogTitle>
423
- </DialogHeader>
424
- <DialogBody>
425
- <p>Are you sure you want to delete this file? This action cannot be undone.</p>
426
- </DialogBody>
427
- <DialogFooter>
428
- <Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
429
- Cancel
430
- </Button>
431
- <Button variant="destructive" onClick={handleDeleteConfirm}>
432
- Delete
433
- </Button>
434
- </DialogFooter>
435
- </DialogContent>
436
- </Dialog>
437
- </>
466
+ <>
467
+ <p className="flex items-center space-x-3 p-3 bg-sec-50 rounded-lg border border-sec-200">
468
+ <span className="text-2xl">
469
+ {getFileIcon(fileReference.file_metadata.fileType || '')}
470
+ </span>
471
+ {showDelete && (
472
+ <>
473
+ <Button
474
+ variant="destructive"
475
+ size="icon"
476
+ onClick={handleDeleteClick}
477
+ className="ml-auto"
478
+ title="Delete file"
479
+ aria-label="Delete file"
480
+ >
481
+ ×
482
+ </Button>
483
+ <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
484
+ <DialogContent size="sm">
485
+ <DialogHeader>
486
+ <DialogTitle>Confirm Delete</DialogTitle>
487
+ </DialogHeader>
488
+ <DialogBody>
489
+ <p>Are you sure you want to delete this file? This action cannot be undone.</p>
490
+ </DialogBody>
491
+ <DialogFooter>
492
+ <Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
493
+ Cancel
494
+ </Button>
495
+ <Button variant="destructive" onClick={handleDeleteConfirm}>
496
+ Delete
497
+ </Button>
498
+ </DialogFooter>
499
+ </DialogContent>
500
+ </Dialog>
501
+ </>
502
+ )}
503
+ </p>
504
+ {children}
505
+ {showMetadata && (
506
+ <figcaption>
507
+ <p>{fileReference.file_metadata.fileName || 'Unknown file'}</p>
508
+ {(fileReference.file_metadata.fileSize || fileReference.file_metadata.fileType) && (
509
+ <p>
510
+ {fileReference.file_metadata.fileSize && formatFileSize(fileReference.file_metadata.fileSize)}
511
+ {fileReference.file_metadata.fileSize && fileReference.file_metadata.fileType && ' • '}
512
+ {fileReference.file_metadata.fileType}
513
+ </p>
514
+ )}
515
+ </figcaption>
438
516
  )}
439
- </div>
517
+ </>
440
518
  )}
441
- {children}
442
- </div>
519
+ </figure>
443
520
  );
444
521
  }
445
522
 
446
523
  // Multiple files display
447
524
  return (
448
- <div className={`space-y-2 ${className}`}>
525
+ <figure className={`space-y-2 ${className}`}>
449
526
  {fileReferences.map((fileRef) => {
450
527
  const isImage = fileRef.file_metadata.fileType?.startsWith('image/');
451
528
  const fileUrl = internalFileUrls.get(fileRef.id) || null;
452
529
  const canDownload = !isImage && fileUrl;
453
530
 
454
531
  return (
455
- <div key={fileRef.id} className="flex items-center space-x-3 p-3 bg-sec-50 rounded-lg border border-sec-200">
532
+ <figure key={fileRef.id} className="flex items-center space-x-3 p-3 bg-sec-50 rounded-lg border border-sec-200">
456
533
  {isImage && fileUrl ? (
457
534
  <img
458
535
  src={fileUrl}
@@ -465,17 +542,23 @@ function FileDisplayContent({
465
542
  {getFileIcon(fileRef.file_metadata.fileType || '')}
466
543
  </span>
467
544
  )}
468
- <div className="flex-1 min-w-0">
469
- <div className="font-medium text-sec-900 truncate">
470
- {fileRef.file_metadata.fileName || 'Unknown file'}
471
- </div>
472
- <div className="text-sm text-sec-500">
473
- {fileRef.file_metadata.fileSize && formatFileSize(fileRef.file_metadata.fileSize)}
474
- {fileRef.file_metadata.fileType && ` • ${fileRef.file_metadata.fileType}`}
475
- {fileRef.file_metadata.category && ` • ${fileRef.file_metadata.category}`}
476
- </div>
477
- </div>
478
- <div className="flex items-center space-x-2">
545
+ {showMetadata && (
546
+ <figcaption className="flex-1 min-w-0">
547
+ <p className="font-medium text-sec-900 truncate">
548
+ {fileRef.file_metadata.fileName || 'Unknown file'}
549
+ </p>
550
+ {(fileRef.file_metadata.fileSize || fileRef.file_metadata.fileType || fileRef.file_metadata.category) && (
551
+ <p className="text-sm text-sec-500">
552
+ {fileRef.file_metadata.fileSize && formatFileSize(fileRef.file_metadata.fileSize)}
553
+ {fileRef.file_metadata.fileSize && fileRef.file_metadata.fileType && ' • '}
554
+ {fileRef.file_metadata.fileType}
555
+ {(fileRef.file_metadata.fileSize || fileRef.file_metadata.fileType) && fileRef.file_metadata.category && ' • '}
556
+ {fileRef.file_metadata.category}
557
+ </p>
558
+ )}
559
+ </figcaption>
560
+ )}
561
+ <p className="flex items-center space-x-2">
479
562
  {canDownload && (
480
563
  <a
481
564
  href={fileRef.file_path}
@@ -486,22 +569,23 @@ function FileDisplayContent({
486
569
 
487
570
  </a>
488
571
  )}
489
- {showDelete && onDelete && (
490
- <button
491
- onClick={handleDeleteClick}
492
- className="text-acc-500 hover:text-acc-700 p-1"
493
- title="Delete file"
494
- aria-label="Delete file"
495
- >
496
- ×
497
- </button>
498
- )}
499
- </div>
500
- </div>
572
+ {showDelete && onDelete && (
573
+ <Button
574
+ variant="destructive"
575
+ size="icon"
576
+ onClick={handleDeleteClick}
577
+ title="Delete file"
578
+ aria-label="Delete file"
579
+ >
580
+ ×
581
+ </Button>
582
+ )}
583
+ </p>
584
+ </figure>
501
585
  );
502
586
  })}
503
587
  {children}
504
- </div>
588
+ </figure>
505
589
  );
506
590
  }
507
591
 
@@ -523,7 +607,10 @@ function FileDisplayPublic({
523
607
  showFallback,
524
608
  generateFallbackText,
525
609
  fallbackText,
526
- fallbackSize
610
+ fallbackSourceText,
611
+ fallbackSize,
612
+ enableChildren,
613
+ showMetadata
527
614
  }: FileDisplayProps) {
528
615
  const publicPageContext = useContext(PublicPageContext);
529
616
  const supabase = publicPageContext?.supabase ?? null;
@@ -552,16 +639,21 @@ function FileDisplayPublic({
552
639
  showFallback={showFallback}
553
640
  generateFallbackText={generateFallbackText}
554
641
  fallbackText={fallbackText}
642
+ fallbackSourceText={fallbackSourceText}
555
643
  fallbackSize={fallbackSize}
644
+ enableChildren={enableChildren}
645
+ showMetadata={showMetadata}
556
646
  />
557
647
  );
558
648
  }
559
649
 
560
650
  // Only show error if fallback is not enabled
561
651
  return (
562
- <div className={`text-sec-500 text-center p-4 ${className}`}>
563
- Supabase client not available in public context
564
- </div>
652
+ <figure className={className} title="Error">
653
+ <p className={getFallbackClasses(fallbackSize || 'md')}>
654
+ Supabase client not available in public context
655
+ </p>
656
+ </figure>
565
657
  );
566
658
  }
567
659
 
@@ -640,7 +732,10 @@ function FileDisplayPublic({
640
732
  showFallback={showFallback}
641
733
  generateFallbackText={generateFallbackText}
642
734
  fallbackText={fallbackText}
735
+ fallbackSourceText={fallbackSourceText}
643
736
  fallbackSize={fallbackSize}
737
+ enableChildren={enableChildren}
738
+ showMetadata={showMetadata}
644
739
  />
645
740
  );
646
741
  }
@@ -663,15 +758,20 @@ function FileDisplayAuthenticated({
663
758
  showFallback,
664
759
  generateFallbackText,
665
760
  fallbackText,
666
- fallbackSize
761
+ fallbackSourceText,
762
+ fallbackSize,
763
+ enableChildren,
764
+ showMetadata
667
765
  }: FileDisplayProps) {
668
766
  const { supabase } = useUnifiedAuth();
669
767
 
670
768
  if (!supabase) {
671
769
  return (
672
- <div className={`text-sec-500 text-center p-4 ${className}`}>
673
- Supabase client not available in authenticated context
674
- </div>
770
+ <figure className={className} title="Error">
771
+ <p className={getFallbackClasses(fallbackSize || 'md')}>
772
+ Supabase client not available in authenticated context
773
+ </p>
774
+ </figure>
675
775
  );
676
776
  }
677
777
 
@@ -771,7 +871,10 @@ function FileDisplayAuthenticated({
771
871
  showFallback={showFallback}
772
872
  generateFallbackText={generateFallbackText}
773
873
  fallbackText={fallbackText}
874
+ fallbackSourceText={fallbackSourceText}
774
875
  fallbackSize={fallbackSize}
876
+ enableChildren={enableChildren}
877
+ showMetadata={showMetadata}
775
878
  />
776
879
  );
777
880
  }
@@ -808,7 +911,10 @@ export function FileDisplay({
808
911
  showFallback,
809
912
  generateFallbackText,
810
913
  fallbackText,
811
- fallbackSize
914
+ fallbackSourceText,
915
+ fallbackSize,
916
+ enableChildren,
917
+ showMetadata
812
918
  }: FileDisplayProps) {
813
919
  // Check which context we're in and route to the appropriate component
814
920
  const isPublicPage = useIsPublicPage();
@@ -830,7 +936,10 @@ export function FileDisplay({
830
936
  showFallback={showFallback}
831
937
  generateFallbackText={generateFallbackText}
832
938
  fallbackText={fallbackText}
939
+ fallbackSourceText={fallbackSourceText}
833
940
  fallbackSize={fallbackSize}
941
+ enableChildren={enableChildren}
942
+ showMetadata={showMetadata}
834
943
  />
835
944
  );
836
945
  }
@@ -852,7 +961,10 @@ export function FileDisplay({
852
961
  showFallback={showFallback}
853
962
  generateFallbackText={generateFallbackText}
854
963
  fallbackText={fallbackText}
964
+ fallbackSourceText={fallbackSourceText}
855
965
  fallbackSize={fallbackSize}
966
+ enableChildren={enableChildren}
967
+ showMetadata={showMetadata}
856
968
  />
857
969
  );
858
970
  }