@open-mercato/core 0.4.5-develop-3ce83a8b24 → 0.4.5-develop-539cff4960

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 (184) hide show
  1. package/dist/generated/entities/catalog_product/index.js +16 -0
  2. package/dist/generated/entities/catalog_product/index.js.map +2 -2
  3. package/dist/generated/entities/catalog_product_unit_conversion/index.js +27 -0
  4. package/dist/generated/entities/catalog_product_unit_conversion/index.js.map +7 -0
  5. package/dist/generated/entities/sales_credit_memo_line/index.js +7 -1
  6. package/dist/generated/entities/sales_credit_memo_line/index.js.map +2 -2
  7. package/dist/generated/entities/sales_invoice_line/index.js +7 -1
  8. package/dist/generated/entities/sales_invoice_line/index.js.map +2 -2
  9. package/dist/generated/entities/sales_order_line/index.js +6 -0
  10. package/dist/generated/entities/sales_order_line/index.js.map +2 -2
  11. package/dist/generated/entities/sales_quote_line/index.js +6 -0
  12. package/dist/generated/entities/sales_quote_line/index.js.map +2 -2
  13. package/dist/generated/entities.ids.generated.js +1 -0
  14. package/dist/generated/entities.ids.generated.js.map +2 -2
  15. package/dist/generated/entity-fields-registry.js +2 -0
  16. package/dist/generated/entity-fields-registry.js.map +2 -2
  17. package/dist/modules/catalog/api/prices/route.js +123 -8
  18. package/dist/modules/catalog/api/prices/route.js.map +2 -2
  19. package/dist/modules/catalog/api/product-unit-conversions/route.js +194 -0
  20. package/dist/modules/catalog/api/product-unit-conversions/route.js.map +7 -0
  21. package/dist/modules/catalog/api/products/route.js +351 -201
  22. package/dist/modules/catalog/api/products/route.js.map +2 -2
  23. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1267 -497
  24. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  25. package/dist/modules/catalog/backend/catalog/products/create/page.js +733 -210
  26. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  27. package/dist/modules/catalog/commands/index.js +1 -0
  28. package/dist/modules/catalog/commands/index.js.map +2 -2
  29. package/dist/modules/catalog/commands/productUnitConversions.js +503 -0
  30. package/dist/modules/catalog/commands/productUnitConversions.js.map +7 -0
  31. package/dist/modules/catalog/commands/products.js +355 -73
  32. package/dist/modules/catalog/commands/products.js.map +2 -2
  33. package/dist/modules/catalog/commands/shared.js +18 -4
  34. package/dist/modules/catalog/commands/shared.js.map +2 -2
  35. package/dist/modules/catalog/components/products/ProductUomSection.js +591 -0
  36. package/dist/modules/catalog/components/products/ProductUomSection.js.map +7 -0
  37. package/dist/modules/catalog/components/products/productForm.js +66 -5
  38. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  39. package/dist/modules/catalog/components/products/productFormUtils.js +68 -0
  40. package/dist/modules/catalog/components/products/productFormUtils.js.map +7 -0
  41. package/dist/modules/catalog/data/entities.js +86 -0
  42. package/dist/modules/catalog/data/entities.js.map +2 -2
  43. package/dist/modules/catalog/data/validators.js +65 -3
  44. package/dist/modules/catalog/data/validators.js.map +2 -2
  45. package/dist/modules/catalog/events.js +3 -0
  46. package/dist/modules/catalog/events.js.map +2 -2
  47. package/dist/modules/catalog/lib/unitCodes.js +7 -0
  48. package/dist/modules/catalog/lib/unitCodes.js.map +7 -0
  49. package/dist/modules/catalog/lib/unitResolution.js +53 -0
  50. package/dist/modules/catalog/lib/unitResolution.js.map +7 -0
  51. package/dist/modules/catalog/migrations/Migration20260218225422.js +19 -0
  52. package/dist/modules/catalog/migrations/Migration20260218225422.js.map +7 -0
  53. package/dist/modules/catalog/migrations/Migration20260219084500.js +27 -0
  54. package/dist/modules/catalog/migrations/Migration20260219084500.js.map +7 -0
  55. package/dist/modules/catalog/search.js +69 -1
  56. package/dist/modules/catalog/search.js.map +2 -2
  57. package/dist/modules/catalog/seed/examples.js +91 -42
  58. package/dist/modules/catalog/seed/examples.js.map +2 -2
  59. package/dist/modules/dashboards/seed/analytics.js +3 -0
  60. package/dist/modules/dashboards/seed/analytics.js.map +2 -2
  61. package/dist/modules/sales/api/order-lines/route.js +98 -15
  62. package/dist/modules/sales/api/order-lines/route.js.map +2 -2
  63. package/dist/modules/sales/api/quote-lines/route.js +101 -14
  64. package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
  65. package/dist/modules/sales/api/quotes/public/[token]/route.js +87 -12
  66. package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
  67. package/dist/modules/sales/commands/documents.js +1424 -260
  68. package/dist/modules/sales/commands/documents.js.map +3 -3
  69. package/dist/modules/sales/commands/shared.js +6 -2
  70. package/dist/modules/sales/commands/shared.js.map +2 -2
  71. package/dist/modules/sales/components/documents/ItemsSection.js +216 -86
  72. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  73. package/dist/modules/sales/components/documents/LineItemDialog.js +913 -241
  74. package/dist/modules/sales/components/documents/LineItemDialog.js.map +3 -3
  75. package/dist/modules/sales/components/documents/ShipmentsSection.js +15 -3
  76. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  77. package/dist/modules/sales/data/entities.js +59 -3
  78. package/dist/modules/sales/data/entities.js.map +2 -2
  79. package/dist/modules/sales/data/validators.js +35 -0
  80. package/dist/modules/sales/data/validators.js.map +2 -2
  81. package/dist/modules/sales/frontend/quote/[token]/page.js +15 -1
  82. package/dist/modules/sales/frontend/quote/[token]/page.js.map +2 -2
  83. package/dist/modules/sales/migrations/Migration20260218225423.js +31 -0
  84. package/dist/modules/sales/migrations/Migration20260218225423.js.map +7 -0
  85. package/dist/modules/sales/migrations/Migration20260219084501.js +71 -0
  86. package/dist/modules/sales/migrations/Migration20260219084501.js.map +7 -0
  87. package/dist/modules/sales/search.js +28 -0
  88. package/dist/modules/sales/search.js.map +2 -2
  89. package/dist/modules/sales/seed/examples.js +14 -1
  90. package/dist/modules/sales/seed/examples.js.map +2 -2
  91. package/dist/modules/sales/widgets/injection/document-history/widget.client.js +1 -1
  92. package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
  93. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +28 -15
  94. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  95. package/dist/modules/staff/translations.js +9 -0
  96. package/dist/modules/staff/translations.js.map +7 -0
  97. package/dist/modules/translations/components/TranslationDrawerAction.js +97 -0
  98. package/dist/modules/translations/components/TranslationDrawerAction.js.map +7 -0
  99. package/dist/modules/translations/lib/extract-record-id.js +31 -2
  100. package/dist/modules/translations/lib/extract-record-id.js.map +2 -2
  101. package/dist/modules/translations/lib/resolve-field-list.js +3 -0
  102. package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
  103. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +105 -36
  104. package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
  105. package/dist/modules/translations/widgets/injection-table.js +18 -29
  106. package/dist/modules/translations/widgets/injection-table.js.map +2 -2
  107. package/generated/entities/catalog_product/index.ts +8 -0
  108. package/generated/entities/catalog_product_unit_conversion/index.ts +12 -0
  109. package/generated/entities/sales_credit_memo_line/index.ts +3 -0
  110. package/generated/entities/sales_invoice_line/index.ts +3 -0
  111. package/generated/entities/sales_order_line/index.ts +3 -0
  112. package/generated/entities/sales_quote_line/index.ts +3 -0
  113. package/generated/entities.ids.generated.ts +1 -0
  114. package/generated/entity-fields-registry.ts +2 -0
  115. package/package.json +2 -2
  116. package/src/modules/auth/i18n/de.json +1 -1
  117. package/src/modules/auth/i18n/en.json +1 -1
  118. package/src/modules/auth/i18n/es.json +1 -1
  119. package/src/modules/auth/i18n/pl.json +1 -1
  120. package/src/modules/catalog/api/prices/route.ts +213 -81
  121. package/src/modules/catalog/api/product-unit-conversions/route.ts +195 -0
  122. package/src/modules/catalog/api/products/route.ts +638 -402
  123. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2085 -1072
  124. package/src/modules/catalog/backend/catalog/products/create/page.tsx +1288 -593
  125. package/src/modules/catalog/commands/index.ts +1 -0
  126. package/src/modules/catalog/commands/productUnitConversions.ts +626 -0
  127. package/src/modules/catalog/commands/products.ts +1151 -693
  128. package/src/modules/catalog/commands/shared.ts +19 -5
  129. package/src/modules/catalog/components/products/ProductUomSection.tsx +745 -0
  130. package/src/modules/catalog/components/products/productForm.ts +369 -256
  131. package/src/modules/catalog/components/products/productFormUtils.ts +82 -0
  132. package/src/modules/catalog/data/entities.ts +82 -1
  133. package/src/modules/catalog/data/validators.ts +118 -34
  134. package/src/modules/catalog/events.ts +3 -0
  135. package/src/modules/catalog/i18n/de.json +56 -0
  136. package/src/modules/catalog/i18n/en.json +56 -0
  137. package/src/modules/catalog/i18n/es.json +56 -0
  138. package/src/modules/catalog/i18n/pl.json +56 -0
  139. package/src/modules/catalog/lib/unitCodes.ts +1 -0
  140. package/src/modules/catalog/lib/unitResolution.ts +62 -0
  141. package/src/modules/catalog/migrations/.snapshot-open-mercato.json +245 -0
  142. package/src/modules/catalog/migrations/Migration20260218225422.ts +21 -0
  143. package/src/modules/catalog/migrations/Migration20260219084500.ts +26 -0
  144. package/src/modules/catalog/search.ts +73 -1
  145. package/src/modules/catalog/seed/examples.ts +552 -479
  146. package/src/modules/dashboards/i18n/de.json +1 -1
  147. package/src/modules/dashboards/i18n/en.json +1 -1
  148. package/src/modules/dashboards/i18n/es.json +1 -1
  149. package/src/modules/dashboards/i18n/pl.json +1 -1
  150. package/src/modules/dashboards/seed/analytics.ts +3 -0
  151. package/src/modules/sales/api/order-lines/route.ts +158 -68
  152. package/src/modules/sales/api/quote-lines/route.ts +161 -67
  153. package/src/modules/sales/api/quotes/public/[token]/route.ts +122 -36
  154. package/src/modules/sales/commands/documents.ts +4250 -2424
  155. package/src/modules/sales/commands/shared.ts +7 -2
  156. package/src/modules/sales/components/documents/ItemsSection.tsx +580 -310
  157. package/src/modules/sales/components/documents/LineItemDialog.tsx +1988 -833
  158. package/src/modules/sales/components/documents/ShipmentsSection.tsx +17 -3
  159. package/src/modules/sales/components/documents/lineItemTypes.ts +6 -0
  160. package/src/modules/sales/data/entities.ts +53 -0
  161. package/src/modules/sales/data/validators.ts +36 -0
  162. package/src/modules/sales/frontend/quote/[token]/page.tsx +25 -1
  163. package/src/modules/sales/i18n/de.json +23 -3
  164. package/src/modules/sales/i18n/en.json +23 -3
  165. package/src/modules/sales/i18n/es.json +23 -3
  166. package/src/modules/sales/i18n/pl.json +23 -3
  167. package/src/modules/sales/lib/types.ts +30 -0
  168. package/src/modules/sales/migrations/.snapshot-open-mercato.json +172 -0
  169. package/src/modules/sales/migrations/Migration20260218225423.ts +37 -0
  170. package/src/modules/sales/migrations/Migration20260219084501.ts +73 -0
  171. package/src/modules/sales/search.ts +28 -0
  172. package/src/modules/sales/seed/examples.ts +20 -1
  173. package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +1 -1
  174. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +8 -0
  175. package/src/modules/staff/translations.ts +5 -0
  176. package/src/modules/translations/components/TranslationDrawerAction.tsx +107 -0
  177. package/src/modules/translations/lib/extract-record-id.ts +47 -3
  178. package/src/modules/translations/lib/resolve-field-list.ts +4 -0
  179. package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +108 -36
  180. package/src/modules/translations/widgets/injection-table.ts +19 -33
  181. package/src/modules/workflows/i18n/de.json +4 -4
  182. package/src/modules/workflows/i18n/en.json +4 -4
  183. package/src/modules/workflows/i18n/es.json +4 -4
  184. package/src/modules/workflows/i18n/pl.json +4 -4
