@maxal_studio/kratosjs-react 1.0.0

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 (529) hide show
  1. package/README.md +44 -0
  2. package/dist/FieldRenderer.d.ts +13 -0
  3. package/dist/FieldRenderer.js +62 -0
  4. package/dist/FormRenderer.d.ts +7 -0
  5. package/dist/FormRenderer.js +78 -0
  6. package/dist/TableRenderer.d.ts +2 -0
  7. package/dist/TableRenderer.js +1 -0
  8. package/dist/api/actionsApi.d.ts +23 -0
  9. package/dist/api/actionsApi.js +46 -0
  10. package/dist/api/authenticatedFetch.d.ts +8 -0
  11. package/dist/api/authenticatedFetch.js +31 -0
  12. package/dist/api/exportApi.d.ts +18 -0
  13. package/dist/api/exportApi.js +50 -0
  14. package/dist/api/http.d.ts +24 -0
  15. package/dist/api/http.js +52 -0
  16. package/dist/api/resourceApi.d.ts +37 -0
  17. package/dist/api/resourceApi.js +52 -0
  18. package/dist/api/tableApi.d.ts +83 -0
  19. package/dist/api/tableApi.js +51 -0
  20. package/dist/api/urls.d.ts +19 -0
  21. package/dist/api/urls.js +46 -0
  22. package/dist/app.d.ts +101 -0
  23. package/dist/app.js +89 -0
  24. package/dist/auth/AuthContext.d.ts +22 -0
  25. package/dist/auth/AuthContext.js +147 -0
  26. package/dist/auth/LoginPage.d.ts +10 -0
  27. package/dist/auth/LoginPage.js +179 -0
  28. package/dist/auth/ProtectedRoute.d.ts +12 -0
  29. package/dist/auth/ProtectedRoute.js +22 -0
  30. package/dist/auth/authApiClient.d.ts +24 -0
  31. package/dist/auth/authApiClient.js +95 -0
  32. package/dist/auth/types.d.ts +103 -0
  33. package/dist/auth/types.js +1 -0
  34. package/dist/components/ActionFormModal.d.ts +22 -0
  35. package/dist/components/ActionFormModal.js +8 -0
  36. package/dist/components/AdminPanel.d.ts +11 -0
  37. package/dist/components/AdminPanel.js +194 -0
  38. package/dist/components/Checkbox.d.ts +10 -0
  39. package/dist/components/Checkbox.js +8 -0
  40. package/dist/components/CheckboxField.d.ts +7 -0
  41. package/dist/components/CheckboxField.js +26 -0
  42. package/dist/components/ColorPickerField.d.ts +7 -0
  43. package/dist/components/ColorPickerField.js +26 -0
  44. package/dist/components/DateTimePickerField.d.ts +7 -0
  45. package/dist/components/DateTimePickerField.js +64 -0
  46. package/dist/components/FileUploadField.d.ts +9 -0
  47. package/dist/components/FileUploadField.js +478 -0
  48. package/dist/components/GlobalSearch.d.ts +22 -0
  49. package/dist/components/GlobalSearch.js +181 -0
  50. package/dist/components/GroupField.d.ts +7 -0
  51. package/dist/components/GroupField.js +23 -0
  52. package/dist/components/HiddenField.d.ts +3 -0
  53. package/dist/components/HiddenField.js +10 -0
  54. package/dist/components/ModalBreadcrumb.d.ts +5 -0
  55. package/dist/components/ModalBreadcrumb.js +33 -0
  56. package/dist/components/ModalDrawer.d.ts +15 -0
  57. package/dist/components/ModalDrawer.js +40 -0
  58. package/dist/components/RadioField.d.ts +7 -0
  59. package/dist/components/RadioField.js +26 -0
  60. package/dist/components/RepeaterField.d.ts +3 -0
  61. package/dist/components/RepeaterField.js +191 -0
  62. package/dist/components/ResourceModalRenderer.d.ts +10 -0
  63. package/dist/components/ResourceModalRenderer.js +80 -0
  64. package/dist/components/RichEditorField.d.ts +3 -0
  65. package/dist/components/RichEditorField.js +655 -0
  66. package/dist/components/SectionField.d.ts +9 -0
  67. package/dist/components/SectionField.js +111 -0
  68. package/dist/components/SelectField.d.ts +8 -0
  69. package/dist/components/SelectField.js +523 -0
  70. package/dist/components/TabsField.d.ts +10 -0
  71. package/dist/components/TabsField.js +214 -0
  72. package/dist/components/TagsInputField.d.ts +7 -0
  73. package/dist/components/TagsInputField.js +172 -0
  74. package/dist/components/TextInputField.d.ts +7 -0
  75. package/dist/components/TextInputField.js +44 -0
  76. package/dist/components/TextareaField.d.ts +7 -0
  77. package/dist/components/TextareaField.js +31 -0
  78. package/dist/components/ToggleField.d.ts +7 -0
  79. package/dist/components/ToggleField.js +57 -0
  80. package/dist/components/ViewModal.d.ts +25 -0
  81. package/dist/components/ViewModal.js +159 -0
  82. package/dist/components/blocks/BlockRenderer.d.ts +7 -0
  83. package/dist/components/blocks/BlockRenderer.js +36 -0
  84. package/dist/components/blocks/FormBlockRenderer.d.ts +6 -0
  85. package/dist/components/blocks/FormBlockRenderer.js +110 -0
  86. package/dist/components/blocks/TableBlockRenderer.d.ts +6 -0
  87. package/dist/components/blocks/TableBlockRenderer.js +12 -0
  88. package/dist/components/blocks/TabsBlockRenderer.d.ts +7 -0
  89. package/dist/components/blocks/TabsBlockRenderer.js +11 -0
  90. package/dist/components/blocks/WidgetBlockRenderer.d.ts +6 -0
  91. package/dist/components/blocks/WidgetBlockRenderer.js +11 -0
  92. package/dist/components/columns/CheckboxColumnComponent.d.ts +6 -0
  93. package/dist/components/columns/CheckboxColumnComponent.js +21 -0
  94. package/dist/components/columns/ColorColumnComponent.d.ts +3 -0
  95. package/dist/components/columns/ColorColumnComponent.js +11 -0
  96. package/dist/components/columns/DeeplinkWrapper.d.ts +15 -0
  97. package/dist/components/columns/DeeplinkWrapper.js +85 -0
  98. package/dist/components/columns/IconColumnComponent.d.ts +3 -0
  99. package/dist/components/columns/IconColumnComponent.js +52 -0
  100. package/dist/components/columns/ImageColumnComponent.d.ts +3 -0
  101. package/dist/components/columns/ImageColumnComponent.js +98 -0
  102. package/dist/components/columns/MediaColumnComponent.d.ts +3 -0
  103. package/dist/components/columns/MediaColumnComponent.js +160 -0
  104. package/dist/components/columns/SelectColumnComponent.d.ts +6 -0
  105. package/dist/components/columns/SelectColumnComponent.js +26 -0
  106. package/dist/components/columns/TagsColumnComponent.d.ts +3 -0
  107. package/dist/components/columns/TagsColumnComponent.js +18 -0
  108. package/dist/components/columns/TextColumnComponent.d.ts +11 -0
  109. package/dist/components/columns/TextColumnComponent.js +107 -0
  110. package/dist/components/columns/TextInputColumnComponent.d.ts +6 -0
  111. package/dist/components/columns/TextInputColumnComponent.js +18 -0
  112. package/dist/components/columns/ToggleColumnComponent.d.ts +6 -0
  113. package/dist/components/columns/ToggleColumnComponent.js +25 -0
  114. package/dist/components/columns/VideoColumnComponent.d.ts +3 -0
  115. package/dist/components/columns/VideoColumnComponent.js +125 -0
  116. package/dist/components/columns/ViewColumnComponent.d.ts +3 -0
  117. package/dist/components/columns/ViewColumnComponent.js +7 -0
  118. package/dist/components/errors/ErrorBoundary.d.ts +23 -0
  119. package/dist/components/errors/ErrorBoundary.js +33 -0
  120. package/dist/components/filters/CustomFilterComponent.d.ts +10 -0
  121. package/dist/components/filters/CustomFilterComponent.js +33 -0
  122. package/dist/components/filters/DateFilterComponent.d.ts +15 -0
  123. package/dist/components/filters/DateFilterComponent.js +132 -0
  124. package/dist/components/filters/QueryBuilderFilterComponent.d.ts +11 -0
  125. package/dist/components/filters/QueryBuilderFilterComponent.js +200 -0
  126. package/dist/components/layout/Header.d.ts +10 -0
  127. package/dist/components/layout/Header.js +70 -0
  128. package/dist/components/layout/PanelBrandMark.d.ts +8 -0
  129. package/dist/components/layout/PanelBrandMark.js +28 -0
  130. package/dist/components/layout/Sidebar.d.ts +35 -0
  131. package/dist/components/layout/Sidebar.js +125 -0
  132. package/dist/components/modals/RelationCreateModal.d.ts +19 -0
  133. package/dist/components/modals/RelationCreateModal.js +57 -0
  134. package/dist/components/modals/ResourceFormModal.d.ts +37 -0
  135. package/dist/components/modals/ResourceFormModal.js +44 -0
  136. package/dist/components/modals/useResourceForm.d.ts +40 -0
  137. package/dist/components/modals/useResourceForm.js +138 -0
  138. package/dist/components/modals/view/RecordActions.d.ts +17 -0
  139. package/dist/components/modals/view/RecordActions.js +16 -0
  140. package/dist/components/modals/view/RecordDetails.d.ts +13 -0
  141. package/dist/components/modals/view/RecordDetails.js +29 -0
  142. package/dist/components/modals/view/RelationPanel.d.ts +18 -0
  143. package/dist/components/modals/view/RelationPanel.js +16 -0
  144. package/dist/components/modals/view/RelationTabs.d.ts +32 -0
  145. package/dist/components/modals/view/RelationTabs.js +42 -0
  146. package/dist/components/modals/view/useRecordView.d.ts +18 -0
  147. package/dist/components/modals/view/useRecordView.js +114 -0
  148. package/dist/components/pages/PageRenderer.d.ts +6 -0
  149. package/dist/components/pages/PageRenderer.js +107 -0
  150. package/dist/components/table/ColumnTogglePopup.d.ts +11 -0
  151. package/dist/components/table/ColumnTogglePopup.js +16 -0
  152. package/dist/components/table/GridCard.d.ts +21 -0
  153. package/dist/components/table/GridCard.js +30 -0
  154. package/dist/components/table/GridView.d.ts +23 -0
  155. package/dist/components/table/GridView.js +49 -0
  156. package/dist/components/table/LayoutToggle.d.ts +7 -0
  157. package/dist/components/table/LayoutToggle.js +9 -0
  158. package/dist/components/table/TableActionsDropdown.d.ts +13 -0
  159. package/dist/components/table/TableActionsDropdown.js +46 -0
  160. package/dist/components/table/TableBulkActions.d.ts +11 -0
  161. package/dist/components/table/TableBulkActions.js +21 -0
  162. package/dist/components/table/TableHeader.d.ts +14 -0
  163. package/dist/components/table/TableHeader.js +23 -0
  164. package/dist/components/table/TablePagination.d.ts +13 -0
  165. package/dist/components/table/TablePagination.js +55 -0
  166. package/dist/components/table/TableRow.d.ts +21 -0
  167. package/dist/components/table/TableRow.js +32 -0
  168. package/dist/components/table/TableSearchBar.d.ts +11 -0
  169. package/dist/components/table/TableSearchBar.js +12 -0
  170. package/dist/components/table/TableTabs.d.ts +14 -0
  171. package/dist/components/table/TableTabs.js +8 -0
  172. package/dist/components/ui/Badge.d.ts +6 -0
  173. package/dist/components/ui/Badge.js +12 -0
  174. package/dist/components/ui/Button.d.ts +22 -0
  175. package/dist/components/ui/Button.js +22 -0
  176. package/dist/components/ui/Card.d.ts +7 -0
  177. package/dist/components/ui/Card.js +5 -0
  178. package/dist/components/ui/ConfirmDialog.d.ts +19 -0
  179. package/dist/components/ui/ConfirmDialog.js +45 -0
  180. package/dist/components/ui/EmptyState.d.ts +9 -0
  181. package/dist/components/ui/EmptyState.js +6 -0
  182. package/dist/components/ui/ErrorAlert.d.ts +7 -0
  183. package/dist/components/ui/ErrorAlert.js +9 -0
  184. package/dist/components/ui/Input.d.ts +11 -0
  185. package/dist/components/ui/Input.js +10 -0
  186. package/dist/components/ui/Label.d.ts +5 -0
  187. package/dist/components/ui/Label.js +5 -0
  188. package/dist/components/ui/PillButton.d.ts +14 -0
  189. package/dist/components/ui/PillButton.js +19 -0
  190. package/dist/components/ui/Select.d.ts +7 -0
  191. package/dist/components/ui/Select.js +7 -0
  192. package/dist/components/ui/Spinner.d.ts +8 -0
  193. package/dist/components/ui/Spinner.js +14 -0
  194. package/dist/components/ui/Toast.d.ts +21 -0
  195. package/dist/components/ui/Toast.js +47 -0
  196. package/dist/components/ui/index.d.ts +24 -0
  197. package/dist/components/ui/index.js +12 -0
  198. package/dist/components/utils/HintDisplay.d.ts +11 -0
  199. package/dist/components/utils/HintDisplay.js +12 -0
  200. package/dist/components/utils/Icon.d.ts +22 -0
  201. package/dist/components/utils/Icon.js +22 -0
  202. package/dist/components/utils/MediaPreviewModal.d.ts +14 -0
  203. package/dist/components/utils/MediaPreviewModal.js +32 -0
  204. package/dist/components/utils/ViewFieldWrapper.d.ts +11 -0
  205. package/dist/components/utils/ViewFieldWrapper.js +9 -0
  206. package/dist/components/utils/layoutHelpers.d.ts +19 -0
  207. package/dist/components/utils/layoutHelpers.js +257 -0
  208. package/dist/components/widgets/ChartWidget.d.ts +16 -0
  209. package/dist/components/widgets/ChartWidget.js +192 -0
  210. package/dist/components/widgets/StatsWidget.d.ts +16 -0
  211. package/dist/components/widgets/StatsWidget.js +39 -0
  212. package/dist/components/widgets/WidgetRenderer.d.ts +10 -0
  213. package/dist/components/widgets/WidgetRenderer.js +50 -0
  214. package/dist/components/widgets/WidgetShell.d.ts +9 -0
  215. package/dist/components/widgets/WidgetShell.js +7 -0
  216. package/dist/contexts/AuthChallengeRegistryContext.d.ts +15 -0
  217. package/dist/contexts/AuthChallengeRegistryContext.js +15 -0
  218. package/dist/contexts/BlockRegistryContext.d.ts +18 -0
  219. package/dist/contexts/BlockRegistryContext.js +8 -0
  220. package/dist/contexts/ColumnRegistryContext.d.ts +8 -0
  221. package/dist/contexts/ColumnRegistryContext.js +30 -0
  222. package/dist/contexts/FieldRegistryContext.d.ts +13 -0
  223. package/dist/contexts/FieldRegistryContext.js +46 -0
  224. package/dist/contexts/PanelMetadataContext.d.ts +26 -0
  225. package/dist/contexts/PanelMetadataContext.js +26 -0
  226. package/dist/contexts/PanelProviders.d.ts +27 -0
  227. package/dist/contexts/PanelProviders.js +24 -0
  228. package/dist/contexts/ResourceModalContext.d.ts +26 -0
  229. package/dist/contexts/ResourceModalContext.js +76 -0
  230. package/dist/contexts/SlotRegistryContext.d.ts +19 -0
  231. package/dist/contexts/SlotRegistryContext.js +24 -0
  232. package/dist/contexts/TableRefreshContext.d.ts +10 -0
  233. package/dist/contexts/TableRefreshContext.js +30 -0
  234. package/dist/contexts/WidgetRegistryContext.d.ts +17 -0
  235. package/dist/contexts/WidgetRegistryContext.js +14 -0
  236. package/dist/contexts/createRegistryContext.d.ts +19 -0
  237. package/dist/contexts/createRegistryContext.js +20 -0
  238. package/dist/hooks/useAfterStateUpdated.d.ts +6 -0
  239. package/dist/hooks/useAfterStateUpdated.js +62 -0
  240. package/dist/hooks/useValidation.d.ts +26 -0
  241. package/dist/hooks/useValidation.js +76 -0
  242. package/dist/i18n/I18nProvider.d.ts +27 -0
  243. package/dist/i18n/I18nProvider.js +101 -0
  244. package/dist/i18n/LocaleSwitcher.d.ts +10 -0
  245. package/dist/i18n/LocaleSwitcher.js +30 -0
  246. package/dist/i18n/activeLocale.d.ts +11 -0
  247. package/dist/i18n/activeLocale.js +34 -0
  248. package/dist/i18n/buildClientI18n.d.ts +28 -0
  249. package/dist/i18n/buildClientI18n.js +67 -0
  250. package/dist/i18n/index.d.ts +11 -0
  251. package/dist/i18n/index.js +9 -0
  252. package/dist/i18n/locales/core/en.d.ts +225 -0
  253. package/dist/i18n/locales/core/en.js +252 -0
  254. package/dist/i18n/locales/core/index.d.ts +2 -0
  255. package/dist/i18n/locales/core/index.js +4 -0
  256. package/dist/i18n/locales/core/sq.d.ts +253 -0
  257. package/dist/i18n/locales/core/sq.js +255 -0
  258. package/dist/i18n/useFormatter.d.ts +18 -0
  259. package/dist/i18n/useFormatter.js +37 -0
  260. package/dist/i18n/useLocale.d.ts +11 -0
  261. package/dist/i18n/useLocale.js +11 -0
  262. package/dist/i18n/useTranslation.d.ts +12 -0
  263. package/dist/i18n/useTranslation.js +12 -0
  264. package/dist/index.d.ts +106 -0
  265. package/dist/index.js +101 -0
  266. package/dist/pages/ResourceListPage.d.ts +8 -0
  267. package/dist/pages/ResourceListPage.js +139 -0
  268. package/dist/plugin.d.ts +79 -0
  269. package/dist/plugin.js +34 -0
  270. package/dist/runtime/conditions.d.ts +35 -0
  271. package/dist/runtime/conditions.js +97 -0
  272. package/dist/runtime/formTraversal.d.ts +25 -0
  273. package/dist/runtime/formTraversal.js +37 -0
  274. package/dist/runtime/serializedFunctions.d.ts +41 -0
  275. package/dist/runtime/serializedFunctions.js +264 -0
  276. package/dist/slots/Slot.d.ts +24 -0
  277. package/dist/slots/Slot.js +29 -0
  278. package/dist/slots/SlotCluster.d.ts +22 -0
  279. package/dist/slots/SlotCluster.js +49 -0
  280. package/dist/slots/index.d.ts +7 -0
  281. package/dist/slots/index.js +4 -0
  282. package/dist/slots/mergeSlots.d.ts +18 -0
  283. package/dist/slots/mergeSlots.js +35 -0
  284. package/dist/slots/types.d.ts +87 -0
  285. package/dist/slots/types.js +30 -0
  286. package/dist/styles.css +1 -0
  287. package/dist/table/TableContext.d.ts +36 -0
  288. package/dist/table/TableContext.js +13 -0
  289. package/dist/table/TableRenderer.d.ts +29 -0
  290. package/dist/table/TableRenderer.js +159 -0
  291. package/dist/table/components/FiltersPanel.d.ts +11 -0
  292. package/dist/table/components/FiltersPanel.js +52 -0
  293. package/dist/table/components/TableToolbar.d.ts +28 -0
  294. package/dist/table/components/TableToolbar.js +27 -0
  295. package/dist/table/components/TableToolbarButton.d.ts +6 -0
  296. package/dist/table/components/TableToolbarButton.js +9 -0
  297. package/dist/table/components/TableView.d.ts +12 -0
  298. package/dist/table/components/TableView.js +21 -0
  299. package/dist/table/defaultRowActions.d.ts +21 -0
  300. package/dist/table/defaultRowActions.js +37 -0
  301. package/dist/table/hooks/useColumnVisibility.d.ts +13 -0
  302. package/dist/table/hooks/useColumnVisibility.js +59 -0
  303. package/dist/table/hooks/useEditableRows.d.ts +22 -0
  304. package/dist/table/hooks/useEditableRows.js +63 -0
  305. package/dist/table/hooks/useTableActions.d.ts +54 -0
  306. package/dist/table/hooks/useTableActions.js +313 -0
  307. package/dist/table/hooks/useTableData.d.ts +28 -0
  308. package/dist/table/hooks/useTableData.js +63 -0
  309. package/dist/table/hooks/useTableLayout.d.ts +12 -0
  310. package/dist/table/hooks/useTableLayout.js +31 -0
  311. package/dist/table/hooks/useTableQuery.d.ts +29 -0
  312. package/dist/table/hooks/useTableQuery.js +135 -0
  313. package/dist/types/index.d.ts +224 -0
  314. package/dist/types/index.js +6 -0
  315. package/dist/utils/classNames.d.ts +7 -0
  316. package/dist/utils/classNames.js +9 -0
  317. package/dist/utils/columnMediaDimensions.d.ts +13 -0
  318. package/dist/utils/columnMediaDimensions.js +29 -0
  319. package/dist/utils/columnVisibilityStorage.d.ts +22 -0
  320. package/dist/utils/columnVisibilityStorage.js +56 -0
  321. package/dist/utils/fieldErrors.d.ts +13 -0
  322. package/dist/utils/fieldErrors.js +25 -0
  323. package/dist/utils/formatValue.d.ts +28 -0
  324. package/dist/utils/formatValue.js +109 -0
  325. package/dist/utils/layoutStorage.d.ts +23 -0
  326. package/dist/utils/layoutStorage.js +53 -0
  327. package/dist/utils/redirectHandler.d.ts +7 -0
  328. package/dist/utils/redirectHandler.js +25 -0
  329. package/dist/utils/tableFormatters.d.ts +14 -0
  330. package/dist/utils/tableFormatters.js +93 -0
  331. package/dist/utils/widgetVisibilityStorage.d.ts +11 -0
  332. package/dist/utils/widgetVisibilityStorage.js +39 -0
  333. package/package.json +101 -0
  334. package/src/FieldRenderer.test.tsx +44 -0
  335. package/src/FieldRenderer.tsx +104 -0
  336. package/src/FormRenderer.containers.test.tsx +121 -0
  337. package/src/FormRenderer.test.tsx +174 -0
  338. package/src/FormRenderer.tsx +140 -0
  339. package/src/TableRenderer.tsx +2 -0
  340. package/src/api/actionsApi.ts +76 -0
  341. package/src/api/authenticatedFetch.ts +40 -0
  342. package/src/api/exportApi.ts +66 -0
  343. package/src/api/http.test.ts +58 -0
  344. package/src/api/http.ts +68 -0
  345. package/src/api/resourceApi.ts +88 -0
  346. package/src/api/tableApi.test.ts +108 -0
  347. package/src/api/tableApi.ts +107 -0
  348. package/src/api/urls.ts +50 -0
  349. package/src/app.test.tsx +67 -0
  350. package/src/app.tsx +181 -0
  351. package/src/auth/AuthContext.tsx +188 -0
  352. package/src/auth/LoginPage.tsx +380 -0
  353. package/src/auth/ProtectedRoute.tsx +39 -0
  354. package/src/auth/authApiClient.ts +109 -0
  355. package/src/auth/authFlow.test.tsx +168 -0
  356. package/src/auth/types.ts +104 -0
  357. package/src/components/ActionFormModal.tsx +45 -0
  358. package/src/components/AdminPanel.tsx +368 -0
  359. package/src/components/Checkbox.tsx +59 -0
  360. package/src/components/CheckboxField.tsx +88 -0
  361. package/src/components/ColorPickerField.tsx +93 -0
  362. package/src/components/DateTimePickerField.tsx +112 -0
  363. package/src/components/FileUploadField.tsx +841 -0
  364. package/src/components/GlobalSearch.tsx +436 -0
  365. package/src/components/GroupField.tsx +85 -0
  366. package/src/components/HiddenField.tsx +14 -0
  367. package/src/components/ModalBreadcrumb.tsx +74 -0
  368. package/src/components/ModalDrawer.tsx +137 -0
  369. package/src/components/RadioField.tsx +80 -0
  370. package/src/components/RepeaterField.tsx +546 -0
  371. package/src/components/ResourceModalRenderer.tsx +144 -0
  372. package/src/components/RichEditorField.tsx +942 -0
  373. package/src/components/SectionField.tsx +242 -0
  374. package/src/components/SelectField.tsx +843 -0
  375. package/src/components/TabsField.test.tsx +151 -0
  376. package/src/components/TabsField.tsx +386 -0
  377. package/src/components/TagsInputField.tsx +411 -0
  378. package/src/components/TextInputField.tsx +91 -0
  379. package/src/components/TextareaField.tsx +110 -0
  380. package/src/components/ToggleField.tsx +126 -0
  381. package/src/components/ViewModal.tsx +353 -0
  382. package/src/components/blocks/BlockRenderer.tsx +56 -0
  383. package/src/components/blocks/FormBlockRenderer.tsx +160 -0
  384. package/src/components/blocks/TableBlockRenderer.tsx +33 -0
  385. package/src/components/blocks/TabsBlockRenderer.tsx +49 -0
  386. package/src/components/blocks/WidgetBlockRenderer.tsx +19 -0
  387. package/src/components/columns/CheckboxColumnComponent.tsx +38 -0
  388. package/src/components/columns/ColorColumnComponent.tsx +23 -0
  389. package/src/components/columns/CustomColumn.test.tsx +55 -0
  390. package/src/components/columns/DeeplinkWrapper.tsx +103 -0
  391. package/src/components/columns/IconColumnComponent.tsx +55 -0
  392. package/src/components/columns/ImageColumnComponent.tsx +220 -0
  393. package/src/components/columns/MediaColumnComponent.tsx +294 -0
  394. package/src/components/columns/SelectColumnComponent.tsx +49 -0
  395. package/src/components/columns/TagsColumnComponent.tsx +46 -0
  396. package/src/components/columns/TextColumnComponent.tsx +191 -0
  397. package/src/components/columns/TextInputColumnComponent.tsx +35 -0
  398. package/src/components/columns/ToggleColumnComponent.tsx +56 -0
  399. package/src/components/columns/VideoColumnComponent.tsx +236 -0
  400. package/src/components/columns/ViewColumnComponent.tsx +9 -0
  401. package/src/components/errors/ErrorBoundary.tsx +58 -0
  402. package/src/components/filters/CustomFilterComponent.tsx +130 -0
  403. package/src/components/filters/DateFilterComponent.tsx +272 -0
  404. package/src/components/filters/QueryBuilderFilterComponent.tsx +502 -0
  405. package/src/components/layout/Header.tsx +212 -0
  406. package/src/components/layout/PanelBrandMark.tsx +61 -0
  407. package/src/components/layout/Sidebar.tsx +283 -0
  408. package/src/components/modals/RelationCreateModal.tsx +107 -0
  409. package/src/components/modals/ResourceFormModal.test.tsx +119 -0
  410. package/src/components/modals/ResourceFormModal.tsx +128 -0
  411. package/src/components/modals/useResourceForm.ts +207 -0
  412. package/src/components/modals/view/RecordActions.tsx +69 -0
  413. package/src/components/modals/view/RecordDetails.tsx +60 -0
  414. package/src/components/modals/view/RelationPanel.tsx +76 -0
  415. package/src/components/modals/view/RelationTabs.tsx +145 -0
  416. package/src/components/modals/view/useRecordView.ts +134 -0
  417. package/src/components/pages/PageRenderer.tsx +173 -0
  418. package/src/components/table/ColumnTogglePopup.tsx +85 -0
  419. package/src/components/table/GridCard.tsx +155 -0
  420. package/src/components/table/GridView.tsx +138 -0
  421. package/src/components/table/LayoutToggle.tsx +24 -0
  422. package/src/components/table/TableActionsDropdown.tsx +114 -0
  423. package/src/components/table/TableBulkActions.tsx +65 -0
  424. package/src/components/table/TableHeader.tsx +96 -0
  425. package/src/components/table/TablePagination.tsx +169 -0
  426. package/src/components/table/TableRow.tsx +155 -0
  427. package/src/components/table/TableSearchBar.tsx +66 -0
  428. package/src/components/table/TableTabs.tsx +49 -0
  429. package/src/components/ui/Badge.tsx +30 -0
  430. package/src/components/ui/Button.test.tsx +78 -0
  431. package/src/components/ui/Button.tsx +102 -0
  432. package/src/components/ui/Card.tsx +23 -0
  433. package/src/components/ui/ConfirmDialog.tsx +112 -0
  434. package/src/components/ui/EmptyState.tsx +24 -0
  435. package/src/components/ui/ErrorAlert.tsx +37 -0
  436. package/src/components/ui/Input.tsx +48 -0
  437. package/src/components/ui/Label.tsx +15 -0
  438. package/src/components/ui/PillButton.tsx +72 -0
  439. package/src/components/ui/Select.tsx +33 -0
  440. package/src/components/ui/Spinner.tsx +39 -0
  441. package/src/components/ui/Toast.tsx +105 -0
  442. package/src/components/ui/index.ts +24 -0
  443. package/src/components/utils/HintDisplay.tsx +26 -0
  444. package/src/components/utils/Icon.tsx +36 -0
  445. package/src/components/utils/MediaPreviewModal.tsx +114 -0
  446. package/src/components/utils/ViewFieldWrapper.tsx +23 -0
  447. package/src/components/utils/layoutHelpers.ts +267 -0
  448. package/src/components/widgets/ChartWidget.tsx +247 -0
  449. package/src/components/widgets/StatsWidget.tsx +72 -0
  450. package/src/components/widgets/WidgetRenderer.tsx +108 -0
  451. package/src/components/widgets/WidgetShell.tsx +37 -0
  452. package/src/contexts/AuthChallengeRegistryContext.tsx +29 -0
  453. package/src/contexts/BlockRegistryContext.tsx +28 -0
  454. package/src/contexts/ColumnRegistryContext.tsx +38 -0
  455. package/src/contexts/FieldRegistryContext.tsx +56 -0
  456. package/src/contexts/PanelMetadataContext.tsx +60 -0
  457. package/src/contexts/PanelProviders.tsx +85 -0
  458. package/src/contexts/ResourceModalContext.tsx +137 -0
  459. package/src/contexts/SlotRegistryContext.tsx +35 -0
  460. package/src/contexts/TableRefreshContext.tsx +44 -0
  461. package/src/contexts/WidgetRegistryContext.tsx +34 -0
  462. package/src/contexts/createRegistryContext.tsx +29 -0
  463. package/src/hooks/useAfterStateUpdated.ts +70 -0
  464. package/src/hooks/useValidation.test.ts +59 -0
  465. package/src/hooks/useValidation.ts +95 -0
  466. package/src/i18n/I18nProvider.tsx +128 -0
  467. package/src/i18n/LocaleSwitcher.tsx +50 -0
  468. package/src/i18n/activeLocale.ts +39 -0
  469. package/src/i18n/buildClientI18n.ts +101 -0
  470. package/src/i18n/i18n.test.tsx +140 -0
  471. package/src/i18n/index.ts +12 -0
  472. package/src/i18n/locales/core/en.ts +274 -0
  473. package/src/i18n/locales/core/index.ts +5 -0
  474. package/src/i18n/locales/core/sq.ts +275 -0
  475. package/src/i18n/useFormatter.ts +42 -0
  476. package/src/i18n/useLocale.ts +16 -0
  477. package/src/i18n/useTranslation.ts +17 -0
  478. package/src/index.ts +244 -0
  479. package/src/pages/ResourceListPage.tsx +205 -0
  480. package/src/plugin.ts +110 -0
  481. package/src/runtime/conditions.test.ts +99 -0
  482. package/src/runtime/conditions.ts +148 -0
  483. package/src/runtime/formTraversal.ts +41 -0
  484. package/src/runtime/serializedFunctions.test.ts +59 -0
  485. package/src/runtime/serializedFunctions.ts +284 -0
  486. package/src/slots/Slot.test.tsx +89 -0
  487. package/src/slots/Slot.tsx +47 -0
  488. package/src/slots/SlotCluster.test.tsx +95 -0
  489. package/src/slots/SlotCluster.tsx +107 -0
  490. package/src/slots/index.ts +15 -0
  491. package/src/slots/mergeSlots.test.ts +71 -0
  492. package/src/slots/mergeSlots.ts +40 -0
  493. package/src/slots/slotNames.test.ts +21 -0
  494. package/src/slots/types.ts +119 -0
  495. package/src/styles.css +437 -0
  496. package/src/table/TableContext.tsx +41 -0
  497. package/src/table/TableRenderer.test.tsx +197 -0
  498. package/src/table/TableRenderer.tsx +390 -0
  499. package/src/table/components/FiltersPanel.tsx +193 -0
  500. package/src/table/components/TableToolbar.tsx +153 -0
  501. package/src/table/components/TableToolbarButton.tsx +14 -0
  502. package/src/table/components/TableView.tsx +106 -0
  503. package/src/table/defaultRowActions.ts +43 -0
  504. package/src/table/hooks/useColumnVisibility.test.ts +51 -0
  505. package/src/table/hooks/useColumnVisibility.ts +71 -0
  506. package/src/table/hooks/useEditableRows.test.ts +69 -0
  507. package/src/table/hooks/useEditableRows.ts +89 -0
  508. package/src/table/hooks/useTableActions.ts +393 -0
  509. package/src/table/hooks/useTableData.ts +89 -0
  510. package/src/table/hooks/useTableLayout.ts +45 -0
  511. package/src/table/hooks/useTableQuery.test.ts +116 -0
  512. package/src/table/hooks/useTableQuery.ts +172 -0
  513. package/src/test/mockFetch.ts +67 -0
  514. package/src/test/setup.ts +25 -0
  515. package/src/types/index.ts +228 -0
  516. package/src/utils/classNames.ts +10 -0
  517. package/src/utils/columnMediaDimensions.ts +45 -0
  518. package/src/utils/columnVisibilityStorage.ts +55 -0
  519. package/src/utils/fieldErrors.test.ts +35 -0
  520. package/src/utils/fieldErrors.ts +27 -0
  521. package/src/utils/formatValue.test.tsx +65 -0
  522. package/src/utils/formatValue.tsx +117 -0
  523. package/src/utils/layoutStorage.ts +52 -0
  524. package/src/utils/redirectHandler.ts +29 -0
  525. package/src/utils/tableFormatters.test.ts +54 -0
  526. package/src/utils/tableFormatters.ts +104 -0
  527. package/src/utils/widgetVisibilityStorage.ts +38 -0
  528. package/tailwind.config.js +9 -0
  529. package/vite.config.ts +17 -0
