@vendure/dashboard 3.4.3-master-202509260228 → 3.5.0-minor-202510012036

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 (295) hide show
  1. package/README.md +4 -0
  2. package/dist/plugin/api/api-extensions.js +11 -14
  3. package/dist/plugin/api/metrics.resolver.d.ts +2 -2
  4. package/dist/plugin/api/metrics.resolver.js +2 -2
  5. package/dist/plugin/config/metrics-strategies.d.ts +9 -9
  6. package/dist/plugin/config/metrics-strategies.js +6 -6
  7. package/dist/plugin/constants.d.ts +2 -0
  8. package/dist/plugin/constants.js +3 -1
  9. package/dist/plugin/dashboard.plugin.js +13 -0
  10. package/dist/plugin/service/metrics.service.d.ts +3 -3
  11. package/dist/plugin/service/metrics.service.js +37 -53
  12. package/dist/plugin/types.d.ts +9 -12
  13. package/dist/plugin/types.js +7 -11
  14. package/dist/vite/vite-plugin-config.js +13 -9
  15. package/dist/vite/vite-plugin-translations.d.ts +22 -0
  16. package/dist/vite/vite-plugin-translations.js +66 -0
  17. package/dist/vite/vite-plugin-vendure-dashboard.js +10 -8
  18. package/lingui.config.js +25 -2
  19. package/package.json +159 -156
  20. package/src/app/app-providers.tsx +0 -4
  21. package/src/app/common/delete-bulk-action.tsx +6 -5
  22. package/src/app/common/duplicate-bulk-action.tsx +4 -5
  23. package/src/app/common/duplicate-entity-dialog.tsx +1 -1
  24. package/src/app/common/set-document-direction.ts +7 -0
  25. package/src/app/main.tsx +50 -17
  26. package/src/app/routes/_authenticated/_administrators/administrators.tsx +8 -6
  27. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +17 -6
  28. package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +2 -2
  29. package/src/app/routes/_authenticated/_assets/assets.tsx +1 -1
  30. package/src/app/routes/_authenticated/_assets/assets_.$id.tsx +4 -4
  31. package/src/app/routes/_authenticated/_assets/components/asset-bulk-actions.tsx +8 -6
  32. package/src/app/routes/_authenticated/_assets/components/asset-tag-filter.tsx +1 -1
  33. package/src/app/routes/_authenticated/_assets/components/asset-tags-editor.tsx +1 -1
  34. package/src/app/routes/_authenticated/_assets/components/manage-tags-dialog.tsx +3 -8
  35. package/src/app/routes/_authenticated/_channels/channels.tsx +3 -6
  36. package/src/app/routes/_authenticated/_channels/channels_.$id.tsx +5 -5
  37. package/src/app/routes/_authenticated/_collections/collections.tsx +10 -6
  38. package/src/app/routes/_authenticated/_collections/collections_.$id.tsx +16 -5
  39. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +1 -1
  40. package/src/app/routes/_authenticated/_collections/components/collection-contents-sheet.tsx +1 -1
  41. package/src/app/routes/_authenticated/_collections/components/move-collections-dialog.tsx +6 -6
  42. package/src/app/routes/_authenticated/_countries/countries.graphql.ts +2 -0
  43. package/src/app/routes/_authenticated/_countries/countries.tsx +2 -3
  44. package/src/app/routes/_authenticated/_countries/countries_.$id.tsx +4 -4
  45. package/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-sheet.tsx +1 -1
  46. package/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-table.tsx +4 -4
  47. package/src/app/routes/_authenticated/_customer-groups/customer-groups.tsx +2 -4
  48. package/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx +13 -6
  49. package/src/app/routes/_authenticated/_customers/components/customer-address-card.tsx +8 -8
  50. package/src/app/routes/_authenticated/_customers/components/customer-address-form.tsx +3 -3
  51. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-container.tsx +1 -1
  52. package/src/app/routes/_authenticated/_customers/components/customer-history/customer-history-utils.tsx +1 -1
  53. package/src/app/routes/_authenticated/_customers/components/customer-history/default-customer-history-components.tsx +1 -1
  54. package/src/app/routes/_authenticated/_customers/components/customer-history/use-customer-history.ts +1 -1
  55. package/src/app/routes/_authenticated/_customers/components/customer-status-badge.tsx +1 -1
  56. package/src/app/routes/_authenticated/_customers/customers.graphql.ts +4 -0
  57. package/src/app/routes/_authenticated/_customers/customers.tsx +23 -11
  58. package/src/app/routes/_authenticated/_customers/customers_.$id.tsx +10 -8
  59. package/src/app/routes/_authenticated/_facets/components/edit-facet-value.tsx +1 -1
  60. package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +6 -5
  61. package/src/app/routes/_authenticated/_facets/components/facet-values-sheet.tsx +1 -1
  62. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +1 -1
  63. package/src/app/routes/_authenticated/_facets/facets.tsx +5 -5
  64. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +7 -5
  65. package/src/app/routes/_authenticated/_facets/facets_.$id.tsx +18 -6
  66. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +5 -5
  67. package/src/app/routes/_authenticated/_orders/components/add-manual-payment-dialog.tsx +19 -21
  68. package/src/app/routes/_authenticated/_orders/components/customer-address-selector.tsx +1 -1
  69. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +22 -22
  70. package/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx +6 -6
  71. package/src/app/routes/_authenticated/_orders/components/fulfillment-details.tsx +15 -9
  72. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +1 -1
  73. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +11 -9
  74. package/src/app/routes/_authenticated/_orders/components/order-history/default-order-history-components.tsx +1 -1
  75. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-container.tsx +1 -1
  76. package/src/app/routes/_authenticated/_orders/components/order-history/order-history-utils.tsx +1 -1
  77. package/src/app/routes/_authenticated/_orders/components/order-history/use-order-history.ts +1 -1
  78. package/src/app/routes/_authenticated/_orders/components/order-line-custom-fields-form.tsx +1 -1
  79. package/src/app/routes/_authenticated/_orders/components/order-modification-preview-dialog.tsx +4 -4
  80. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +1 -1
  81. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +27 -27
  82. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +2 -2
  83. package/src/app/routes/_authenticated/_orders/components/order-tax-summary.tsx +1 -1
  84. package/src/app/routes/_authenticated/_orders/components/payment-details.tsx +26 -20
  85. package/src/app/routes/_authenticated/_orders/components/seller-orders-card.tsx +3 -1
  86. package/src/app/routes/_authenticated/_orders/components/settle-refund-dialog.tsx +6 -6
  87. package/src/app/routes/_authenticated/_orders/components/shipping-method-selector.tsx +1 -1
  88. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +1 -1
  89. package/src/app/routes/_authenticated/_orders/components/use-transition-order-to-state.tsx +3 -2
  90. package/src/app/routes/_authenticated/_orders/orders.tsx +5 -9
  91. package/src/app/routes/_authenticated/_orders/orders_.$aggregateOrderId_.seller-orders.$sellerOrderId.tsx +1 -1
  92. package/src/app/routes/_authenticated/_orders/orders_.$id.tsx +1 -1
  93. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +4 -4
  94. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +17 -17
  95. package/src/app/routes/_authenticated/_orders/utils/order-detail-loaders.tsx +1 -1
  96. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +5 -6
  97. package/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx +13 -6
  98. package/src/app/routes/_authenticated/_product-variants/components/product-variant-bulk-actions.tsx +1 -1
  99. package/src/app/routes/_authenticated/_product-variants/components/variant-price-detail.tsx +1 -1
  100. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +10 -0
  101. package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +9 -2
  102. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +13 -6
  103. package/src/app/routes/_authenticated/_products/components/add-option-group-dialog.tsx +5 -5
  104. package/src/app/routes/_authenticated/_products/components/add-product-variant-dialog.tsx +5 -5
  105. package/src/app/routes/_authenticated/_products/components/assign-facet-values-dialog.tsx +5 -4
  106. package/src/app/routes/_authenticated/_products/components/create-product-options-dialog.tsx +9 -12
  107. package/src/app/routes/_authenticated/_products/components/create-product-variants-dialog.tsx +1 -1
  108. package/src/app/routes/_authenticated/_products/components/create-product-variants.tsx +4 -4
  109. package/src/app/routes/_authenticated/_products/components/option-groups-editor.tsx +1 -1
  110. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +1 -1
  111. package/src/app/routes/_authenticated/_products/components/product-option-group-badge.tsx +19 -0
  112. package/src/app/routes/_authenticated/_products/components/product-option-select.tsx +3 -3
  113. package/src/app/routes/_authenticated/_products/components/product-options-table.tsx +114 -0
  114. package/src/app/routes/_authenticated/_products/product-option-groups.graphql.ts +103 -0
  115. package/src/app/routes/_authenticated/_products/products.graphql.ts +44 -32
  116. package/src/app/routes/_authenticated/_products/products.tsx +34 -5
  117. package/src/app/routes/_authenticated/_products/products_.$id.tsx +29 -12
  118. package/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx +11 -11
  119. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$id.tsx +177 -0
  120. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +208 -0
  121. package/src/app/routes/_authenticated/_profile/profile.tsx +4 -4
  122. package/src/app/routes/_authenticated/_promotions/promotions.tsx +2 -4
  123. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +16 -9
  124. package/src/app/routes/_authenticated/_roles/components/permissions-table-grid.tsx +1 -1
  125. package/src/app/routes/_authenticated/_roles/roles.tsx +3 -6
  126. package/src/app/routes/_authenticated/_roles/roles_.$id.tsx +4 -6
  127. package/src/app/routes/_authenticated/_sellers/sellers.tsx +3 -4
  128. package/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx +4 -4
  129. package/src/app/routes/_authenticated/_shipping-methods/components/price-display.tsx +5 -5
  130. package/src/app/routes/_authenticated/_shipping-methods/components/shipping-method-test-result-wrapper.tsx +1 -1
  131. package/src/app/routes/_authenticated/_shipping-methods/components/test-address-form.tsx +11 -11
  132. package/src/app/routes/_authenticated/_shipping-methods/components/test-order-builder.tsx +1 -1
  133. package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods-result.tsx +8 -8
  134. package/src/app/routes/_authenticated/_shipping-methods/components/test-shipping-methods-sheet.tsx +1 -1
  135. package/src/app/routes/_authenticated/_shipping-methods/components/test-single-method-result.tsx +8 -8
  136. package/src/app/routes/_authenticated/_shipping-methods/components/test-single-shipping-method-sheet.tsx +4 -4
  137. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +2 -3
  138. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +2 -2
  139. package/src/app/routes/_authenticated/_stock-locations/stock-locations.tsx +3 -4
  140. package/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx +13 -6
  141. package/src/app/routes/_authenticated/_system/healthchecks.tsx +10 -4
  142. package/src/app/routes/_authenticated/_system/job-queue.tsx +10 -13
  143. package/src/app/routes/_authenticated/_system/scheduled-tasks.tsx +18 -16
  144. package/src/app/routes/_authenticated/_tax-categories/tax-categories.tsx +2 -4
  145. package/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx +13 -6
  146. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +8 -12
  147. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +6 -4
  148. package/src/app/routes/_authenticated/_zones/components/zone-countries-sheet.tsx +4 -1
  149. package/src/app/routes/_authenticated/_zones/zones.tsx +4 -4
  150. package/src/app/routes/_authenticated/_zones/zones_.$id.tsx +8 -5
  151. package/src/app/routes/_authenticated/index.tsx +46 -25
  152. package/src/app/styles.css +4 -0
  153. package/src/i18n/common-strings.ts +111 -0
  154. package/src/i18n/locales/ar.po +4777 -0
  155. package/src/i18n/locales/cs.po +4777 -0
  156. package/src/i18n/locales/de.po +4299 -1101
  157. package/src/i18n/locales/en.po +3857 -659
  158. package/src/i18n/locales/es.po +4777 -0
  159. package/src/i18n/locales/fa.po +4777 -0
  160. package/src/i18n/locales/fr.po +4777 -0
  161. package/src/i18n/locales/he.po +4777 -0
  162. package/src/i18n/locales/hr.po +4777 -0
  163. package/src/i18n/locales/it.po +4777 -0
  164. package/src/i18n/locales/ja.po +4777 -0
  165. package/src/i18n/locales/ko.po +4628 -0
  166. package/src/i18n/locales/nb.po +4777 -0
  167. package/src/i18n/locales/ne.po +4777 -0
  168. package/src/i18n/locales/nl.po +4628 -0
  169. package/src/i18n/locales/pl.po +4777 -0
  170. package/src/i18n/locales/pt_BR.po +4777 -0
  171. package/src/i18n/locales/pt_PT.po +4777 -0
  172. package/src/i18n/locales/ru.po +4777 -0
  173. package/src/i18n/locales/sv.po +4777 -0
  174. package/src/i18n/locales/tr.po +4777 -0
  175. package/src/i18n/locales/uk.po +4777 -0
  176. package/src/i18n/locales/zh_Hans.po +4777 -0
  177. package/src/i18n/locales/zh_Hant.po +4777 -0
  178. package/src/lib/components/data-display/json.tsx +16 -1
  179. package/src/lib/components/data-input/combination-mode-input.tsx +1 -1
  180. package/src/lib/components/data-input/custom-field-list-input.tsx +11 -7
  181. package/src/lib/components/data-input/customer-group-input.tsx +27 -33
  182. package/src/lib/components/data-input/datetime-input.tsx +40 -1
  183. package/src/lib/components/data-input/default-relation-input.tsx +5 -4
  184. package/src/lib/components/data-input/index.ts +3 -0
  185. package/src/lib/components/data-input/product-multi-selector-input.tsx +14 -14
  186. package/src/lib/components/data-input/relation-selector.tsx +1 -1
  187. package/src/lib/components/data-input/select-with-options.tsx +1 -1
  188. package/src/lib/components/data-input/slug-input.tsx +290 -0
  189. package/src/lib/components/data-table/add-filter-menu.tsx +17 -10
  190. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +45 -8
  191. package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
  192. package/src/lib/components/data-table/data-table-column-header.tsx +13 -8
  193. package/src/lib/components/data-table/data-table-context.tsx +91 -0
  194. package/src/lib/components/data-table/data-table-faceted-filter.tsx +2 -1
  195. package/src/lib/components/data-table/data-table-filter-badge.tsx +9 -5
  196. package/src/lib/components/data-table/data-table-filter-dialog.tsx +1 -1
  197. package/src/lib/components/data-table/data-table-utils.ts +21 -4
  198. package/src/lib/components/data-table/data-table-view-options.tsx +21 -10
  199. package/src/lib/components/data-table/data-table.tsx +146 -94
  200. package/src/lib/components/data-table/filters/data-table-boolean-filter.tsx +4 -4
  201. package/src/lib/components/data-table/global-views-bar.tsx +97 -0
  202. package/src/lib/components/data-table/global-views-sheet.tsx +11 -0
  203. package/src/lib/components/data-table/human-readable-operator.tsx +1 -1
  204. package/src/lib/components/data-table/manage-global-views-button.tsx +26 -0
  205. package/src/lib/components/data-table/my-views-button.tsx +47 -0
  206. package/src/lib/components/data-table/refresh-button.tsx +12 -3
  207. package/src/lib/components/data-table/save-view-button.tsx +41 -0
  208. package/src/lib/components/data-table/save-view-dialog.tsx +113 -0
  209. package/src/lib/components/data-table/use-generated-columns.tsx +13 -8
  210. package/src/lib/components/data-table/user-views-sheet.tsx +11 -0
  211. package/src/lib/components/data-table/views-sheet.tsx +305 -0
  212. package/src/lib/components/date-range-picker.tsx +186 -0
  213. package/src/lib/components/layout/app-sidebar.tsx +3 -1
  214. package/src/lib/components/layout/channel-switcher.tsx +8 -10
  215. package/src/lib/components/layout/dev-mode-indicator.tsx +1 -1
  216. package/src/lib/components/layout/generated-breadcrumbs.tsx +10 -8
  217. package/src/lib/components/layout/language-dialog.tsx +34 -13
  218. package/src/lib/components/layout/manage-languages-dialog.tsx +1 -1
  219. package/src/lib/components/layout/nav-main.tsx +23 -13
  220. package/src/lib/components/layout/nav-user.tsx +19 -23
  221. package/src/lib/components/login/login-form.tsx +1 -1
  222. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +4 -4
  223. package/src/lib/components/shared/asset/asset-focal-point-editor.tsx +1 -1
  224. package/src/lib/components/shared/asset/asset-gallery.tsx +15 -14
  225. package/src/lib/components/shared/assign-to-channel-bulk-action.tsx +11 -11
  226. package/src/lib/components/shared/assign-to-channel-dialog.tsx +6 -5
  227. package/src/lib/components/shared/channel-code-label.tsx +1 -1
  228. package/src/lib/components/shared/channel-selector.tsx +4 -4
  229. package/src/lib/components/shared/configurable-operation-multi-selector.tsx +16 -14
  230. package/src/lib/components/shared/configurable-operation-selector.tsx +1 -1
  231. package/src/lib/components/shared/confirmation-dialog.tsx +8 -8
  232. package/src/lib/components/shared/country-selector.tsx +1 -1
  233. package/src/lib/components/shared/currency-selector.tsx +4 -4
  234. package/src/lib/components/shared/custom-fields-form.tsx +8 -24
  235. package/src/lib/components/shared/customer-address-form.tsx +3 -3
  236. package/src/lib/components/shared/customer-group-selector.tsx +1 -1
  237. package/src/lib/components/shared/customer-selector.tsx +1 -1
  238. package/src/lib/components/shared/error-page.tsx +1 -1
  239. package/src/lib/components/shared/facet-value-selector.tsx +10 -10
  240. package/src/lib/components/shared/history-timeline/history-note-checkbox.tsx +1 -1
  241. package/src/lib/components/shared/history-timeline/history-note-editor.tsx +1 -1
  242. package/src/lib/components/shared/history-timeline/history-note-entry.tsx +1 -1
  243. package/src/lib/components/shared/language-selector.tsx +4 -4
  244. package/src/lib/components/shared/navigation-confirmation.tsx +1 -1
  245. package/src/lib/components/shared/paginated-list-data-table.tsx +64 -34
  246. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +6 -5
  247. package/src/lib/components/shared/rich-text-editor/image-dialog.tsx +1 -1
  248. package/src/lib/components/shared/rich-text-editor/link-dialog.tsx +1 -1
  249. package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +1 -1
  250. package/src/lib/components/shared/rich-text-editor/table-edit-icons.tsx +1 -1
  251. package/src/lib/components/shared/role-code-label.tsx +1 -1
  252. package/src/lib/components/shared/role-selector.tsx +4 -4
  253. package/src/lib/components/shared/seller-selector.tsx +1 -1
  254. package/src/lib/components/shared/stock-level-label.tsx +3 -5
  255. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +3 -1
  256. package/src/lib/components/shared/tax-category-selector.tsx +1 -1
  257. package/src/lib/components/shared/translatable-form-field.tsx +15 -15
  258. package/src/lib/components/shared/zone-selector.tsx +1 -1
  259. package/src/lib/components/ui/button.tsx +1 -1
  260. package/src/lib/framework/dashboard-widget/base-widget.tsx +11 -9
  261. package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +35 -6
  262. package/src/lib/framework/dashboard-widget/metrics-widget/index.tsx +18 -12
  263. package/src/lib/framework/dashboard-widget/metrics-widget/metrics-widget.graphql.ts +9 -3
  264. package/src/lib/framework/dashboard-widget/orders-summary/index.tsx +26 -79
  265. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +35 -0
  266. package/src/lib/framework/defaults.ts +34 -63
  267. package/src/lib/framework/document-introspection/add-custom-fields.spec.ts +319 -9
  268. package/src/lib/framework/document-introspection/add-custom-fields.ts +60 -31
  269. package/src/lib/framework/document-introspection/get-document-structure.spec.ts +1 -159
  270. package/src/lib/framework/document-introspection/include-only-selected-list-fields.spec.ts +1840 -0
  271. package/src/lib/framework/document-introspection/include-only-selected-list-fields.ts +940 -0
  272. package/src/lib/framework/document-introspection/testing-utils.ts +161 -0
  273. package/src/lib/framework/extension-api/display-component-extensions.tsx +2 -0
  274. package/src/lib/framework/extension-api/types/data-table.ts +62 -4
  275. package/src/lib/framework/extension-api/types/navigation.ts +16 -0
  276. package/src/lib/framework/form-engine/utils.ts +34 -0
  277. package/src/lib/framework/layout-engine/page-layout.tsx +36 -36
  278. package/src/lib/framework/page/detail-page.tsx +10 -10
  279. package/src/lib/framework/page/list-page.tsx +289 -4
  280. package/src/lib/framework/page/use-extended-router.tsx +101 -34
  281. package/src/lib/graphql/api.ts +6 -2
  282. package/src/lib/graphql/graphql-env.d.ts +38 -26
  283. package/src/lib/hooks/use-display-locale.ts +40 -0
  284. package/src/lib/hooks/use-dynamic-translations.ts +46 -0
  285. package/src/lib/hooks/use-extended-detail-query.ts +1 -1
  286. package/src/lib/hooks/use-extended-list-query.ts +6 -1
  287. package/src/lib/hooks/use-local-format.ts +15 -1
  288. package/src/lib/hooks/use-saved-views.ts +230 -0
  289. package/src/lib/hooks/use-ui-language-loader.ts +30 -0
  290. package/src/lib/index.ts +15 -0
  291. package/src/lib/lib/load-i18n-messages.ts +17 -0
  292. package/src/lib/lib/trans.tsx +15 -11
  293. package/src/lib/providers/i18n-provider.tsx +7 -14
  294. package/src/lib/types/saved-views.ts +39 -0
  295. package/src/lib/utils/saved-views-utils.ts +40 -0