@@ -4,7 +4,9 @@ import * as React from "react";
4
4
  import dynamic from "next/dynamic";
5
5
  import { useRouter, useSearchParams } from "next/navigation";
6
6
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
7
- import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
7
+ import {
8
+ CrudForm
9
+ } from "@open-mercato/ui/backend/CrudForm";
8
10
  import { createCrud } from "@open-mercato/ui/backend/utils/crud";
9
11
  import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
10
12
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
@@ -13,11 +15,25 @@ import { Button } from "@open-mercato/ui/primitives/button";
13
15
  import { Input } from "@open-mercato/ui/primitives/input";
14
16
  import { Label } from "@open-mercato/ui/primitives/label";
15
17
  import { cn } from "@open-mercato/shared/lib/utils";
16
- import { Plus, Trash2, FileText, AlignLeft, ChevronLeft, ChevronRight, AlertCircle, Settings } from "lucide-react";
17
- import { apiCall, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
18
+ import {
19
+ Plus,
20
+ Trash2,
21
+ FileText,
22
+ AlignLeft,
23
+ ChevronLeft,
24
+ ChevronRight,
25
+ AlertCircle,
26
+ Settings
27
+ } from "lucide-react";
28
+ import {
29
+ apiCall,
30
+ readApiResultOrThrow
31
+ } from "@open-mercato/ui/backend/utils/apiCall";
18
32
  import { useT } from "@open-mercato/shared/lib/i18n/context";
19
33
  import { E } from "../../../../../../generated/entities.ids.generated.js";
20
- import { ProductMediaManager } from "@open-mercato/core/modules/catalog/components/products/ProductMediaManager";
34
+ import {
35
+ ProductMediaManager
36
+ } from "@open-mercato/core/modules/catalog/components/products/ProductMediaManager";
21
37
  import { ProductCategorizeSection } from "@open-mercato/core/modules/catalog/components/products/ProductCategorizeSection";
22
38
  import {
23
39
  PRODUCT_FORM_STEPS,
@@ -39,7 +55,18 @@ import {
39
55
  updateDimensionValue,
40
56
  updateWeightValue
41
57
  } from "@open-mercato/core/modules/catalog/components/products/productForm";
42
- import { buildAttachmentImageUrl, slugifyAttachmentFileName } from "@open-mercato/core/modules/attachments/lib/imageUrls";
58
+ import {
59
+ buildAttachmentImageUrl,
60
+ slugifyAttachmentFileName
61
+ } from "@open-mercato/core/modules/attachments/lib/imageUrls";
62
+ import { ProductUomSection } from "@open-mercato/core/modules/catalog/components/products/ProductUomSection";
63
+ import { canonicalizeUnitCode } from "@open-mercato/core/modules/catalog/lib/unitCodes";
64
+ import {
65
+ UNIT_PRICE_REFERENCE_UNITS,
66
+ toPositiveNumberOrNull,
67
+ toIntegerInRangeOrDefault,
68
+ normalizeProductConversionInputs
69
+ } from "@open-mercato/core/modules/catalog/components/products/productFormUtils";
43
70
  const productFormTypedSchema = productFormSchema;
44
71
  const MarkdownEditor = dynamic(() => import("@uiw/react-md-editor"), {
45
72
  ssr: false,
@@ -58,8 +85,27 @@ const STEP_FIELD_MATCHERS = {
58
85
  matchPrefix("dimensions"),
59
86
  matchPrefix("weight")
60
87
  ],
61
- organize: [matchField("categoryIds"), matchField("channelIds"), matchField("tags")],
62
- variants: [matchField("hasVariants"), matchPrefix("options"), matchPrefix("variants")]
88
+ organize: [
89
+ matchField("categoryIds"),
90
+ matchField("channelIds"),
91
+ matchField("tags")
92
+ ],
93
+ uom: [
94
+ matchField("defaultUnit"),
95
+ matchField("defaultSalesUnit"),
96
+ matchField("defaultSalesUnitQuantity"),
97
+ matchField("uomRoundingScale"),
98
+ matchField("uomRoundingMode"),
99
+ matchField("unitPriceEnabled"),
100
+ matchField("unitPriceReferenceUnit"),
101
+ matchField("unitPriceBaseQuantity"),
102
+ matchPrefix("unitConversions")
103
+ ],
104
+ variants: [
105
+ matchField("hasVariants"),
106
+ matchPrefix("options"),
107
+ matchPrefix("variants")
108
+ ]
63
109
  };
64
110
  function resolveStepForField(fieldId) {
65
111
  const normalized = fieldId?.trim();
@@ -116,11 +162,12 @@ function CreateCatalogProductPage() {
116
162
  React.useEffect(() => {
117
163
  const loadPriceKinds = async () => {
118
164
  try {
119
- const payload = await readApiResultOrThrow(
120
- "/api/catalog/price-kinds?pageSize=100",
121
- void 0,
122
- { errorMessage: t("catalog.priceKinds.errors.load", "Failed to load price kinds.") }
123
- );
165
+ const payload = await readApiResultOrThrow("/api/catalog/price-kinds?pageSize=100", void 0, {
166
+ errorMessage: t(
167
+ "catalog.priceKinds.errors.load",
168
+ "Failed to load price kinds."
169
+ )
170
+ });
124
171
  const items = Array.isArray(payload.items) ? payload.items : [];
125
172
  setPriceKinds(
126
173
  items.map((item) => normalizePriceKindSummary(item)).filter((item) => item !== null)
@@ -136,18 +183,23 @@ function CreateCatalogProductPage() {
136
183
  React.useEffect(() => {
137
184
  const loadTaxRates = async () => {
138
185
  try {
139
- const payload = await readApiResultOrThrow(
140
- "/api/sales/tax-rates?pageSize=200",
141
- void 0,
142
- { errorMessage: t("catalog.products.create.taxRates.error", "Failed to load tax rates."), fallback: { items: [] } }
143
- );
186
+ const payload = await readApiResultOrThrow("/api/sales/tax-rates?pageSize=100", void 0, {
187
+ errorMessage: t(
188
+ "catalog.products.create.taxRates.error",
189
+ "Failed to load tax rates."
190
+ ),
191
+ fallback: { items: [] }
192
+ });
144
193
  const items = Array.isArray(payload.items) ? payload.items : [];
145
194
  setTaxRates(
146
195
  items.map((item) => {
147
196
  const rawRate = typeof item.rate === "number" ? item.rate : Number(item.rate ?? Number.NaN);
148
197
  return {
149
198
  id: String(item.id),
150
- name: typeof item.name === "string" && item.name.trim().length ? item.name : t("catalog.products.create.taxRates.unnamed", "Untitled tax rate"),
199
+ name: typeof item.name === "string" && item.name.trim().length ? item.name : t(
200
+ "catalog.products.create.taxRates.unnamed",
201
+ "Untitled tax rate"
202
+ ),
151
203
  code: typeof item.code === "string" && item.code.trim().length ? item.code : null,
152
204
  rate: Number.isFinite(rawRate) ? rawRate : null,
153
205
  isDefault: resolveBooleanFlag(
@@ -164,37 +216,51 @@ function CreateCatalogProductPage() {
164
216
  loadTaxRates().catch(() => {
165
217
  });
166
218
  }, [t]);
167
- const groups = React.useMemo(() => [
168
- {
169
- id: "builder",
170
- column: 1,
171
- component: ({ values, setValue, errors }) => /* @__PURE__ */ jsx(
172
- ProductBuilder,
173
- {
219
+ const groups = React.useMemo(
220
+ () => [
221
+ {
222
+ id: "builder",
223
+ column: 1,
224
+ component: ({
174
225
  values,
175
226
  setValue,
176
- errors,
177
- priceKinds,
178
- taxRates
179
- }
180
- )
181
- },
182
- {
183
- id: "product-meta",
184
- column: 2,
185
- title: t("catalog.products.create.meta.title", "Product meta"),
186
- description: t("catalog.products.create.meta.description", "Manage subtitle and handle for storefronts."),
187
- component: ({ values, setValue, errors }) => /* @__PURE__ */ jsx(
188
- ProductMetaSection,
189
- {
227
+ errors
228
+ }) => /* @__PURE__ */ jsx(
229
+ ProductBuilder,
230
+ {
231
+ values,
232
+ setValue,
233
+ errors,
234
+ priceKinds,
235
+ taxRates
236
+ }
237
+ )
238
+ },
239
+ {
240
+ id: "product-meta",
241
+ column: 2,
242
+ title: t("catalog.products.create.meta.title", "Product meta"),
243
+ description: t(
244
+ "catalog.products.create.meta.description",
245
+ "Manage subtitle and handle for storefronts."
246
+ ),
247
+ component: ({
190
248
  values,
191
249
  setValue,
192
- errors,
193
- taxRates
194
- }
195
- )
196
- }
197
- ], [priceKinds, taxRates, t]);
250
+ errors
251
+ }) => /* @__PURE__ */ jsx(
252
+ ProductMetaSection,
253
+ {
254
+ values,
255
+ setValue,
256
+ errors,
257
+ taxRates
258
+ }
259
+ )
260
+ }
261
+ ],
262
+ [priceKinds, taxRates, t]
263
+ );
198
264
  return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
199
265
  CrudForm,
200
266
  {
@@ -210,9 +276,18 @@ function CreateCatalogProductPage() {
210
276
  onSubmit: async (formValues) => {
211
277
  const title = formValues.title?.trim();
212
278
  if (!title) {
213
- throw createCrudFormError(t("catalog.products.create.errors.title", "Provide a product title."), {
214
- title: t("catalog.products.create.errors.title", "Provide a product title.")
215
- });
279
+ throw createCrudFormError(
280
+ t(
281
+ "catalog.products.create.errors.title",
282
+ "Provide a product title."
283
+ ),
284
+ {
285
+ title: t(
286
+ "catalog.products.create.errors.title",
287
+ "Provide a product title."
288
+ )
289
+ }
290
+ );
216
291
  }
217
292
  const handle = formValues.handle?.trim() || void 0;
218
293
  const description = formValues.description?.trim() || void 0;
@@ -224,8 +299,13 @@ function CreateCatalogProductPage() {
224
299
  const defaultMediaUrl = defaultMediaEntry ? buildAttachmentImageUrl(defaultMediaEntry.id, {
225
300
  slug: slugifyAttachmentFileName(defaultMediaEntry.fileName)
226
301
  }) : null;
227
- const optionSchemaDefinition = buildOptionSchemaDefinition(formValues.options, title);
228
- const dimensions = sanitizeProductDimensions(formValues.dimensions ?? null);
302
+ const optionSchemaDefinition = buildOptionSchemaDefinition(
303
+ formValues.options,
304
+ title
305
+ );
306
+ const dimensions = sanitizeProductDimensions(
307
+ formValues.dimensions ?? null
308
+ );
229
309
  const weight = sanitizeProductWeight(formValues.weight ?? null);
230
310
  const resolveTaxRateValue = (taxRateId) => {
231
311
  if (!taxRateId) return null;
@@ -239,6 +319,85 @@ function CreateCatalogProductPage() {
239
319
  const resolvedVariantTaxRate = resolveTaxRateValue(resolvedVariantTaxRateId) ?? (resolvedVariantTaxRateId ? null : productTaxRate ?? null);
240
320
  return { resolvedVariantTaxRateId, resolvedVariantTaxRate };
241
321
  };
322
+ const defaultUnit = canonicalizeUnitCode(formValues.defaultUnit);
323
+ const defaultSalesUnit = canonicalizeUnitCode(
324
+ formValues.defaultSalesUnit
325
+ );
326
+ const defaultSalesUnitQuantity = toPositiveNumberOrNull(formValues.defaultSalesUnitQuantity) ?? 1;
327
+ const uomRoundingScale = toIntegerInRangeOrDefault(
328
+ formValues.uomRoundingScale,
329
+ 0,
330
+ 6,
331
+ 4
332
+ );
333
+ const uomRoundingMode = formValues.uomRoundingMode === "down" || formValues.uomRoundingMode === "up" ? formValues.uomRoundingMode : "half_up";
334
+ const unitPriceEnabled = Boolean(formValues.unitPriceEnabled);
335
+ const unitPriceReferenceUnit = canonicalizeUnitCode(
336
+ formValues.unitPriceReferenceUnit
337
+ );
338
+ const unitPriceBaseQuantity = toPositiveNumberOrNull(
339
+ formValues.unitPriceBaseQuantity
340
+ );
341
+ if (defaultSalesUnit && !defaultUnit) {
342
+ const message = t(
343
+ "catalog.products.uom.errors.baseRequired",
344
+ "Base unit is required when default sales unit is set."
345
+ );
346
+ throw createCrudFormError(message, { defaultSalesUnit: message });
347
+ }
348
+ const conversionInputs = normalizeProductConversionInputs(
349
+ formValues.unitConversions,
350
+ t(
351
+ "catalog.products.uom.errors.duplicateConversion",
352
+ "Duplicate conversion unit is not allowed."
353
+ )
354
+ );
355
+ if (conversionInputs.length && !defaultUnit) {
356
+ const message = t(
357
+ "catalog.products.uom.errors.baseRequiredForConversions",
358
+ "Base unit is required when conversions are configured."
359
+ );
360
+ throw createCrudFormError(message, { defaultUnit: message });
361
+ }
362
+ const defaultUnitKey = defaultUnit?.toLowerCase() ?? null;
363
+ const defaultSalesUnitKey = defaultSalesUnit?.toLowerCase() ?? null;
364
+ if (defaultUnitKey && defaultSalesUnitKey && defaultSalesUnitKey !== defaultUnitKey) {
365
+ const hasDefaultSalesConversion = conversionInputs.some(
366
+ (entry) => entry.isActive && entry.unitCode.toLowerCase() === defaultSalesUnitKey
367
+ );
368
+ if (!hasDefaultSalesConversion) {
369
+ const message = t(
370
+ "catalog.products.uom.errors.defaultSalesConversionRequired",
371
+ "Active conversion for default sales unit is required when it differs from base unit."
372
+ );
373
+ throw createCrudFormError(message, {
374
+ defaultSalesUnit: message,
375
+ unitConversions: message
376
+ });
377
+ }
378
+ }
379
+ if (unitPriceEnabled) {
380
+ if (!unitPriceReferenceUnit || !UNIT_PRICE_REFERENCE_UNITS.has(
381
+ unitPriceReferenceUnit
382
+ )) {
383
+ const message = t(
384
+ "catalog.products.unitPrice.errors.referenceUnit",
385
+ "Reference unit is required when unit price display is enabled."
386
+ );
387
+ throw createCrudFormError(message, {
388
+ unitPriceReferenceUnit: message
389
+ });
390
+ }
391
+ if (unitPriceBaseQuantity === null) {
392
+ const message = t(
393
+ "catalog.products.unitPrice.errors.baseQuantity",
394
+ "Base quantity is required when unit price display is enabled."
395
+ );
396
+ throw createCrudFormError(message, {
397
+ unitPriceBaseQuantity: message
398
+ });
399
+ }
400
+ }
242
401
  const productPayload = {
243
402
  title,
244
403
  subtitle: formValues.subtitle?.trim() || void 0,
@@ -251,7 +410,15 @@ function CreateCatalogProductPage() {
251
410
  defaultMediaUrl: defaultMediaUrl ?? void 0,
252
411
  dimensions,
253
412
  weightValue: weight?.value ?? null,
254
- weightUnit: weight?.unit ?? null
413
+ weightUnit: weight?.unit ?? null,
414
+ defaultUnit: defaultUnit ?? null,
415
+ defaultSalesUnit: defaultSalesUnit ?? defaultUnit ?? null,
416
+ defaultSalesUnitQuantity,
417
+ uomRoundingScale,
418
+ uomRoundingMode,
419
+ unitPriceEnabled,
420
+ unitPriceReferenceUnit: unitPriceEnabled ? unitPriceReferenceUnit : void 0,
421
+ unitPriceBaseQuantity: unitPriceEnabled ? unitPriceBaseQuantity : void 0
255
422
  };
256
423
  if (optionSchemaDefinition) {
257
424
  productPayload.optionSchema = optionSchemaDefinition;
@@ -278,7 +445,11 @@ function CreateCatalogProductPage() {
278
445
  defaultMediaUrl: defaultMediaUrl ?? void 0
279
446
  }));
280
447
  }
281
- const variantDrafts = (Array.isArray(formValues.variants) && formValues.variants.length ? formValues.variants : [createVariantDraft(formValues.taxRateId ?? null, { isDefault: true })]) ?? [];
448
+ const variantDrafts = (Array.isArray(formValues.variants) && formValues.variants.length ? formValues.variants : [
449
+ createVariantDraft(formValues.taxRateId ?? null, {
450
+ isDefault: true
451
+ })
452
+ ]) ?? [];
282
453
  const priceRequests = [];
283
454
  for (const variant of variantDrafts) {
284
455
  const { resolvedVariantTaxRateId, resolvedVariantTaxRate } = resolveVariantTax(variant);
@@ -288,13 +459,19 @@ function CreateCatalogProductPage() {
288
459
  const numeric = Number(value);
289
460
  if (Number.isNaN(numeric) || !Number.isFinite(numeric) || numeric < 0) {
290
461
  throw createCrudFormError(
291
- t("catalog.products.create.errors.priceNonNegative", "Prices must be zero or greater.")
462
+ t(
463
+ "catalog.products.create.errors.priceNonNegative",
464
+ "Prices must be zero or greater."
465
+ )
292
466
  );
293
467
  }
294
468
  const currencyCode = typeof priceKind.currencyCode === "string" && priceKind.currencyCode.trim().length ? priceKind.currencyCode.trim().toUpperCase() : "";
295
469
  if (!currencyCode) {
296
470
  throw createCrudFormError(
297
- t("catalog.products.create.errors.currency", "Provide a currency for all price kinds."),
471
+ t(
472
+ "catalog.products.create.errors.currency",
473
+ "Provide a currency for all price kinds."
474
+ ),
298
475
  {}
299
476
  );
300
477
  }
@@ -311,12 +488,29 @@ function CreateCatalogProductPage() {
311
488
  }
312
489
  const cleanupState = { productId: null, variantIds: [] };
313
490
  try {
314
- const { result: created } = await createCrud("catalog/products", productPayload);
491
+ const { result: created } = await createCrud(
492
+ "catalog/products",
493
+ productPayload
494
+ );
315
495
  const productId = created?.id;
316
496
  if (!productId) {
317
- throw createCrudFormError(t("catalog.products.create.errors.id", "Product id missing after create."));
497
+ throw createCrudFormError(
498
+ t(
499
+ "catalog.products.create.errors.id",
500
+ "Product id missing after create."
501
+ )
502
+ );
318
503
  }
319
504
  cleanupState.productId = productId;
505
+ for (const conversion of conversionInputs) {
506
+ await createCrud("catalog/product-unit-conversions", {
507
+ productId,
508
+ unitCode: conversion.unitCode,
509
+ toBaseFactor: conversion.toBaseFactor,
510
+ sortOrder: conversion.sortOrder,
511
+ isActive: conversion.isActive
512
+ });
513
+ }
320
514
  const variantIdMap = {};
321
515
  for (const variant of variantDrafts) {
322
516
  const { resolvedVariantTaxRateId, resolvedVariantTaxRate } = resolveVariantTax(variant);
@@ -330,13 +524,15 @@ function CreateCatalogProductPage() {
330
524
  taxRateId: resolvedVariantTaxRateId ?? null,
331
525
  taxRate: resolvedVariantTaxRate ?? null
332
526
  };
333
- const { result: variantResult } = await createCrud(
334
- "catalog/variants",
335
- variantPayload
336
- );
527
+ const { result: variantResult } = await createCrud("catalog/variants", variantPayload);
337
528
  const variantId = variantResult?.variantId ?? variantResult?.id;
338
529
  if (!variantId) {
339
- throw createCrudFormError(t("catalog.products.create.errors.variant", "Failed to create variant."));
530
+ throw createCrudFormError(
531
+ t(
532
+ "catalog.products.create.errors.variant",
533
+ "Failed to create variant."
534
+ )
535
+ );
340
536
  }
341
537
  variantIdMap[variant.id] = variantId;
342
538
  cleanupState.variantIds.push(variantId);
@@ -378,7 +574,10 @@ function CreateCatalogProductPage() {
378
574
  { fallback: null }
379
575
  );
380
576
  if (!transfer.ok) {
381
- console.error("attachments.transfer.failed", transfer.result?.error);
577
+ console.error(
578
+ "attachments.transfer.failed",
579
+ transfer.result?.error
580
+ );
382
581
  }
383
582
  }
384
583
  if (inboxDraft) {
@@ -398,17 +597,31 @@ function CreateCatalogProductPage() {
398
597
  }
399
598
  );
400
599
  } catch {
401
- flash(t("inbox_ops.flash.complete_failed", "Product created but failed to update inbox action status."), "warning");
600
+ flash(
601
+ t(
602
+ "inbox_ops.flash.complete_failed",
603
+ "Product created but failed to update inbox action status."
604
+ ),
605
+ "warning"
606
+ );
402
607
  }
403
608
  }
404
- flash(t("catalog.products.create.success", "Product created."), "success");
609
+ flash(
610
+ t("catalog.products.create.success", "Product created."),
611
+ "success"
612
+ );
405
613
  if (inboxDraft) {
406
- router.push(`/backend/inbox-ops/proposals/${encodeURIComponent(inboxDraft.proposalId)}`);
614
+ router.push(
615
+ `/backend/inbox-ops/proposals/${encodeURIComponent(inboxDraft.proposalId)}`
616
+ );
407
617
  } else {
408
618
  router.push("/backend/catalog/products");
409
619
  }
410
620
  } catch (err) {
411
- await cleanupFailedProduct(cleanupState.productId, cleanupState.variantIds);
621
+ await cleanupFailedProduct(
622
+ cleanupState.productId,
623
+ cleanupState.variantIds
624
+ );
412
625
  throw err;
413
626
  }
414
627
  }
@@ -419,15 +632,22 @@ async function cleanupFailedProduct(productId, variantIds) {
419
632
  if (!productId && variantIds.length === 0) return;
420
633
  if (variantIds.length) {
421
634
  const variantDeletes = variantIds.map(
422
- (variantId) => apiCall(`/api/catalog/variants?id=${encodeURIComponent(variantId)}`, { method: "DELETE" }).catch(() => null)
635
+ (variantId) => apiCall(`/api/catalog/variants?id=${encodeURIComponent(variantId)}`, {
636
+ method: "DELETE"
637
+ }).catch(() => null)
423
638
  );
424
639
  await Promise.allSettled(variantDeletes);
425
640
  }
426
641
  if (productId) {
427
- await apiCall(`/api/catalog/products?id=${encodeURIComponent(productId)}`, { method: "DELETE" }).catch(() => null);
642
+ await apiCall(`/api/catalog/products?id=${encodeURIComponent(productId)}`, {
643
+ method: "DELETE"
644
+ }).catch(() => null);
428
645
  }
429
646
  }
430
- function ProductDimensionsFields({ values, setValue }) {
647
+ function ProductDimensionsFields({
648
+ values,
649
+ setValue
650
+ }) {
431
651
  const t = useT();
432
652
  const dimensionValues = normalizeProductDimensions(values.dimensions);
433
653
  const weightValues = normalizeProductWeight(values.weight);
@@ -441,7 +661,14 @@ function ProductDimensionsFields({ values, setValue }) {
441
661
  {
442
662
  type: "number",
443
663
  value: dimensionValues?.width ?? "",
444
- onChange: (event) => setValue("dimensions", updateDimensionValue(values.dimensions ?? null, "width", event.target.value)),
664
+ onChange: (event) => setValue(
665
+ "dimensions",
666
+ updateDimensionValue(
667
+ values.dimensions ?? null,
668
+ "width",
669
+ event.target.value
670
+ )
671
+ ),
445
672
  placeholder: "0"
446
673
  }
447
674
  )
@@ -453,7 +680,14 @@ function ProductDimensionsFields({ values, setValue }) {
453
680
  {
454
681
  type: "number",
455
682
  value: dimensionValues?.height ?? "",
456
- onChange: (event) => setValue("dimensions", updateDimensionValue(values.dimensions ?? null, "height", event.target.value)),
683
+ onChange: (event) => setValue(
684
+ "dimensions",
685
+ updateDimensionValue(
686
+ values.dimensions ?? null,
687
+ "height",
688
+ event.target.value
689
+ )
690
+ ),
457
691
  placeholder: "0"
458
692
  }
459
693
  )
@@ -465,7 +699,14 @@ function ProductDimensionsFields({ values, setValue }) {
465
699
  {
466
700
  type: "number",
467
701
  value: dimensionValues?.depth ?? "",
468
- onChange: (event) => setValue("dimensions", updateDimensionValue(values.dimensions ?? null, "depth", event.target.value)),
702
+ onChange: (event) => setValue(
703
+ "dimensions",
704
+ updateDimensionValue(
705
+ values.dimensions ?? null,
706
+ "depth",
707
+ event.target.value
708
+ )
709
+ ),
469
710
  placeholder: "0"
470
711
  }
471
712
  )
@@ -476,7 +717,14 @@ function ProductDimensionsFields({ values, setValue }) {
476
717
  Input,
477
718
  {
478
719
  value: dimensionValues?.unit ?? "",
479
- onChange: (event) => setValue("dimensions", updateDimensionValue(values.dimensions ?? null, "unit", event.target.value)),
720
+ onChange: (event) => setValue(
721
+ "dimensions",
722
+ updateDimensionValue(
723
+ values.dimensions ?? null,
724
+ "unit",
725
+ event.target.value
726
+ )
727
+ ),
480
728
  placeholder: "cm"
481
729
  }
482
730
  )
@@ -488,7 +736,14 @@ function ProductDimensionsFields({ values, setValue }) {
488
736
  {
489
737
  type: "number",
490
738
  value: weightValues?.value ?? "",
491
- onChange: (event) => setValue("weight", updateWeightValue(values.weight ?? null, "value", event.target.value)),
739
+ onChange: (event) => setValue(
740
+ "weight",
741
+ updateWeightValue(
742
+ values.weight ?? null,
743
+ "value",
744
+ event.target.value
745
+ )
746
+ ),
492
747
  placeholder: "0"
493
748
  }
494
749
  )
@@ -499,7 +754,14 @@ function ProductDimensionsFields({ values, setValue }) {
499
754
  Input,
500
755
  {
501
756
  value: weightValues?.unit ?? "",
502
- onChange: (event) => setValue("weight", updateWeightValue(values.weight ?? null, "unit", event.target.value)),
757
+ onChange: (event) => setValue(
758
+ "weight",
759
+ updateWeightValue(
760
+ values.weight ?? null,
761
+ "unit",
762
+ event.target.value
763
+ )
764
+ ),
503
765
  placeholder: "kg"
504
766
  }
505
767
  )
@@ -507,7 +769,13 @@ function ProductDimensionsFields({ values, setValue }) {
507
769
  ] })
508
770
  ] });
509
771
  }
510
- function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
772
+ function ProductBuilder({
773
+ values,
774
+ setValue,
775
+ errors,
776
+ priceKinds,
777
+ taxRates
778
+ }) {
511
779
  const t = useT();
512
780
  const steps = PRODUCT_FORM_STEPS;
513
781
  const [currentStep, setCurrentStep] = React.useState(0);
@@ -523,10 +791,13 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
523
791
  setValue("taxRateId", fallback.id);
524
792
  }, [taxRates, setValue, values.taxRateId]);
525
793
  const stepErrors = React.useMemo(() => {
526
- const map = steps.reduce((acc, step) => {
527
- acc[step] = [];
528
- return acc;
529
- }, {});
794
+ const map = steps.reduce(
795
+ (acc, step) => {
796
+ acc[step] = [];
797
+ return acc;
798
+ },
799
+ {}
800
+ );
530
801
  Object.entries(errors).forEach(([fieldId, message]) => {
531
802
  const step = resolveStepForField(fieldId);
532
803
  if (!step) return;
@@ -535,14 +806,20 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
535
806
  });
536
807
  return map;
537
808
  }, [errors, steps]);
538
- const errorSignature = React.useMemo(() => Object.keys(errors).sort().join("|"), [errors]);
809
+ const errorSignature = React.useMemo(
810
+ () => Object.keys(errors).sort().join("|"),
811
+ [errors]
812
+ );
539
813
  const lastErrorSignatureRef = React.useRef(null);
540
814
  React.useEffect(() => {
541
- if (!errorSignature || errorSignature === lastErrorSignatureRef.current) return;
815
+ if (!errorSignature || errorSignature === lastErrorSignatureRef.current)
816
+ return;
542
817
  lastErrorSignatureRef.current = errorSignature;
543
818
  const currentStepKey2 = steps[currentStep];
544
819
  if (currentStepKey2 && stepErrors[currentStepKey2]?.length) return;
545
- const fallbackIndex = steps.findIndex((step) => (stepErrors[step] ?? []).length > 0);
820
+ const fallbackIndex = steps.findIndex(
821
+ (step) => (stepErrors[step] ?? []).length > 0
822
+ );
546
823
  if (fallbackIndex >= 0 && fallbackIndex !== currentStep) {
547
824
  setCurrentStep(fallbackIndex);
548
825
  }
@@ -556,11 +833,16 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
556
833
  if (currentStep >= steps.length) setCurrentStep(0);
557
834
  }, [currentStep, steps.length]);
558
835
  const currentStepKey = steps[currentStep] ?? steps[0];
559
- const mediaItems = Array.isArray(values.mediaItems) ? values.mediaItems : [];
836
+ const mediaItems = React.useMemo(
837
+ () => Array.isArray(values.mediaItems) ? values.mediaItems : [],
838
+ [values.mediaItems]
839
+ );
560
840
  const handleMediaItemsChange = React.useCallback(
561
841
  (nextItems) => {
562
842
  setValue("mediaItems", nextItems);
563
- const hasCurrent = nextItems.some((item) => item.id === values.defaultMediaId);
843
+ const hasCurrent = nextItems.some(
844
+ (item) => item.id === values.defaultMediaId
845
+ );
564
846
  if (!hasCurrent) {
565
847
  const fallbackId = nextItems[0]?.id ?? null;
566
848
  setValue("defaultMediaId", fallbackId);
@@ -589,7 +871,9 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
589
871
  if (target) {
590
872
  setValue(
591
873
  "defaultMediaUrl",
592
- buildAttachmentImageUrl(target.id, { slug: slugifyAttachmentFileName(target.fileName) })
874
+ buildAttachmentImageUrl(target.id, {
875
+ slug: slugifyAttachmentFileName(target.fileName)
876
+ })
593
877
  );
594
878
  }
595
879
  },
@@ -599,13 +883,20 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
599
883
  const optionDefinitions = Array.isArray(values.options) ? values.options : [];
600
884
  if (!values.hasVariants || !optionDefinitions.length) {
601
885
  if (!values.variants || !values.variants.length) {
602
- setValue("variants", [createVariantDraft(values.taxRateId ?? null, { isDefault: true })]);
886
+ setValue("variants", [
887
+ createVariantDraft(values.taxRateId ?? null, { isDefault: true })
888
+ ]);
603
889
  }
604
890
  return;
605
891
  }
606
892
  const combos = buildVariantCombinations(optionDefinitions);
607
893
  const existing = Array.isArray(values.variants) ? values.variants : [];
608
- const existingByKey = new Map(existing.map((variant) => [buildOptionValuesKey(variant.optionValues), variant]));
894
+ const existingByKey = new Map(
895
+ existing.map((variant) => [
896
+ buildOptionValuesKey(variant.optionValues),
897
+ variant
898
+ ])
899
+ );
609
900
  let hasDefault = existing.some((variant) => variant.isDefault);
610
901
  let changed = existing.length !== combos.length;
611
902
  const nextVariants = combos.map((combo, index) => {
@@ -636,7 +927,13 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
636
927
  if (changed) {
637
928
  setValue("variants", nextVariants);
638
929
  }
639
- }, [values.options, values.variants, values.hasVariants, setValue]);
930
+ }, [
931
+ values.options,
932
+ values.variants,
933
+ values.hasVariants,
934
+ values.taxRateId,
935
+ setValue
936
+ ]);
640
937
  React.useEffect(() => {
641
938
  ensureVariants();
642
939
  }, [ensureVariants]);
@@ -656,10 +953,12 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
656
953
  }, [values.taxRateId, values.variants, setValue]);
657
954
  const setVariantField = React.useCallback(
658
955
  (variantId, field, value) => {
659
- const next = (Array.isArray(values.variants) ? values.variants : []).map((variant) => {
660
- if (variant.id !== variantId) return variant;
661
- return { ...variant, [field]: value };
662
- });
956
+ const next = (Array.isArray(values.variants) ? values.variants : []).map(
957
+ (variant) => {
958
+ if (variant.id !== variantId) return variant;
959
+ return { ...variant, [field]: value };
960
+ }
961
+ );
663
962
  setValue("variants", next);
664
963
  },
665
964
  [values.variants, setValue]
@@ -667,51 +966,72 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
667
966
  const setVariantPrice = React.useCallback(
668
967
  (variantId, priceKindId, amount) => {
669
968
  if (amount.trim().startsWith("-")) return;
670
- const next = (Array.isArray(values.variants) ? values.variants : []).map((variant) => {
671
- if (variant.id !== variantId) return variant;
672
- const nextPrices = { ...variant.prices ?? {} };
673
- if (amount === "") {
674
- delete nextPrices[priceKindId];
675
- } else {
676
- nextPrices[priceKindId] = { amount };
969
+ const next = (Array.isArray(values.variants) ? values.variants : []).map(
970
+ (variant) => {
971
+ if (variant.id !== variantId) return variant;
972
+ const nextPrices = { ...variant.prices ?? {} };
973
+ if (amount === "") {
974
+ delete nextPrices[priceKindId];
975
+ } else {
976
+ nextPrices[priceKindId] = { amount };
977
+ }
978
+ return {
979
+ ...variant,
980
+ prices: nextPrices
981
+ };
677
982
  }
678
- return {
983
+ );
984
+ setValue("variants", next);
985
+ },
986
+ [values.variants, setValue]
987
+ );
988
+ const markDefaultVariant = React.useCallback(
989
+ (variantId) => {
990
+ const next = (Array.isArray(values.variants) ? values.variants : []).map(
991
+ (variant) => ({
679
992
  ...variant,
680
- prices: nextPrices
681
- };
682
- });
993
+ isDefault: variant.id === variantId
994
+ })
995
+ );
683
996
  setValue("variants", next);
684
997
  },
685
998
  [values.variants, setValue]
686
999
  );
687
- const markDefaultVariant = React.useCallback((variantId) => {
688
- const next = (Array.isArray(values.variants) ? values.variants : []).map((variant) => ({
689
- ...variant,
690
- isDefault: variant.id === variantId
691
- }));
692
- setValue("variants", next);
693
- }, [values.variants, setValue]);
694
- const handleOptionTitleChange = React.useCallback((optionId, title) => {
695
- const next = (Array.isArray(values.options) ? values.options : []).map((option) => {
696
- if (option.id !== optionId) return option;
697
- return { ...option, title };
698
- });
699
- setValue("options", next);
700
- }, [values.options, setValue]);
701
- const setOptionValues = React.useCallback((optionId, labels) => {
702
- const normalized = labels.map((label) => label.trim()).filter((label) => label.length);
703
- const unique = Array.from(new Set(normalized));
704
- const next = (Array.isArray(values.options) ? values.options : []).map((option) => {
705
- if (option.id !== optionId) return option;
706
- const existingByLabel = new Map(option.values.map((value) => [value.label, value]));
707
- const nextValues = unique.map((label) => existingByLabel.get(label) ?? { id: createLocalId(), label });
708
- return {
709
- ...option,
710
- values: nextValues
711
- };
712
- });
713
- setValue("options", next);
714
- }, [values.options, setValue]);
1000
+ const handleOptionTitleChange = React.useCallback(
1001
+ (optionId, title) => {
1002
+ const next = (Array.isArray(values.options) ? values.options : []).map(
1003
+ (option) => {
1004
+ if (option.id !== optionId) return option;
1005
+ return { ...option, title };
1006
+ }
1007
+ );
1008
+ setValue("options", next);
1009
+ },
1010
+ [values.options, setValue]
1011
+ );
1012
+ const setOptionValues = React.useCallback(
1013
+ (optionId, labels) => {
1014
+ const normalized = labels.map((label) => label.trim()).filter((label) => label.length);
1015
+ const unique = Array.from(new Set(normalized));
1016
+ const next = (Array.isArray(values.options) ? values.options : []).map(
1017
+ (option) => {
1018
+ if (option.id !== optionId) return option;
1019
+ const existingByLabel = new Map(
1020
+ option.values.map((value) => [value.label, value])
1021
+ );
1022
+ const nextValues = unique.map(
1023
+ (label) => existingByLabel.get(label) ?? { id: createLocalId(), label }
1024
+ );
1025
+ return {
1026
+ ...option,
1027
+ values: nextValues
1028
+ };
1029
+ }
1030
+ );
1031
+ setValue("options", next);
1032
+ },
1033
+ [values.options, setValue]
1034
+ );
715
1035
  const addOption = React.useCallback(() => {
716
1036
  const next = [
717
1037
  ...Array.isArray(values.options) ? values.options : [],
@@ -719,10 +1039,15 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
719
1039
  ];
720
1040
  setValue("options", next);
721
1041
  }, [values.options, setValue]);
722
- const removeOption = React.useCallback((optionId) => {
723
- const next = (Array.isArray(values.options) ? values.options : []).filter((option) => option.id !== optionId);
724
- setValue("options", next);
725
- }, [values.options, setValue]);
1042
+ const removeOption = React.useCallback(
1043
+ (optionId) => {
1044
+ const next = (Array.isArray(values.options) ? values.options : []).filter(
1045
+ (option) => option.id !== optionId
1046
+ );
1047
+ setValue("options", next);
1048
+ },
1049
+ [values.options, setValue]
1050
+ );
726
1051
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
727
1052
  /* @__PURE__ */ jsx("nav", { className: "flex gap-6 border-b pb-2 text-sm font-medium", children: steps.map((step, index) => /* @__PURE__ */ jsxs(
728
1053
  Button,
@@ -738,8 +1063,15 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
738
1063
  children: [
739
1064
  step === "general" && t("catalog.products.create.steps.general", "General data"),
740
1065
  step === "organize" && t("catalog.products.create.steps.organize", "Organize"),
1066
+ step === "uom" && t("catalog.products.uom.title", "Units of measure"),
741
1067
  step === "variants" && t("catalog.products.create.steps.variants", "Variants"),
742
- (stepErrors[step]?.length ?? 0) > 0 ? /* @__PURE__ */ jsx("span", { className: "absolute -right-2 top-0 h-2 w-2 rounded-full bg-destructive", "aria-hidden": "true" }) : null,
1068
+ (stepErrors[step]?.length ?? 0) > 0 ? /* @__PURE__ */ jsx(
1069
+ "span",
1070
+ {
1071
+ className: "absolute -right-2 top-0 h-2 w-2 rounded-full bg-destructive",
1072
+ "aria-hidden": "true"
1073
+ }
1074
+ ) : null,
743
1075
  currentStep === index ? /* @__PURE__ */ jsx("span", { className: "absolute inset-x-0 -bottom-px h-0.5 bg-foreground rounded-full" }) : null
744
1076
  ]
745
1077
  },
@@ -756,7 +1088,10 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
756
1088
  {
757
1089
  value: values.title,
758
1090
  onChange: (event) => setValue("title", event.target.value),
759
- placeholder: t("catalog.products.create.placeholders.title", "e.g., Summer sneaker")
1091
+ placeholder: t(
1092
+ "catalog.products.create.placeholders.title",
1093
+ "e.g., Summer sneaker"
1094
+ )
760
1095
  }
761
1096
  ),
762
1097
  errors.title ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: errors.title }) : null
@@ -774,26 +1109,42 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
774
1109
  className: "gap-2 text-xs",
775
1110
  children: [
776
1111
  values.useMarkdown ? /* @__PURE__ */ jsx(AlignLeft, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(FileText, { className: "h-4 w-4" }),
777
- values.useMarkdown ? t("catalog.products.create.actions.usePlain", "Use plain text") : t("catalog.products.create.actions.useMarkdown", "Use markdown")
1112
+ values.useMarkdown ? t(
1113
+ "catalog.products.create.actions.usePlain",
1114
+ "Use plain text"
1115
+ ) : t(
1116
+ "catalog.products.create.actions.useMarkdown",
1117
+ "Use markdown"
1118
+ )
778
1119
  ]
779
1120
  }
780
1121
  )
781
1122
  ] }),
782
- values.useMarkdown ? /* @__PURE__ */ jsx("div", { "data-color-mode": "light", className: "overflow-hidden rounded-md border", children: /* @__PURE__ */ jsx(
783
- MarkdownEditor,
1123
+ values.useMarkdown ? /* @__PURE__ */ jsx(
1124
+ "div",
784
1125
  {
785
- value: values.description,
786
- height: 260,
787
- onChange: (val) => setValue("description", val ?? ""),
788
- previewOptions: { remarkPlugins: [] }
1126
+ "data-color-mode": "light",
1127
+ className: "overflow-hidden rounded-md border",
1128
+ children: /* @__PURE__ */ jsx(
1129
+ MarkdownEditor,
1130
+ {
1131
+ value: values.description,
1132
+ height: 260,
1133
+ onChange: (val) => setValue("description", val ?? ""),
1134
+ previewOptions: { remarkPlugins: [] }
1135
+ }
1136
+ )
789
1137
  }
790
- ) }) : /* @__PURE__ */ jsx(
1138
+ ) : /* @__PURE__ */ jsx(
791
1139
  "textarea",
792
1140
  {
793
1141
  className: "min-h-[180px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
794
1142
  value: values.description,
795
1143
  onChange: (event) => setValue("description", event.target.value),
796
- placeholder: t("catalog.products.create.placeholders.description", "Describe the product...")
1144
+ placeholder: t(
1145
+ "catalog.products.create.placeholders.description",
1146
+ "Describe the product..."
1147
+ )
797
1148
  }
798
1149
  )
799
1150
  ] }),
@@ -808,7 +1159,13 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
808
1159
  onDefaultChange: handleDefaultMediaChange
809
1160
  }
810
1161
  ),
811
- /* @__PURE__ */ jsx(ProductDimensionsFields, { values, setValue })
1162
+ /* @__PURE__ */ jsx(
1163
+ ProductDimensionsFields,
1164
+ {
1165
+ values,
1166
+ setValue
1167
+ }
1168
+ )
812
1169
  ] }) : null,
813
1170
  currentStepKey === "organize" ? /* @__PURE__ */ jsx(
814
1171
  ProductCategorizeSection,
@@ -818,6 +1175,15 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
818
1175
  errors
819
1176
  }
820
1177
  ) : null,
1178
+ currentStepKey === "uom" ? /* @__PURE__ */ jsx(
1179
+ ProductUomSection,
1180
+ {
1181
+ values,
1182
+ setValue,
1183
+ errors,
1184
+ embedded: true
1185
+ }
1186
+ ) : null,
821
1187
  currentStepKey === "variants" ? /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
822
1188
  /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm font-medium", children: [
823
1189
  /* @__PURE__ */ jsx(
@@ -829,69 +1195,141 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
829
1195
  onChange: (event) => setValue("hasVariants", event.target.checked)
830
1196
  }
831
1197
  ),
832
- t("catalog.products.create.variantsBuilder.toggle", "Yes, this is a product with variants")
1198
+ t(
1199
+ "catalog.products.create.variantsBuilder.toggle",
1200
+ "Yes, this is a product with variants"
1201
+ )
833
1202
  ] }),
834
1203
  values.hasVariants ? /* @__PURE__ */ jsxs("div", { className: "space-y-4 rounded-lg border p-4", children: [
835
1204
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
836
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t("catalog.products.create.optionsBuilder.title", "Product options") }),
837
- /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: addOption, children: [
838
- /* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
839
- t("catalog.products.create.optionsBuilder.add", "Add option")
840
- ] })
1205
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t(
1206
+ "catalog.products.create.optionsBuilder.title",
1207
+ "Product options"
1208
+ ) }),
1209
+ /* @__PURE__ */ jsxs(
1210
+ Button,
1211
+ {
1212
+ type: "button",
1213
+ variant: "outline",
1214
+ size: "sm",
1215
+ onClick: addOption,
1216
+ children: [
1217
+ /* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4" }),
1218
+ t(
1219
+ "catalog.products.create.optionsBuilder.add",
1220
+ "Add option"
1221
+ )
1222
+ ]
1223
+ }
1224
+ )
841
1225
  ] }),
842
- (Array.isArray(values.options) ? values.options : []).map((option) => /* @__PURE__ */ jsxs("div", { className: "rounded-md bg-muted/40 p-4", children: [
843
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
844
- /* @__PURE__ */ jsx(
845
- Input,
846
- {
847
- value: option.title,
848
- onChange: (event) => handleOptionTitleChange(option.id, event.target.value),
849
- placeholder: t("catalog.products.create.optionsBuilder.placeholder", "e.g., Color"),
850
- className: "flex-1"
851
- }
852
- ),
853
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", type: "button", onClick: () => removeOption(option.id), children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }) })
854
- ] }),
855
- /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-2", children: [
856
- /* @__PURE__ */ jsx(Label, { className: "text-xs uppercase text-muted-foreground", children: t("catalog.products.create.optionsBuilder.values", "Values") }),
857
- /* @__PURE__ */ jsx(
858
- TagsInput,
859
- {
860
- value: option.values.map((value) => value.label),
861
- onChange: (labels) => setOptionValues(option.id, labels),
862
- placeholder: t("catalog.products.create.optionsBuilder.valuePlaceholder", "Type a value and press Enter")
863
- }
864
- )
865
- ] })
866
- ] }, option.id)),
867
- !values.options?.length ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("catalog.products.create.optionsBuilder.empty", "No options yet. Add your first option to generate variants.") }) : null
1226
+ (Array.isArray(values.options) ? values.options : []).map(
1227
+ (option) => /* @__PURE__ */ jsxs("div", { className: "rounded-md bg-muted/40 p-4", children: [
1228
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1229
+ /* @__PURE__ */ jsx(
1230
+ Input,
1231
+ {
1232
+ value: option.title,
1233
+ onChange: (event) => handleOptionTitleChange(option.id, event.target.value),
1234
+ placeholder: t(
1235
+ "catalog.products.create.optionsBuilder.placeholder",
1236
+ "e.g., Color"
1237
+ ),
1238
+ className: "flex-1"
1239
+ }
1240
+ ),
1241
+ /* @__PURE__ */ jsx(
1242
+ Button,
1243
+ {
1244
+ variant: "ghost",
1245
+ size: "icon",
1246
+ type: "button",
1247
+ onClick: () => removeOption(option.id),
1248
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
1249
+ }
1250
+ )
1251
+ ] }),
1252
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 space-y-2", children: [
1253
+ /* @__PURE__ */ jsx(Label, { className: "text-xs uppercase text-muted-foreground", children: t(
1254
+ "catalog.products.create.optionsBuilder.values",
1255
+ "Values"
1256
+ ) }),
1257
+ /* @__PURE__ */ jsx(
1258
+ TagsInput,
1259
+ {
1260
+ value: option.values.map((value) => value.label),
1261
+ onChange: (labels) => setOptionValues(option.id, labels),
1262
+ placeholder: t(
1263
+ "catalog.products.create.optionsBuilder.valuePlaceholder",
1264
+ "Type a value and press Enter"
1265
+ )
1266
+ }
1267
+ )
1268
+ ] })
1269
+ ] }, option.id)
1270
+ ),
1271
+ !values.options?.length ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t(
1272
+ "catalog.products.create.optionsBuilder.empty",
1273
+ "No options yet. Add your first option to generate variants."
1274
+ ) }) : null
868
1275
  ] }) : null,
