@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,31 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Bold, Italic, Underline } from 'lucide-react';
3
+ import { ToggleGroup, ToggleGroupItem } from './toggle-group.js';
4
+
5
+ const meta = {
6
+ title: 'UI/Toggle Group',
7
+ component: ToggleGroup,
8
+ parameters: {
9
+ layout: 'centered',
10
+ },
11
+ tags: ['autodocs'],
12
+ } satisfies Meta<typeof ToggleGroup>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Playground: Story = {
18
+ render: () => (
19
+ <ToggleGroup type="multiple">
20
+ <ToggleGroupItem value="bold" aria-label="Toggle bold">
21
+ <Bold className="h-4 w-4" />
22
+ </ToggleGroupItem>
23
+ <ToggleGroupItem value="italic" aria-label="Toggle italic">
24
+ <Italic className="h-4 w-4" />
25
+ </ToggleGroupItem>
26
+ <ToggleGroupItem value="underline" aria-label="Toggle underline">
27
+ <Underline className="h-4 w-4" />
28
+ </ToggleGroupItem>
29
+ </ToggleGroup>
30
+ ),
31
+ };
@@ -0,0 +1,39 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Bold } from 'lucide-react';
3
+ import { Toggle } from './toggle.js';
4
+
5
+ const meta = {
6
+ title: 'UI/Toggle',
7
+ component: Toggle,
8
+ parameters: {
9
+ layout: 'centered',
10
+ },
11
+ tags: ['autodocs'],
12
+ argTypes: {
13
+ variant: {
14
+ control: 'select',
15
+ options: ['default', 'outline'],
16
+ description: 'Toggle variant',
17
+ },
18
+ size: {
19
+ control: 'select',
20
+ options: ['default', 'sm', 'lg'],
21
+ description: 'Toggle size',
22
+ },
23
+ },
24
+ } satisfies Meta<typeof Toggle>;
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof meta>;
28
+
29
+ export const Playground: Story = {
30
+ args: {
31
+ variant: 'default',
32
+ size: 'default',
33
+ },
34
+ render: args => (
35
+ <Toggle {...args}>
36
+ <Bold className="h-4 w-4" />
37
+ </Toggle>
38
+ ),
39
+ };
@@ -0,0 +1,30 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Button } from './button.js';
3
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip.js';
4
+
5
+ const meta = {
6
+ title: 'UI/Tooltip',
7
+ component: Tooltip,
8
+ parameters: {
9
+ layout: 'centered',
10
+ },
11
+ tags: ['autodocs'],
12
+ } satisfies Meta<typeof Tooltip>;
13
+
14
+ export default meta;
15
+ type Story = StoryObj<typeof meta>;
16
+
17
+ export const Playground: Story = {
18
+ render: () => (
19
+ <TooltipProvider>
20
+ <Tooltip>
21
+ <TooltipTrigger asChild>
22
+ <Button>Hover me</Button>
23
+ </TooltipTrigger>
24
+ <TooltipContent>
25
+ <p>This is a tooltip</p>
26
+ </TooltipContent>
27
+ </Tooltip>
28
+ </TooltipProvider>
29
+ ),
30
+ };
@@ -36,13 +36,13 @@ function TooltipContent({
36
36
  data-slot="tooltip-content"
37
37
  sideOffset={sideOffset}
38
38
  className={cn(
39
- 'bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-w-sm rounded-md px-3 py-1.5 text-xs',
39
+ 'bg-secondary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-w-sm rounded-md px-3 py-1.5 text-xs',
40
40
  className,
41
41
  )}
42
42
  {...props}
43
43
  >
44
44
  {children}
45
- <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
45
+ <TooltipPrimitive.Arrow className="bg-secondary fill-secondary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
46
46
  </TooltipPrimitive.Content>
47
47
  </TooltipPrimitive.Portal>
48
48
  );
@@ -1,4 +1,3 @@
1
- import { useEffect, useState } from 'react';
2
1
  import { DashboardAlertDefinition } from '../extension-api/types/alerts.js';
3
2
  import { globalRegistry } from '../registry/global-registry.js';
4
3
 
@@ -18,13 +17,3 @@ export function getAlertRegistry() {
18
17
  export function getAlert(id: string) {
19
18
  return getAlertRegistry().get(id);
20
19
  }
21
-
22
- export function useAlerts() {
23
- const [alerts, setAlerts] = useState<DashboardAlertDefinition[]>([]);
24
-
25
- useEffect(() => {
26
- setAlerts(Array.from(getAlertRegistry().values()));
27
- }, []);
28
-
29
- return { alerts };
30
- }
@@ -1,44 +1,39 @@
1
1
  import { Button } from '@/vdb/components/ui/button.js';
2
+ import { Alert } from '@/vdb/hooks/use-alerts.js';
2
3
  import { cn } from '@/vdb/lib/utils.js';
3
- import { useQuery } from '@tanstack/react-query';
4
4
  import { ComponentProps } from 'react';
5
5
 
6
- import { DashboardAlertDefinition } from '../extension-api/types/alerts.js';
7
-
8
6
  interface AlertItemProps extends ComponentProps<'div'> {
9
- alert: DashboardAlertDefinition;
7
+ alert: Alert;
10
8
  }
11
9
 
12
10
  export function AlertItem({ alert, className, ...props }: Readonly<AlertItemProps>) {
13
- const { data } = useQuery({
14
- queryKey: ['alert', alert.id],
15
- queryFn: () => alert.check(),
16
- refetchInterval: alert.recheckInterval,
17
- });
18
-
19
- const isAlertActive = alert.shouldShow?.(data);
20
-
21
- if (!isAlertActive) {
11
+ if (!alert.active) {
22
12
  return null;
23
13
  }
14
+ const { definition: def } = alert;
24
15
 
25
16
  return (
26
17
  <div className={cn('flex items-center justify-between gap-1', className)} {...props}>
27
18
  <div className="flex flex-col">
28
- <span className="font-semibold">
29
- {typeof alert.title === 'string' ? alert.title : alert.title(data)}
19
+ <span className="text-sm">
20
+ {typeof def.title === 'string' ? def.title : def.title(alert.lastData)}
30
21
  </span>
31
- <span className="text-sm text-muted-foreground">
32
- {typeof alert.description === 'string' ? alert.description : alert.description?.(data)}
22
+ <span className="text-xs text-muted-foreground">
23
+ {typeof def.description === 'string'
24
+ ? def.description
25
+ : def.description?.(alert.lastData)}
33
26
  </span>
34
27
  </div>
35
28
  <div className="flex items-center gap-1">
36
- {alert.actions?.map(action => (
29
+ {def.actions?.map(action => (
37
30
  <Button
38
31
  key={action.label}
39
32
  variant="secondary"
40
33
  size="sm"
41
- onClick={() => action.onClick(data)}
34
+ onClick={async () => {
35
+ await action.onClick({ data: alert.lastData, dismiss: () => alert.dismiss() });
36
+ }}
42
37
  >
43
38
  {action.label}
44
39
  </Button>
@@ -1,23 +1,22 @@
1
- import { useQueries } from '@tanstack/react-query';
2
- import { useAlerts } from './alert-extensions.js';
1
+ import { useAlerts } from '@/vdb/hooks/use-alerts.js';
2
+ import { cn } from '@/vdb/lib/utils.js';
3
3
 
4
4
  export function AlertsIndicator() {
5
- const { alerts } = useAlerts();
5
+ const { activeCount, highestSeverity } = useAlerts();
6
6
 
7
- const alertsCount = useQueries({
8
- queries: alerts.map(alert => ({
9
- queryKey: ['alert', alert.id],
10
- queryFn: () => alert.check(),
11
- })),
12
- combine: results => {
13
- return results.filter((result, idx) => result.data && alerts[idx].shouldShow?.(result.data))
14
- .length;
15
- },
16
- });
7
+ if (activeCount === 0) {
8
+ return null;
9
+ }
17
10
 
18
11
  return (
19
- <div className="absolute -right-1 -top-1 rounded-full bg-red-500 text-xs w-4 h-4 flex items-center justify-center">
20
- {alertsCount}
12
+ <div
13
+ className={cn(
14
+ `absolute -right-1 -top-1 rounded-full bg-primary text-xs w-4 h-4 flex items-center justify-center`,
15
+ highestSeverity === 'error' && 'bg-destructive',
16
+ highestSeverity === 'warning' && 'bg-yellow-400 dark:bg-yellow-600',
17
+ )}
18
+ >
19
+ {activeCount}
21
20
  </div>
22
21
  );
23
22
  }
@@ -0,0 +1,41 @@
1
+ import { api } from '@/vdb/graphql/api.js';
2
+ import { graphql } from '@/vdb/graphql/graphql.js';
3
+ import { toast } from 'sonner';
4
+
5
+ import { DashboardAlertDefinition } from '../../extension-api/types/index.js';
6
+
7
+ const pendingSearchIndexUpdatesDocument = graphql(`
8
+ query GetPendingSearchIndexUpdates {
9
+ pendingSearchIndexUpdates
10
+ }
11
+ `);
12
+
13
+ export const runPendingSearchIndexUpdatesDocument = graphql(`
14
+ mutation RunPendingSearchIndexUpdates {
15
+ runPendingSearchIndexUpdates {
16
+ success
17
+ }
18
+ }
19
+ `);
20
+
21
+ export const searchIndexBufferAlert: DashboardAlertDefinition<number> = {
22
+ id: 'search-index-buffer-alert',
23
+ check: async () => {
24
+ const data = await api.query(pendingSearchIndexUpdatesDocument);
25
+ return data.pendingSearchIndexUpdates;
26
+ },
27
+ shouldShow: data => data > 0,
28
+ title: data => /* i18n*/ `${data} pending search index updates`,
29
+ severity: data => (data < 10 ? 'info' : 'warning'),
30
+ actions: [
31
+ {
32
+ label: /* i18n*/ `Run pending updates`,
33
+ onClick: async ({ dismiss }) => {
34
+ await api.mutate(runPendingSearchIndexUpdatesDocument, {});
35
+ toast.success(/* i18n*/ 'Running pending search index updates');
36
+ dismiss();
37
+ },
38
+ },
39
+ ],
40
+ recheckInterval: 60_000,
41
+ };
@@ -1,24 +1,13 @@
1
- import {
2
- DashboardFormComponent,
3
- DashboardFormComponentProps,
4
- } from '@/vdb/framework/form-engine/form-engine-types.js';
1
+ import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
5
2
  import * as React from 'react';
6
3
  import { getDisplayComponent } from '../extension-api/display-component-extensions.js';
7
4
  import { getInputComponent } from '../extension-api/input-component-extensions.js';
8
5
 
9
- export interface ComponentRegistryEntry<Props extends Record<string, any>> {
10
- component: React.ComponentType<Props>;
11
- }
12
-
13
- // Display component interface (unchanged)
14
- export interface DataDisplayComponentProps {
6
+ export type DataDisplayComponentProps<T extends Record<string, any> = Record<string, any>> = {
15
7
  value: any;
16
-
17
- [key: string]: any;
18
- }
8
+ } & T;
19
9
 
20
10
  export type DataDisplayComponent = React.ComponentType<DataDisplayComponentProps>;
21
- export type { DashboardFormComponentProps as DataInputComponentProps };
22
11
 
23
12
  // Component registry hook that uses the global registry
24
13
  export function useComponentRegistry() {
@@ -1,8 +1,7 @@
1
1
  import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/vdb/components/ui/card.js';
2
2
  import { DashboardBaseWidgetProps } from '@/vdb/framework/extension-api/types/index.js';
3
- import { Trans } from '@lingui/react/macro';
4
- import { useLingui } from '@lingui/react/macro';
5
3
  import { cn } from '@/vdb/lib/utils.js';
4
+ import { Trans, useLingui } from '@lingui/react/macro';
6
5
  import { createContext, useContext, useEffect, useRef, useState } from 'react';
7
6
 
8
7
  type WidgetDimensions = {
@@ -21,14 +20,24 @@ export const useWidgetDimensions = () => {
21
20
  return context;
22
21
  };
23
22
 
23
+ /**
24
+ * @description
25
+ * A wrapper component that should be used for any custom Insights page widgets.
26
+ * This ensures that your custom widget has all the basic functionality needed to be
27
+ * correctly rendered on the Insights page.
28
+ *
29
+ * @docsCategory extensions-api
30
+ * @docsPage widgets
31
+ * @since 3.3.0
32
+ */
24
33
  export function DashboardBaseWidget({
25
- id,
26
- config,
27
- children,
28
- title,
29
- description,
30
- actions,
31
- }: DashboardBaseWidgetProps) {
34
+ id,
35
+ config,
36
+ children,
37
+ title,
38
+ description,
39
+ actions,
40
+ }: DashboardBaseWidgetProps) {
32
41
  const headerRef = useRef<HTMLDivElement>(null);
33
42
  const wrapperRef = useRef<HTMLDivElement>(null);
34
43
  const contentRef = useRef<HTMLDivElement>(null);
@@ -5,7 +5,6 @@ import {
5
5
  OrderStateCell,
6
6
  } from '@/vdb/components/shared/table-cell/order-table-cell-components.js';
7
7
  import { Button } from '@/vdb/components/ui/button.js';
8
- import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
9
8
  import { useLingui } from '@lingui/react/macro';
10
9
  import { Link } from '@tanstack/react-router';
11
10
  import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
@@ -39,7 +38,6 @@ export function LatestOrdersWidget() {
39
38
  },
40
39
  },
41
40
  ]);
42
- const { formatCurrency } = useLocalFormat();
43
41
 
44
42
  // Update filters when date range changes
45
43
  useEffect(() => {
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { useLingui } from '@lingui/react/macro';
4
- import { createContext, useContext, PropsWithChildren } from 'react';
4
+ import { createContext, PropsWithChildren, useContext } from 'react';
5
5
 
6
6
  export interface DefinedDateRange {
7
7
  from: Date;
@@ -14,17 +14,18 @@ export interface WidgetFilters {
14
14
 
15
15
  const WidgetFiltersContext = createContext<WidgetFilters | undefined>(undefined);
16
16
 
17
- export function WidgetFiltersProvider({
18
- children,
19
- filters
20
- }: PropsWithChildren<{ filters: WidgetFilters }>) {
21
- return (
22
- <WidgetFiltersContext.Provider value={filters}>
23
- {children}
24
- </WidgetFiltersContext.Provider>
25
- );
17
+ export function WidgetFiltersProvider({ children, filters }: PropsWithChildren<{ filters: WidgetFilters }>) {
18
+ return <WidgetFiltersContext.Provider value={filters}>{children}</WidgetFiltersContext.Provider>;
26
19
  }
27
20
 
21
+ /**
22
+ * @description
23
+ * Exposes a context object for use in building Insights page widgets.
24
+ *
25
+ * @docsCategory hooks
26
+ * @docsPage useWidgetFilters
27
+ * @since 3.5.0
28
+ */
28
29
  export function useWidgetFilters() {
29
30
  const { t } = useLingui();
30
31
  const context = useContext(WidgetFiltersContext);
@@ -32,4 +33,4 @@ export function useWidgetFilters() {
32
33
  throw new Error(t`useWidgetFilters must be used within a WidgetFiltersProvider`);
33
34
  }
34
35
  return context;
35
- }
36
+ }
@@ -1,13 +1,7 @@
1
+ import { registerAlert } from '@/vdb/framework/alert/alert-extensions.js';
2
+ import { searchIndexBufferAlert } from '@/vdb/framework/alert/search-index-buffer-alert/search-index-buffer-alert.js';
1
3
  import { setNavMenuConfig } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
2
- import {
3
- LayoutDashboardIcon,
4
- Mail,
5
- Settings2,
6
- ShoppingCart,
7
- SquareTerminal,
8
- Terminal,
9
- Users,
10
- } from 'lucide-react';
4
+ import { ChartLine, Percent, Settings2, ShoppingBag, Tags, Terminal, Users } from 'lucide-react';
11
5
 
12
6
  import { LatestOrdersWidget } from './dashboard-widget/latest-orders-widget/index.js';
13
7
  import { MetricsWidget } from './dashboard-widget/metrics-widget/index.js';
@@ -21,14 +15,14 @@ export function registerDefaults() {
21
15
  id: 'insights',
22
16
  title: /* i18n*/ 'Insights',
23
17
  placement: 'top',
24
- icon: LayoutDashboardIcon,
18
+ icon: ChartLine,
25
19
  url: '/',
26
20
  order: 100,
27
21
  },
28
22
  {
29
23
  id: 'catalog',
30
24
  title: /* i18n*/ 'Catalog',
31
- icon: SquareTerminal,
25
+ icon: Tags,
32
26
  placement: 'top',
33
27
  order: 200,
34
28
  items: [
@@ -72,7 +66,7 @@ export function registerDefaults() {
72
66
  {
73
67
  id: 'sales',
74
68
  title: /* i18n*/ 'Sales',
75
- icon: ShoppingCart,
69
+ icon: ShoppingBag,
76
70
  placement: 'top',
77
71
  order: 300,
78
72
  items: [
@@ -111,7 +105,7 @@ export function registerDefaults() {
111
105
  {
112
106
  id: 'marketing',
113
107
  title: /* i18n*/ 'Marketing',
114
- icon: Mail,
108
+ icon: Percent,
115
109
  placement: 'top',
116
110
  order: 500,
117
111
  items: [
@@ -271,4 +265,6 @@ export function registerDefaults() {
271
265
  component: OrdersSummaryWidget,
272
266
  defaultSize: { w: 6, h: 3, x: 6, y: 0 },
273
267
  });
268
+
269
+ registerAlert(searchIndexBufferAlert);
274
270
  }
@@ -15,6 +15,11 @@ import { globalRegistry } from '../registry/global-registry.js';
15
15
 
16
16
  globalRegistry.register('inputComponents', new Map<string, DashboardFormComponent>());
17
17
 
18
+ const DefaultProductInput: DashboardFormComponent = props => (
19
+ <DefaultRelationInput {...props} entityType="ProductVariant" />
20
+ );
21
+ DefaultProductInput.metadata = { isListInput: 'dynamic' };
22
+
18
23
  // Register built-in input components
19
24
  const inputComponents = globalRegistry.get('inputComponents');
20
25
  inputComponents.set('facet-value-input', FacetValueInput);
@@ -28,7 +33,7 @@ inputComponents.set('textarea-form-input', TextareaInput);
28
33
  inputComponents.set('html-editor-form-input', RichTextInput);
29
34
  inputComponents.set('rich-text-form-input', RichTextInput);
30
35
  inputComponents.set('password-form-input', PasswordInput);
31
- inputComponents.set('product-selector-form-input', DefaultRelationInput);
36
+ inputComponents.set('product-selector-form-input', DefaultProductInput);
32
37
  inputComponents.set('relation-form-input', DefaultRelationInput);
33
38
  inputComponents.set('select-form-input', SelectWithOptions);
34
39
  inputComponents.set('product-multi-form-input', ProductMultiInput);
@@ -1,10 +1,11 @@
1
- import { globalRegistry } from '../../registry/global-registry.js';
1
+ import { registerAlert } from '@/vdb/framework/alert/alert-extensions.js';
2
+
2
3
  import { DashboardAlertDefinition } from '../types/alerts.js';
3
4
 
4
5
  export function registerAlertExtensions(alerts?: DashboardAlertDefinition[]) {
5
6
  if (alerts) {
6
7
  for (const alert of alerts) {
7
- globalRegistry.get('dashboardAlertRegistry').set(alert.id, alert);
8
+ registerAlert(alert);
8
9
  }
9
10
  }
10
11
  }
@@ -1,3 +1,5 @@
1
+ export type AlertSeverity = 'info' | 'warning' | 'error';
2
+
1
3
  /**
2
4
  * @description
3
5
  * Allows you to define custom alerts that can be displayed in the dashboard.
@@ -26,7 +28,7 @@ export interface DashboardAlertDefinition<TResponse = any> {
26
28
  * @description
27
29
  * The severity level of the alert.
28
30
  */
29
- severity: 'info' | 'warning' | 'error';
31
+ severity: AlertSeverity | ((data: TResponse) => AlertSeverity);
30
32
  /**
31
33
  * @description
32
34
  * A function that checks the condition and returns the response data.
@@ -34,20 +36,24 @@ export interface DashboardAlertDefinition<TResponse = any> {
34
36
  check: () => Promise<TResponse> | TResponse;
35
37
  /**
36
38
  * @description
37
- * The interval in milliseconds to recheck the condition.
39
+ * A function that determines whether the alert should be rendered based on the response data.
38
40
  */
39
- recheckInterval?: number;
41
+ shouldShow: (data: TResponse) => boolean;
40
42
  /**
41
43
  * @description
42
- * A function that determines whether the alert should be shown based on the response data.
44
+ * The interval in milliseconds to recheck the condition.
43
45
  */
44
- shouldShow?: (data: TResponse) => boolean;
46
+ recheckInterval?: number;
45
47
  /**
46
48
  * @description
47
49
  * Optional actions that can be performed when the alert is shown.
50
+ *
51
+ * The `onClick()` handler will receive the data returned by the `check` function,
52
+ * as well as a `dismiss()` function that can be used to immediately dismiss the
53
+ * current alert.
48
54
  */
49
55
  actions?: Array<{
50
56
  label: string;
51
- onClick: (data: TResponse) => void;
57
+ onClick: (args: { data: TResponse; dismiss: () => void }) => void | Promise<any>;
52
58
  }>;
53
59
  }
@@ -1,5 +1,8 @@
1
+ import { DataDisplayComponentProps } from '@/vdb/framework/component-registry/component-registry.js';
1
2
  import { Table } from '@tanstack/react-table';
3
+ import { CellContext } from '@tanstack/table-core';
2
4
  import { DocumentNode } from 'graphql';
5
+ import React from 'react';
3
6
 
4
7
  /**
5
8
  * @description
@@ -21,7 +24,7 @@ export interface DashboardDataTableDisplayComponent {
21
24
  * The React component that will be rendered as the display.
22
25
  * It should accept `value` and other standard display props.
23
26
  */
24
- component: React.ComponentType<{ value: any; [key: string]: any }>;
27
+ component: React.ComponentType<DataDisplayComponentProps<CellContext<any, any>>>;
25
28
  }
26
29
 
27
30
  export type BulkActionContext<Item extends { id: string } & Record<string, any>> = {
@@ -120,7 +123,7 @@ export type BulkAction = {
120
123
  * This allows you to customize aspects of existing data tables in the dashboard.
121
124
  *
122
125
  * @docsCategory extensions-api
123
- * @docsPage DataTable
126
+ * @docsPage DataTables
124
127
  * @since 3.4.0
125
128
  */
126
129
  export interface DashboardDataTableExtensionDefinition {
@@ -93,9 +93,49 @@ export type PageBlockLocation = {
93
93
  * @since 3.3.0
94
94
  */
95
95
  export interface DashboardPageBlockDefinition {
96
+ /**
97
+ * @description
98
+ * An ID for the page block. Should be unique at least
99
+ * to the page in which it appears.
100
+ */
96
101
  id: string;
102
+ /**
103
+ * @description
104
+ * An optional title for the page block
105
+ */
97
106
  title?: React.ReactNode;
107
+ /**
108
+ * @description
109
+ * The location of the page block. It specifies the pageId, and then the
110
+ * relative location compared to another existing block.
111
+ */
98
112
  location: PageBlockLocation;
99
- component: React.FunctionComponent<{ context: PageContextValue }>;
113
+ /**
114
+ * @description
115
+ * The component to be rendered inside the page block.
116
+ */
117
+ component?: React.FunctionComponent<{ context: PageContextValue }>;
118
+ /**
119
+ * @description
120
+ * Control whether to render the page block depending on your custom
121
+ * logic.
122
+ *
123
+ * This can also be used to disable any built-in blocks you
124
+ * do not need to display.
125
+ *
126
+ * If you need to query aspects about the current context not immediately
127
+ * provided in the `PageContextValue`, you can also use hooks such as
128
+ * `useChannel` in this function.
129
+ *
130
+ * @since 3.5.0
131
+ */
132
+ shouldRender?: (context: PageContextValue) => boolean;
133
+ /**
134
+ * @description
135
+ * If provided, the logged-in user must have one or more of the specified
136
+ * permissions in order for the block to render.
137
+ *
138
+ * For more advanced control over rendering, use the `shouldRender` function.
139
+ */
100
140
  requiresPermission?: string | string[];
101
141
  }
@@ -48,22 +48,6 @@ export interface LoginAfterFormExtension {
48
48
  component: React.ComponentType;
49
49
  }
50
50
 
51
- /**
52
- * @description
53
- * Defines a custom login image component that replaces the default image panel.
54
- *
55
- * @docsCategory extensions-api
56
- * @docsPage Login
57
- * @since 3.4.0
58
- */
59
- export interface LoginImageExtension {
60
- /**
61
- * @description
62
- * A React component that will replace the default login image panel.
63
- */
64
- component: React.ComponentType;
65
- }
66
-
67
51
  /**
68
52
  * @description
69
53
  * Defines all available login page extensions.
@@ -89,9 +73,4 @@ export interface DashboardLoginExtensions {
89
73
  * Component to render after the login form.
90
74
  */
91
75
  afterForm?: LoginAfterFormExtension;
92
- /**
93
- * @description
94
- * Custom login image component to replace the default image panel.
95
- */
96
- loginImage?: LoginImageExtension;
97
76
  }