@vendure/dashboard 3.7.1-master-202607110305 → 3.7.1

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 (141) hide show
  1. package/dist/bundle/chunks/{2SSKDMRQ-BXz8vjrT.js → 2SSKDMRQ-CGPzX7ok.js} +2 -2
  2. package/dist/bundle/chunks/{AP7HFJJL-BcewBvAf.js → AP7HFJJL-BANaC6nc.js} +1 -1
  3. package/dist/bundle/chunks/{WDYDFRGG-Cd364FN_.js → WDYDFRGG-DWwEqh2h.js} +2 -2
  4. package/dist/bundle/chunks/ar-DCEbHB2w.js +4 -0
  5. package/dist/bundle/chunks/bg-Dfcm3H-7.js +4 -0
  6. package/dist/bundle/chunks/cs-CAXaYQpr.js +4 -0
  7. package/dist/bundle/chunks/de-2rMAToGI.js +4 -0
  8. package/dist/bundle/chunks/en-CBy7m12i.js +4 -0
  9. package/dist/bundle/chunks/es-DhcrnvxO.js +4 -0
  10. package/dist/bundle/chunks/fa-BvgI8zZ5.js +4 -0
  11. package/dist/bundle/chunks/fr-B1Aw3cxh.js +4 -0
  12. package/dist/bundle/chunks/he-Dvum09Nk.js +4 -0
  13. package/dist/bundle/chunks/hr-CiUxzJsU.js +4 -0
  14. package/dist/bundle/chunks/hu-nmofxtH_.js +4 -0
  15. package/dist/bundle/chunks/it-CHi8ynGm.js +4 -0
  16. package/dist/bundle/chunks/ja-zasGFj1j.js +4 -0
  17. package/dist/bundle/chunks/ko-CBy7m12i.js +4 -0
  18. package/dist/bundle/chunks/{locale-YfvnivAQ.js → locale--aauL66m.js} +1 -1
  19. package/dist/bundle/chunks/{main-CG6JVORh.js → main-DVt3RsH7.js} +385 -1076
  20. package/dist/bundle/chunks/nb-DL1AcZeO.js +4 -0
  21. package/dist/bundle/chunks/ne-4aOoOw2J.js +4 -0
  22. package/dist/bundle/chunks/nl-Bf1kuLeC.js +4 -0
  23. package/dist/bundle/chunks/pl-Ak_kyBWq.js +4 -0
  24. package/dist/bundle/chunks/pt_BR-DS-J3yJI.js +4 -0
  25. package/dist/bundle/chunks/pt_PT-CQT5peF9.js +4 -0
  26. package/dist/bundle/chunks/ro-BJ2PuiOl.js +4 -0
  27. package/dist/bundle/chunks/ru-Bc_E807P.js +4 -0
  28. package/dist/bundle/chunks/sv-CZhEzelP.js +4 -0
  29. package/dist/bundle/chunks/tr-mdPJPyeZ.js +4 -0
  30. package/dist/bundle/chunks/uk-fAchGf1H.js +4 -0
  31. package/dist/bundle/chunks/{use-job-queue-polling-BESJ2s1g.js → use-job-queue-polling-mUjHlukj.js} +1329 -12523
  32. package/dist/bundle/chunks/uz-B5L-lMAZ.js +4 -0
  33. package/dist/bundle/chunks/zh_Hans-C8s-LcKM.js +4 -0
  34. package/dist/bundle/chunks/zh_Hant-CGwnZIiO.js +4 -0
  35. package/dist/bundle/lib.js +584 -577
  36. package/dist/bundle/main.js +4 -2
  37. package/dist/plugin/service/metrics.service.js +1 -1
  38. package/dist/vite/lib-externals.d.ts +44 -0
  39. package/dist/vite/lib-externals.js +80 -0
  40. package/dist/vite/vite-plugin-config.js +8 -0
  41. package/package.json +4 -3
  42. package/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-bulk-actions.tsx +69 -0
  43. package/src/app/routes/_authenticated/_customer-groups/components/customer-group-members-table.tsx +12 -2
  44. package/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx +79 -44
  45. package/src/app/routes/_authenticated/_products/components/force-remove-option-group-dialog.tsx +66 -0
  46. package/src/app/routes/_authenticated/_products/components/product-option-group-badge.tsx +66 -12
  47. package/src/app/routes/_authenticated/_products/hooks/use-remove-option-group.ts +90 -0
  48. package/src/app/routes/_authenticated/_products/products_.$id.tsx +15 -21
  49. package/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx +34 -75
  50. package/src/i18n/locales/ar.po +89 -63
  51. package/src/i18n/locales/bg.po +89 -63
  52. package/src/i18n/locales/cs.po +89 -63
  53. package/src/i18n/locales/de.po +89 -63
  54. package/src/i18n/locales/en.po +89 -63
  55. package/src/i18n/locales/es.po +89 -63
  56. package/src/i18n/locales/fa.po +89 -63
  57. package/src/i18n/locales/fr.po +89 -63
  58. package/src/i18n/locales/he.po +89 -63
  59. package/src/i18n/locales/hr.po +89 -63
  60. package/src/i18n/locales/hu.po +89 -63
  61. package/src/i18n/locales/it.po +89 -63
  62. package/src/i18n/locales/ja.po +89 -63
  63. package/src/i18n/locales/ko.po +16 -0
  64. package/src/i18n/locales/nb.po +89 -63
  65. package/src/i18n/locales/ne.po +89 -63
  66. package/src/i18n/locales/nl.po +89 -63
  67. package/src/i18n/locales/pl.po +89 -63
  68. package/src/i18n/locales/pt_BR.po +89 -63
  69. package/src/i18n/locales/pt_PT.po +89 -63
  70. package/src/i18n/locales/ro.po +89 -63
  71. package/src/i18n/locales/ru.po +89 -63
  72. package/src/i18n/locales/sv.po +89 -63
  73. package/src/i18n/locales/tr.po +89 -63
  74. package/src/i18n/locales/uk.po +89 -63
  75. package/src/i18n/locales/uz.po +89 -63
  76. package/src/i18n/locales/zh_Hans.po +89 -63
  77. package/src/i18n/locales/zh_Hant.po +89 -63
  78. package/src/lib/components/layout/nav-main.tsx +3 -1
  79. package/src/lib/framework/document-introspection/add-custom-fields.ts +2 -2
  80. package/src/lib/framework/document-introspection/include-only-selected-list-fields.ts +4 -1
  81. package/src/lib/framework/extension-api/custom-providers.ts +1 -1
  82. package/dist/bundle/chunks/ar-BGF27StW.js +0 -4
  83. package/dist/bundle/chunks/bg-DkqOGEvG.js +0 -4
  84. package/dist/bundle/chunks/cs-CKnEitnA.js +0 -4
  85. package/dist/bundle/chunks/de-B-hSTdcF.js +0 -4
  86. package/dist/bundle/chunks/en-4EbLSxP4.js +0 -4
  87. package/dist/bundle/chunks/es-BJr_pTZZ.js +0 -4
  88. package/dist/bundle/chunks/fa-NHk9jRAC.js +0 -4
  89. package/dist/bundle/chunks/fr-gP2g5_ht.js +0 -4
  90. package/dist/bundle/chunks/he-Xr8Cx_mh.js +0 -4
  91. package/dist/bundle/chunks/hr-DzjCNMEU.js +0 -4
  92. package/dist/bundle/chunks/hu-CXOlSiP6.js +0 -4
  93. package/dist/bundle/chunks/it-DYujYua2.js +0 -4
  94. package/dist/bundle/chunks/ja-ZaE7h4EQ.js +0 -4
  95. package/dist/bundle/chunks/ko-4EbLSxP4.js +0 -4
  96. package/dist/bundle/chunks/nb-BGkcDLkm.js +0 -4
  97. package/dist/bundle/chunks/ne-DDJwrpmB.js +0 -4
  98. package/dist/bundle/chunks/nl-DaJt2O1a.js +0 -4
  99. package/dist/bundle/chunks/pl-DQ-xjSLt.js +0 -4
  100. package/dist/bundle/chunks/pt_BR-Bv3fwQyM.js +0 -4
  101. package/dist/bundle/chunks/pt_PT-DdlVuwFg.js +0 -4
  102. package/dist/bundle/chunks/ro-CsBx1KAe.js +0 -4
  103. package/dist/bundle/chunks/ru-CfK8XmB-.js +0 -4
  104. package/dist/bundle/chunks/sv-DpW1nyx4.js +0 -4
  105. package/dist/bundle/chunks/tr-iM7XvP3W.js +0 -4
  106. package/dist/bundle/chunks/uk-Cc2aTucO.js +0 -4
  107. package/dist/bundle/chunks/uz-Bp06aVxW.js +0 -4
  108. package/dist/bundle/chunks/zh_Hans-D1qOG21m.js +0 -4
  109. package/dist/bundle/chunks/zh_Hant-C2Tj1zDP.js +0 -4
  110. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/dev-config.js +0 -251
  111. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/package.json +0 -4
  112. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/nav-modifier-plugin/nav-modifier-plugin.js +0 -23
  113. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/api/api-extensions.js +0 -99
  114. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/api/product-entity.resolver.js +0 -73
  115. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/api/product-review-admin.resolver.js +0 -146
  116. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/api/product-review-entity.resolver.js +0 -79
  117. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/api/product-review-shop.resolver.js +0 -79
  118. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/entities/product-review-translation.entity.js +0 -47
  119. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/entities/product-review.entity.js +0 -90
  120. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/generated-admin-types.js +0 -1021
  121. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/generated-shop-types.js +0 -974
  122. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/reviews-plugin.js +0 -46
  123. package/dist/vite/.vendure-dashboard-temp/compiler/4346-7f116ae5-e526-4aad-b671-b514921c7c92/test-plugins/reviews/types.js +0 -2
  124. package/dist/vite/.vendure-dashboard-temp/gql-tada/4346-7f116ae5-e526-4aad-b671-b514921c7c92/schema.graphql +0 -6844
  125. package/dist/vite/.vendure-dashboard-temp/gql-tada/4346-7f116ae5-e526-4aad-b671-b514921c7c92/tsconfig.json +0 -10
  126. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/dev-config.js +0 -0
  127. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/package.json +0 -0
  128. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/nav-modifier-plugin/nav-modifier-plugin.js +0 -0
  129. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/api/api-extensions.js +0 -0
  130. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/api/product-entity.resolver.js +0 -0
  131. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/api/product-review-admin.resolver.js +0 -0
  132. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/api/product-review-entity.resolver.js +0 -0
  133. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/api/product-review-shop.resolver.js +0 -0
  134. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/entities/product-review-translation.entity.js +0 -0
  135. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/entities/product-review.entity.js +0 -0
  136. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/generated-admin-types.js +0 -0
  137. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/generated-shop-types.js +0 -0
  138. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/reviews-plugin.js +0 -0
  139. /package/dist/vite/.vendure-dashboard-temp/compiler/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/test-plugins/reviews/types.js +0 -0
  140. /package/dist/vite/.vendure-dashboard-temp/gql-tada/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/schema.graphql +0 -0
  141. /package/dist/vite/.vendure-dashboard-temp/gql-tada/{3392-6cc5883f-d536-4750-80a8-e56ef4d1f34c → 3313-17d5f23c-1ffd-4e86-b905-7235d2fbfa10}/tsconfig.json +0 -0