package/src/app/main.tsx CHANGED
@@ -7,40 +7,66 @@ import { useExtendedRouter } from '@/vdb/framework/page/use-extended-router.js';
7
7
  import { useAuth } from '@/vdb/hooks/use-auth.js';
8
8
  import { useServerConfig } from '@/vdb/hooks/use-server-config.js';
9
9
  import { defaultLocale, dynamicActivate } from '@/vdb/providers/i18n-provider.js';
10
- import { createRouter, RouterProvider } from '@tanstack/react-router';
10
+ import { AnyRoute, createRouter, RouterOptions, RouterProvider } from '@tanstack/react-router';
11
11
  import React, { useEffect } from 'react';
12
12
  import ReactDOM from 'react-dom/client';
13
13
 
14
+ import { useDisplayLocale } from '@/vdb/hooks/use-display-locale.js';
15
+ import { useUiLanguageLoader } from '@/vdb/hooks/use-ui-language-loader.js';
16
+ import { useUserSettings } from '@/vdb/hooks/use-user-settings.js';
17
+ import { DirectionProvider } from '@radix-ui/react-direction';
18
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
14
19
  import { AppProviders, queryClient } from './app-providers.js';
20
+ import { setDocumentDirection } from './common/set-document-direction.js';
15
21
  import { routeTree } from './routeTree.gen.js';