869
1276
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg border", children: [
870
1277
  /* @__PURE__ */ jsx("div", { className: "w-full overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-[900px] table-fixed border-collapse text-sm", children: [
871
1278
  /* @__PURE__ */ jsx("thead", { className: "bg-muted/40", children: /* @__PURE__ */ jsxs("tr", { children: [
872
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t("catalog.products.create.variantsBuilder.defaultOption", "Default option") }),
1279
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t(
1280
+ "catalog.products.create.variantsBuilder.defaultOption",
1281
+ "Default option"
1282
+ ) }),
873
1283
  /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t("catalog.products.form.variants", "Variant title") }),
874
1284
  /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t("catalog.products.create.variantsBuilder.sku", "SKU") }),
875
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t("catalog.products.create.variantsBuilder.vatColumn", "Tax class") }),
1285
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: t(
1286
+ "catalog.products.create.variantsBuilder.vatColumn",
1287
+ "Tax class"
1288
+ ) }),
876
1289
  priceKinds.map((kind) => /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
877
1290
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
878
- /* @__PURE__ */ jsx("span", { children: t("catalog.products.create.variantsBuilder.priceColumn", "Price {{title}}").replace("{{title}}", kind.title) }),
1291
+ /* @__PURE__ */ jsx("span", { children: t(
1292
+ "catalog.products.create.variantsBuilder.priceColumn",
1293
+ "Price {{title}}"
1294
+ ).replace("{{title}}", kind.title) }),
879
1295
  /* @__PURE__ */ jsx(
880
1296
  "small",
881
1297
  {
882
- title: kind.displayMode === "including-tax" ? t("catalog.priceKinds.form.displayMode.include", "Including tax") : t("catalog.priceKinds.form.displayMode.exclude", "Excluding tax"),
1298
+ title: kind.displayMode === "including-tax" ? t(
1299
+ "catalog.priceKinds.form.displayMode.include",
1300
+ "Including tax"
1301
+ ) : t(
1302
+ "catalog.priceKinds.form.displayMode.exclude",
1303
+ "Excluding tax"
1304
+ ),
883
1305
  className: "text-xs text-muted-foreground",
884
1306
  children: kind.displayMode === "including-tax" ? "\u24C9" : "\u24C3"
885
1307
  }
886
1308
  )
887
1309
  ] }),