@@ -1,6 +1,8 @@
1
1
  import "react/jsx-runtime";
2
- import "./chunks/use-job-queue-polling-BESJ2s1g.js";
3
- import "./chunks/main-CG6JVORh.js";
2
+ import "./chunks/use-job-queue-polling-mUjHlukj.js";
3
+ import "sonner";
4
+ import "./chunks/main-DVt3RsH7.js";
5
+ import "@tanstack/react-router";
4
6
  import "react";
5
7
  import "react-dom";
6
8
  import "react-dom/client";
@@ -34,7 +34,7 @@ let MetricsService = class MetricsService {
34
34
  .update(JSON.stringify({
35
35
  startDate: calculatedStartDate,
36
36
  endDate: calculatedEndDate,
37
- types: [...types].sort(),
37
+ types: [...types].sort((a, b) => a.localeCompare(b)),
38
38
  channel: ctx.channel.token,
39
39
  }))
40
40
  .digest('base64');
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Dependencies kept **external** in the pre-built dashboard bundle
3
+ * (`dist/bundle`, produced by `vite.lib.config.mts`).
4
+ *
5
+ * Anything not listed here is frozen into the bundle's shared chunks. That is
6
+ * fine for leaf utilities, but a library that (a) owns a React Context or other
7
+ * module-level singleton AND (b) is reachable by extension code through the
8
+ * `@vendure/dashboard` public API MUST be external. Otherwise the consumer's
9
+ * separate extension build re-bundles a second copy: the two module instances
10
+ * each call `createContext()`, and the extension's hook reads a different
11
+ * context than the one the dashboard's provider populated — the "No QueryClient
12
+ * set" class of failure. See issue #4919.
13
+ *
14
+ * A duplication audit that turns this contract into a test lives in
15
+ * `vite/tests/bundle-singleton.spec.ts`.
16
+ */
17
+ /**
18
+ * Always-external: resolved from the consumer's own module graph at runtime
19
+ * (React, Lingui) or injected by the consumer's Vite plugins (`virtual:*`).
20
+ */
21
+ export declare const runtimePeers: Array<string | RegExp>;
22
+ /**
23
+ * Context- and singleton-bearing libraries that must be **shared** between the
24
+ * dashboard app and extension code rather than frozen into the bundle. Keeping
25
+ * them external means both `main.js` (which renders the providers) and an
26
+ * extension's separately-built code resolve to the consumer's single instance.
27
+ */
28
+ export declare const singletonSharedDeps: Array<string | RegExp>;
29
+ /**
30
+ * Bare specifiers of {@link singletonSharedDeps}, for the consumer dev server's
31
+ * dep optimizer (`optimizeDeps.include`) so every bare import — from the
32
+ * pre-built `main.js`, from `lib.js`, and from extension code — resolves to one
33
+ * pre-bundled instance.
34
+ */
35
+ export declare const singletonSharedDepNames: string[];
36
+ /**
37
+ * The full external list for the dashboard bundle build.
38
+ */
39
+ export declare const dashboardBundleExternals: Array<string | RegExp>;
40
+ /**
41
+ * Returns `true` if `id` is matched by any entry in `externals` (string exact
42
+ * match or subpath, or RegExp test).
43
+ */
44
+ export declare function isExternalId(id: string, externals: Array<string | RegExp>): boolean;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Dependencies kept **external** in the pre-built dashboard bundle
3
+ * (`dist/bundle`, produced by `vite.lib.config.mts`).
4
+ *
5
+ * Anything not listed here is frozen into the bundle's shared chunks. That is
6
+ * fine for leaf utilities, but a library that (a) owns a React Context or other
7
+ * module-level singleton AND (b) is reachable by extension code through the
8
+ * `@vendure/dashboard` public API MUST be external. Otherwise the consumer's
9
+ * separate extension build re-bundles a second copy: the two module instances
10
+ * each call `createContext()`, and the extension's hook reads a different
11
+ * context than the one the dashboard's provider populated — the "No QueryClient
12
+ * set" class of failure. See issue #4919.
13
+ *
14
+ * A duplication audit that turns this contract into a test lives in
15
+ * `vite/tests/bundle-singleton.spec.ts`.
16
+ */
17
+ /**
18
+ * Always-external: resolved from the consumer's own module graph at runtime
19
+ * (React, Lingui) or injected by the consumer's Vite plugins (`virtual:*`).
20
+ */
21
+ export const runtimePeers = [
22
+ // React core (peer deps in user projects)
23
+ 'react',
24
+ 'react-dom',
25
+ 'react-dom/client',
26
+ 'react/jsx-runtime',
27
+ 'react/jsx-dev-runtime',
28
+ // Lingui — macros are transformed separately; keep the runtime external
29
+ /^@lingui\//,
30
+ // Virtual modules — resolved by the consumer's vendureDashboardPlugin
31
+ /^virtual:/,
32
+ // @vendure/common is a peer of every Vendure plugin
33
+ /^@vendure\/common(\/|$)/,
34
+ // The dashboard's own subpath exports
35
+ '@vendure/dashboard/plugin',
36
+ '@vendure/dashboard/vite',
37
+ ];
38
+ /**
39
+ * Context- and singleton-bearing libraries that must be **shared** between the
40
+ * dashboard app and extension code rather than frozen into the bundle. Keeping
41
+ * them external means both `main.js` (which renders the providers) and an
42
+ * extension's separately-built code resolve to the consumer's single instance.
43
+ */
44
+ export const singletonSharedDeps = [
45
+ // @tanstack/react-query — QueryClientContext
46
+ '@tanstack/react-query',
47
+ // react-hook-form — FormProvider context
48
+ 'react-hook-form',
49
+ // @tanstack/react-router — RouterContext (companion packages travel with it
50
+ // since the whole package stays external)
51
+ /^@tanstack\/react-router(\/|$)/,
52
+ // sonner — module-level toast observer
53
+ 'sonner',
54
+ ];
55
+ /**
56
+ * Bare specifiers of {@link singletonSharedDeps}, for the consumer dev server's
57
+ * dep optimizer (`optimizeDeps.include`) so every bare import — from the
58
+ * pre-built `main.js`, from `lib.js`, and from extension code — resolves to one
59
+ * pre-bundled instance.
60
+ */
61
+ export const singletonSharedDepNames = [
62
+ '@tanstack/react-query',
63
+ 'react-hook-form',
64
+ '@tanstack/react-router',
65
+ 'sonner',
66
+ ];
67
+ /**
68
+ * The full external list for the dashboard bundle build.
69
+ */
70
+ export const dashboardBundleExternals = [
71
+ ...runtimePeers,
72
+ ...singletonSharedDeps,
73
+ ];
74
+ /**
75
+ * Returns `true` if `id` is matched by any entry in `externals` (string exact
76
+ * match or subpath, or RegExp test).
77
+ */
78
+ export function isExternalId(id, externals) {
79
+ return externals.some(entry => typeof entry === 'string' ? id === entry || id.startsWith(`${entry}/`) : entry.test(id));
80
+ }
@@ -1,4 +1,5 @@
1
1
  import path from 'path';
2
+ import { singletonSharedDepNames } from './lib-externals.js';
2
3
  export function viteConfigPlugin({ packageRoot, useExperimentalBundle }) {
3
4
  return {
4
5
  name: 'vendure:vite-config-plugin',
@@ -67,6 +68,13 @@ export function viteConfigPlugin({ packageRoot, useExperimentalBundle }) {
67
68
  'use-sync-external-store/shim',
68
69
  'use-sync-external-store/shim/with-selector',
69
70
  '@messageformat/parser',
71
+ // In bundle mode these context/singleton libraries are kept
72
+ // external from the pre-built bundle (see lib-externals.ts),
73
+ // so pre-bundle them here to guarantee that main.js, lib.js
74
+ // and extension code all resolve to ONE instance. Without
75
+ // this the dashboard's provider and an extension's hook can
76
+ // bind to different module instances — issue #4919.
77
+ ...(useExperimentalBundle ? singletonSharedDepNames : []),
70
78
  ] });
71
79
  return config;
72
80
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vendure/dashboard",
3
3
  "private": false,
4
- "version": "3.7.1-master-202607110305",
4
+ "version": "3.7.1",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
@@ -22,6 +22,7 @@
22
22
  "build": "npm run check-types && npm run build:vite && npm run build:plugin && npm run build:lib",
23
23
  "watch": "tsc --project tsconfig.vite.json --watch",
24
24
  "test": "vitest run",
25
+ "audit:bundle": "cross-env RUN_BUNDLE_AUDIT=true vitest run vite/tests/bundle-singleton.spec.ts vite/tests/bundle-singleton-dev-serving.spec.ts",
25
26
  "lint": "eslint .",
26
27
  "preview": "vite preview",
27
28
  "generate-index": "node scripts/generate-index.js",
@@ -138,8 +139,8 @@
138
139
  "@storybook/addon-vitest": "^10.3.1",
139
140
  "@storybook/react-vite": "^10.3.1",
140
141
  "@types/node": "^22.19.0",
141
- "@vendure/common": "^3.7.1-master-202607110305",
142
- "@vendure/core": "^3.7.1-master-202607110305",
142
+ "@vendure/common": "3.7.1",
143
+ "@vendure/core": "3.7.1",
143
144
  "@vitest/browser": "^3.2.4",
144
145
  "@vitest/coverage-v8": "^3.2.4",
145
146
  "eslint": "^9.39.0",
@@ -0,0 +1,69 @@
1
+ import { DataTableBulkActionItem } from '@/vdb/components/data-table/data-table-bulk-action-item.js';
2
+ import { BulkActionComponent } from '@/vdb/framework/extension-api/types/data-table.js';
3
+ import { api } from '@/vdb/graphql/api.js';
4
+ import { usePaginatedList } from '@/vdb/hooks/use-paginated-list.js';
5
+ import { plural } from '@lingui/core/macro';
6
+ import { Plural, Trans, useLingui } from '@lingui/react/macro';
7
+ import { useMutation } from '@tanstack/react-query';
8
+ import { UserMinus } from 'lucide-react';
9
+ import { toast } from 'sonner';
10
+ import { removeCustomersFromGroupDocument } from '../customer-groups.graphql.js';
11
+
12
+ /**
13
+ * The group id is only known by the parent table, so the bulk action is built as a factory rather
14
+ * than a plain component. Callers must memoize the result: `DataTableBulkActions` renders the
15
+ * component by identity, so a fresh one each render would remount it and discard the confirmation
16
+ * dialog's open state mid-interaction.
17
+ */
18
+ export function createRemoveFromGroupBulkAction(customerGroupId: string): BulkActionComponent<any> {
19
+ return function RemoveFromGroupBulkAction({ selection, table }) {
20
+ const { refetchPaginatedList } = usePaginatedList();
21
+ const { t } = useLingui();
22
+ const count = selection.length;
23
+
24
+ const { mutate, isPending } = useMutation({
25
+ mutationFn: api.mutate(removeCustomersFromGroupDocument),
26
+ onSuccess: () => {
27
+ // `plural` from @lingui/core/macro is used (not useLingui's `t`) because this is a
28
+ // non-JSX message: it compiles to an i18n._() call against the same global instance
29
+ // the I18nProvider activates. Do NOT nest t`...` inside the arms — that breaks extraction.
30
+ toast.success(
31
+ plural(count, {
32
+ one: `Removed ${count} customer from group`,
33
+ other: `Removed ${count} customers from group`,
34
+ }),
35
+ );
36
+ refetchPaginatedList();
37
+ table.resetRowSelection();
38
+ },
39
+ onError: error => {
40
+ toast.error(t`Failed to remove customers from group`, {
41
+ description: error.message,
42
+ });
43
+ },
44
+ });
45
+
46
+ return (
47
+ <DataTableBulkActionItem
48
+ requiresPermission={['UpdateCustomerGroup']}
49
+ onClick={() =>
50
+ mutate({
51
+ customerGroupId,
52
+ customerIds: selection.map(s => s.id),
53
+ })
54
+ }
55
+ disabled={isPending}
56
+ label={<Trans>Remove from group</Trans>}
57
+ confirmationText={
58
+ <Plural
59
+ value={count}
60
+ one={`Are you sure you want to remove ${count} customer from this group?`}
61
+ other={`Are you sure you want to remove ${count} customers from this group?`}
62
+ />
63
+ }
64
+ icon={UserMinus}
65
+ className="text-destructive"
66
+ />
67
+ );
68
+ };
69
+ }
@@ -11,9 +11,10 @@ import { Trans, useLingui } from '@lingui/react/macro';
11
11
  import { useMutation, useQueryClient } from '@tanstack/react-query';
12
12
  import { Link } from '@tanstack/react-router';
13
13
  import { ColumnFiltersState, SortingState } from '@tanstack/react-table';
14
- import { useState } from 'react';
14
+ import { useMemo, useState } from 'react';
15
15
  import { toast } from 'sonner';
16
16
  import { addCustomerToGroupDocument } from '../../_customers/customers.graphql.js';
17
+ import { createRemoveFromGroupBulkAction } from './customer-group-members-bulk-actions.js';
17
18
 
18
19
  export const customerGroupMemberListDocument = graphql(`
19
20
  query CustomerGroupMemberList($id: ID!, $options: CustomerListOptions) {
@@ -49,6 +50,11 @@ export function CustomerGroupMembersTable({
49
50
  const { t } = useLingui();
50
51
  const queryClient = useQueryClient();
51
52
 
53
+ const bulkActions = useMemo(
54
+ () => [{ component: createRemoveFromGroupBulkAction(customerGroupId) }],
55
+ [customerGroupId],
56
+ );
57
+
52
58
  const { mutate: addCustomerToGroup } = useMutation({
53
59
  mutationFn: api.mutate(addCustomerToGroupDocument),
54
60
  onSuccess: () => {
@@ -70,6 +76,7 @@ export function CustomerGroupMembersTable({
70
76
  ...variables,
71
77
  id: customerGroupId,
72
78
  })}
79
+ bulkActions={bulkActions}
73
80
  page={page}
74
81
  itemsPerPage={pageSize}
75
82
  sorting={sorting}
@@ -100,7 +107,10 @@ export function CustomerGroupMembersTable({
100
107
  cell: ({ row }) => {
101
108
  const value = `${row.original.firstName} ${row.original.lastName}`;
102
109
  return (
103
- <Button render={<Link to="/customers/$id" params={{ id: row.original.id }} />} variant="ghost">
110
+ <Button
111
+ render={<Link to="/customers/$id" params={{ id: row.original.id }} />}
112
+ variant="ghost"
113
+ >
104
114
  {value}
105
115
  </Button>
106
116
  );
@@ -14,7 +14,7 @@ import {
14
14
  VisibilityState,
15
15
  } from '@tanstack/react-table';
16
16
  import { Trash2 } from 'lucide-react';
17
- import { useMemo, useState } from 'react';
17
+ import { useEffect, useMemo, useState } from 'react';
18
18
  import {
19
19
  couponCodeSelectorPromotionListDocument,
20
20
  draftOrderEligibleShippingMethodsDocument,
@@ -27,6 +27,77 @@ import { OrderTableTotals } from './order-table-totals.js';
27
27
  import { ShippingMethodSelector } from './shipping-method-selector.js';
28
28
 
29
29
  type OrderFragment = NonNullable<ResultOf<typeof orderDetailDocument>['order']>;
30
+
31
+ function QuantityCell({
32
+ line,
33
+ onAdjustLine,
34
+ onRemoveLine,
35
+ }: {
36
+ line: OrderLineFragment & { customFields?: Record<string, any> };
37
+ onAdjustLine: OrderTableProps['onAdjustLine'];
38
+ onRemoveLine: OrderTableProps['onRemoveLine'];
39
+ }) {
40
+ const [localValue, setLocalValue] = useState<string>(String(line.quantity));
41
+
42
+ // Sync local value when server state changes (e.g. after mutation completes)
43
+ useEffect(() => {
44
+ setLocalValue(String(line.quantity));
45
+ }, [line.quantity]);
46
+
47
+ const commitValue = (removeOnZero: boolean) => {
48
+ const numValue = localValue === '' ? 0 : Number(localValue);
49
+ if (numValue <= 0 || Number.isNaN(numValue)) {
50
+ if (removeOnZero) {
51
+ onRemoveLine({ lineId: line.id });
52
+ } else {
53
+ // Revert to the server value on blur
54
+ setLocalValue(String(line.quantity));
55
+ }
56
+ } else if (numValue !== line.quantity) {
57
+ onAdjustLine({ lineId: line.id, quantity: numValue, customFields: line.customFields });
58
+ }
59
+ };
60
+
61
+ return (
62
+ <div className="flex gap-2">
63
+ <Input
64
+ type="number"
65
+ value={localValue}
66
+ min={0}
67
+ placeholder="0"
68
+ data-testid="order-line-quantity"
69
+ onChange={e => setLocalValue(e.target.value)}
70
+ onKeyDown={e => {
71
+ if (e.key === 'Enter') {
72
+ e.preventDefault();
73
+ commitValue(true);
74
+ }
75
+ }}
76
+ onBlur={() => commitValue(false)}
77
+ />
78
+ <Button
79
+ variant="outline"
80
+ type="button"
81
+ size="icon"
82
+ onClick={() => onRemoveLine({ lineId: line.id })}
83
+ >
84
+ <Trash2 />
85
+ </Button>
86
+ {line.customFields && (
87
+ <OrderLineCustomFieldsForm
88
+ onUpdate={customFields => {
89
+ onAdjustLine({
90
+ lineId: line.id,
91
+ quantity: line.quantity,
92
+ customFields: customFields,
93
+ });
94
+ }}
95
+ value={line.customFields}
96
+ />
97
+ )}
98
+ </div>
99
+ );
100
+ }
30
101
  type OrderLineFragment = ResultOf<typeof orderLineFragment>;
31
102
 
32
103
  type ShippingMethodQuote = ResultOf<
@@ -110,49 +181,13 @@ export function EditOrderTable({
110
181
  {
111
182
  header: t`Quantity`,
112
183
  accessorKey: 'quantity',
113
- cell: ({ row }) => {
114
- return (
115
- <div className="flex gap-2">
116
- <Input
117
- type="number"
118
- value={row.original.quantity}
119
- min={0}
120
- data-testid="order-line-quantity"
121
- onChange={e => {
122
- const value = Number.isNaN(e.target.valueAsNumber)
123
- ? 0
124
- : e.target.valueAsNumber;
125
- onAdjustLine({
126
- lineId: row.original.id,
127
- quantity: value,
128
- customFields: row.original.customFields,
129
- });
130
- }}
131
- />
132
- <Button
133
- variant="outline"
134
- type="button"
135
- size="icon"
136
- disabled={row.original.quantity === 0}
137
- onClick={() => onRemoveLine({ lineId: row.original.id })}
138
- >
139
- <Trash2 />
140
- </Button>
141
- {row.original.customFields && (
142
- <OrderLineCustomFieldsForm
143
- onUpdate={customFields => {
144
- onAdjustLine({
145
- lineId: row.original.id,
146
- quantity: row.original.quantity,
147
- customFields: customFields,
148
- });
149
- }}
150
- value={row.original.customFields}
151
- />
152
- )}
153
- </div>
154
- );
155
- },
184
+ cell: ({ row }) => (
185
+ <QuantityCell
186
+ line={row.original}
187
+ onAdjustLine={onAdjustLine}
188
+ onRemoveLine={onRemoveLine}
189
+ />
190
+ ),
156
191
  },
157
192
  {
158
193
  header: t`Total`,
@@ -0,0 +1,66 @@
1
+ import {
2
+ AlertDialog,
3
+ AlertDialogAction,
4
+ AlertDialogCancel,
5
+ AlertDialogContent,
6
+ AlertDialogDescription,
7
+ AlertDialogFooter,
8
+ AlertDialogHeader,
9
+ AlertDialogTitle,
10
+ } from '@/vdb/components/ui/alert-dialog.js';
11
+ import { Trans } from '@lingui/react/macro';
12
+
13
+ interface ForceRemoveOptionGroupDialogProps {
14
+ open: boolean;
15
+ onOpenChange: (open: boolean) => void;
16
+ onConfirm: () => void;
17
+ isPending?: boolean;
18
+ }
19
+
20
+ /**
21
+ * Confirmation shown when an option group cannot be removed normally because it is
22
+ * still in use by existing variants (`ProductOptionInUseError`). Shared by the
23
+ * product-detail badge and the Manage Variants page so the copy and behaviour stay
24
+ * in one place.
25
+ */
26
+ export function ForceRemoveOptionGroupDialog({
27
+ open,
28
+ onOpenChange,
29
+ onConfirm,
30
+ isPending,
31
+ }: Readonly<ForceRemoveOptionGroupDialogProps>) {
32
+ return (
33
+ <AlertDialog open={open} onOpenChange={onOpenChange}>
34
+ <AlertDialogContent>
35
+ <AlertDialogHeader>
36
+ <AlertDialogTitle>
37
+ <Trans>Force remove option group</Trans>
38
+ </AlertDialogTitle>
39
+ <AlertDialogDescription>
40
+ <Trans>
41
+ This option group is in use by existing variants. Force removing it may affect
42
+ those variants. Are you sure?
43
+ </Trans>
44
+ </AlertDialogDescription>
45
+ </AlertDialogHeader>
46
+ <AlertDialogFooter>
47
+ <AlertDialogCancel disabled={isPending}>
48
+ <Trans>Cancel</Trans>
49
+ </AlertDialogCancel>
50
+ <AlertDialogAction
51
+ disabled={isPending}
52
+ // preventDefault keeps the dialog open while the mutation is in
53
+ // flight so the disabled state can block a double-submit; the hook
54
+ // closes it on success.
55
+ onClick={event => {
56
+ event.preventDefault();
57
+ onConfirm();
58
+ }}
59
+ >
60
+ <Trans>Force remove</Trans>
61
+ </AlertDialogAction>
62
+ </AlertDialogFooter>
63
+ </AlertDialogContent>
64
+ </AlertDialog>
65
+ );
66
+ }
@@ -1,24 +1,78 @@
1
+ import { ConfirmationDialog } from '@/vdb/components/shared/confirmation-dialog.js';
2
+ import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
1
3
  import { Badge } from '@/vdb/components/ui/badge.js';
4
+ import { useLingui } from '@lingui/react/macro';
2
5
  import { Link } from '@tanstack/react-router';
3
- import { Edit2 } from 'lucide-react';
6
+ import { Edit2, Trash2 } from 'lucide-react';
7
+
8
+ import { useRemoveOptionGroup } from '../hooks/use-remove-option-group.js';
9
+ import { ForceRemoveOptionGroupDialog } from './force-remove-option-group-dialog.js';
4
10
 
5
11
  interface ProductOptionGroupBadgeProps {
6
12
  id: string;
7
13
  name: string;
8
14
  productId: string;
15
+ /**
16
+ * When provided, the badge renders a remove control that detaches the option
17
+ * group from the product (issue #4703 — a wrongly-added option group could
18
+ * not be removed from the product detail page). Called after a successful
19
+ * removal so the parent can refresh.
20
+ */
21
+ onRemoved?: () => void;
9
22
  }
10
23
 
11
- export function ProductOptionGroupBadge({ id, name, productId }: ProductOptionGroupBadgeProps) {
24
+ export function ProductOptionGroupBadge({
25
+ id,
26
+ name,
27
+ productId,
28
+ onRemoved,
29
+ }: Readonly<ProductOptionGroupBadgeProps>) {
30
+ const { t } = useLingui();
31
+ const { remove, forceRemove, inUseGroupId, clearInUseGroup, isPending } = useRemoveOptionGroup(
32
+ productId,
33
+ { onRemoved },
34
+ );
35
+
12
36
  return (
13
- <Badge variant="secondary" className="text-xs">
14
- <span>{name}</span>
15
- <Link
16
- to={`/option-groups/${id}`}
17
- search={{ from: 'product', productId }}
18
- className="ml-1.5 inline-flex"
19
- >
20
- <Edit2 className="h-3 w-3" />
21
- </Link>
22
- </Badge>
37
+ <>
38
+ <Badge variant="secondary" className="text-xs">
39
+ <span>{name}</span>
40
+ <Link
41
+ to={`/option-groups/${id}`}
42
+ search={{ from: 'product', productId }}
43
+ className="ml-1.5 inline-flex"
44
+ >
45
+ <Edit2 className="h-3 w-3" />
46
+ </Link>
47
+ {onRemoved && (
48
+ <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
49
+ <ConfirmationDialog
50
+ title={t`Remove option group`}
51
+ description={t`Are you sure you want to remove this option group from the product?`}
52
+ onConfirm={() => remove(id)}
53
+ >
54
+ <button
55
+ type="button"
56
+ aria-label={t`Remove option group`}
57
+ disabled={isPending}
58
+ className="ml-1 inline-flex"
59
+ >
60
+ <Trash2 className="h-3 w-3 text-destructive" />
61
+ </button>
62
+ </ConfirmationDialog>
63
+ </PermissionGuard>
64
+ )}
65
+ </Badge>
66
+ <ForceRemoveOptionGroupDialog
67
+ open={inUseGroupId === id}
68
+ onOpenChange={open => {
69
+ if (!open) {
70
+ clearInUseGroup();
71
+ }
72
+ }}
73
+ onConfirm={forceRemove}
74
+ isPending={isPending}
75
+ />
76
+ </>
23
77
  );
24
78
  }