@vendure/dashboard 3.5.0-minor-202510031341 → 3.5.0-minor-202510161257

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 (220) hide show
  1. package/dist/plugin/dashboard.plugin.js +1 -1
  2. package/dist/plugin/default-page.html +1 -1
  3. package/dist/vite/utils/ast-utils.spec.js +3 -3
  4. package/dist/vite/utils/tsconfig-utils.js +2 -1
  5. package/dist/vite/vite-plugin-hmr.d.ts +8 -0
  6. package/dist/vite/vite-plugin-hmr.js +34 -0
  7. package/dist/vite/vite-plugin-theme.js +6 -6
  8. package/dist/vite/vite-plugin-transform-index.js +6 -1
  9. package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
  10. package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
  11. package/package.json +18 -5
  12. package/src/app/app-providers.tsx +4 -1
  13. package/src/app/common/map-faceted-filter-fields.ts +21 -0
  14. package/src/app/main.tsx +3 -1
  15. package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
  16. package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
  17. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
  18. package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
  19. package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
  20. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  21. package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
  22. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
  23. package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
  24. package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
  25. package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
  26. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
  27. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
  28. package/src/app/routes/_authenticated/_global-settings/global-settings.tsx +4 -8
  29. package/src/app/routes/_authenticated/_global-settings/utils/global-languages.ts +268 -0
  30. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
  31. package/src/app/routes/_authenticated/_orders/components/order-address.tsx +15 -15
  32. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +5 -5
  33. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
  34. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
  35. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
  36. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
  37. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
  38. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
  39. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
  40. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
  41. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
  42. package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
  43. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
  44. package/src/app/routes/_authenticated/_product-variants/components/add-currency-dropdown.tsx +49 -0
  45. package/src/app/routes/_authenticated/_product-variants/components/add-stock-location-dropdown.tsx +56 -0
  46. package/src/app/routes/_authenticated/_product-variants/product-variants.graphql.ts +12 -0
  47. package/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx +178 -50
  48. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
  49. package/src/app/routes/_authenticated/_products/components/product-variants-table.tsx +0 -11
  50. package/src/app/routes/_authenticated/_products/products.tsx +6 -2
  51. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
  52. package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
  53. package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
  54. package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
  55. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +3 -10
  56. package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
  57. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
  58. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
  59. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
  60. package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
  61. package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
  62. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
  63. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
  64. package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
  65. package/src/app/routes/login.tsx +2 -2
  66. package/src/i18n/locales/ar.po +420 -289
  67. package/src/i18n/locales/cs.po +420 -289
  68. package/src/i18n/locales/de.po +420 -289
  69. package/src/i18n/locales/en.po +420 -289
  70. package/src/i18n/locales/es.po +420 -289
  71. package/src/i18n/locales/fa.po +420 -289
  72. package/src/i18n/locales/fr.po +468 -337
  73. package/src/i18n/locales/he.po +420 -289
  74. package/src/i18n/locales/hr.po +420 -289
  75. package/src/i18n/locales/it.po +420 -289
  76. package/src/i18n/locales/ja.po +420 -289
  77. package/src/i18n/locales/nb.po +420 -289
  78. package/src/i18n/locales/ne.po +420 -289
  79. package/src/i18n/locales/pl.po +420 -289
  80. package/src/i18n/locales/pt_BR.po +420 -289
  81. package/src/i18n/locales/pt_PT.po +420 -289
  82. package/src/i18n/locales/ru.po +420 -289
  83. package/src/i18n/locales/sv.po +420 -289
  84. package/src/i18n/locales/tr.po +420 -289
  85. package/src/i18n/locales/uk.po +420 -289
  86. package/src/i18n/locales/zh_Hans.po +420 -289
  87. package/src/i18n/locales/zh_Hant.po +420 -289
  88. package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
  89. package/src/lib/components/data-input/affixed-input.tsx +5 -2
  90. package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
  91. package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
  92. package/src/lib/components/data-input/customer-group-input.tsx +0 -1
  93. package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
  94. package/src/lib/components/data-input/datetime-input.tsx +27 -13
  95. package/src/lib/components/data-input/default-relation-input.tsx +18 -12
  96. package/src/lib/components/data-input/money-input.stories.tsx +88 -0
  97. package/src/lib/components/data-input/money-input.tsx +7 -11
  98. package/src/lib/components/data-input/number-input.stories.tsx +103 -0
  99. package/src/lib/components/data-input/number-input.tsx +16 -5
  100. package/src/lib/components/data-input/password-input.stories.tsx +65 -0
  101. package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
  102. package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
  103. package/src/lib/components/data-input/slug-input.tsx +9 -10
  104. package/src/lib/components/data-input/text-input.stories.tsx +52 -0
  105. package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
  106. package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
  107. package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
  108. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
  109. package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
  110. package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
  111. package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
  112. package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
  113. package/src/lib/components/data-table/data-table-filter-badge.tsx +28 -14
  114. package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
  115. package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
  116. package/src/lib/components/data-table/data-table.stories.tsx +249 -0
  117. package/src/lib/components/data-table/data-table.tsx +39 -11
  118. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
  119. package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
  120. package/src/lib/components/layout/generated-breadcrumbs.tsx +4 -12
  121. package/src/lib/components/layout/nav-user.tsx +19 -13
  122. package/src/lib/components/login/login-form.tsx +39 -123
  123. package/src/lib/components/shared/alerts.tsx +29 -17
  124. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
  125. package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
  126. package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
  127. package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
  128. package/src/lib/components/shared/configurable-operation-input.tsx +1 -1
  129. package/src/lib/components/shared/customer-group-selector.tsx +5 -2
  130. package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
  131. package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
  132. package/src/lib/components/shared/facet-value-selector.tsx +130 -34
  133. package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
  134. package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
  135. package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
  136. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
  137. package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
  138. package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
  139. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
  140. package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
  141. package/src/lib/components/shared/vendure-image.tsx +6 -7
  142. package/src/lib/components/ui/accordion.stories.tsx +33 -0
  143. package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
  144. package/src/lib/components/ui/alert.stories.tsx +35 -0
  145. package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
  146. package/src/lib/components/ui/badge.stories.tsx +28 -0
  147. package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
  148. package/src/lib/components/ui/button.stories.tsx +38 -0
  149. package/src/lib/components/ui/calendar.stories.tsx +22 -0
  150. package/src/lib/components/ui/card.stories.tsx +28 -0
  151. package/src/lib/components/ui/carousel.stories.tsx +34 -0
  152. package/src/lib/components/ui/checkbox.stories.tsx +31 -0
  153. package/src/lib/components/ui/collapsible.stories.tsx +39 -0
  154. package/src/lib/components/ui/command.stories.tsx +44 -0
  155. package/src/lib/components/ui/context-menu.stories.tsx +38 -0
  156. package/src/lib/components/ui/dialog.stories.tsx +52 -0
  157. package/src/lib/components/ui/drawer.stories.tsx +50 -0
  158. package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
  159. package/src/lib/components/ui/hover-card.stories.tsx +38 -0
  160. package/src/lib/components/ui/input-group.tsx +148 -0
  161. package/src/lib/components/ui/input-otp.stories.tsx +30 -0
  162. package/src/lib/components/ui/input.stories.tsx +38 -0
  163. package/src/lib/components/ui/label.stories.tsx +24 -0
  164. package/src/lib/components/ui/menubar.stories.tsx +53 -0
  165. package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
  166. package/src/lib/components/ui/pagination.stories.tsx +51 -0
  167. package/src/lib/components/ui/password-input.stories.tsx +32 -0
  168. package/src/lib/components/ui/password-input.tsx +33 -0
  169. package/src/lib/components/ui/popover.stories.tsx +33 -0
  170. package/src/lib/components/ui/progress.stories.tsx +27 -0
  171. package/src/lib/components/ui/radio-group.stories.tsx +34 -0
  172. package/src/lib/components/ui/resizable.stories.tsx +32 -0
  173. package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
  174. package/src/lib/components/ui/select.stories.tsx +36 -0
  175. package/src/lib/components/ui/separator.stories.tsx +35 -0
  176. package/src/lib/components/ui/sheet.stories.tsx +50 -0
  177. package/src/lib/components/ui/sidebar-context.ts +16 -0
  178. package/src/lib/components/ui/sidebar.tsx +2 -13
  179. package/src/lib/components/ui/skeleton.stories.tsx +26 -0
  180. package/src/lib/components/ui/slider.stories.tsx +37 -0
  181. package/src/lib/components/ui/switch.stories.tsx +31 -0
  182. package/src/lib/components/ui/table.stories.tsx +52 -0
  183. package/src/lib/components/ui/tabs.stories.tsx +29 -0
  184. package/src/lib/components/ui/textarea.stories.tsx +32 -0
  185. package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
  186. package/src/lib/components/ui/toggle.stories.tsx +39 -0
  187. package/src/lib/components/ui/tooltip.stories.tsx +30 -0
  188. package/src/lib/components/ui/tooltip.tsx +2 -2
  189. package/src/lib/framework/alert/alert-extensions.tsx +0 -11
  190. package/src/lib/framework/alert/alert-item.tsx +14 -19
  191. package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
  192. package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
  193. package/src/lib/framework/component-registry/component-registry.tsx +3 -14
  194. package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
  195. package/src/lib/framework/dashboard-widget/latest-orders-widget/index.tsx +0 -2
  196. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
  197. package/src/lib/framework/defaults.ts +9 -13
  198. package/src/lib/framework/extension-api/input-component-extensions.tsx +6 -1
  199. package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
  200. package/src/lib/framework/extension-api/types/alerts.ts +12 -6
  201. package/src/lib/framework/extension-api/types/data-table.ts +5 -2
  202. package/src/lib/framework/extension-api/types/layout.ts +41 -1
  203. package/src/lib/framework/extension-api/types/login.ts +0 -21
  204. package/src/lib/framework/form-engine/value-transformers.ts +8 -1
  205. package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
  206. package/src/lib/framework/layout-engine/page-layout.tsx +69 -57
  207. package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
  208. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
  209. package/src/lib/framework/page/detail-page.stories.tsx +151 -0
  210. package/src/lib/framework/page/detail-page.tsx +12 -15
  211. package/src/lib/framework/page/list-page.stories.tsx +217 -0
  212. package/src/lib/framework/page/list-page.tsx +8 -1
  213. package/src/lib/graphql/api.ts +18 -1
  214. package/src/lib/graphql/graphql-env.d.ts +1 -1
  215. package/src/lib/hooks/use-alerts.ts +84 -0
  216. package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
  217. package/src/lib/index.ts +12 -5
  218. package/src/lib/providers/alerts-provider.tsx +60 -0
  219. package/src/lib/providers/channel-provider.tsx +1 -0
  220. package/src/lib/providers/theme-provider.tsx +6 -3