888
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: kind.currencyCode?.toUpperCase() ?? t("catalog.products.create.variantsBuilder.currencyMissing", "Currency missing") })
1310
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: kind.currencyCode?.toUpperCase() ?? t(
1311
+ "catalog.products.create.variantsBuilder.currencyMissing",
1312
+ "Currency missing"
1313
+ ) })
889
1314
  ] }) }, kind.id)),
890
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t("catalog.products.create.variantsBuilder.manageInventory", "Managed inventory") }),
891
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t("catalog.products.create.variantsBuilder.allowBackorder", "Allow backorder") }),
892
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t("catalog.products.create.variantsBuilder.inventoryKit", "Has inventory kit") })
1315
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t(
1316
+ "catalog.products.create.variantsBuilder.manageInventory",
1317
+ "Managed inventory"
1318
+ ) }),
1319
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t(
1320
+ "catalog.products.create.variantsBuilder.allowBackorder",
1321
+ "Allow backorder"
1322
+ ) }),
1323
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-center", children: t(
1324
+ "catalog.products.create.variantsBuilder.inventoryKit",
1325
+ "Has inventory kit"
1326
+ ) })
893
1327
  ] }) }),
894
- /* @__PURE__ */ jsx("tbody", { children: (Array.isArray(values.variants) && values.variants.length ? values.variants : [createVariantDraft(values.taxRateId ?? null, { isDefault: true })]).map((variant) => /* @__PURE__ */ jsxs("tr", { className: "border-t", children: [
1328
+ /* @__PURE__ */ jsx("tbody", { children: (Array.isArray(values.variants) && values.variants.length ? values.variants : [
1329
+ createVariantDraft(values.taxRateId ?? null, {
1330
+ isDefault: true
1331
+ })
1332
+ ]).map((variant) => /* @__PURE__ */ jsxs("tr", { className: "border-t", children: [
895
1333
  /* @__PURE__ */ jsxs("td", { className: "px-3 py-2", children: [
896
1334
  /* @__PURE__ */ jsxs("label", { className: "inline-flex items-center gap-1 text-xs", children: [
897
1335
  /* @__PURE__ */ jsx(
@@ -903,7 +1341,13 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
903
1341
  onChange: () => markDefaultVariant(variant.id)
904
1342
  }
905
1343
  ),
906
- variant.isDefault ? t("catalog.products.create.variantsBuilder.defaultLabel", "Default option value") : t("catalog.products.create.variantsBuilder.makeDefault", "Set as default")
1344
+ variant.isDefault ? t(
1345
+ "catalog.products.create.variantsBuilder.defaultLabel",
1346
+ "Default option value"
1347
+ ) : t(
1348
+ "catalog.products.create.variantsBuilder.makeDefault",
1349
+ "Set as default"
1350
+ )
907
1351
  ] }),
908
1352
  values.hasVariants && variant.optionValues ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: Object.values(variant.optionValues).join(" / ") }) : null
909
1353
  ] }),
@@ -911,16 +1355,30 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
911
1355
  Input,
912
1356
  {
913
1357
  value: variant.title,
914
- onChange: (event) => setVariantField(variant.id, "title", event.target.value),
915
- placeholder: t("catalog.products.create.variantsBuilder.titlePlaceholder", "Variant title")
1358
+ onChange: (event) => setVariantField(
1359
+ variant.id,
1360
+ "title",
1361
+ event.target.value
1362
+ ),
1363
+ placeholder: t(
1364
+ "catalog.products.create.variantsBuilder.titlePlaceholder",
1365
+ "Variant title"
1366
+ )
916
1367
  }
917
1368
  ) }),
918
1369
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx(
919
1370
  Input,
920
1371
  {
921
1372
  value: variant.sku,
922
- onChange: (event) => setVariantField(variant.id, "sku", event.target.value),
923
- placeholder: t("catalog.products.create.variantsBuilder.skuPlaceholder", "e.g., SKU-001")
1373
+ onChange: (event) => setVariantField(
1374
+ variant.id,
1375
+ "sku",
1376
+ event.target.value
1377
+ ),
1378
+ placeholder: t(
1379
+ "catalog.products.create.variantsBuilder.skuPlaceholder",
1380
+ "e.g., SKU-001"
1381
+ )
924
1382
  }
925
1383
  ) }),
926
1384
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsxs(
@@ -928,10 +1386,20 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
928
1386
  {
929
1387
  className: "flex h-9 w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
930
1388
  value: variant.taxRateId ?? "",
931
- onChange: (event) => setVariantField(variant.id, "taxRateId", event.target.value || null),
1389
+ onChange: (event) => setVariantField(
1390
+ variant.id,
1391
+ "taxRateId",
1392
+ event.target.value || null
1393
+ ),
932
1394
  disabled: !taxRates.length,
933
1395
  children: [
934
- /* @__PURE__ */ jsx("option", { value: "", children: defaultTaxRateLabel ? t("catalog.products.create.variantsBuilder.vatOptionDefault", "Use product tax class ({{label}})").replace("{{label}}", defaultTaxRateLabel) : t("catalog.products.create.variantsBuilder.vatOptionNone", "No tax class") }),
1396
+ /* @__PURE__ */ jsx("option", { value: "", children: defaultTaxRateLabel ? t(
1397
+ "catalog.products.create.variantsBuilder.vatOptionDefault",
1398
+ "Use product tax class ({{label}})"
1399
+ ).replace("{{label}}", defaultTaxRateLabel) : t(
1400
+ "catalog.products.create.variantsBuilder.vatOptionNone",
1401
+ "No tax class"
1402
+ ) }),
935
1403
  taxRates.map((rate) => /* @__PURE__ */ jsx("option", { value: rate.id, children: formatTaxRateLabel(rate) }, rate.id))
936
1404
  ]
937
1405
  }
@@ -944,7 +1412,11 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
944
1412
  type: "number",
945
1413
  className: "w-full rounded-md border px-2 py-1",
946
1414
  value: variant.prices?.[kind.id]?.amount ?? "",
947
- onChange: (event) => setVariantPrice(variant.id, kind.id, event.target.value),
1415
+ onChange: (event) => setVariantPrice(
1416
+ variant.id,
1417
+ kind.id,
1418
+ event.target.value
1419
+ ),
948
1420
  placeholder: "0.00",
949
1421
  min: 0
950
1422
  }
@@ -956,7 +1428,11 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
956
1428
  type: "checkbox",
957
1429
  className: "h-4 w-4 rounded border disabled:cursor-not-allowed disabled:opacity-60",
958
1430
  checked: variant.manageInventory,
959
- onChange: (event) => setVariantField(variant.id, "manageInventory", event.target.checked),
1431
+ onChange: (event) => setVariantField(
1432
+ variant.id,
1433
+ "manageInventory",
1434
+ event.target.checked
1435
+ ),
960
1436
  disabled: true,
961
1437
  title: inventoryDisabledHint
962
1438
  }
@@ -967,7 +1443,11 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
967
1443
  type: "checkbox",
968
1444
  className: "h-4 w-4 rounded border disabled:cursor-not-allowed disabled:opacity-60",
969
1445
  checked: variant.allowBackorder,
970
- onChange: (event) => setVariantField(variant.id, "allowBackorder", event.target.checked),
1446
+ onChange: (event) => setVariantField(
1447
+ variant.id,
1448
+ "allowBackorder",
1449
+ event.target.checked
1450
+ ),
971
1451
  disabled: true,
972
1452
  title: inventoryDisabledHint
973
1453
  }
@@ -978,7 +1458,11 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
978
1458
  type: "checkbox",
979
1459
  className: "h-4 w-4 rounded border disabled:cursor-not-allowed disabled:opacity-60",
980
1460
  checked: variant.hasInventoryKit,
981
- onChange: (event) => setVariantField(variant.id, "hasInventoryKit", event.target.checked),
1461
+ onChange: (event) => setVariantField(
1462
+ variant.id,
1463
+ "hasInventoryKit",
1464
+ event.target.checked
1465
+ ),
982
1466
  disabled: true,
983
1467
  title: inventoryDisabledHint
984
1468
  }
@@ -987,7 +1471,10 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
987
1471
  ] }) }),
