@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
@@ -0,0 +1,90 @@
1
+ import { api } from '@/vdb/graphql/api.js';
2
+ import { ResultOf } from '@/vdb/graphql/graphql.js';
3
+ import { useLingui } from '@lingui/react/macro';
4
+ import { useMutation } from '@tanstack/react-query';
5
+ import { useState } from 'react';
6
+ import { toast } from 'sonner';
7
+
8
+ import { removeOptionGroupFromProductDocument } from '../products.graphql.js';
9
+
10
+ type RemoveOptionGroupResult = ResultOf<
11
+ typeof removeOptionGroupFromProductDocument
12
+ >['removeOptionGroupFromProduct'];
13
+
14
+ export interface UseRemoveOptionGroupOptions {
15
+ /** Called after a group is successfully (force-)removed, e.g. to refetch. */
16
+ onRemoved?: () => void;
17
+ }
18
+
19
+ /**
20
+ * @description
21
+ * Shared logic for removing an option group from a product (issue #4703). Owns the
22
+ * single mutation, the typed `ProductOptionInUseError` discrimination, the toasts,
23
+ * and the "in use" state that drives the force-remove confirmation. Consumers own
24
+ * only their trigger UI and the {@link ForceRemoveOptionGroupDialog} placement.
25
+ *
26
+ * `remove` handles the interactive single-group flow (surfacing the force dialog on
27
+ * `ProductOptionInUseError`); `removeOptionGroupAsync` is the low-level typed
28
+ * primitive for batch callers that inspect the result themselves.
29
+ */
30
+ export function useRemoveOptionGroup(productId: string, options?: UseRemoveOptionGroupOptions) {
31
+ const { t } = useLingui();
32
+ // The group that hit ProductOptionInUseError and is awaiting force confirmation.
33
+ // When a single hook instance is shared across many groups (the Manage Variants
34
+ // page), this also identifies which group's force dialog is open. Per-instance
35
+ // callers (the detail-page badge) only ever see null or their own group id.
36
+ const [inUseGroupId, setInUseGroupId] = useState<string | null>(null);
37
+ const mutation = useMutation({ mutationFn: api.mutate(removeOptionGroupFromProductDocument) });
38
+
39
+ const removeOptionGroupAsync = (optionGroupId: string, force = false): Promise<RemoveOptionGroupResult> =>
40
+ mutation
41
+ .mutateAsync({ productId, optionGroupId, force })
42
+ .then(result => result.removeOptionGroupFromProduct);
43
+
44
+ const remove = async (optionGroupId: string) => {
45
+ try {
46
+ const result = await removeOptionGroupAsync(optionGroupId);
47
+ if (result.__typename === 'ProductOptionInUseError') {
48
+ setInUseGroupId(optionGroupId);
49
+ return;
50
+ }
51
+ toast.success(t`Option group removed`);
52
+ options?.onRemoved?.();
53
+ } catch (error) {
54
+ toast.error(t`Failed to remove option group`, {
55
+ description: error instanceof Error ? error.message : t`Unknown error`,
56
+ });
57
+ }
58
+ };
59
+
60
+ const forceRemove = async () => {
61
+ if (!inUseGroupId) {
62
+ return;
63
+ }
64
+ try {
65
+ await removeOptionGroupAsync(inUseGroupId, true);
66
+ setInUseGroupId(null);
67
+ toast.success(t`Option group removed`);
68
+ options?.onRemoved?.();
69
+ } catch (error) {
70
+ // Keep the dialog open on failure so the user can retry the force-remove
71
+ // without restarting the whole flow; only a success clears `inUseGroupId`.
72
+ toast.error(t`Failed to remove option group`, {
73
+ description: error instanceof Error ? error.message : t`Unknown error`,
74
+ });
75
+ }
76
+ };
77
+
78
+ return {
79
+ /** Remove a single group, surfacing the force dialog if it is in use. */
80
+ remove,
81
+ /** Confirm force-removal of the group currently in `inUseGroupId`. */
82
+ forceRemove,
83
+ /** Typed remove primitive for batch callers that inspect the result. */
84
+ removeOptionGroupAsync,
85
+ /** Non-null while the force-remove dialog should be shown for that group. */
86
+ inUseGroupId,
87
+ clearInUseGroup: () => setInUseGroupId(null),
88
+ isPending: mutation.isPending,
89
+ };
90
+ }
@@ -23,7 +23,6 @@ import { detailPageRouteLoader } from '@/vdb/framework/page/detail-page-route-lo
23
23
  import { useDetailPage } from '@/vdb/framework/page/use-detail-page.js';
24
24
  import { Trans, useLingui } from '@lingui/react/macro';
25
25
  import { createFileRoute, Link, useNavigate } from '@tanstack/react-router';
26
- import { useMutation } from '@tanstack/react-query';
27
26
  import { Layers, Package, PlusIcon } from 'lucide-react';
28
27
  import { useRef, useState } from 'react';
29
28
  import { toast } from 'sonner';
