@vendure/dashboard 3.5.0-minor-202510071456 → 3.5.0-minor-202510201346

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 (204) hide show
  1. package/dist/plugin/dashboard.plugin.js +1 -1
  2. package/dist/vite/utils/ast-utils.spec.js +3 -3
  3. package/dist/vite/vite-plugin-hmr.d.ts +8 -0
  4. package/dist/vite/vite-plugin-hmr.js +34 -0
  5. package/dist/vite/vite-plugin-theme.js +6 -6
  6. package/dist/vite/vite-plugin-transform-index.js +6 -1
  7. package/dist/vite/vite-plugin-vendure-dashboard.d.ts +31 -4
  8. package/dist/vite/vite-plugin-vendure-dashboard.js +89 -34
  9. package/package.json +17 -5
  10. package/src/app/app-providers.tsx +4 -1
  11. package/src/app/common/map-faceted-filter-fields.ts +21 -0
  12. package/src/app/main.tsx +3 -1
  13. package/src/app/routes/_authenticated/_administrators/administrators.graphql.ts +2 -2
  14. package/src/app/routes/_authenticated/_administrators/administrators.tsx +13 -3
  15. package/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx +6 -13
  16. package/src/app/routes/_authenticated/_administrators/components/role-permissions-display.tsx +1 -1
  17. package/src/app/routes/_authenticated/_assets/assets.tsx +17 -1
  18. package/src/app/routes/_authenticated/_collections/collections.graphql.ts +1 -0
  19. package/src/app/routes/_authenticated/_collections/collections.tsx +5 -0
  20. package/src/app/routes/_authenticated/_collections/components/collection-bulk-actions.tsx +0 -1
  21. package/src/app/routes/_authenticated/_customers/customers.tsx +9 -5
  22. package/src/app/routes/_authenticated/_facets/components/facet-bulk-actions.tsx +0 -6
  23. package/src/app/routes/_authenticated/_facets/components/facet-value-bulk-actions.tsx +16 -0
  24. package/src/app/routes/_authenticated/_facets/components/facet-values-table.tsx +43 -12
  25. package/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx +14 -5
  26. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +117 -92
  27. package/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx +1 -1
  28. package/src/app/routes/_authenticated/_orders/components/order-modification-summary.tsx +2 -1
  29. package/src/app/routes/_authenticated/_orders/components/order-table-totals.tsx +26 -27
  30. package/src/app/routes/_authenticated/_orders/components/order-table.tsx +5 -3
  31. package/src/app/routes/_authenticated/_orders/components/state-transition-control.tsx +6 -9
  32. package/src/app/routes/_authenticated/_orders/orders.graphql.ts +17 -1
  33. package/src/app/routes/_authenticated/_orders/orders.tsx +2 -0
  34. package/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx +48 -281
  35. package/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx +59 -40
  36. package/src/app/routes/_authenticated/_orders/utils/order-utils.ts +73 -0
  37. package/src/app/routes/_authenticated/_orders/utils/use-modify-order.ts +312 -0
  38. package/src/app/routes/_authenticated/_payment-methods/payment-methods.graphql.ts +2 -2
  39. package/src/app/routes/_authenticated/_payment-methods/payment-methods.tsx +4 -0
  40. package/src/app/routes/_authenticated/_product-variants/product-variants.tsx +2 -0
  41. package/src/app/routes/_authenticated/_products/components/product-bulk-actions.tsx +0 -6
  42. package/src/app/routes/_authenticated/_products/products.tsx +6 -2
  43. package/src/app/routes/_authenticated/_products/products_.$productId.option-groups.$productOptionGroupId.options_.$id.tsx +4 -8
  44. package/src/app/routes/_authenticated/_promotions/components/promotion-bulk-actions.tsx +0 -10
  45. package/src/app/routes/_authenticated/_promotions/promotions.graphql.ts +2 -2
  46. package/src/app/routes/_authenticated/_promotions/promotions.tsx +12 -0
  47. package/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx +6 -2
  48. package/src/app/routes/_authenticated/_sellers/sellers.graphql.ts +2 -2
  49. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.graphql.ts +2 -2
  50. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods.tsx +4 -0
  51. package/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx +4 -10
  52. package/src/app/routes/_authenticated/_stock-locations/stock-locations.graphql.ts +2 -2
  53. package/src/app/routes/_authenticated/_tax-categories/tax-categories.graphql.ts +2 -2
  54. package/src/app/routes/_authenticated/_tax-rates/tax-rates.tsx +9 -0
  55. package/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx +1 -0
  56. package/src/app/routes/_authenticated/_zones/zones.graphql.ts +2 -2
  57. package/src/app/routes/login.tsx +2 -2
  58. package/src/i18n/locales/ar.po +420 -289
  59. package/src/i18n/locales/cs.po +420 -289
  60. package/src/i18n/locales/de.po +420 -289
  61. package/src/i18n/locales/en.po +420 -289
  62. package/src/i18n/locales/es.po +420 -289
  63. package/src/i18n/locales/fa.po +420 -289
  64. package/src/i18n/locales/fr.po +468 -337
  65. package/src/i18n/locales/he.po +420 -289
  66. package/src/i18n/locales/hr.po +420 -289
  67. package/src/i18n/locales/it.po +420 -289
  68. package/src/i18n/locales/ja.po +420 -289
  69. package/src/i18n/locales/nb.po +420 -289
  70. package/src/i18n/locales/ne.po +420 -289
  71. package/src/i18n/locales/pl.po +420 -289
  72. package/src/i18n/locales/pt_BR.po +420 -289
  73. package/src/i18n/locales/pt_PT.po +420 -289
  74. package/src/i18n/locales/ru.po +420 -289
  75. package/src/i18n/locales/sv.po +420 -289
  76. package/src/i18n/locales/tr.po +420 -289
  77. package/src/i18n/locales/uk.po +420 -289
  78. package/src/i18n/locales/zh_Hans.po +420 -289
  79. package/src/i18n/locales/zh_Hant.po +420 -289
  80. package/src/lib/components/data-input/affixed-input.stories.tsx +93 -0
  81. package/src/lib/components/data-input/affixed-input.tsx +5 -2
  82. package/src/lib/components/data-input/boolean-input.stories.tsx +102 -0
  83. package/src/lib/components/data-input/checkbox-input.stories.tsx +61 -0
  84. package/src/lib/components/data-input/datetime-input.stories.tsx +62 -0
  85. package/src/lib/components/data-input/datetime-input.tsx +27 -13
  86. package/src/lib/components/data-input/default-relation-input.tsx +18 -12
  87. package/src/lib/components/data-input/money-input.stories.tsx +88 -0
  88. package/src/lib/components/data-input/number-input.stories.tsx +103 -0
  89. package/src/lib/components/data-input/number-input.tsx +10 -4
  90. package/src/lib/components/data-input/password-form-input.stories.tsx +65 -0
  91. package/src/lib/components/data-input/{password-input.tsx → password-form-input.tsx} +1 -1
  92. package/src/lib/components/data-input/rich-text-input.stories.tsx +92 -0
  93. package/src/lib/components/data-input/slug-input.stories.tsx +232 -0
  94. package/src/lib/components/data-input/slug-input.tsx +9 -10
  95. package/src/lib/components/data-input/text-input.stories.tsx +52 -0
  96. package/src/lib/components/data-input/textarea-input.stories.tsx +55 -0
  97. package/src/lib/components/data-table/add-filter-menu.tsx +6 -1
  98. package/src/lib/components/data-table/column-header-wrapper.tsx +106 -0
  99. package/src/lib/components/data-table/data-table-bulk-action-item.tsx +11 -9
  100. package/src/lib/components/data-table/data-table-bulk-actions.tsx +4 -4
  101. package/src/lib/components/data-table/data-table-column-header.tsx +17 -14
  102. package/src/lib/components/data-table/data-table-faceted-filter.tsx +33 -11
  103. package/src/lib/components/data-table/data-table-filter-badge-editable.tsx +35 -0
  104. package/src/lib/components/data-table/data-table-filter-badge.tsx +23 -16
  105. package/src/lib/components/data-table/data-table-filter-dialog.tsx +28 -8
  106. package/src/lib/components/data-table/data-table-pagination.tsx +23 -7
  107. package/src/lib/components/data-table/data-table.stories.tsx +249 -0
  108. package/src/lib/components/data-table/data-table.tsx +37 -9
  109. package/src/lib/components/data-table/filters/data-table-datetime-filter.tsx +79 -34
  110. package/src/lib/components/data-table/use-generated-columns.tsx +55 -27
  111. package/src/lib/components/layout/nav-user.tsx +19 -13
  112. package/src/lib/components/login/login-form.tsx +39 -123
  113. package/src/lib/components/shared/alerts.tsx +29 -17
  114. package/src/lib/components/shared/asset/asset-bulk-actions.tsx +3 -3
  115. package/src/lib/components/shared/asset/asset-gallery.stories.tsx +76 -0
  116. package/src/lib/components/shared/asset/asset-gallery.tsx +147 -113
  117. package/src/lib/components/shared/asset/asset-picker-dialog.stories.tsx +58 -0
  118. package/src/lib/components/shared/customer-group-selector.tsx +5 -2
  119. package/src/lib/components/shared/detail-page-button.stories.tsx +52 -0
  120. package/src/lib/components/shared/facet-value-selector.stories.tsx +48 -0
  121. package/src/lib/components/shared/facet-value-selector.tsx +130 -34
  122. package/src/lib/components/shared/paginated-list-data-table.stories.tsx +212 -0
  123. package/src/lib/components/shared/paginated-list-data-table.tsx +12 -12
  124. package/src/lib/components/shared/permission-guard.stories.tsx +46 -0
  125. package/src/lib/components/shared/remove-from-channel-bulk-action.tsx +2 -0
  126. package/src/lib/components/shared/rich-text-editor/responsive-toolbar.tsx +8 -4
  127. package/src/lib/components/shared/rich-text-editor/rich-text-editor.tsx +1 -0
  128. package/src/lib/components/shared/table-cell/order-table-cell-components.tsx +40 -0
  129. package/src/lib/components/shared/vendure-image.stories.tsx +167 -0
  130. package/src/lib/components/shared/vendure-image.tsx +6 -7
  131. package/src/lib/components/ui/accordion.stories.tsx +33 -0
  132. package/src/lib/components/ui/alert-dialog.stories.tsx +48 -0
  133. package/src/lib/components/ui/alert.stories.tsx +35 -0
  134. package/src/lib/components/ui/aspect-ratio.stories.tsx +28 -0
  135. package/src/lib/components/ui/badge.stories.tsx +28 -0
  136. package/src/lib/components/ui/breadcrumb.stories.tsx +41 -0
  137. package/src/lib/components/ui/button.stories.tsx +38 -0
  138. package/src/lib/components/ui/calendar.stories.tsx +22 -0
  139. package/src/lib/components/ui/card.stories.tsx +28 -0
  140. package/src/lib/components/ui/carousel.stories.tsx +34 -0
  141. package/src/lib/components/ui/checkbox.stories.tsx +31 -0
  142. package/src/lib/components/ui/collapsible.stories.tsx +39 -0
  143. package/src/lib/components/ui/command.stories.tsx +44 -0
  144. package/src/lib/components/ui/context-menu.stories.tsx +38 -0
  145. package/src/lib/components/ui/dialog.stories.tsx +52 -0
  146. package/src/lib/components/ui/drawer.stories.tsx +50 -0
  147. package/src/lib/components/ui/dropdown-menu.stories.tsx +41 -0
  148. package/src/lib/components/ui/hover-card.stories.tsx +38 -0
  149. package/src/lib/components/ui/input-group.tsx +148 -0
  150. package/src/lib/components/ui/input-otp.stories.tsx +30 -0
  151. package/src/lib/components/ui/input.stories.tsx +38 -0
  152. package/src/lib/components/ui/label.stories.tsx +24 -0
  153. package/src/lib/components/ui/menubar.stories.tsx +53 -0
  154. package/src/lib/components/ui/navigation-menu.stories.tsx +54 -0
  155. package/src/lib/components/ui/pagination.stories.tsx +51 -0
  156. package/src/lib/components/ui/password-input.stories.tsx +32 -0
  157. package/src/lib/components/ui/password-input.tsx +29 -0
  158. package/src/lib/components/ui/popover.stories.tsx +33 -0
  159. package/src/lib/components/ui/progress.stories.tsx +27 -0
  160. package/src/lib/components/ui/radio-group.stories.tsx +34 -0
  161. package/src/lib/components/ui/resizable.stories.tsx +32 -0
  162. package/src/lib/components/ui/scroll-area.stories.tsx +31 -0
  163. package/src/lib/components/ui/select.stories.tsx +36 -0
  164. package/src/lib/components/ui/separator.stories.tsx +35 -0
  165. package/src/lib/components/ui/sheet.stories.tsx +50 -0
  166. package/src/lib/components/ui/sidebar-context.ts +16 -0
  167. package/src/lib/components/ui/sidebar.tsx +2 -13
  168. package/src/lib/components/ui/skeleton.stories.tsx +26 -0
  169. package/src/lib/components/ui/slider.stories.tsx +37 -0
  170. package/src/lib/components/ui/switch.stories.tsx +31 -0
  171. package/src/lib/components/ui/table.stories.tsx +52 -0
  172. package/src/lib/components/ui/tabs.stories.tsx +29 -0
  173. package/src/lib/components/ui/textarea.stories.tsx +32 -0
  174. package/src/lib/components/ui/toggle-group.stories.tsx +31 -0
  175. package/src/lib/components/ui/toggle.stories.tsx +39 -0
  176. package/src/lib/components/ui/tooltip.stories.tsx +30 -0
  177. package/src/lib/components/ui/tooltip.tsx +2 -2
  178. package/src/lib/framework/alert/alert-extensions.tsx +0 -11
  179. package/src/lib/framework/alert/alert-item.tsx +14 -19
  180. package/src/lib/framework/alert/alerts-indicator.tsx +14 -15
  181. package/src/lib/framework/alert/search-index-buffer-alert/search-index-buffer-alert.ts +41 -0
  182. package/src/lib/framework/component-registry/component-registry.tsx +3 -14
  183. package/src/lib/framework/dashboard-widget/base-widget.tsx +18 -9
  184. package/src/lib/framework/dashboard-widget/widget-filters-context.tsx +12 -11
  185. package/src/lib/framework/defaults.ts +9 -13
  186. package/src/lib/framework/extension-api/input-component-extensions.tsx +8 -3
  187. package/src/lib/framework/extension-api/logic/alerts.ts +3 -2
  188. package/src/lib/framework/extension-api/types/alerts.ts +12 -6
  189. package/src/lib/framework/extension-api/types/data-table.ts +5 -2
  190. package/src/lib/framework/extension-api/types/login.ts +0 -21
  191. package/src/lib/framework/layout-engine/custom-form-page.stories.tsx +344 -0
  192. package/src/lib/framework/layout-engine/page-layout.tsx +11 -9
  193. package/src/lib/framework/layout-engine/page.stories.tsx +275 -0
  194. package/src/lib/framework/nav-menu/nav-menu-extensions.ts +32 -19
  195. package/src/lib/framework/page/detail-page.stories.tsx +151 -0
  196. package/src/lib/framework/page/list-page.stories.tsx +217 -0
  197. package/src/lib/framework/page/list-page.tsx +8 -1
  198. package/src/lib/graphql/api.ts +18 -1
  199. package/src/lib/graphql/graphql-env.d.ts +1 -1
  200. package/src/lib/hooks/use-alerts.ts +84 -0
  201. package/src/lib/hooks/use-floating-bulk-actions.ts +2 -3
  202. package/src/lib/index.ts +14 -1
  203. package/src/lib/providers/alerts-provider.tsx +60 -0
  204. 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 { AlertEntry } 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: AlertEntry;
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);
@@ -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
  }
@@ -8,13 +8,18 @@ import {
8
8
  RichTextInput,
9
9
  SelectWithOptions,
10
10
  } from '@/vdb/components/data-input/index.js';
11
- import { PasswordInput } from '@/vdb/components/data-input/password-input.js';
11
+ import { PasswordFormInput } from '@/vdb/components/data-input/password-form-input.js';
12
12
  import { TextareaInput } from '@/vdb/components/data-input/textarea-input.js';
13
13
  import { DashboardFormComponent } from '@/vdb/framework/form-engine/form-engine-types.js';
14
14
  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);
@@ -27,8 +32,8 @@ inputComponents.set('json-editor-form-input', TextareaInput);
27
32
  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
- inputComponents.set('password-form-input', PasswordInput);
31
- inputComponents.set('product-selector-form-input', DefaultRelationInput);
35
+ inputComponents.set('password-form-input', PasswordFormInput);
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 {
@@ -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
  }