@@ -0,0 +1,174 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import React from 'react';
3
+ import { render, screen, waitFor } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { FormRenderer } from './FormRenderer';
6
+ import { FieldRegistryProvider } from './contexts/FieldRegistryContext';
7
+
8
+ function renderForm(schema: any, onSubmit = vi.fn(), defaultValues?: Record<string, any>) {
9
+ render(
10
+ <FieldRegistryProvider>
11
+ <FormRenderer schema={schema} onSubmit={onSubmit} defaultValues={defaultValues} />
12
+ </FieldRegistryProvider>,
13
+ );
14
+ return onSubmit;
15
+ }
16
+
17
+ describe('FormRenderer', () => {
18
+ it('renders basic field types', () => {
19
+ renderForm({
20
+ type: 'form',
21
+ components: [
22
+ { type: 'text-input', name: 'name', label: 'Name' },
23
+ { type: 'textarea', name: 'bio', label: 'Bio' },
24
+ { type: 'checkbox', name: 'agree', label: 'Agree' },
25
+ { type: 'hidden', name: 'secret', default: 'x' },
26
+ ],
27
+ });
28
+
29
+ expect(screen.getByLabelText(/name/i)).toBeInTheDocument();
30
+ expect(screen.getByLabelText(/bio/i)).toBeInTheDocument();
31
+ expect(screen.getByText('Agree')).toBeInTheDocument();
32
+ expect(screen.getByRole('button', { name: /submit/i })).toBeInTheDocument();
33
+ });
34
+
35
+ it('shows a warning box for unknown field types', () => {
36
+ renderForm({ type: 'form', components: [{ type: 'does-not-exist', name: 'x' }] });
37
+ expect(screen.getByText(/unknown field type "does-not-exist"/i)).toBeInTheDocument();
38
+ });
39
+
40
+ it('submits values including schema defaults', async () => {
41
+ const user = userEvent.setup();
42
+ const onSubmit = renderForm({
43
+ type: 'form',
44
+ components: [
45
+ { type: 'text-input', name: 'name', label: 'Name' },
46
+ { type: 'hidden', name: 'kind', default: 'admin' },
47
+ ],
48
+ });
49
+
50
+ await user.type(screen.getByLabelText(/name/i), 'Jane');
51
+ await user.click(screen.getByRole('button', { name: /submit/i }));
52
+
53
+ await waitFor(() => expect(onSubmit).toHaveBeenCalled());
54
+ expect(onSubmit.mock.calls[0][0]).toMatchObject({ name: 'Jane', kind: 'admin' });
55
+ });
56
+
57
+ it('extracts nested defaults from sections and groups', async () => {
58
+ const user = userEvent.setup();
59
+ const onSubmit = renderForm({
60
+ type: 'form',
61
+ components: [
62
+ {
63
+ type: 'section',
64
+ name: 'main',
65
+ heading: 'Main',
66
+ isLayout: true,
67
+ childScope: 'inherit',
68
+ schema: [{ type: 'text-input', name: 'inner', label: 'Inner', default: 'preset' }],
69
+ },
70
+ ],
71
+ });
72
+
73
+ await user.click(screen.getByRole('button', { name: /submit/i }));
74
+ await waitFor(() => expect(onSubmit).toHaveBeenCalled());
75
+ expect(onSubmit.mock.calls[0][0]).toMatchObject({ inner: 'preset' });
76
+ });
77
+
78
+ it('seeds repeater rows from minItems', async () => {
79
+ const user = userEvent.setup();
80
+ const onSubmit = renderForm({
81
+ type: 'form',
82
+ components: [
83
+ {
84
+ type: 'repeater',
85
+ name: 'items',
86
+ label: 'Items',
87
+ minItems: 2,
88
+ childScope: 'array',
89
+ schema: [{ type: 'text-input', name: 'title', label: 'Title', default: 't' }],
90
+ },
91
+ ],
92
+ });
93
+
94
+ await user.click(screen.getByRole('button', { name: /submit/i }));
95
+ await waitFor(() => expect(onSubmit).toHaveBeenCalled());
96
+ expect(onSubmit.mock.calls[0][0].items).toEqual([{ title: 't' }, { title: 't' }]);
97
+ });
98
+
99
+ it('extracts defaults from a custom (plugin) container via the declarative contract', async () => {
100
+ const user = userEvent.setup();
101
+ // The core has no renderer for 'custom-fieldset', so FieldRenderer warns and falls back;
102
+ // the default-extraction contract under test still runs.
103
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
104
+ const onSubmit = renderForm({
105
+ type: 'form',
106
+ components: [
107
+ {
108
+ // A container type the core does not know about — discovered purely via metadata.
109
+ type: 'custom-fieldset',
110
+ isLayout: true,
111
+ childScope: 'inherit',
112
+ schema: [{ type: 'text-input', name: 'pluginValue', default: 'from-plugin' }],
113
+ },
114
+ ],
115
+ });
116
+
117
+ await user.click(screen.getByRole('button', { name: /submit/i }));
118
+ await waitFor(() => expect(onSubmit).toHaveBeenCalled());
119
+ expect(onSubmit.mock.calls[0][0]).toMatchObject({ pluginValue: 'from-plugin' });
120
+ expect(warnSpy).toHaveBeenCalledWith('Unknown field type: custom-fieldset');
121
+ warnSpy.mockRestore();
122
+ });
123
+
124
+ it('accepts decimals and negatives for numeric fields', async () => {
125
+ const user = userEvent.setup();
126
+ const onSubmit = renderForm({
127
+ type: 'form',
128
+ components: [
129
+ { type: 'text-input', name: 'price', label: 'Price', validation: { rules: [{ rule: 'numeric' }] } },
130
+ ],
131
+ });
132
+
133
+ await user.type(screen.getByLabelText('Price'), '10.50');
134
+ await user.click(screen.getByRole('button', { name: /submit/i }));
135
+
136
+ // Submission would be blocked if the numeric rule rejected the decimal.
137
+ await waitFor(() => expect(onSubmit).toHaveBeenCalled());
138
+ expect(onSubmit.mock.calls[0][0]).toMatchObject({ price: '10.50' });
139
+ });
140
+
141
+ it('hides fields via a structured hiddenWhen condition', () => {
142
+ renderForm({
143
+ type: 'form',
144
+ components: [
145
+ { type: 'text-input', name: 'status', label: 'Status', default: 'draft' },
146
+ {
147
+ type: 'text-input',
148
+ name: 'publishedAt',
149
+ label: 'Published at',
150
+ hiddenWhen: { op: 'eq', field: 'status', value: 'draft' },
151
+ },
152
+ ],
153
+ });
154
+
155
+ expect(screen.getByLabelText(/status/i)).toBeInTheDocument();
156
+ expect(screen.queryByLabelText(/published at/i)).not.toBeInTheDocument();
157
+ });
158
+
159
+ it('user-provided defaultValues override schema defaults', async () => {
160
+ const user = userEvent.setup();
161
+ const onSubmit = renderForm(
162
+ {
163
+ type: 'form',
164
+ components: [{ type: 'text-input', name: 'name', label: 'Name', default: 'schema-default' }],
165
+ },
166
+ vi.fn(),
167
+ { name: 'record-value' },
168
+ );
169
+
170
+ await user.click(screen.getByRole('button', { name: /submit/i }));
171
+ await waitFor(() => expect(onSubmit).toHaveBeenCalled());
172
+ expect(onSubmit.mock.calls[0][0]).toMatchObject({ name: 'record-value' });
173
+ });
174
+ });
@@ -0,0 +1,140 @@
1
+ import React, { useMemo } from 'react';
2
+ import { useForm, FormProvider } from 'react-hook-form';
3
+ import { FormRendererProps } from './types';
4
+ import { FieldRenderer } from './FieldRenderer';
5
+ import { cn } from './utils/classNames';
6
+ import { getGridClasses } from './components/utils/layoutHelpers';
7
+ import { Button } from './components/ui/Button';
8
+ import { SerializedComponent } from '@maxal_studio/kratosjs';
9
+ import { getChildComponents, isArrayScope, isLayout } from './runtime/formTraversal';
10
+ import { useTranslation } from './i18n/useTranslation';
11
+ import { Slot } from './slots/Slot';
12
+
13
+ /**
14
+ * Extract default values from a schema using the declarative children contract.
15
+ * Layout containers are transparent (recurse into children); array-scope
16
+ * containers (repeaters) seed their own array value from the item template; leaf
17
+ * fields contribute their `default`.
18
+ */
19
+ function extractDefaultValues(components: SerializedComponent[]): Record<string, any> {
20
+ const defaults: Record<string, any> = {};
21
+
22
+ components.forEach(component => {
23
+ // Array-scope container (Repeater): seed the array value, never leak item fields.
24
+ if (isArrayScope(component)) {
25
+ if (!component.name) return;
26
+ const template = getChildComponents(component);
27
+
28
+ if (component.default !== undefined && Array.isArray(component.default)) {
29
+ defaults[component.name] = component.default;
30
+ return;
31
+ }
32
+
33
+ const count =
34
+ typeof component.defaultItems === 'number' && component.defaultItems > 0
35
+ ? component.defaultItems
36
+ : typeof (component as any).minItems === 'number' && (component as any).minItems > 0
37
+ ? ((component as any).minItems as number)
38
+ : 0;
39
+
40
+ if (count > 0 && template.length > 0) {
41
+ const itemDefaults = extractDefaultValues(template);
42
+ defaults[component.name] = Array.from({ length: count }, () => ({ ...itemDefaults }));
43
+ }
44
+ return;
45
+ }
46
+
47
+ // Layout container: transparent, recurse into children.
48
+ if (isLayout(component)) {
49
+ Object.assign(defaults, extractDefaultValues(getChildComponents(component)));
50
+ return;
51
+ }
52
+
53
+ // Leaf field with a default value.
54
+ if (component.name && component.default !== undefined) {
55
+ defaults[component.name] = component.default;
56
+ }
57
+ });
58
+
59
+ return defaults;
60
+ }
61
+
62
+ /**
63
+ * FormRenderer component
64
+ * Main component that accepts JSON schema and renders the form
65
+ */
66
+ export function FormRenderer({
67
+ schema,
68
+ onSubmit,
69
+ defaultValues = {},
70
+ className,
71
+ apiBaseUrl,
72
+ resource,
73
+ operation,
74
+ children,
75
+ }: FormRendererProps) {
76
+ const { t } = useTranslation();
77
+ // Extract default values from schema and merge with user-provided defaults
78
+ const mergedDefaults = useMemo(() => {
79
+ const schemaDefaults = extractDefaultValues(schema.components);
80
+ return { ...schemaDefaults, ...defaultValues };
81
+ }, [schema.components, defaultValues]);
82
+
83
+ const methods = useForm({
84
+ defaultValues: mergedDefaults,
85
+ mode: 'onChange', // Validate and re-validate on change
86
+ });
87
+
88
+ const handleSubmit = async (data: any) => {
89
+ try {
90
+ await onSubmit(data);
91
+ } catch (error) {
92
+ console.error('Form submission error:', error);
93
+ }
94
+ };
95
+
96
+ // Calculate grid classes based on the columns prop (static class maps — purge-safe)
97
+ const formGridClasses =
98
+ !schema.columns || schema.columns === 1 ? 'space-y-6' : `${getGridClasses(schema.columns)} gap-y-4`;
99
+
100
+ return (
101
+ <FormProvider {...methods}>
102
+ {children}
103
+ <form onSubmit={methods.handleSubmit(handleSubmit)} className={cn(className)} noValidate>
104
+ <Slot
105
+ name="form.header"
106
+ context={{ resourceSlug: resource, record: defaultValues, schema }}
107
+ as="div"
108
+ className="mb-4 space-y-3 empty:hidden"
109
+ />
110
+ <div className={formGridClasses}>
111
+ {schema.components.map((field, index) => (
112
+ <FieldRenderer
113
+ key={field.name || index}
114
+ field={field}
115
+ apiBaseUrl={apiBaseUrl}
116
+ resource={resource}
117
+ operation={operation}
118
+ mode="edit"
119
+ />
120
+ ))}
121
+ </div>
122
+
123
+ <div className="flex flex-wrap items-center justify-end gap-3 pt-6">
124
+ <Slot
125
+ name="form.footer"
126
+ context={{ resourceSlug: resource, record: defaultValues, schema }}
127
+ as="div"
128
+ className="mr-auto flex flex-wrap items-center gap-2 empty:hidden"
129
+ />
130
+ <Button variant="secondary" onClick={() => methods.reset()}>
131
+ {t('core:common.reset')}
132
+ </Button>
133
+ <Button type="submit" loading={methods.formState.isSubmitting}>
134
+ {methods.formState.isSubmitting ? t('core:common.submitting') : t('core:common.submit')}
135
+ </Button>
136
+ </div>
137
+ </form>
138
+ </FormProvider>
139
+ );
140
+ }
@@ -0,0 +1,2 @@
1
+ export { TableRenderer } from './table/TableRenderer';
2
+ export type { TableRendererProps } from './table/TableRenderer';
@@ -0,0 +1,76 @@
1
+ import { ApiError, apiPost } from './http';
2
+ import { deriveApiBaseUrl } from './urls';
3
+ import { translate } from '../i18n/activeLocale';
4
+
5
+ export interface ActionResult {
6
+ success: boolean;
7
+ message?: string;
8
+ data?: any;
9
+ redirect?: string; // Redirect path if present
10
+ [key: string]: any; // Allow additional fields (like redirect)
11
+ }
12
+
13
+ async function runAction(
14
+ apiUrl: string,
15
+ actionName: string,
16
+ recordIds: string[],
17
+ formData: any,
18
+ isBulk: boolean,
19
+ ): Promise<ActionResult> {
20
+ try {
21
+ const result = await apiPost<any>(
22
+ `${apiUrl}/actions`,
23
+ {
24
+ action: actionName,
25
+ data: { recordIds, formData: formData || {} },
26
+ ...(isBulk ? { isBulk: true } : {}),
27
+ },
28
+ deriveApiBaseUrl(apiUrl),
29
+ );
30
+ return {
31
+ ...result,
32
+ success: result.success !== false,
33
+ message: result.message,
34
+ data: result.data,
35
+ redirect: result.redirect,
36
+ };
37
+ } catch (error: any) {
38
+ const fallback = isBulk ? translate('core:error.action_failed_bulk') : translate('core:error.action_failed');
39
+ return {
40
+ success: false,
41
+ message: error instanceof ApiError ? error.message : error?.message || fallback,
42
+ };
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Execute a single row action
48
+ * @param apiUrl - The base API URL for the resource (e.g., http://localhost:3001/api/users)
49
+ * @param actionName - The name of the action to execute
50
+ * @param recordId - The record ID (backend will populate the full record)
51
+ * @param formData - Optional form data from the action form
52
+ */
53
+ export function executeAction(
54
+ apiUrl: string,
55
+ actionName: string,
56
+ recordId: string,
57
+ formData?: any,
58
+ ): Promise<ActionResult> {
59
+ return runAction(apiUrl, actionName, [recordId], formData, false);
60
+ }
61
+
62
+ /**
63
+ * Execute a bulk action on multiple rows
64
+ * @param apiUrl - The base API URL for the resource (e.g., http://localhost:3001/api/users)
65
+ * @param actionName - The name of the action to execute
66
+ * @param recordIds - Array of record IDs (backend will populate the full records)
67
+ * @param formData - Optional form data from the action form
68
+ */
69
+ export function executeBulkAction(
70
+ apiUrl: string,
71
+ actionName: string,
72
+ recordIds: string[],
73
+ formData?: any,
74
+ ): Promise<ActionResult> {
75
+ return runAction(apiUrl, actionName, recordIds, formData, true);
76
+ }
@@ -0,0 +1,40 @@
1
+ import { AuthApiClient } from '../auth/authApiClient';
2
+ import { getActiveLocale } from '../i18n/activeLocale';
3
+
4
+ /**
5
+ * Fetch wrapper that automatically retries with a silent token refresh on 401.
6
+ * Auth tokens are carried by HTTP-only cookies — no Authorization header needed.
7
+ *
8
+ * Sends `X-KratosJs-Locale` so the server localizes labels/schemas/action
9
+ * messages to the admin's current UI locale.
10
+ */
11
+ export async function authenticatedFetch(
12
+ url: string,
13
+ options: RequestInit = {},
14
+ apiBaseUrl: string,
15
+ ): Promise<Response> {
16
+ const headers = new Headers({
17
+ 'Content-Type': 'application/json',
18
+ 'X-KratosJs-Locale': getActiveLocale(),
19
+ ...options.headers,
20
+ });
21
+
22
+ let response = await fetch(url, { ...options, headers, credentials: 'include' });
23
+
24
+ if (response.status === 401) {
25
+ const authClient = new AuthApiClient(apiBaseUrl);
26
+ const tokens = await authClient.refreshToken();
27
+
28
+ if (tokens) {
29
+ response = await fetch(url, { ...options, headers, credentials: 'include' });
30
+ } else {
31
+ return response;
32
+ }
33
+ }
34
+
35
+ if (response.ok && response.headers.get('X-KratosJs-Refresh-Badges')) {
36
+ window.dispatchEvent(new CustomEvent('kratosjs-refresh-badges'));
37
+ }
38
+
39
+ return response;
40
+ }
@@ -0,0 +1,66 @@
1
+ import { authenticatedFetch } from './authenticatedFetch';
2
+
3
+ export interface ExportOptions {
4
+ /** Export format, e.g. 'csv'. */
5
+ format: string;
6
+ /** Originating action name (used for server-side per-action authorization). */
7
+ action?: string;
8
+ /** Current table query (search/filters/sort) — exports all matching rows. */
9
+ query?: Record<string, any>;
10
+ /** Specific record ids — exports only these (bulk "export selected"). */
11
+ recordIds?: any[];
12
+ }
13
+
14
+ /**
15
+ * POST to a resource's `/export` endpoint and trigger a browser download of the
16
+ * returned file. Used by header/bulk actions marked with `exportsTo(format)`.
17
+ *
18
+ * @param resourceUrl - The resource endpoint (e.g. `${apiBaseUrl}/users`)
19
+ * @param apiBaseUrl - Panel API base (for token refresh)
20
+ */
21
+ export async function downloadExport(resourceUrl: string, apiBaseUrl: string, options: ExportOptions): Promise<void> {
22
+ const { format, action, query, recordIds } = options;
23
+ const url = `${resourceUrl.replace(/\/$/, '')}/export`;
24
+
25
+ const body: Record<string, any> = { format, ...(query || {}) };
26
+ if (action) {
27
+ body.action = action;
28
+ }
29
+ if (recordIds && recordIds.length > 0) {
30
+ body.recordIds = recordIds;
31
+ }
32
+
33
+ const response = await authenticatedFetch(url, { method: 'POST', body: JSON.stringify(body) }, apiBaseUrl);
34
+
35
+ if (!response.ok) {
36
+ let message = `Export failed (${response.status})`;
37
+ try {
38
+ const errorBody = await response.json();
39
+ message = errorBody.message || message;
40
+ } catch {
41
+ // response had no JSON body
42
+ }
43
+ throw new Error(message);
44
+ }
45
+
46
+ const blob = await response.blob();
47
+ const filename = parseContentDispositionFilename(response.headers.get('Content-Disposition')) || `export.${format}`;
48
+ triggerBrowserDownload(blob, filename);
49
+ }
50
+
51
+ function parseContentDispositionFilename(header: string | null): string | undefined {
52
+ if (!header) return undefined;
53
+ const match = /filename\*?=(?:UTF-8'')?["']?([^"';]+)["']?/i.exec(header);
54
+ return match ? decodeURIComponent(match[1]) : undefined;
55
+ }
56
+
57
+ function triggerBrowserDownload(blob: Blob, filename: string): void {
58
+ const objectUrl = URL.createObjectURL(blob);
59
+ const anchor = document.createElement('a');
60
+ anchor.href = objectUrl;
61
+ anchor.download = filename;
62
+ document.body.appendChild(anchor);
63
+ anchor.click();
64
+ anchor.remove();
65
+ URL.revokeObjectURL(objectUrl);
66
+ }
@@ -0,0 +1,58 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
2
+ import { ApiError, apiFetch, apiGet, apiPost } from './http';
3
+ import { mockFetch, restoreFetch } from '../test/mockFetch';
4
+
5
+ const API = 'http://api.test/admin';
6
+
7
+ describe('apiFetch', () => {
8
+ beforeEach(() => {
9
+ localStorage.clear();
10
+ });
11
+
12
+ afterEach(() => {
13
+ restoreFetch();
14
+ });
15
+
16
+ it('returns parsed JSON on success', async () => {
17
+ mockFetch([{ match: '/things', body: { hello: 'world' } }]);
18
+ const result = await apiGet<{ hello: string }>(`${API}/things`, API);
19
+ expect(result).toEqual({ hello: 'world' });
20
+ });
21
+
22
+ it('throws ApiError with the server message on failure', async () => {
23
+ mockFetch([{ match: '/things', status: 422, body: { message: 'Name is required' } }]);
24
+
25
+ const error = await apiPost(`${API}/things`, {}, API).catch(e => e);
26
+ expect(error).toBeInstanceOf(ApiError);
27
+ expect(error.status).toBe(422);
28
+ expect(error.message).toBe('Name is required');
29
+ expect(error.details).toEqual({ message: 'Name is required' });
30
+ });
31
+
32
+ it('uses a friendly message for 401s', async () => {
33
+ mockFetch([{ match: '/things', status: 401, body: { message: 'jwt expired' } }]);
34
+
35
+ const error = await apiGet(`${API}/things`, API).catch(e => e);
36
+ expect(error).toBeInstanceOf(ApiError);
37
+ expect(error.isUnauthorized).toBe(true);
38
+ expect(error.message).toBe('Unauthorized - Please login again');
39
+ });
40
+
41
+ it('tolerates empty response bodies', async () => {
42
+ mockFetch([]);
43
+ // Manually return empty 204-like response
44
+ restoreFetch();
45
+ const { vi } = await import('vitest');
46
+ vi.stubGlobal('fetch', async () => new Response('', { status: 200 }));
47
+
48
+ const result = await apiFetch(`${API}/things`, { apiBaseUrl: API });
49
+ expect(result).toBeNull();
50
+ });
51
+
52
+ it('posts JSON payloads', async () => {
53
+ const requests = mockFetch([{ match: '/things', body: { ok: true } }]);
54
+ await apiPost(`${API}/things`, { a: 1 }, API);
55
+ expect(requests[0].method).toBe('POST');
56
+ expect(requests[0].body).toEqual({ a: 1 });
57
+ });
58
+ });
@@ -0,0 +1,68 @@
1
+ import { authenticatedFetch } from './authenticatedFetch';
2
+
3
+ /**
4
+ * Typed error thrown by apiFetch for any non-2xx response.
5
+ * Carries the HTTP status and whatever the server put in the body so
6
+ * callers can map field-level validation errors or show the message.
7
+ */
8
+ export class ApiError extends Error {
9
+ readonly status: number;
10
+ readonly details?: unknown;
11
+
12
+ constructor(message: string, status: number, details?: unknown) {
13
+ super(message);
14
+ this.name = 'ApiError';
15
+ this.status = status;
16
+ this.details = details;
17
+ }
18
+
19
+ get isUnauthorized(): boolean {
20
+ return this.status === 401;
21
+ }
22
+ }
23
+
24
+ export interface ApiFetchOptions extends RequestInit {
25
+ /** Base URL of the panel API, used for token refresh on 401 */
26
+ apiBaseUrl: string;
27
+ }
28
+
29
+ /**
30
+ * JSON fetch with auth + token refresh + uniform error handling.
31
+ * Throws ApiError on any non-ok response, preserving the server message.
32
+ */
33
+ export async function apiFetch<T = unknown>(url: string, options: ApiFetchOptions): Promise<T> {
34
+ const { apiBaseUrl, ...init } = options;
35
+ const response = await authenticatedFetch(url, init, apiBaseUrl);
36
+
37
+ let body: any = null;
38
+ const text = await response.text();
39
+ if (text) {
40
+ try {
41
+ body = JSON.parse(text);
42
+ } catch {
43
+ body = text;
44
+ }
45
+ }
46
+
47
+ if (!response.ok) {
48
+ const message =
49
+ response.status === 401
50
+ ? 'Unauthorized - Please login again'
51
+ : (body && typeof body === 'object' && (body.message || body.error)) ||
52
+ response.statusText ||
53
+ `Request failed with status ${response.status}`;
54
+ throw new ApiError(message, response.status, body);
55
+ }
56
+
57
+ return body as T;
58
+ }
59
+
60
+ /** Convenience JSON POST */
61
+ export function apiPost<T = unknown>(url: string, payload: unknown, apiBaseUrl: string): Promise<T> {
62
+ return apiFetch<T>(url, { method: 'POST', body: JSON.stringify(payload), apiBaseUrl });
63
+ }
64
+
65
+ /** Convenience JSON GET */
66
+ export function apiGet<T = unknown>(url: string, apiBaseUrl: string): Promise<T> {
67
+ return apiFetch<T>(url, { apiBaseUrl });
68
+ }