@@ -0,0 +1,217 @@
1
+ import { DetailPageButton } from '@/vdb/components/shared/detail-page-button.js';
2
+ import { Button } from '@/vdb/components/ui/button.js';
3
+ import { PageActionBarRight } from '@/vdb/framework/layout-engine/page-layout.js';
4
+ import { ListPage, ListPageProps } from '@/vdb/framework/page/list-page.js';
5
+ import { graphql } from '@/vdb/graphql/graphql.js';
6
+ import type { Meta, StoryObj } from '@storybook/react';
7
+ import { PlusIcon } from 'lucide-react';
8
+
9
+ import { DemoRouterProvider } from '../../../../.storybook/providers.js';
10
+
11
+ // Sample GraphQL query for countries
12
+ const countryItemFragment = graphql(`
13
+ fragment CountryItem on Country {
14
+ id
15
+ createdAt
16
+ updatedAt
17
+ name
18
+ code
19
+ enabled
20
+ }
21
+ `);
22
+
23
+ const countriesListQuery = graphql(
24
+ `
25
+ query CountriesList($options: CountryListOptions) {
26
+ countries(options: $options) {
27
+ items {
28
+ ...CountryItem
29
+ }
30
+ totalItems
31
+ }
32
+ }
33
+ `,
34
+ [countryItemFragment],
35
+ );
36
+
37
+ const deleteCountryDocument = graphql(`
38
+ mutation DeleteCountry($id: ID!) {
39
+ deleteCountry(id: $id) {
40
+ result
41
+ message
42
+ }
43
+ }
44
+ `);
45
+
46
+ function ListPageStoryWrapper(props: Omit<ListPageProps<any, any, any, any>, 'route'>) {
47
+ return <DemoRouterProvider component={route => <ListPage {...props} route={() => route} />} />;
48
+ }
49
+
50
+ const meta = {
51
+ title: 'Layout/ListPage',
52
+ component: ListPageStoryWrapper,
53
+ parameters: {
54
+ layout: 'fullscreen',
55
+ },
56
+ tags: ['autodocs'],
57
+ argTypes: {
58
+ pageId: {
59
+ control: 'text',
60
+ description: 'Unique identifier for the list page',
61
+ },
62
+ title: {
63
+ control: 'text',
64
+ description: 'Page title displayed in the header',
65
+ },
66
+ listQuery: {
67
+ control: false,
68
+ description: 'GraphQL query document for fetching list data',
69
+ },
70
+ deleteMutation: {
71
+ control: false,
72
+ description: 'GraphQL mutation document for deleting items',
73
+ },
74
+ customizeColumns: {
75
+ control: false,
76
+ description: 'Customize column rendering and behavior',
77
+ },
78
+ defaultVisibility: {
79
+ control: 'object',
80
+ description: 'Default visible columns',
81
+ },
82
+ },
83
+ } satisfies Meta<typeof ListPageStoryWrapper>;
84
+
85
+ export default meta;
86
+ type Story = StoryObj<typeof meta>;
87
+
88
+ /**
89
+ * Basic example of a ListPage showing countries from the demo API.
90
+ * This demonstrates the minimal configuration needed to render a list.
91
+ */
92
+ export const BasicList: Story = {
93
+ args: {
94
+ pageId: 'country-list',
95
+ listQuery: countriesListQuery,
96
+ title: 'Countries',
97
+ defaultVisibility: {
98
+ name: true,
99
+ code: true,
100
+ enabled: true,
101
+ },
102
+ },
103
+ };
104
+
105
+ /**
106
+ * ListPage with custom column rendering and delete mutation.
107
+ * Shows how to customize the name column to render as a detail page button.
108
+ */
109
+ export const WithCustomColumns: Story = {
110
+ args: {
111
+ pageId: 'country-list-custom',
112
+ listQuery: countriesListQuery,
113
+ deleteMutation: deleteCountryDocument,
114
+ title: 'Countries',
115
+ defaultVisibility: {
116
+ name: true,
117
+ code: true,
118
+ enabled: true,
119
+ },
120
+ customizeColumns: {
121
+ name: {
122
+ cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
123
+ },
124
+ },
125
+ },
126
+ };
127
+
128
+ /**
129
+ * ListPage with search functionality.
130
+ * Demonstrates how to configure search to filter on multiple fields.
131
+ */
132
+ export const WithSearch: Story = {
133
+ args: {
134
+ pageId: 'country-list-search',
135
+ listQuery: countriesListQuery,
136
+ title: 'Countries',
137
+ defaultVisibility: {
138
+ name: true,
139
+ code: true,
140
+ enabled: true,
141
+ },
142
+ onSearchTermChange: (searchTerm: string) => {
143
+ return {
144
+ name: {
145
+ contains: searchTerm,
146
+ },
147
+ code: {
148
+ contains: searchTerm,
149
+ },
150
+ };
151
+ },
152
+ transformVariables: (variables: any) => {
153
+ return {
154
+ ...variables,
155
+ options: {
156
+ ...variables.options,
157
+ filterOperator: 'OR',
158
+ },
159
+ };
160
+ },
161
+ customizeColumns: {
162
+ name: {
163
+ cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
164
+ },
165
+ },
166
+ },
167
+ };
168
+
169
+ /**
170
+ * Complete example with action bar and all features.
171
+ * Shows the full ListPage configuration including custom action buttons.
172
+ */
173
+ export const Complete: Story = {
174
+ args: {
175
+ pageId: 'country-list-complete',
176
+ listQuery: countriesListQuery,
177
+ deleteMutation: deleteCountryDocument,
178
+ title: 'Countries',
179
+ defaultVisibility: {
180
+ name: true,
181
+ code: true,
182
+ enabled: true,
183
+ },
184
+ onSearchTermChange: (searchTerm: string) => {
185
+ return {
186
+ name: {
187
+ contains: searchTerm,
188
+ },
189
+ code: {
190
+ contains: searchTerm,
191
+ },
192
+ };
193
+ },
194
+ transformVariables: (variables: any) => {
195
+ return {
196
+ ...variables,
197
+ options: {
198
+ ...variables.options,
199
+ filterOperator: 'OR',
200
+ },
201
+ };
202
+ },
203
+ customizeColumns: {
204
+ name: {
205
+ cell: ({ row }) => <DetailPageButton id={row.original.id} label={row.original.name} />,
206
+ },
207
+ },
208
+ children: (
209
+ <PageActionBarRight>
210
+ <Button>
211
+ <PlusIcon />
212
+ Add Country
213
+ </Button>
214
+ </PageActionBarRight>
215
+ ),
216
+ },
217
+ };
@@ -17,7 +17,13 @@ import { ColumnFiltersState, SortingState, Table } from '@tanstack/react-table';
17
17
  import { TableOptions } from '@tanstack/table-core';
