@base44/app-plugin-commerce 0.1.3 → 0.1.4

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 (83) hide show
  1. package/README.md +8 -8
  2. package/base44/agents/commerce/StoreAdmin.jsonc +7 -7
  3. package/base44/entities/commerce.Order.jsonc +1 -1
  4. package/base44/entities/commerce.PaymentGateway.jsonc +3 -3
  5. package/base44/entities/commerce.Product.jsonc +9 -45
  6. package/base44/entities/commerce.ProductAttribute.jsonc +7 -19
  7. package/base44/entities/commerce.ProductAttributeTerm.jsonc +4 -10
  8. package/base44/entities/commerce.ProductTag.jsonc +1 -8
  9. package/base44/entities/commerce.ProductVariation.jsonc +1 -1
  10. package/base44/entities/commerce.StoreSettings.jsonc +1 -1
  11. package/base44/functions/commerce/admin-products/entry.ts +107 -35
  12. package/base44/functions/commerce/admin-refunds/entry.ts +1 -1
  13. package/base44/functions/commerce/admin-reports/entry.ts +1 -1
  14. package/base44/functions/commerce/admin-tools/entry.ts +4 -3
  15. package/base44/functions/commerce/payments/entry.ts +0 -2
  16. package/base44/functions/commerce/seed-store/defaults.ts +13 -39
  17. package/base44/functions/commerce/seed-store/entry.ts +47 -35
  18. package/base44/functions/commerce/seed-store/sample-data.ts +28 -66
  19. package/base44/functions/commerce/storefront-cart/entry.ts +0 -7
  20. package/base44/functions/commerce/storefront-catalog/entry.ts +12 -21
  21. package/base44/functions/commerce/storefront-checkout/entry.ts +5 -12
  22. package/base44/shared/commerce/coupons.ts +4 -7
  23. package/base44/shared/commerce/email-templates.ts +15 -13
  24. package/base44/shared/commerce/emails.ts +4 -3
  25. package/base44/shared/commerce/payments.ts +8 -20
  26. package/base44/shared/commerce/products.ts +24 -0
  27. package/base44/shared/commerce/settings.ts +2 -3
  28. package/base44/shared/commerce/stock.ts +2 -4
  29. package/base44/shared/commerce/totals.ts +37 -42
  30. package/package.json +1 -1
  31. package/scripts/install.js +21 -4
  32. package/skills/commerce/SKILL.md +6 -5
  33. package/skills/commerce/docs/api-admin.md +16 -14
  34. package/skills/commerce/docs/api-storefront.md +39 -35
  35. package/skills/commerce/installation-guidelines.md +4 -3
  36. package/skills/commerce/post-installation.md +22 -25
  37. package/skills/commerce/references/admin-product-form.md +56 -0
  38. package/skills/commerce/references/emails.md +4 -3
  39. package/skills/commerce/references/guest-access-security.md +2 -2
  40. package/skills/commerce/references/limits-and-performance.md +1 -1
  41. package/skills/commerce/references/online-payments.md +8 -8
  42. package/skills/commerce/references/product-render.md +25 -23
  43. package/skills/commerce/references/storefront-product-page.md +9 -9
  44. package/skills/commerce/references/webhooks.md +3 -1
  45. package/src/commerce/admin/README.md +4 -4
  46. package/src/commerce/admin/context/BasePathContext.jsx +5 -5
  47. package/src/commerce/admin/context/SettingsContext.jsx +4 -4
  48. package/src/commerce/admin/index.jsx +3 -3
  49. package/src/commerce/admin/layout/Sidebar.jsx +1 -9
  50. package/src/commerce/admin/lib/constants.js +0 -7
  51. package/src/commerce/admin/lib/paths.js +6 -6
  52. package/src/commerce/admin/lib/product-utils.js +18 -15
  53. package/src/commerce/admin/pages/orders/components/AddProductDialog.jsx +8 -8
  54. package/src/commerce/admin/pages/products/ProductEditor.jsx +10 -12
  55. package/src/commerce/admin/pages/products/ProductsList.jsx +3 -15
  56. package/src/commerce/admin/pages/products/components/AttributesSection.jsx +426 -0
  57. package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +33 -69
  58. package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +1 -1
  59. package/src/commerce/admin/pages/products/components/tabs/LinkedTab.jsx +1 -14
  60. package/src/commerce/admin/pages/products/components/tabs/ModifiersTab.jsx +19 -0
  61. package/src/commerce/admin/pages/products/components/tabs/PriceInventoryTab.jsx +728 -0
  62. package/src/commerce/admin/pages/reports/Reports.jsx +3 -3
  63. package/src/commerce/admin/pages/settings/EmailsSettings.jsx +12 -14
  64. package/src/commerce/admin/pages/settings/GeneralSettings.jsx +6 -116
  65. package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +101 -62
  66. package/src/commerce/admin/pages/settings/SettingsLayout.jsx +2 -6
  67. package/src/commerce/admin/pages/settings/TaxSettings.jsx +0 -1
  68. package/src/commerce/admin/pages/status/WebhookEditor.jsx +3 -3
  69. package/src/commerce/admin/pages/status/Webhooks.jsx +2 -2
  70. package/src/commerce/admin/routes.jsx +10 -19
  71. package/src/commerce/utils/index.js +2 -2
  72. package/src/commerce/utils/shipping-promos.js +9 -6
  73. package/src/commerce/utils/variants.js +21 -21
  74. package/src/commerce/admin/pages/products/AttributeTerms.jsx +0 -180
  75. package/src/commerce/admin/pages/products/Attributes.jsx +0 -183
  76. package/src/commerce/admin/pages/products/Tags.jsx +0 -150
  77. package/src/commerce/admin/pages/products/components/tabs/AdvancedTab.jsx +0 -48
  78. package/src/commerce/admin/pages/products/components/tabs/AttributesTab.jsx +0 -208
  79. package/src/commerce/admin/pages/products/components/tabs/ExternalTab.jsx +0 -41
  80. package/src/commerce/admin/pages/products/components/tabs/GeneralTab.jsx +0 -103
  81. package/src/commerce/admin/pages/products/components/tabs/InventoryTab.jsx +0 -93
  82. package/src/commerce/admin/pages/products/components/tabs/ShippingTab.jsx +0 -86
  83. 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
- }