@base44/app-plugin-commerce 0.1.3 → 0.1.5
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.
- package/README.md +11 -11
- package/base44/agents/commerce/StoreAdmin.jsonc +7 -7
- package/base44/entities/commerce.Order.jsonc +1 -1
- package/base44/entities/commerce.PaymentGateway.jsonc +3 -3
- package/base44/entities/commerce.Product.jsonc +9 -45
- package/base44/entities/commerce.ProductAttribute.jsonc +7 -19
- package/base44/entities/commerce.ProductAttributeTerm.jsonc +4 -10
- package/base44/entities/commerce.ProductTag.jsonc +1 -8
- package/base44/entities/commerce.ProductVariation.jsonc +1 -1
- package/base44/entities/commerce.StoreSettings.jsonc +1 -1
- package/base44/functions/commerce/admin-products/entry.ts +73 -121
- package/base44/functions/commerce/admin-refunds/entry.ts +1 -1
- package/base44/functions/commerce/admin-reports/entry.ts +1 -1
- package/base44/functions/commerce/admin-tools/entry.ts +4 -3
- package/base44/functions/commerce/payments/entry.ts +0 -2
- package/base44/functions/commerce/seed-store/defaults.ts +13 -39
- package/base44/functions/commerce/seed-store/entry.ts +50 -166
- package/base44/functions/commerce/seed-store/sample-data.ts +28 -66
- package/base44/functions/commerce/seed-store/seed-catalog.ts +717 -0
- package/base44/functions/commerce/storefront-cart/entry.ts +0 -7
- package/base44/functions/commerce/storefront-catalog/entry.ts +12 -21
- package/base44/functions/commerce/storefront-checkout/entry.ts +5 -12
- package/base44/shared/commerce/catalog.ts +134 -0
- package/base44/shared/commerce/coupons.ts +4 -7
- package/base44/shared/commerce/email-templates.ts +15 -13
- package/base44/shared/commerce/emails.ts +4 -3
- package/base44/shared/commerce/payments.ts +8 -20
- package/base44/shared/commerce/products.ts +24 -0
- package/base44/shared/commerce/settings.ts +2 -3
- package/base44/shared/commerce/stock.ts +2 -4
- package/base44/shared/commerce/totals.ts +37 -42
- package/package.json +1 -1
- package/scripts/install.js +23 -7
- package/skills/commerce/SKILL.md +9 -7
- package/skills/commerce/docs/api-admin.md +20 -14
- package/skills/commerce/docs/api-storefront.md +39 -35
- package/skills/commerce/installation-guidelines.md +8 -7
- package/skills/commerce/post-installation.md +205 -84
- package/skills/commerce/references/admin-product-form.md +56 -0
- package/skills/commerce/references/emails.md +4 -3
- package/skills/commerce/references/guest-access-security.md +2 -2
- package/skills/commerce/references/limits-and-performance.md +1 -1
- package/skills/commerce/references/online-payments.md +8 -8
- package/skills/commerce/references/product-render.md +25 -23
- package/skills/commerce/references/reviews.md +20 -0
- package/skills/commerce/references/storefront-product-page.md +9 -9
- package/skills/commerce/references/webhooks.md +3 -1
- package/src/commerce/admin/README.md +4 -4
- package/src/commerce/admin/context/BasePathContext.jsx +5 -5
- package/src/commerce/admin/context/SettingsContext.jsx +4 -4
- package/src/commerce/admin/index.jsx +3 -3
- package/src/commerce/admin/layout/Sidebar.jsx +1 -9
- package/src/commerce/admin/lib/constants.js +0 -7
- package/src/commerce/admin/lib/paths.js +6 -6
- package/src/commerce/admin/lib/product-utils.js +18 -15
- package/src/commerce/admin/pages/orders/components/AddProductDialog.jsx +8 -8
- package/src/commerce/admin/pages/products/ProductEditor.jsx +10 -12
- package/src/commerce/admin/pages/products/ProductsList.jsx +3 -15
- package/src/commerce/admin/pages/products/components/AttributesSection.jsx +426 -0
- package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +33 -69
- package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +1 -1
- package/src/commerce/admin/pages/products/components/tabs/LinkedTab.jsx +1 -14
- package/src/commerce/admin/pages/products/components/tabs/ModifiersTab.jsx +19 -0
- package/src/commerce/admin/pages/products/components/tabs/PriceInventoryTab.jsx +728 -0
- package/src/commerce/admin/pages/reports/Reports.jsx +3 -3
- package/src/commerce/admin/pages/settings/EmailsSettings.jsx +12 -14
- package/src/commerce/admin/pages/settings/GeneralSettings.jsx +6 -116
- package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +101 -62
- package/src/commerce/admin/pages/settings/SettingsLayout.jsx +2 -6
- package/src/commerce/admin/pages/settings/TaxSettings.jsx +0 -1
- package/src/commerce/admin/pages/status/WebhookEditor.jsx +3 -3
- package/src/commerce/admin/pages/status/Webhooks.jsx +2 -2
- package/src/commerce/admin/routes.jsx +10 -19
- package/src/commerce/utils/index.js +2 -2
- package/src/commerce/utils/shipping-promos.js +9 -6
- package/src/commerce/utils/variants.js +21 -21
- package/src/commerce/admin/pages/products/AttributeTerms.jsx +0 -180
- package/src/commerce/admin/pages/products/Attributes.jsx +0 -183
- package/src/commerce/admin/pages/products/Tags.jsx +0 -150
- package/src/commerce/admin/pages/products/components/tabs/AdvancedTab.jsx +0 -48
- package/src/commerce/admin/pages/products/components/tabs/AttributesTab.jsx +0 -208
- package/src/commerce/admin/pages/products/components/tabs/ExternalTab.jsx +0 -41
- package/src/commerce/admin/pages/products/components/tabs/GeneralTab.jsx +0 -103
- package/src/commerce/admin/pages/products/components/tabs/InventoryTab.jsx +0 -93
- package/src/commerce/admin/pages/products/components/tabs/ShippingTab.jsx +0 -86
- package/src/commerce/admin/pages/products/components/tabs/VariationsTab.jsx +0 -377
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
import React, { useMemo, useState } from "react";
|
|
2
|
-
import { Button } from "@/components/ui/button";
|
|
3
|
-
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
|
4
|
-
import { Input } from "@/components/ui/input";
|
|
5
|
-
import { Label } from "@/components/ui/label";
|
|
6
|
-
import { Checkbox } from "@/components/ui/checkbox";
|
|
7
|
-
import { Card } from "@/components/ui/card";
|
|
8
|
-
import {
|
|
9
|
-
Select,
|
|
10
|
-
SelectContent,
|
|
11
|
-
SelectItem,
|
|
12
|
-
SelectTrigger,
|
|
13
|
-
SelectValue,
|
|
14
|
-
} from "@/components/ui/select";
|
|
15
|
-
import { ChevronDown, ChevronRight, Trash2, TriangleAlert, Wand2 } from "lucide-react";
|
|
16
|
-
import { toast } from "sonner";
|
|
17
|
-
|
|
18
|
-
import MoneyInput from "../../../../components/MoneyInput";
|
|
19
|
-
import MediaUploader from "../../../../components/MediaUploader";
|
|
20
|
-
import ConfirmDialog from "../../../../components/ConfirmDialog";
|
|
21
|
-
import { generateVariationCombos, sameCombo, variationLabel } from "../../../../lib/product-utils";
|
|
22
|
-
import { BACKORDER_OPTIONS } from "../../../../lib/constants";
|
|
23
|
-
|
|
24
|
-
const NONE = "__none__";
|
|
25
|
-
const ANY = "__any__";
|
|
26
|
-
const num = (v) => (v === "" ? null : Number(v));
|
|
27
|
-
|
|
28
|
-
const NEW_VARIATION = {
|
|
29
|
-
status: "publish",
|
|
30
|
-
sku: "",
|
|
31
|
-
regular_price: null,
|
|
32
|
-
sale_price: null,
|
|
33
|
-
virtual: false,
|
|
34
|
-
downloadable: false,
|
|
35
|
-
downloads: [],
|
|
36
|
-
download_limit: -1,
|
|
37
|
-
download_expiry: -1,
|
|
38
|
-
tax_status: "parent",
|
|
39
|
-
tax_class: "",
|
|
40
|
-
manage_stock: "parent",
|
|
41
|
-
stock_quantity: null,
|
|
42
|
-
stock_status: "instock",
|
|
43
|
-
backorders: "no",
|
|
44
|
-
weight: null,
|
|
45
|
-
dimensions: { length: null, width: null, height: null },
|
|
46
|
-
shipping_class_id: "",
|
|
47
|
-
image: null,
|
|
48
|
-
menu_order: 0,
|
|
49
|
-
description: "",
|
|
50
|
-
meta_data: [],
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/** Per-variation expandable edit form. */
|
|
54
|
-
function VariationRow({ variation, onChange, onRemove, refs }) {
|
|
55
|
-
const [open, setOpen] = useState(false);
|
|
56
|
-
const set = (patch) => onChange({ ...variation, ...patch });
|
|
57
|
-
const dims = variation.dimensions || {};
|
|
58
|
-
const classes = refs.shippingClasses || [];
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<Card className="overflow-hidden">
|
|
62
|
-
<div className="flex items-center justify-between gap-2 px-3 py-2">
|
|
63
|
-
<button
|
|
64
|
-
type="button"
|
|
65
|
-
className="flex min-w-0 flex-1 items-center gap-2 text-left"
|
|
66
|
-
onClick={() => setOpen((o) => !o)}
|
|
67
|
-
>
|
|
68
|
-
{open ? <ChevronDown className="h-4 w-4 shrink-0" /> : <ChevronRight className="h-4 w-4 shrink-0" />}
|
|
69
|
-
<span className="truncate font-medium">{variationLabel(variation)}</span>
|
|
70
|
-
{variation.sku && <span className="truncate text-xs text-muted-foreground">SKU: {variation.sku}</span>}
|
|
71
|
-
</button>
|
|
72
|
-
<label className="flex items-center gap-1.5 text-sm">
|
|
73
|
-
<Checkbox
|
|
74
|
-
checked={variation.status !== "private" && variation.status !== "draft"}
|
|
75
|
-
onCheckedChange={(v) => set({ status: v ? "publish" : "private" })}
|
|
76
|
-
/>
|
|
77
|
-
Enabled
|
|
78
|
-
</label>
|
|
79
|
-
<Button type="button" variant="ghost" size="icon" onClick={onRemove}>
|
|
80
|
-
<Trash2 className="h-4 w-4 text-muted-foreground" />
|
|
81
|
-
</Button>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
{open && (
|
|
85
|
-
<div className="grid gap-4 border-t p-3 sm:grid-cols-[140px_1fr]">
|
|
86
|
-
<MediaUploader value={variation.image || null} onChange={(image) => set({ image })} label="Image" />
|
|
87
|
-
|
|
88
|
-
<div className="grid gap-3 sm:grid-cols-2">
|
|
89
|
-
<div className="space-y-1.5">
|
|
90
|
-
<Label className="text-xs">SKU</Label>
|
|
91
|
-
<Input value={variation.sku || ""} onChange={(e) => set({ sku: e.target.value })} />
|
|
92
|
-
</div>
|
|
93
|
-
<div />
|
|
94
|
-
<div className="space-y-1.5">
|
|
95
|
-
<Label className="text-xs">Regular price</Label>
|
|
96
|
-
<MoneyInput value={variation.regular_price} onChange={(v) => set({ regular_price: v })} />
|
|
97
|
-
</div>
|
|
98
|
-
<div className="space-y-1.5">
|
|
99
|
-
<Label className="text-xs">Sale price</Label>
|
|
100
|
-
<MoneyInput value={variation.sale_price} onChange={(v) => set({ sale_price: v })} />
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
<div className="space-y-1.5">
|
|
104
|
-
<Label className="text-xs">Stock management</Label>
|
|
105
|
-
<Select value={variation.manage_stock || "parent"} onValueChange={(v) => set({ manage_stock: v })}>
|
|
106
|
-
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
107
|
-
<SelectContent>
|
|
108
|
-
<SelectItem value="parent">Same as parent</SelectItem>
|
|
109
|
-
<SelectItem value="yes">Track quantity</SelectItem>
|
|
110
|
-
<SelectItem value="no">Don't track</SelectItem>
|
|
111
|
-
</SelectContent>
|
|
112
|
-
</Select>
|
|
113
|
-
</div>
|
|
114
|
-
{variation.manage_stock === "yes" && (
|
|
115
|
-
<>
|
|
116
|
-
<div className="space-y-1.5">
|
|
117
|
-
<Label className="text-xs">Stock quantity</Label>
|
|
118
|
-
<Input
|
|
119
|
-
type="number"
|
|
120
|
-
value={variation.stock_quantity ?? ""}
|
|
121
|
-
onChange={(e) => set({ stock_quantity: num(e.target.value) })}
|
|
122
|
-
/>
|
|
123
|
-
</div>
|
|
124
|
-
<div className="space-y-1.5">
|
|
125
|
-
<Label className="text-xs">Backorders</Label>
|
|
126
|
-
<Select value={variation.backorders || "no"} onValueChange={(v) => set({ backorders: v })}>
|
|
127
|
-
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
128
|
-
<SelectContent>
|
|
129
|
-
{BACKORDER_OPTIONS.map((o) => (
|
|
130
|
-
<SelectItem key={o.value} value={o.value}>{o.label}</SelectItem>
|
|
131
|
-
))}
|
|
132
|
-
</SelectContent>
|
|
133
|
-
</Select>
|
|
134
|
-
</div>
|
|
135
|
-
</>
|
|
136
|
-
)}
|
|
137
|
-
|
|
138
|
-
<div className="space-y-1.5">
|
|
139
|
-
<Label className="text-xs">Weight</Label>
|
|
140
|
-
<Input
|
|
141
|
-
type="number"
|
|
142
|
-
step="any"
|
|
143
|
-
value={variation.weight ?? ""}
|
|
144
|
-
onChange={(e) => set({ weight: num(e.target.value) })}
|
|
145
|
-
/>
|
|
146
|
-
</div>
|
|
147
|
-
<div className="space-y-1.5">
|
|
148
|
-
<Label className="text-xs">Dimensions (L×W×H)</Label>
|
|
149
|
-
<div className="grid grid-cols-3 gap-1">
|
|
150
|
-
{["length", "width", "height"].map((k) => (
|
|
151
|
-
<Input
|
|
152
|
-
key={k}
|
|
153
|
-
type="number"
|
|
154
|
-
step="any"
|
|
155
|
-
placeholder={k[0].toUpperCase()}
|
|
156
|
-
value={dims[k] ?? ""}
|
|
157
|
-
onChange={(e) => set({ dimensions: { ...dims, [k]: num(e.target.value) } })}
|
|
158
|
-
/>
|
|
159
|
-
))}
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
<div className="space-y-1.5">
|
|
164
|
-
<Label className="text-xs">Shipping class</Label>
|
|
165
|
-
<Select
|
|
166
|
-
value={variation.shipping_class_id || NONE}
|
|
167
|
-
onValueChange={(v) => set({ shipping_class_id: v === NONE ? "" : v })}
|
|
168
|
-
>
|
|
169
|
-
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
170
|
-
<SelectContent>
|
|
171
|
-
<SelectItem value={NONE}>Same as parent</SelectItem>
|
|
172
|
-
{classes.map((c) => (
|
|
173
|
-
<SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>
|
|
174
|
-
))}
|
|
175
|
-
</SelectContent>
|
|
176
|
-
</Select>
|
|
177
|
-
</div>
|
|
178
|
-
|
|
179
|
-
<div className="flex items-end gap-4 text-sm">
|
|
180
|
-
<label className="flex items-center gap-1.5">
|
|
181
|
-
<Checkbox checked={variation.virtual} onCheckedChange={(v) => set({ virtual: Boolean(v) })} />
|
|
182
|
-
Virtual
|
|
183
|
-
</label>
|
|
184
|
-
<label className="flex items-center gap-1.5">
|
|
185
|
-
<Checkbox
|
|
186
|
-
checked={variation.downloadable}
|
|
187
|
-
onCheckedChange={(v) => set({ downloadable: Boolean(v) })}
|
|
188
|
-
/>
|
|
189
|
-
Downloadable
|
|
190
|
-
</label>
|
|
191
|
-
</div>
|
|
192
|
-
|
|
193
|
-
{variation.downloadable && (
|
|
194
|
-
<div className="space-y-2 sm:col-span-2">
|
|
195
|
-
<Label className="text-xs">Downloadable files</Label>
|
|
196
|
-
{(variation.downloads || []).map((d, i) => (
|
|
197
|
-
<div key={i} className="flex items-center gap-2">
|
|
198
|
-
<Input
|
|
199
|
-
className="w-48"
|
|
200
|
-
placeholder="Name"
|
|
201
|
-
value={d.name || ""}
|
|
202
|
-
onChange={(e) =>
|
|
203
|
-
set({
|
|
204
|
-
downloads: variation.downloads.map((x, j) =>
|
|
205
|
-
j === i ? { ...x, name: e.target.value } : x
|
|
206
|
-
),
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
/>
|
|
210
|
-
<Input
|
|
211
|
-
className="flex-1"
|
|
212
|
-
placeholder="File URL"
|
|
213
|
-
value={d.file_url || ""}
|
|
214
|
-
onChange={(e) =>
|
|
215
|
-
set({
|
|
216
|
-
downloads: variation.downloads.map((x, j) =>
|
|
217
|
-
j === i ? { ...x, file_url: e.target.value } : x
|
|
218
|
-
),
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
/>
|
|
222
|
-
<Button
|
|
223
|
-
type="button"
|
|
224
|
-
variant="ghost"
|
|
225
|
-
size="icon"
|
|
226
|
-
onClick={() => set({ downloads: variation.downloads.filter((_, j) => j !== i) })}
|
|
227
|
-
>
|
|
228
|
-
<Trash2 className="h-4 w-4" />
|
|
229
|
-
</Button>
|
|
230
|
-
</div>
|
|
231
|
-
))}
|
|
232
|
-
<Button
|
|
233
|
-
type="button"
|
|
234
|
-
variant="outline"
|
|
235
|
-
size="sm"
|
|
236
|
-
onClick={() => set({ downloads: [...(variation.downloads || []), { name: "", file_url: "" }] })}
|
|
237
|
-
>
|
|
238
|
-
Add file
|
|
239
|
-
</Button>
|
|
240
|
-
</div>
|
|
241
|
-
)}
|
|
242
|
-
</div>
|
|
243
|
-
</div>
|
|
244
|
-
)}
|
|
245
|
-
</Card>
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Variations manager for variable products: generate combos from the
|
|
251
|
-
* variation-flagged attributes, set defaults, edit each variation inline.
|
|
252
|
-
* All changes persist on the editor's single Save.
|
|
253
|
-
*/
|
|
254
|
-
export default function VariationsTab({ product, up, refs, variations, setVariations }) {
|
|
255
|
-
const [confirmGenerate, setConfirmGenerate] = useState(null); // pending new combos
|
|
256
|
-
|
|
257
|
-
const variationAttrs = useMemo(
|
|
258
|
-
() => (product.attributes || []).filter((a) => a.variation && (a.options || []).length > 0),
|
|
259
|
-
[product.attributes]
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
const generate = () => {
|
|
263
|
-
const combos = generateVariationCombos(product.attributes);
|
|
264
|
-
if (!combos.length) {
|
|
265
|
-
toast.error("Add at least one attribute with 'Used for variations' checked first.");
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
const missing = combos.filter((c) => !variations.some((v) => sameCombo(v, c)));
|
|
269
|
-
if (!missing.length) {
|
|
270
|
-
toast.info("All attribute combinations already exist.");
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
setConfirmGenerate(missing);
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const applyGenerate = () => {
|
|
277
|
-
const start = variations.length;
|
|
278
|
-
setVariations([
|
|
279
|
-
...variations,
|
|
280
|
-
...confirmGenerate.map((c, i) => ({ ...NEW_VARIATION, ...c, menu_order: start + i })),
|
|
281
|
-
]);
|
|
282
|
-
toast.success(`${confirmGenerate.length} variation(s) added — save to persist.`);
|
|
283
|
-
setConfirmGenerate(null);
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const setDefault = (attrName, option) => {
|
|
287
|
-
const rest = (product.default_attributes || []).filter((d) => d.name !== attrName);
|
|
288
|
-
const attr = variationAttrs.find((a) => a.name === attrName);
|
|
289
|
-
up({
|
|
290
|
-
default_attributes:
|
|
291
|
-
option === ANY
|
|
292
|
-
? rest
|
|
293
|
-
: [...rest, { attribute_id: attr?.attribute_id || "", name: attrName, option }],
|
|
294
|
-
});
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
if (!variationAttrs.length) {
|
|
298
|
-
return (
|
|
299
|
-
<Alert>
|
|
300
|
-
<TriangleAlert className="h-4 w-4" />
|
|
301
|
-
<AlertTitle>This variable product can't be bought yet</AlertTitle>
|
|
302
|
-
<AlertDescription>
|
|
303
|
-
It has no attribute marked <span className="font-medium">Used for variations</span>, so there is
|
|
304
|
-
nothing to vary and no variation to add to a cart. Adding an attribute to the store's attribute
|
|
305
|
-
list is not enough — open the <span className="font-medium">Attributes</span> tab, add the
|
|
306
|
-
attribute <em>to this product</em> with its options, tick “Used for variations”, then generate the
|
|
307
|
-
variations here.
|
|
308
|
-
</AlertDescription>
|
|
309
|
-
</Alert>
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return (
|
|
314
|
-
<div className="space-y-4">
|
|
315
|
-
{variations.length === 0 && (
|
|
316
|
-
<Alert>
|
|
317
|
-
<TriangleAlert className="h-4 w-4" />
|
|
318
|
-
<AlertTitle>No variations yet — customers can't buy this product</AlertTitle>
|
|
319
|
-
<AlertDescription>
|
|
320
|
-
A variable product is sold through its variations, so with none defined the storefront has
|
|
321
|
-
nothing to add to a cart. Use “Generate variations from attributes” below, then set each one's
|
|
322
|
-
price and stock.
|
|
323
|
-
</AlertDescription>
|
|
324
|
-
</Alert>
|
|
325
|
-
)}
|
|
326
|
-
|
|
327
|
-
<div className="flex flex-wrap items-end gap-2">
|
|
328
|
-
<Button type="button" variant="outline" onClick={generate}>
|
|
329
|
-
<Wand2 className="mr-2 h-4 w-4" /> Generate variations from attributes
|
|
330
|
-
</Button>
|
|
331
|
-
<span className="text-sm text-muted-foreground">{variations.length} variation(s)</span>
|
|
332
|
-
</div>
|
|
333
|
-
|
|
334
|
-
<div className="flex flex-wrap gap-3">
|
|
335
|
-
{variationAttrs.map((attr) => {
|
|
336
|
-
const current = (product.default_attributes || []).find((d) => d.name === attr.name);
|
|
337
|
-
return (
|
|
338
|
-
<div key={attr.name} className="space-y-1">
|
|
339
|
-
<Label className="text-xs">Default {attr.name}</Label>
|
|
340
|
-
<Select value={current?.option || ANY} onValueChange={(v) => setDefault(attr.name, v)}>
|
|
341
|
-
<SelectTrigger className="h-8 w-44"><SelectValue /></SelectTrigger>
|
|
342
|
-
<SelectContent>
|
|
343
|
-
<SelectItem value={ANY}>No default</SelectItem>
|
|
344
|
-
{(attr.options || []).map((o) => (
|
|
345
|
-
<SelectItem key={o} value={o}>{o}</SelectItem>
|
|
346
|
-
))}
|
|
347
|
-
</SelectContent>
|
|
348
|
-
</Select>
|
|
349
|
-
</div>
|
|
350
|
-
);
|
|
351
|
-
})}
|
|
352
|
-
</div>
|
|
353
|
-
|
|
354
|
-
<div className="space-y-2">
|
|
355
|
-
{variations.map((v, i) => (
|
|
356
|
-
<VariationRow
|
|
357
|
-
key={v.id || `new-${i}`}
|
|
358
|
-
variation={v}
|
|
359
|
-
refs={refs}
|
|
360
|
-
onChange={(nv) => setVariations(variations.map((x, j) => (j === i ? nv : x)))}
|
|
361
|
-
onRemove={() => setVariations(variations.filter((_, j) => j !== i))}
|
|
362
|
-
/>
|
|
363
|
-
))}
|
|
364
|
-
</div>
|
|
365
|
-
|
|
366
|
-
<ConfirmDialog
|
|
367
|
-
open={Boolean(confirmGenerate)}
|
|
368
|
-
onOpenChange={(o) => !o && setConfirmGenerate(null)}
|
|
369
|
-
title={`Create ${confirmGenerate?.length || 0} new variation(s)?`}
|
|
370
|
-
description="One variation is created for each missing attribute combination. They are saved when you save the product."
|
|
371
|
-
confirmLabel="Generate"
|
|
372
|
-
destructive={false}
|
|
373
|
-
onConfirm={applyGenerate}
|
|
374
|
-
/>
|
|
375
|
-
</div>
|
|
376
|
-
);
|
|
377
|
-
}
|