18
18
 
19
19
  import { BulkAction } from '@/vdb/framework/extension-api/types/index.js';
20
- import { FullWidthPageBlock, Page, PageActionBar, PageLayout, PageTitle } from '../layout-engine/page-layout.js';
20
+ import {
21
+ FullWidthPageBlock,
22
+ Page,
23
+ PageActionBar,
24
+ PageLayout,
25
+ PageTitle,
26
+ } from '../layout-engine/page-layout.js';
21
27
 
22
28
  /**
23
29
  * @description
@@ -393,6 +399,7 @@ export interface ListPageProps<
393
399
  * body
394
400
  * customFields
395
401
  * }
402
+ * totalItems
396
403
  * }
397
404
  * }
398
405
  * `);
@@ -69,6 +69,20 @@ const awesomeClient = new AwesomeGraphQLClient({
69
69
  },
70
70
  });
71
71
 
72
+ /**
73
+ * @description
74
+ * Handles the scenario where there's an invalid channel token in local storage.
75
+ * Most often seen in local development when testing multiple backends on the same
76
+ * localhost origin.
77
+ */
78
+ function handleInvalidChannelToken(err: unknown) {
79
+ if (err instanceof Error) {
80
+ if ((err as any).extensions?.code === 'CHANNEL_NOT_FOUND') {
81
+ localStorage.removeItem(LS_KEY_SELECTED_CHANNEL_TOKEN);
82
+ }
83
+ }
84
+ }
85
+
72
86
  export type VariablesAndRequestHeadersArgs<V extends Variables> =