@@ -31,11 +30,11 @@ import { AddOptionGroupDialog } from './components/add-option-group-dialog.js';
31
30
  import { GenerateVariantsPanel } from './components/generate-variants-panel.js';
32
31
  import { ProductOptionGroupBadge } from './components/product-option-group-badge.js';
33
32
  import { ProductVariantsTable } from './components/product-variants-table.js';
33
+ import { useRemoveOptionGroup } from './hooks/use-remove-option-group.js';
34
34
  import {
35
35
  assignProductsToChannelDocument,
36
36
  createProductDocument,
37
37
  productDetailDocument,
38
- removeOptionGroupFromProductDocument,
39
38
  removeProductsFromChannelDocument,
40
39
  updateProductDocument,
41
40
  } from './products.graphql.js';
@@ -171,28 +170,22 @@ function ProductDetailPage() {
171
170
  },
172
171
  });
173
172
 
174
- const removeOptionGroupMutation = useMutation({
175
- mutationFn: api.mutate(removeOptionGroupFromProductDocument),
176
- });
173
+ // The empty-string fallback only ever applies before `entity` has loaded;
174
+ // `removeAllOptionGroups` is reachable exclusively from the onBack handler below,
175
+ // which is rendered only once `entity` exists, so the real id is always used.
176
+ const { removeOptionGroupAsync } = useRemoveOptionGroup(entity?.id ?? '');
177
177
 