16
22
  import './styles.css';
17
23
 
18
- // Register things for typesafety
19
- declare module '@tanstack/react-router' {
20
- interface Register {
21
- router: typeof router;
22
- }
23
- }
24
+ const processedBaseUrl = (() => {
25
+ const baseUrl = import.meta.env.BASE_URL;
26
+ if (!baseUrl || baseUrl === '/') return undefined;
27
+ // Ensure leading slash, remove trailing slash
28
+ const normalized = baseUrl.startsWith('/') ? baseUrl : '/' + baseUrl;
29
+ return normalized.endsWith('/') ? normalized.slice(0, -1) : normalized;
30
+ })();
24
31
 
25
- export const router = createRouter({
26
- routeTree,
27
- defaultPreload: 'intent',
32
+ const routerOptions: RouterOptions<AnyRoute, any> = {
33
+ defaultPreload: 'intent' as const,
28
34
  scrollRestoration: true,
29
- // In case the dashboard gets served from a subpath, we need to set the basepath based on the environment variable
30
- ...(import.meta.env.BASE_URL ? { basepath: import.meta.env.BASE_URL } : {}),
35
+ basepath: processedBaseUrl,
31
36
  context: {
32
37
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
33
38
  auth: undefined!, // This will be set after we wrap the app in an AuthProvider
34
39
  queryClient,
35
40
  },
36
41
  defaultErrorComponent: ({ error }: { error: Error }) => <div>Uh Oh!!! {error.message}</div>,
42
+ };
43
+
44
+ // Create a type-only router instance for TypeScript type registration
45
+ // The actual runtime router is created in InnerApp component
46
+ const typeRouter = createRouter({
47
+ ...routerOptions,
48
+ routeTree,
37
49
  });
38
50
 
51
+ // Register the router type for TypeScript
52
+ declare module '@tanstack/react-router' {
53
+ interface Register {
54
+ router: typeof typeRouter;
55
+ }
56
+ }
57
+
39
58
  function InnerApp() {
40
59
  const auth = useAuth();
41
- const extendedRouter = useExtendedRouter(router);
60
+ const router = useExtendedRouter(routeTree, routerOptions);
42
61
  const serverConfig = useServerConfig();
62
+ const { isRTL } = useDisplayLocale();
43
63
  const [hasSetCustomFieldsMap, setHasSetCustomFieldsMap] = React.useState(false);
64
+ const { settings } = useUserSettings();
65
+ const { loadAndActivateLocale } = useUiLanguageLoader();
66
+
67
+ useEffect(() => {
68
+ void loadAndActivateLocale(settings.displayLanguage);
69
+ }, [settings.displayLanguage]);
44
70
 
45
71
  useEffect(() => {
46
72
  if (!serverConfig) {
@@ -50,11 +76,18 @@ function InnerApp() {
50
76
  setHasSetCustomFieldsMap(true);
51
77
  }, [serverConfig?.entityCustomFields.length]);
52
78
 
79
+ useEffect(() => {
80
+ setDocumentDirection(isRTL ? 'rtl' : 'ltr');
81
+ }, [isRTL]);
82
+
53
83
  return (
54
84
  <>
55
- {(hasSetCustomFieldsMap || auth.status === 'unauthenticated') && (
56
- <RouterProvider router={extendedRouter} context={{ auth, queryClient }} />
57
- )}
85
+ <DirectionProvider dir={isRTL ? 'rtl' : 'ltr'}>
86
+ {(hasSetCustomFieldsMap || auth.status === 'unauthenticated') && (
87
+ <RouterProvider router={router} context={{ auth, queryClient }} />
88
+ )}
89
+ {settings.devMode ? <ReactQueryDevtools /> : null}
90
+ </DirectionProvider>
58
91
  </>
59
92
  );
60
93
  }
@@ -64,7 +97,7 @@ function App() {
64
97
  const { extensionsLoaded } = useDashboardExtensions();
65
98
  useEffect(() => {
66
99
  // With this method we dynamically load the catalogs
67
- dynamicActivate(defaultLocale, () => {
100
+ void dynamicActivate(defaultLocale, () => {
68
101
  setI18nLoaded(true);
69
102
  });
70
103
  registerDefaults();
@@ -5,7 +5,7 @@ import { Badge } from '@/vdb/components/ui/badge.js';
5
5
  import { Button } from '@/vdb/components/ui/button.js';
6
6
  import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
7
7
  import { ListPage } from '@/vdb/framework/page/list-page.js';
8
- import { Trans } from '@/vdb/lib/trans.js';
8
+ import { Trans } from '@lingui/react/macro';
9
9
  import { createFileRoute, Link } from '@tanstack/react-router';
10
10
  import { PlusIcon } from 'lucide-react';
11
11
  import { administratorListDocument } from './administrators.graphql.js';
@@ -20,7 +20,7 @@ function AdministratorListPage() {
20
20
  return (
21
21
  <ListPage
22
22
  pageId="administrator-list"
23
- title="Administrators"
23
+ title={<Trans>Administrators</Trans>}
24
24
  listQuery={administratorListDocument}
25
25
  route={Route}
26
26
  onSearchTermChange={searchTerm => {
@@ -32,7 +32,8 @@ function AdministratorListPage() {
32
32
  }}
33
33
  additionalColumns={{
34
34
  name: {
35
- header: 'Name',
35
+ meta: { dependencies: ['id', 'firstName', 'lastName'] },
36
+ header: () => <Trans>Name</Trans>,
36
37
  cell: ({ row }) => (
37
38
  <DetailPageButton
38
39
  id={row.original.id}
@@ -41,11 +42,12 @@ function AdministratorListPage() {
41
42
  ),
42
43
  },
43
44
  roles: {
44
- header: 'Roles',
45
+ meta: { dependencies: ['user'] },
46
+ header: () => <Trans>Roles</Trans>,
45
47
  cell: ({ row }) => {
46
48
  return (
47
49
  <div className="flex flex-wrap gap-2">
48
- {row.original.user.roles.map(role => {
50
+ {row.original.user?.roles.map(role => {
49
51
  return (
50
52
  <Badge variant="secondary" key={role.id}>
51
53
  <RoleCodeLabel code={role.code} />
@@ -59,7 +61,6 @@ function AdministratorListPage() {
59
61
  }}
60
62
  customizeColumns={{
61
63
  emailAddress: {
62
- id: 'Identifier',
63
64
  header: () => <Trans>Identifier</Trans>,
64
65
  cell: ({ row }) => {
65
66
  return <div>{row.original.emailAddress}</div>;
@@ -68,6 +69,7 @@ function AdministratorListPage() {
68
69
  }}
69
70
  defaultVisibility={{
70
71
  emailAddress: true,
72
+ name: true,
71
73
  }}
72
74
  defaultColumnOrder={['name', 'emailAddress', 'roles']}
73
75
  bulkActions={[
@@ -16,7 +16,7 @@ import {
16
16
  } from '@/vdb/framework/layout-engine/page-layout.js';
17
17
  import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
18
18
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
19
- import { Trans, useLingui } from '@/vdb/lib/trans.js';
19
+ import { Trans, useLingui } from '@lingui/react/macro';
20
20
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
21
21
  import { toast } from 'sonner';
22
22
  import {
@@ -48,7 +48,7 @@ function AdministratorDetailPage() {
48
48
  const params = Route.useParams();
49
49
  const navigate = useNavigate();
50
50
  const creatingNewEntity = params.id === NEW_ENTITY_PATH;
51
- const { i18n } = useLingui();
51
+ const { t } = useLingui();
52
52
 
53
53
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
54
54
  pageId,
@@ -74,16 +74,27 @@ function AdministratorDetailPage() {
74
74
  },
75
75
  params: { id: params.id },
76
76
  onSuccess: async data => {
77
- toast(i18n.t(creatingNewEntity ? 'Successfully created administrator' : 'Successfully updated administrator'));
77
+ toast(
78
+ i18n.t(
79
+ creatingNewEntity
80
+ ? 'Successfully created administrator'
81
+ : 'Successfully updated administrator',
82
+ ),
83
+ );
78
84
  resetForm();
79
85
  if (creatingNewEntity) {
80
86
  await navigate({ to: `../$id`, params: { id: data.id } });
81
87
  }
82
88
  },
83
89
  onError: err => {
84
- toast(i18n.t(creatingNewEntity ? 'Failed to create administrator' : 'Failed to update administrator'), {
85
- description: err instanceof Error ? err.message : 'Unknown error',
86
- });
90
+ toast(
91
+ i18n.t(
92
+ creatingNewEntity ? 'Failed to create administrator' : 'Failed to update administrator',
93
+ ),
94
+ {
95
+ description: err instanceof Error ? err.message : 'Unknown error',
96
+ },
97
+ );
87
98
  },
88
99
  });
89
100
 
@@ -5,7 +5,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/vdb/
5
5
  import { api } from '@/vdb/graphql/api.js';
6
6
  import { graphql } from '@/vdb/graphql/graphql.js';
7
7
  import { useGroupedPermissions } from '@/vdb/hooks/use-grouped-permissions.js';
8
- import { useLingui } from '@/vdb/lib/trans.js';
8
+ import { useLingui } from '@lingui/react/macro';
9
9
  import { useQuery } from '@tanstack/react-query';
10
10
 
11
11
  const rolesByIdDocument = graphql(`
@@ -29,7 +29,7 @@ interface RolePermissionsDisplayProps {
29
29
  }
30
30
 
31
31
  export function RolePermissionsDisplay({ value = [] }: Readonly<RolePermissionsDisplayProps>) {
32
- const { i18n } = useLingui();
32
+ const { t } = useLingui();
33
33
  const groupedPermissions = useGroupedPermissions();
34
34
 
35
35
  const { data } = useQuery({
@@ -1,6 +1,6 @@
1
1
  import { AssetGallery } from '@/vdb/components/shared/asset/asset-gallery.js';
2
2
  import { Page, PageBlock, PageTitle } from '@/vdb/framework/layout-engine/page-layout.js';
3
- import { Trans } from '@/vdb/lib/trans.js';
3
+ import { Trans } from '@lingui/react/macro';
4
4
  import { createFileRoute } from '@tanstack/react-router';
5
5
  import { DeleteAssetsBulkAction } from './components/asset-bulk-actions.js';
6
6
 
@@ -18,7 +18,7 @@ import {
18
18
  } from '@/vdb/framework/layout-engine/page-layout.js';
19
19
  import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
20
20
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
21
- import { Trans, useLingui } from '@/vdb/lib/trans.js';
21
+ import { Trans, useLingui } from '@lingui/react/macro';
22
22
  import { createFileRoute } from '@tanstack/react-router';
23
23
  import { FocusIcon } from 'lucide-react';
24
24
  import { useRef, useState } from 'react';
@@ -45,7 +45,7 @@ export const Route = createFileRoute('/_authenticated/_assets/assets_/$id')({
45
45
 
46
46
  function AssetDetailPage() {
47
47
  const params = Route.useParams();
48
- const { i18n } = useLingui();
48
+ const { t } = useLingui();
49
49
 
50
50
  const imageRef = useRef<HTMLImageElement>(null);
51
51
  const [size, setSize] = useState<PreviewPreset>('medium');
@@ -67,11 +67,11 @@ function AssetDetailPage() {
67
67
  },
68
68
  params: { id: params.id },
69
69
  onSuccess: async () => {
70
- toast(i18n.t('Successfully updated asset'));
70
+ toast(t`Successfully updated asset`);
71
71
  form.reset(form.getValues());
72
72
  },
73
73
  onError: err => {
74
- toast(i18n.t('Failed to update asset'), {
74
+ toast(t`Failed to update asset`, {
75
75
  description: err instanceof Error ? err.message : 'Unknown error',
76
76
  });
77
77
  },
@@ -6,7 +6,7 @@ import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-
6
6
  import { api } from '@/vdb/graphql/api.js';
7
7
  import { AssetFragment } from '@/vdb/graphql/fragments.js';
8
8
  import { ResultOf } from '@/vdb/graphql/graphql.js';
9
- import { Trans, useLingui } from '@/vdb/lib/trans.js';
9
+ import { Trans, useLingui } from '@lingui/react/macro';
10
10
  import { deleteAssetsDocument } from '../assets.graphql.js';
11
11
 
12
12
  export const DeleteAssetsBulkAction = ({
@@ -16,19 +16,21 @@ export const DeleteAssetsBulkAction = ({
16
16
  selection: AssetFragment[];
17
17
  refetch: () => void;
18
18
  }) => {
19
- const { i18n } = useLingui();
19
+ const { t } = useLingui();
20
+ const selectionLength = selection.length;
20
21
  const { mutate } = useMutation({
21
22
  mutationFn: api.mutate(deleteAssetsDocument),
22
23
  onSuccess: (result: ResultOf<typeof deleteAssetsDocument>) => {
23
24
  if (result.deleteAssets.result === 'DELETED') {
24
- toast.success(i18n.t(`Deleted ${selection.length} assets`));
25
+ toast.success(t`Deleted ${selectionLength} assets`);
25
26
  } else {
26
- toast.error(i18n.t(`Failed to delete assets: ${result.deleteAssets.message}`));
27
+ const message = result.deleteAssets.message;
28
+ toast.error(t`Failed to delete assets: ${message}`);
27
29
  }
28
30
  refetch();
29
31
  },
30
32
  onError: () => {
31
- toast.error(`Failed to delete ${selection.length} assets`);
33
+ toast.error(`Failed to delete ${selectionLength} assets`);
32
34
  },
33
35
  });
34
36
 
@@ -37,7 +39,7 @@ export const DeleteAssetsBulkAction = ({
37
39
  requiresPermission={['DeleteCatalog', 'DeleteAsset']}
38
40
  onClick={() => mutate({ input: { assetIds: selection.map(s => s.id) } })}
39
41
  label={<Trans>Delete</Trans>}
40
- confirmationText={<Trans>Are you sure you want to delete {selection.length} assets?</Trans>}
42
+ confirmationText={<Trans>Are you sure you want to delete {selectionLength} assets?</Trans>}
41
43
  icon={TrashIcon}
42
44
  className="text-destructive"
43
45
  />
@@ -10,8 +10,8 @@ import {
10
10
  } from '@/vdb/components/ui/command.js';
11
11
  import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
12
12
  import { api } from '@/vdb/graphql/api.js';
13
- import { Trans } from '@/vdb/lib/trans.js';
14
13
  import { cn } from '@/vdb/lib/utils.js';
14
+ import { Trans } from '@lingui/react/macro';
15
15
  import { useInfiniteQuery } from '@tanstack/react-query';
16
16
  import { useDebounce } from '@uidotdev/usehooks';
17
17
  import { Check, Filter, Loader2, X } from 'lucide-react';
@@ -10,8 +10,8 @@ import {
10
10
  import { Label } from '@/vdb/components/ui/label.js';
11
11
  import { Popover, PopoverContent, PopoverTrigger } from '@/vdb/components/ui/popover.js';
12
12
  import { api } from '@/vdb/graphql/api.js';
13
- import { Trans } from '@/vdb/lib/trans.js';
14
13
  import { cn } from '@/vdb/lib/utils.js';
14
+ import { Trans } from '@lingui/react/macro';
15
15
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
16
16
  import { Check, ChevronsUpDown, Settings2, X } from 'lucide-react';
17
17
  import { useCallback, useState } from 'react';
@@ -9,8 +9,8 @@ import {
9
9
  } from '@/vdb/components/ui/dialog.js';
10
10
  import { Input } from '@/vdb/components/ui/input.js';
11
11
  import { api } from '@/vdb/graphql/api.js';
12
- import { Trans } from '@/vdb/lib/trans.js';
13
12
  import { cn } from '@/vdb/lib/utils.js';
13
+ import { Trans } from '@lingui/react/macro';
14
14
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
15
15
  import { Trash2 } from 'lucide-react';
16
16
  import { useState } from 'react';
@@ -108,10 +108,7 @@ export function ManageTagsDialog({ open, onOpenChange, onTagsUpdated }: Readonly
108
108
  return (
109
109
  <div
110
110
  key={tag.id}
111
- className={cn(
112
- 'flex items-center gap-2 p-2 rounded-md',
113
- isDeleted && 'opacity-50',
114
- )}
111
+ className={cn('flex items-center gap-2 p-2 rounded-md', isDeleted && 'opacity-50')}
115
112
  >
116
113
  <Input
117
114
  value={getDisplayValue(tag.id)}
@@ -199,9 +196,7 @@ export function ManageTagsDialog({ open, onOpenChange, onTagsUpdated }: Readonly
199
196
  </DialogDescription>
200
197
  </DialogHeader>
201
198
 
202
- <div className="max-h-[400px] overflow-y-auto space-y-2 py-4">
203
- {renderTagsList()}
204
- </div>
199
+ <div className="max-h-[400px] overflow-y-auto space-y-2 py-4">{renderTagsList()}</div>
205
200
 
206
201
  <DialogFooter>
207
202
  <Button variant="outline" onClick={handleCancel} disabled={isSaving}>
@@ -5,7 +5,7 @@ import { Button } from '@/vdb/components/ui/button.js';
5
5
  import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
6
6
  import { ListPage } from '@/vdb/framework/page/list-page.js';
7
7
  import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
8
- import { Trans } from '@/vdb/lib/trans.js';
8
+ import { Trans } from '@lingui/react/macro';
9
9
  import { createFileRoute, Link } from '@tanstack/react-router';
10
10
  import { PlusIcon } from 'lucide-react';
11
11
  import { channelListQuery } from './channels.graphql.js';
@@ -21,7 +21,7 @@ function ChannelListPage() {
21
21
  return (
22
22
  <ListPage
23
23
  pageId="channel-list"
24
- title="Channels"
24
+ title={<Trans>Channels</Trans>}
25
25
  listQuery={channelListQuery}
26
26
  route={Route}
27
27
  defaultVisibility={{
@@ -39,7 +39,6 @@ function ChannelListPage() {
39
39
  }}
40
40
  customizeColumns={{
41
41
  code: {
42
- header: 'Code',
43
42
  cell: ({ row }) => {
44
43
  return (
45
44
  <DetailPageButton
@@ -50,13 +49,11 @@ function ChannelListPage() {
50
49
  },
51
50
  },
52
51
  seller: {
53
- header: 'Seller',
54
52
  cell: ({ row }) => {
55
53
  return row.original.seller?.name;
56
54
  },
57
55
  },
58
56
  defaultLanguageCode: {
59
- header: 'Default Language',
60
57
  cell: ({ row }) => {
61
58
  return formatLanguageName(row.original.defaultLanguageCode);
62
59
  },
@@ -74,7 +71,7 @@ function ChannelListPage() {
74
71
  <Button asChild>
75
72
  <Link to="./new">
76
73
  <PlusIcon className="mr-2 h-4 w-4" />
77
- New Channel
74
+ <Trans>New Channel</Trans>
78
75
  </Link>
79
76
  </Button>
80
77
  </PermissionGuard>
@@ -23,7 +23,7 @@ import {
23
23
  import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
24
24
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
25
25
  import { useChannel } from '@/vdb/hooks/use-channel.js';
26
- import { Trans, useLingui } from '@/vdb/lib/trans.js';
26
+ import { Trans, useLingui } from '@lingui/react/macro';
27
27
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
28
28
  import { toast } from 'sonner';
29
29
  import { channelDetailDocument, createChannelDocument, updateChannelDocument } from './channels.graphql.js';
@@ -49,7 +49,7 @@ function ChannelDetailPage() {
49
49
  const params = Route.useParams();
50
50
  const navigate = useNavigate();
51
51
  const creatingNewEntity = params.id === NEW_ENTITY_PATH;
52
- const { i18n } = useLingui();
52
+ const { t } = useLingui();
53
53
  const { refreshChannels } = useChannel();
54
54
 
55
55
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
@@ -82,20 +82,20 @@ function ChannelDetailPage() {
82
82
  params: { id: params.id },
83
83
  onSuccess: async data => {
84
84
  if (data.__typename === 'Channel') {
85
- toast(i18n.t(creatingNewEntity ? 'Successfully created channel' : 'Successfully updated channel'));
85
+ toast(creatingNewEntity ? t`Successfully created channel` : t`Successfully updated channel`);
86
86
  refreshChannels();
87
87
  resetForm();
88
88
  if (creatingNewEntity) {
89
89
  await navigate({ to: `../$id`, params: { id: data.id } });
90
90
  }
91
91
  } else {
92
- toast(i18n.t(creatingNewEntity ? 'Failed to create channel' : 'Failed to update channel'), {
92
+ toast(creatingNewEntity ? t`Failed to create channel` : t`Failed to update channel`, {
93
93
  description: data.message,
94
94
  });
95
95
  }
96
96
  },
97
97
  onError: err => {
98
- toast(i18n.t(creatingNewEntity ? 'Failed to create channel' : 'Failed to update channel'), {
98
+ toast(creatingNewEntity ? t`Failed to create channel` : t`Failed to update channel`, {
99
99
  description: err instanceof Error ? err.message : 'Unknown error',
100
100
  });
101
101
  },
@@ -4,7 +4,7 @@ import { Button } from '@/vdb/components/ui/button.js';
4
4
  import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
5
5
  import { ListPage } from '@/vdb/framework/page/list-page.js';
6
6
  import { api } from '@/vdb/graphql/api.js';
7
- import { Trans } from '@/vdb/lib/trans.js';
7
+ import { Trans } from '@lingui/react/macro';
8
8
  import { FetchQueryOptions, useQueries } from '@tanstack/react-query';
9
9
  import { createFileRoute, Link } from '@tanstack/react-router';
10
10
  import { ExpandedState, getExpandedRowModel } from '@tanstack/react-table';
@@ -82,7 +82,7 @@ function CollectionListPage() {
82
82
  <>
83
83
  <ListPage
84
84
  pageId="collection-list"
85
- title="Collections"
85
+ title={<Trans>Collections</Trans>}
86
86
  listQuery={collectionListDocument}
87
87
  transformVariables={input => {
88
88
  const filterTerm = input.options?.filter?.name?.contains;
@@ -96,13 +96,17 @@ function CollectionListPage() {
96
96
  }}
97
97
  customizeColumns={{
98
98
  name: {
99
- header: 'Collection Name',
99
+ meta: {
100
+ // This column needs the following fields to always be available
101
+ // in order to correctly render.
102
+ dependencies: ['children', 'breadcrumbs'],
103
+ },
100
104
  cell: ({ row }) => {
101
105
  const isExpanded = row.getIsExpanded();
102
106
  const hasChildren = !!row.original.children?.length;
103
107
  return (
104
108
  <div
105
- style={{ marginLeft: (row.original.breadcrumbs.length - 2) * 20 + 'px' }}
109
+ style={{ marginLeft: (row.original.breadcrumbs?.length - 2) * 20 + 'px' }}
106
110
  className="flex gap-2 items-center"
107
111
  >
108
112
  <Button
@@ -136,14 +140,14 @@ function CollectionListPage() {
136
140
  },
137
141
  },
138
142
  productVariants: {
139
- header: 'Contents',
143
+ header: () => <Trans>Contents</Trans>,
140
144
  cell: ({ row }) => {
141
145
  return (
142
146
  <CollectionContentsSheet
143
147
  collectionId={row.original.id}
144
148
  collectionName={row.original.name}
145
149
  >
146
- <Trans>{row.original.productVariants.totalItems} variants</Trans>
150
+ <Trans>{row.original.productVariants?.totalItems} variants</Trans>
147
151
  </CollectionContentsSheet>
148
152
  );
149
153
  },
@@ -1,3 +1,4 @@
1
+ import { SlugInput } from '@/vdb/components/data-input/index.js';
1
2
  import { RichTextInput } from '@/vdb/components/data-input/rich-text-input.js';
2
3
  import { EntityAssets } from '@/vdb/components/shared/entity-assets.js';
3
4
  import { ErrorPage } from '@/vdb/components/shared/error-page.js';
@@ -21,7 +22,7 @@ import {
21
22
  } from '@/vdb/framework/layout-engine/page-layout.js';
22
23
  import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-loader.js';
23
24
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
24
- import { Trans, useLingui } from '@/vdb/lib/trans.js';
25
+ import { Trans, useLingui } from '@lingui/react/macro';
25
26
  import { createFileRoute, useNavigate } from '@tanstack/react-router';
26
27
  import { toast } from 'sonner';
27
28
  import {
@@ -52,7 +53,7 @@ function CollectionDetailPage() {
52
53
  const params = Route.useParams();
53
54
  const navigate = useNavigate();
54
55
  const creatingNewEntity = params.id === NEW_ENTITY_PATH;
55
- const { i18n } = useLingui();
56
+ const { t } = useLingui();
56
57
 
57
58
  const { form, submitHandler, entity, isPending, resetForm } = useDetailPage({
58
59
  pageId,
@@ -89,14 +90,16 @@ function CollectionDetailPage() {
89
90
  },
90
91
  params: { id: params.id },
91
92
  onSuccess: async data => {
92
- toast(i18n.t(creatingNewEntity ? 'Successfully created collection' : 'Successfully updated collection'));
93
+ toast(
94
+ creatingNewEntity ? t`Successfully created collection` : t`Successfully updated collection`,
95
+ );
93
96
  resetForm();
94
97
  if (creatingNewEntity) {
95
98
  await navigate({ to: `../$id`, params: { id: data.id } });
96
99
  }
97
100
  },
98
101
  onError: err => {
99
- toast(i18n.t(creatingNewEntity ? 'Failed to create collection' : 'Failed to update collection'), {
102
+ toast(creatingNewEntity ? t`Failed to create collection` : t`Failed to update collection`, {
100
103
  description: err instanceof Error ? err.message : 'Unknown error',
101
104
  });
102
105
  },
@@ -147,7 +150,15 @@ function CollectionDetailPage() {
147
150
  control={form.control}
148
151
  name="slug"
149
152
  label={<Trans>Slug</Trans>}
150
- render={({ field }) => <Input {...field} />}
153
+ render={({ field }) => (
154
+ <SlugInput
155
+ fieldName="slug"
156
+ watchFieldName="name"
157
+ entityName="Collection"
158
+ entityId={entity?.id}
159
+ {...field}
160
+ />
161
+ )}
151
162
  />
152
163
  </DetailFormGrid>
153
164
  <TranslatableFormFieldWrapper
@@ -9,7 +9,7 @@ import { RemoveFromChannelBulkAction } from '@/vdb/components/shared/remove-from
9
9
  import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
10
10
  import { api } from '@/vdb/graphql/api.js';
11
11
  import { useChannel } from '@/vdb/hooks/use-channel.js';
12
- import { Trans } from '@/vdb/lib/trans.js';
12
+ import { Trans } from '@lingui/react/macro';
13
13
  import { DeleteBulkAction } from '../../../../common/delete-bulk-action.js';
14
14
  import { DuplicateBulkAction } from '../../../../common/duplicate-bulk-action.js';
15
15
  import {
@@ -7,7 +7,7 @@ import {
7
7
  SheetTitle,
8
8
  SheetTrigger,
9
9
  } from '@/vdb/components/ui/sheet.js';
10
- import { Trans } from '@/vdb/lib/trans.js';
10
+ import { Trans } from '@lingui/react/macro';
11
11
  import { PanelLeftOpen } from 'lucide-react';
12
12
  import { CollectionContentsTable } from './collection-contents-table.js';
13
13