988
1472
  !priceKinds.length ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-t px-4 py-3 text-sm text-muted-foreground", children: [
989
1473
  /* @__PURE__ */ jsx(AlertCircle, { className: "h-4 w-4" }),
990
- t("catalog.products.create.variantsBuilder.noPriceKinds", "Configure price kinds in Catalog settings to add price columns.")
1474
+ t(
1475
+ "catalog.products.create.variantsBuilder.noPriceKinds",
1476
+ "Configure price kinds in Catalog settings to add price columns."
1477
+ )
991
1478
  ] }) : null
992
1479
  ] })
993
1480
  ] }) : null,
@@ -1024,7 +1511,12 @@ function ProductBuilder({ values, setValue, errors, priceKinds, taxRates }) {
1024
1511
  ] })
1025
1512
  ] });
1026
1513
  }
1027
- function ProductMetaSection({ values, setValue, errors, taxRates }) {
1514
+ function ProductMetaSection({
1515
+ values,
1516
+ setValue,
1517
+ errors,
1518
+ taxRates
1519
+ }) {
1028
1520
  const t = useT();
1029
1521
  const handleValue = typeof values.handle === "string" ? values.handle : "";
1030
1522
  const titleSource = typeof values.title === "string" ? values.title : "";
@@ -1064,7 +1556,10 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
1064
1556
  {
1065
1557
  value: typeof values.subtitle === "string" ? values.subtitle : "",
1066
1558
  onChange: (event) => setValue("subtitle", event.target.value),
1067
- placeholder: t("catalog.products.create.placeholders.subtitle", "Optional subtitle")
1559
+ placeholder: t(
1560
+ "catalog.products.create.placeholders.subtitle",
1561
+ "Optional subtitle"
1562
+ )
1068
1563
  }
1069
1564
  ),
1070
1565
  errors.subtitle ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: errors.subtitle }) : null