178
- const removeAllOptionGroups = async (
179
- productId: string,
180
- optionGroups: Array<{ id: string }>,
181
- ) => {
178
+ // Batch-removes every option group when the user backs out of variant setup. This is
179
+ // reached only while the product has zero variants, so ProductOptionInUseError cannot
180
+ // realistically fire here — the branch is kept as a defensive guard. Note the loop is
181
+ // not transactional: a mid-list failure leaves earlier groups already removed.
182
+ const removeAllOptionGroups = async (optionGroups: Array<{ id: string }>) => {
182
183
  try {
183
184
  for (const group of optionGroups) {
184
- const result = await removeOptionGroupMutation.mutateAsync({
185
- productId,
186
- optionGroupId: group.id,
187
- });
188
- const removeResult = result?.removeOptionGroupFromProduct;
189
- if (
190
- removeResult &&
191
- '__typename' in removeResult &&
192
- removeResult.__typename === 'ProductOptionInUseError'
193
- ) {
185
+ const result = await removeOptionGroupAsync(group.id);
186
+ if (result.__typename === 'ProductOptionInUseError') {
194
187
  toast.error(t`Could not remove option group`, {
195
- description: removeResult.message,
188
+ description: result.message,
196
189
  });
197
190
  refreshEntity();
198
191
  return;
@@ -301,7 +294,7 @@ function ProductDetailPage() {
301
294
  optionGroups={entity.optionGroups}
302
295
  onSuccess={() => refreshEntity()}
303
296
  onBack={{
304
- handler: () => removeAllOptionGroups(entity.id, entity.optionGroups),
297
+ handler: () => removeAllOptionGroups(entity.optionGroups),
305
298
  confirmation: {
306
299
  title: t`Remove option groups?`,
307
300
  description: t`This will remove all option groups from this product and return to the variant setup choice.`,
@@ -320,6 +313,7 @@ function ProductDetailPage() {
320
313
  id={g.id}
321
314
  name={g.name}
322
315
  productId={entity.id}
316
+ onRemoved={() => refreshEntity()}
323
317
  />
324
318
  ))}
325
319
  </div>
@@ -1,16 +1,7 @@
1
1
  import { ConfirmationDialog } from '@/vdb/components/shared/confirmation-dialog.js';
2
2
  import { ErrorPage } from '@/vdb/components/shared/error-page.js';
3
3
  import { FormFieldWrapper } from '@/vdb/components/shared/form-field-wrapper.js';
4
- import {
5
- AlertDialog,
6
- AlertDialogAction,
7
- AlertDialogCancel,
8
- AlertDialogContent,
9
- AlertDialogDescription,
10
- AlertDialogFooter,
11
- AlertDialogHeader,
12
- AlertDialogTitle,
13
- } from '@/vdb/components/ui/alert-dialog.js';
4
+ import { PermissionGuard } from '@/vdb/components/shared/permission-guard.js';
14
5
  import { Badge } from '@/vdb/components/ui/badge.js';
15
6
  import { Button } from '@/vdb/components/ui/button.js';
16
7
  import {
@@ -41,11 +32,12 @@ import { useForm } from 'react-hook-form';
41
32
  import { toast } from 'sonner';
42
33
  import { AddOptionGroupDialog } from './components/add-option-group-dialog.js';
43
34
  import { AddProductVariantDialog } from './components/add-product-variant-dialog.js';
35
+ import { ForceRemoveOptionGroupDialog } from './components/force-remove-option-group-dialog.js';
36
+ import { useRemoveOptionGroup } from './hooks/use-remove-option-group.js';
44
37
  import {
45
38
  createProductOptionDocument,
46
39
  deleteProductVariantDocument,
47
40
  productDetailWithVariantsDocument,
48
- removeOptionGroupFromProductDocument,
49
41
  updateProductVariantDocument,
50
42
  } from './products.graphql.js';
51
43
 
@@ -198,29 +190,13 @@ function ManageProductVariants() {
198
190
  },
199
191
  });
200
192
 
201
- const [forceRemoveGroupId, setForceRemoveGroupId] = useState<string | null>(null);
202
-
203
- const removeOptionGroupMutation = useMutation({
204
- mutationFn: api.mutate(removeOptionGroupFromProductDocument),
205
- onSuccess: (result: any, variables: any) => {
206
- const removeResult = result?.removeOptionGroupFromProduct;
207
- if (removeResult && '__typename' in removeResult && removeResult.__typename === 'ProductOptionInUseError') {
208
- setForceRemoveGroupId(variables.optionGroupId);
209
- return;
210
- }
211
- toast.success(t`Option group removed`);
212
- refetch();
213
- },
214
- });
215
-
216
- const forceRemoveOptionGroupMutation = useMutation({
217
- mutationFn: api.mutate(removeOptionGroupFromProductDocument),
218
- onSuccess: () => {
219
- setForceRemoveGroupId(null);
220
- toast.success(t`Option group removed`);
221
- refetch();
222
- },
223
- });
193
+ const {
194
+ remove: removeOptionGroup,
195
+ forceRemove: forceRemoveOptionGroup,
196
+ inUseGroupId: forceRemoveGroupId,
197
+ clearInUseGroup,
198
+ isPending: isRemovingOptionGroup,
199
+ } = useRemoveOptionGroup(id, { onRemoved: refetch });
224
200
 
225
201
  const setOptionToAddToVariant = (variantId: string, groupId: string, optionId: string | undefined) => {
226
202
  if (!optionId) {
@@ -316,22 +292,21 @@ function ManageProductVariants() {
316
292
  </div>
317
293
  </div>
318
294
  <div className="col-span-1 flex items-end justify-end">
319
- <ConfirmationDialog
320
- title={t`Remove option group`}
321
- description={t`Are you sure you want to remove this option group from the product?`}
322
- onConfirm={() => removeOptionGroupMutation.mutate({
323
- productId: id,
324
- optionGroupId: group.id,
325
- })}
326
- >
327
- <Button
328
- size="icon"
329
- variant="ghost"
330
- disabled={removeOptionGroupMutation.isPending}
295
+ <PermissionGuard requires={['UpdateProduct', 'UpdateCatalog']}>
296
+ <ConfirmationDialog
297
+ title={t`Remove option group`}
298
+ description={t`Are you sure you want to remove this option group from the product?`}
299
+ onConfirm={() => removeOptionGroup(group.id)}
331
300
  >
332
- <Trash2 className="h-4 w-4 text-destructive" />
333
- </Button>
334
- </ConfirmationDialog>
301
+ <Button
302
+ size="icon"
303
+ variant="ghost"
304
+ disabled={isRemovingOptionGroup}
305
+ >
306
+ <Trash2 className="h-4 w-4 text-destructive" />
307
+ </Button>
308
+ </ConfirmationDialog>
309
+ </PermissionGuard>
335
310
  </div>
336
311
  </div>
337
312
  ))
@@ -485,32 +460,16 @@ function ManageProductVariants() {
485
460
  )}
486
461
  </PageBlock>
487
462
  </PageLayout>
488
- <AlertDialog open={!!forceRemoveGroupId} onOpenChange={(open) => { if (!open) setForceRemoveGroupId(null); }}>
489
- <AlertDialogContent>
490
- <AlertDialogHeader>
491
- <AlertDialogTitle><Trans>Force remove option group</Trans></AlertDialogTitle>
492
- <AlertDialogDescription>
493
- <Trans>This option group is in use by existing variants. Force removing it may affect those variants. Are you sure?</Trans>
494
- </AlertDialogDescription>
495
- </AlertDialogHeader>
496
- <AlertDialogFooter>
497
- <AlertDialogCancel onClick={() => setForceRemoveGroupId(null)}>
498
- <Trans>Cancel</Trans>
499
- </AlertDialogCancel>
500
- <AlertDialogAction onClick={() => {
501
- if (forceRemoveGroupId) {
502
- forceRemoveOptionGroupMutation.mutate({
503
- productId: id,
504
- optionGroupId: forceRemoveGroupId,
505
- force: true,
506
- });
507
- }
508
- }}>
509
- <Trans>Force remove</Trans>
510
- </AlertDialogAction>
511
- </AlertDialogFooter>
512
- </AlertDialogContent>
513
- </AlertDialog>
463
+ <ForceRemoveOptionGroupDialog
464
+ open={!!forceRemoveGroupId}
465
+ onOpenChange={open => {
466
+ if (!open) {
467
+ clearInUseGroup();
468
+ }
469
+ }}
470
+ onConfirm={forceRemoveOptionGroup}
471
+ isPending={isRemovingOptionGroup}
472
+ />
514
473
  </Page>
515
474
  );
516
475
  }