73
87
  V extends Record<any, never>
74
88
  ? [variables?: V, requestHeaders?: HeadersInit]
@@ -79,7 +93,10 @@ function query<T, V extends Variables = Variables>(
79
93
  variables?: V,
80
94
  ): Promise<T> {
81
95
  const documentString = typeof document === 'string' ? document : print(document);
82
- return awesomeClient.request(documentString, variables) as any;
96
+ return awesomeClient.request(documentString, variables).catch(err => {
97
+ handleInvalidChannelToken(err);
98
+ throw err;
99
+ }) as any;
83
100
  }
84
101
 
85
102
  function mutate<T, V extends Variables = Variables>(
@@ -416,7 +416,7 @@ export type introspection_types = {
416
416
  'TaxCategorySortParameter': { kind: 'INPUT_OBJECT'; name: 'TaxCategorySortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
417
417
  'TaxLine': { kind: 'OBJECT'; name: 'TaxLine'; fields: { 'description': { name: 'description'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'taxRate': { name: 'taxRate'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; }; };
418
418
  'TaxRate': { kind: 'OBJECT'; name: 'TaxRate'; fields: { 'category': { name: 'category'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TaxCategory'; ofType: null; }; } }; 'createdAt': { name: 'createdAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'customFields': { name: 'customFields'; type: { kind: 'SCALAR'; name: 'JSON'; ofType: null; } }; 'customerGroup': { name: 'customerGroup'; type: { kind: 'OBJECT'; name: 'CustomerGroup'; ofType: null; } }; 'enabled': { name: 'enabled'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'id': { name: 'id'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'ID'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'updatedAt': { name: 'updatedAt'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'value': { name: 'value'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Float'; ofType: null; }; } }; 'zone': { name: 'zone'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Zone'; ofType: null; }; } }; }; };
419
- 'TaxRateFilterParameter': { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanOperators'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }]; };
419
+ 'TaxRateFilterParameter': { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; isOneOf: false; inputFields: [{ name: 'zoneId'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'categoryId'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'id'; type: { kind: 'INPUT_OBJECT'; name: 'IDOperators'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'INPUT_OBJECT'; name: 'DateOperators'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'INPUT_OBJECT'; name: 'StringOperators'; ofType: null; }; defaultValue: null }, { name: 'enabled'; type: { kind: 'INPUT_OBJECT'; name: 'BooleanOperators'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'INPUT_OBJECT'; name: 'NumberOperators'; ofType: null; }; defaultValue: null }, { name: '_and'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }, { name: '_or'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; }; }; defaultValue: null }]; };
420
420
  'TaxRateList': { kind: 'OBJECT'; name: 'TaxRateList'; fields: { 'items': { name: 'items'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TaxRate'; ofType: null; }; }; }; } }; 'totalItems': { name: 'totalItems'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; };
421
421
  'TaxRateListOptions': { kind: 'INPUT_OBJECT'; name: 'TaxRateListOptions'; isOneOf: false; inputFields: [{ name: 'skip'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'take'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'sort'; type: { kind: 'INPUT_OBJECT'; name: 'TaxRateSortParameter'; ofType: null; }; defaultValue: null }, { name: 'filter'; type: { kind: 'INPUT_OBJECT'; name: 'TaxRateFilterParameter'; ofType: null; }; defaultValue: null }, { name: 'filterOperator'; type: { kind: 'ENUM'; name: 'LogicalOperator'; ofType: null; }; defaultValue: null }]; };
422
422
  'TaxRateSortParameter': { kind: 'INPUT_OBJECT'; name: 'TaxRateSortParameter'; isOneOf: false; inputFields: [{ name: 'id'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'createdAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'updatedAt'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'name'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }, { name: 'value'; type: { kind: 'ENUM'; name: 'SortOrder'; ofType: null; }; defaultValue: null }]; };
@@ -0,0 +1,84 @@
1
+ import { AlertSeverity, DashboardAlertDefinition } from '@/vdb/framework/extension-api/types/alerts.js';
2
+ import { useAlertsContext } from '@/vdb/providers/alerts-provider.js';
3
+ import { useMemo } from 'react';
4
+
5
+ /**
6
+ * @description
7
+ * An individual Alert item.
8
+ *
9
+ * @docsCategory hooks
10
+ * @docsPage useAlerts
11
+ * @since 3.5.0
12
+ */
13
+ export interface Alert {
14
+ definition: DashboardAlertDefinition;
15
+ active: boolean;
16
+ currentSeverity?: AlertSeverity;
17
+ lastData: any;
18
+ dismiss: () => void;
19
+ }
20
+
21
+ /**
22
+ * @description
23
+ * Returns information about all registered Alerts, including how many are
24
+ * active and at what severity.
25
+ *
26
+ * @docsCategory hooks
27
+ * @docsPage useAlerts
28
+ * @docsWeight 0
29
+ * @since 3.5.0
30
+ */
31
+ export function useAlerts(): { alerts: Alert[]; activeCount: number; highestSeverity: AlertSeverity } {
32
+ const { alertDefs, rawResults, dismissedAlerts, setDismissedAlerts } = useAlertsContext();
33
+
34
+ const alerts = useMemo(() => {
35
+ return rawResults.map((result, idx) => {
36
+ const alertDef = alertDefs[idx];
37
+ const dismissedAt = dismissedAlerts.get(alertDef.id);
38
+ const isDismissed =
39
+ dismissedAt !== undefined &&
40
+ result.dataUpdatedAt !== undefined &&
41
+ dismissedAt > result.dataUpdatedAt;
42
+ const active = alertDef.shouldShow(result.data) && !isDismissed;
43
+ const currentSeverity = getSeverity(alertDef, result.data);
44
+ return {
45
+ definition: alertDef,
46
+ active,
47
+ lastData: result.data,
48
+ currentSeverity,
49
+ dismiss: () => {
50
+ setDismissedAlerts(prev => new Map(prev).set(alertDef.id, Date.now()));
51
+ },
52
+ };
53
+ });
54
+ }, [rawResults, alertDefs, dismissedAlerts]);
55
+
56
+ const activeCount = useMemo(() => alerts.filter(alert => alert.active).length, [alerts]);
57
+ const highestSeverity: AlertSeverity =
58
+ alerts.length > 0
59
+ ? alerts.reduce((highest, a) => {
60
+ if (highest === 'warning' && a.currentSeverity === 'error') {
61
+ return 'error';
62
+ }
63
+ if (
64
+ highest === 'info' &&
65
+ (a.currentSeverity === 'warning' || a.currentSeverity === 'error')
66
+ ) {
67
+ return a.currentSeverity;
68
+ }
69
+ return highest;
70
+ }, 'info' as AlertSeverity)
71
+ : 'info';
72
+
73
+ return { alerts, activeCount, highestSeverity };
74
+ }
75
+
76
+ function getSeverity(alertDef: DashboardAlertDefinition, data: any) {
77
+ if (typeof alertDef.severity === 'string') {
78
+ return alertDef.severity;
79
+ } else if (typeof alertDef.severity === 'function') {
80
+ return alertDef.severity(data);
81
+ } else {
82
+ return 'info';
83
+ }
84
+ }
@@ -32,9 +32,8 @@ export function useFloatingBulkActions({
32
32
  }
33
33
 
34
34
  const updatePosition = () => {
35
- // Find the container by searching upwards from the current component
36
- const currentElement = document.activeElement || document.body;
37
- const container = currentElement.closest(containerSelector) as HTMLElement;
35
+ // Find the container in the document
36
+ const container = document.querySelector(containerSelector) as HTMLElement;
38
37
  if (!container) return;
39
38
 
40
39
  const containerRect = container.getBoundingClientRect();
package/src/lib/index.ts CHANGED
@@ -204,6 +204,8 @@ export * from './framework/document-extension/extend-document.js';
204
204
  export * from './framework/document-introspection/add-custom-fields.js';
205
205
  export * from './framework/document-introspection/get-document-structure.js';
206
206
  export * from './framework/document-introspection/hooks.js';
207
+ export * from './framework/document-introspection/include-only-selected-list-fields.js';
208
+ export * from './framework/document-introspection/testing-utils.js';
207
209
  export * from './framework/extension-api/define-dashboard-extension.js';
208
210
  export * from './framework/extension-api/display-component-extensions.js';
209
211
  export * from './framework/extension-api/extension-api-types.js';
@@ -249,6 +251,7 @@ export * from './framework/layout-engine/page-provider.js';
249
251
  export * from './framework/nav-menu/nav-menu-extensions.js';
250
252
  export * from './framework/page/detail-page-route-loader.js';
251
253
  export * from './framework/page/detail-page.js';
254
+ export * from './framework/page/list-page.stories.js';
252
255
  export * from './framework/page/list-page.js';
253
256
  export * from './framework/page/page-api.js';
254
257
  export * from './framework/page/page-types.js';
@@ -256,14 +259,11 @@ export * from './framework/page/use-detail-page.js';
256
259
  export * from './framework/page/use-extended-router.js';
257
260
  export * from './framework/registry/global-registry.js';
258
261
  export * from './framework/registry/registry-types.js';
259
- export * from './graphql/api.js';
260
- export * from './graphql/common-operations.js';
261
- export * from './graphql/fragments.js';
262
- export * from './graphql/graphql.js';
263
- export * from './graphql/settings-store-operations.js';
264
262
  export * from './hooks/use-auth.js';
265
263
  export * from './hooks/use-channel.js';
266
264
  export * from './hooks/use-custom-field-config.js';
265
+ export * from './hooks/use-display-locale.js';
266
+ export * from './hooks/use-dynamic-translations.js';
267
267
  export * from './hooks/use-extended-detail-query.js';
268
268
  export * from './hooks/use-extended-list-query.js';
269
269
  export * from './hooks/use-floating-bulk-actions.js';
@@ -276,6 +276,13 @@ export * from './hooks/use-permissions.js';
276
276
  export * from './hooks/use-saved-views.js';
277
277
  export * from './hooks/use-server-config.js';
278
278
  export * from './hooks/use-theme.js';
279
+ export * from './hooks/use-ui-language-loader.js';
279
280
  export * from './hooks/use-user-settings.js';
281
+ export * from './lib/load-i18n-messages.js';
280
282
  export * from './lib/trans.js';
281
283
  export * from './lib/utils.js';
284
+ export * from './graphql/api.js';
285
+ export * from './graphql/common-operations.js';
286
+ export * from './graphql/fragments.js';
287
+ export * from './graphql/graphql.js';
288
+ export * from './graphql/settings-store-operations.js';
@@ -0,0 +1,60 @@
1
+ import { getAlertRegistry } from '@/vdb/framework/alert/alert-extensions.js';
2
+ import { DashboardAlertDefinition } from '@/vdb/framework/extension-api/types/alerts.js';
3
+ import { useQueries, UseQueryOptions } from '@tanstack/react-query';
4
+ import { createContext, ReactNode, useContext, useEffect, useState } from 'react';
5
+
6
+ interface AlertsContextValue {
7
+ alertDefs: DashboardAlertDefinition[];
8
+ rawResults: any[];
9
+ dismissedAlerts: Map<string, number>;
10
+ setDismissedAlerts: React.Dispatch<React.SetStateAction<Map<string, number>>>;
11
+ enabledQueries: boolean;
12
+ }
13
+
14
+ const AlertsContext = createContext<AlertsContextValue | undefined>(undefined);
15
+
16
+ export function AlertsProvider({ children }: { children: ReactNode }) {
17
+ const initialDelayMs = 5_000;
18
+ const [alertDefs, setAlertDefs] = useState<DashboardAlertDefinition[]>([]);
19
+ const [enabledQueries, setEnabledQueries] = useState(false);
20
+ const [dismissedAlerts, setDismissedAlerts] = useState<Map<string, number>>(new Map());
21
+
22
+ useEffect(() => {
23
+ setAlertDefs(Array.from(getAlertRegistry().values()));
24
+ }, []);
25
+
26
+ useEffect(() => {
27
+ const timer = setTimeout(() => {
28
+ setEnabledQueries(true);
29
+ }, initialDelayMs);
30
+ return () => clearTimeout(timer);
31
+ }, []);
32
+
33
+ const rawResults = useQueries({
34
+ queries: alertDefs.map(
35
+ alert =>
36
+ ({
37
+ queryKey: ['alert', alert.id],
38
+ queryFn: () => alert.check(),
39
+ refetchInterval: alert.recheckInterval,
40
+ enabled: enabledQueries,
41
+ }) as UseQueryOptions,
42
+ ),
43
+ });
44
+
45
+ return (
46
+ <AlertsContext.Provider
47
+ value={{ alertDefs, rawResults, dismissedAlerts, setDismissedAlerts, enabledQueries }}
48
+ >
49
+ {children}
50
+ </AlertsContext.Provider>
51
+ );
52
+ }
53
+
54
+ export function useAlertsContext() {
55
+ const context = useContext(AlertsContext);
56
+ if (!context) {
57
+ throw new Error('useAlertsContext must be used within AlertsProvider');
58
+ }
59
+ return context;
60
+ }
@@ -16,6 +16,7 @@ const channelFragment = graphql(`
16
16
  defaultCurrencyCode
17
17
  pricesIncludeTax
18
18
  availableLanguageCodes
19
+ availableCurrencyCodes
19
20
  }
20
21
  `);
21
22
 
@@ -29,15 +29,18 @@ export function ThemeProvider({ children, defaultTheme = 'system', ...props }: R
29
29
 
30
30
  root.classList.remove('light', 'dark');
31
31
 
32
- if (settings.theme === 'system') {
32
+ const activeTheme =
33
+ defaultTheme !== 'system' && settings.theme === 'system' ? defaultTheme : settings.theme;
34
+
35
+ if (activeTheme === 'system') {
33
36
  const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
34
37
 
35
38
  root.classList.add(systemTheme);
36
39
  return;
37
40
  }
38
41
 
39
- root.classList.add(settings.theme);
40
- }, [settings.theme]);
42
+ root.classList.add(activeTheme);
43
+ }, [settings.theme, defaultTheme]);
41
44
 
42
45
  return (
43
46
  <ThemeProviderContext.Provider