@@ -1077,7 +1572,10 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
1077
1572
  {
1078
1573
  value: handleValue,
1079
1574
  onChange: handleHandleInputChange,
1080
- placeholder: t("catalog.products.create.placeholders.handle", "e.g., summer-sneaker"),
1575
+ placeholder: t(
1576
+ "catalog.products.create.placeholders.handle",
1577
+ "e.g., summer-sneaker"
1578
+ ),
1081
1579
  className: "font-mono lowercase"
1082
1580
  }
1083
1581
  ),
@@ -1091,7 +1589,10 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
1091
1589
  }
1092
1590
  )
1093
1591
  ] }),
1094
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("catalog.products.create.handleHelp", "Handle is used for URLs and must be unique.") }),
1592
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
1593
+ "catalog.products.create.handleHelp",
1594
+ "Handle is used for URLs and must be unique."
1595
+ ) }),
1095
1596
  errors.handle ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: errors.handle }) : null
1096
1597
  ] }),
1097
1598
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
@@ -1105,14 +1606,24 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
1105
1606
  size: "icon",
1106
1607
  onClick: () => {
1107
1608
  if (typeof window !== "undefined") {
1108
- window.open("/backend/config/sales?section=tax-rates", "_blank", "noopener,noreferrer");
1609
+ window.open(
1610
+ "/backend/config/sales?section=tax-rates",
1611
+ "_blank",
1612
+ "noopener,noreferrer"
1613
+ );
1109
1614
  }
1110
1615
  },
1111
- title: t("catalog.products.create.taxRates.manage", "Manage tax classes"),
1616
+ title: t(
1617
+ "catalog.products.create.taxRates.manage",
1618
+ "Manage tax classes"
1619
+ ),
1112
1620
  className: "text-muted-foreground hover:text-foreground",
1113
1621
  children: [
1114
1622
  /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" }),
1115
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: t("catalog.products.create.taxRates.manage", "Manage tax classes") })
1623
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: t(
1624
+ "catalog.products.create.taxRates.manage",
1625
+ "Manage tax classes"
1626
+ ) })
1116
1627
  ]
1117
1628
  }
1118
1629
  )
@@ -1125,12 +1636,24 @@ function ProductMetaSection({ values, setValue, errors, taxRates }) {
1125
1636
  onChange: (event) => setValue("taxRateId", event.target.value || null),
1126
1637
  disabled: !taxRates.length,
1127
1638
  children: [
1128
- /* @__PURE__ */ jsx("option", { value: "", children: taxRates.length ? t("catalog.products.create.taxRates.noneSelected", "No tax class selected") : t("catalog.products.create.taxRates.emptyOption", "No tax classes available") }),
1639
+ /* @__PURE__ */ jsx("option", { value: "", children: taxRates.length ? t(
1640
+ "catalog.products.create.taxRates.noneSelected",
1641
+ "No tax class selected"
1642
+ ) : t(
1643
+ "catalog.products.create.taxRates.emptyOption",
1644
+ "No tax classes available"
1645
+ ) }),
1129
1646
  taxRates.map((rate) => /* @__PURE__ */ jsx("option", { value: rate.id, children: formatTaxRateLabel(rate) }, rate.id))
1130
1647
  ]
1131
1648
  }
1132
1649
  ),
1133
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: taxRates.length ? t("catalog.products.create.taxRates.help", "Applied to new prices unless overridden per variant.") : t("catalog.products.create.taxRates.empty", "Define tax classes under Sales \u2192 Configuration.") }),
1650
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: taxRates.length ? t(
1651
+ "catalog.products.create.taxRates.help",
1652
+ "Applied to new prices unless overridden per variant."
1653
+ ) : t(
1654
+ "catalog.products.create.taxRates.empty",
1655
+ "Define tax classes under Sales \u2192 Configuration."
1656
+ ) }),
1134
1657
  errors.taxRateId ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600", children: errors.taxRateId }) : null
1135
1658
  ] })
1136
1659
  ] });