@base44/app-plugin-commerce 0.1.5 → 0.1.7

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 (95) hide show
  1. package/README.md +11 -11
  2. package/base44/agents/commerce/StoreAdmin.jsonc +2 -2
  3. package/base44/entities/commerce.Cart.jsonc +1 -1
  4. package/base44/entities/commerce.Coupon.jsonc +5 -0
  5. package/base44/entities/commerce.Order.jsonc +6 -7
  6. package/base44/entities/commerce.OrderRefund.jsonc +1 -1
  7. package/base44/entities/commerce.PaymentGateway.jsonc +1 -1
  8. package/base44/entities/commerce.Product.jsonc +6 -16
  9. package/base44/entities/{commerce.ProductTag.jsonc → commerce.ProductRibbon.jsonc} +2 -2
  10. package/base44/entities/commerce.ProductVariation.jsonc +1 -9
  11. package/base44/entities/commerce.ShippingTaxLocation.jsonc +85 -0
  12. package/base44/entities/commerce.Webhook.jsonc +1 -1
  13. package/base44/functions/commerce/admin-orders/helpers.ts +7 -13
  14. package/base44/functions/commerce/admin-products/entry.ts +11 -17
  15. package/base44/functions/commerce/admin-refunds/entry.ts +10 -9
  16. package/base44/functions/commerce/admin-reports/entry.ts +3 -3
  17. package/base44/functions/commerce/admin-tools/entry.ts +9 -36
  18. package/base44/functions/commerce/payment-webhook/entry.ts +50 -89
  19. package/base44/functions/commerce/payments/entry.ts +46 -42
  20. package/base44/functions/commerce/seed-store/defaults.ts +35 -42
  21. package/base44/functions/commerce/seed-store/entry.ts +84 -31
  22. package/base44/functions/commerce/seed-store/sample-data.ts +2 -15
  23. package/base44/functions/commerce/seed-store/seed-catalog.ts +105 -55
  24. package/base44/functions/commerce/storefront-cart/cart-pricing.ts +6 -11
  25. package/base44/functions/commerce/storefront-cart/entry.ts +36 -2
  26. package/base44/functions/commerce/storefront-catalog/entry.ts +55 -72
  27. package/base44/functions/commerce/storefront-checkout/cart-pricing.ts +6 -11
  28. package/base44/functions/commerce/storefront-checkout/entry.ts +43 -56
  29. package/base44/shared/commerce/card-payment.ts +80 -0
  30. package/base44/shared/commerce/coupons.ts +16 -10
  31. package/base44/shared/commerce/emails.ts +30 -18
  32. package/base44/shared/commerce/money.ts +11 -24
  33. package/base44/shared/commerce/payments.ts +55 -286
  34. package/base44/shared/commerce/scan.ts +1 -1
  35. package/base44/shared/commerce/sequence.ts +1 -1
  36. package/base44/shared/commerce/settings.ts +4 -9
  37. package/base44/shared/commerce/shipping.ts +65 -133
  38. package/base44/shared/commerce/tax.ts +48 -96
  39. package/base44/shared/commerce/totals.ts +77 -91
  40. package/package.json +1 -1
  41. package/scripts/install.js +28 -7
  42. package/skills/commerce/SKILL.md +14 -14
  43. package/skills/commerce/docs/api-admin.md +23 -26
  44. package/skills/commerce/docs/api-storefront.md +67 -61
  45. package/skills/commerce/installation-guidelines.md +8 -8
  46. package/skills/commerce/post-installation.md +76 -41
  47. package/skills/commerce/references/admin-product-form.md +15 -12
  48. package/skills/commerce/references/emails.md +2 -2
  49. package/skills/commerce/references/guest-access-security.md +2 -2
  50. package/skills/commerce/references/online-payments.md +24 -191
  51. package/skills/commerce/references/product-render.md +18 -18
  52. package/skills/commerce/references/reviews.md +14 -8
  53. package/skills/commerce/references/storefront-product-page.md +1 -1
  54. package/src/commerce/admin/README.md +4 -5
  55. package/src/commerce/admin/bot/Markdown.jsx +1 -1
  56. package/src/commerce/admin/hooks/useMoney.js +13 -22
  57. package/src/commerce/admin/layout/AuthGuard.jsx +1 -1
  58. package/src/commerce/admin/lib/constants.js +2 -29
  59. package/src/commerce/admin/lib/order-utils.js +1 -1
  60. package/src/commerce/admin/pages/coupons/CouponEditor.jsx +131 -140
  61. package/src/commerce/admin/pages/coupons/CouponsList.jsx +14 -7
  62. package/src/commerce/admin/pages/orders/OrderEditor.jsx +3 -3
  63. package/src/commerce/admin/pages/orders/components/PaymentPanel.jsx +17 -28
  64. package/src/commerce/admin/pages/orders/components/RefundPanel.jsx +5 -11
  65. package/src/commerce/admin/pages/products/Categories.jsx +147 -177
  66. package/src/commerce/admin/pages/products/ProductEditor.jsx +23 -18
  67. package/src/commerce/admin/pages/products/Reviews.jsx +37 -1
  68. package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +33 -47
  69. package/src/commerce/admin/pages/products/components/PublishBox.jsx +13 -34
  70. package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +29 -29
  71. package/src/commerce/admin/pages/products/components/tabs/PriceInventoryTab.jsx +14 -44
  72. package/src/commerce/admin/pages/reports/Reports.jsx +2 -2
  73. package/src/commerce/admin/pages/settings/EmailsSettings.jsx +101 -68
  74. package/src/commerce/admin/pages/settings/GeneralSettings.jsx +40 -38
  75. package/src/commerce/admin/pages/settings/InventorySettings.jsx +1 -41
  76. package/src/commerce/admin/pages/settings/LocationEditor.jsx +377 -0
  77. package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +137 -119
  78. package/src/commerce/admin/pages/settings/SettingsLayout.jsx +2 -4
  79. package/src/commerce/admin/pages/settings/ShippingTaxSettings.jsx +191 -0
  80. package/src/commerce/admin/routes.jsx +6 -12
  81. package/src/commerce/utils/index.js +2 -2
  82. package/src/commerce/utils/shipping-promos.js +45 -49
  83. package/src/commerce/utils/variants.js +1 -1
  84. package/base44/entities/commerce.ShippingClass.jsonc +0 -30
  85. package/base44/entities/commerce.ShippingZone.jsonc +0 -41
  86. package/base44/entities/commerce.ShippingZoneMethod.jsonc +0 -84
  87. package/base44/entities/commerce.TaxClass.jsonc +0 -23
  88. package/base44/entities/commerce.TaxRate.jsonc +0 -68
  89. package/base44/shared/commerce/stripe.ts +0 -463
  90. package/src/commerce/admin/hooks/usePaymentProvider.js +0 -27
  91. package/src/commerce/admin/pages/settings/ProductsSettings.jsx +0 -118
  92. package/src/commerce/admin/pages/settings/ShippingSettings.jsx +0 -304
  93. package/src/commerce/admin/pages/settings/ShippingZoneEditor.jsx +0 -514
  94. package/src/commerce/admin/pages/settings/TaxRatesTable.jsx +0 -231
  95. package/src/commerce/admin/pages/settings/TaxSettings.jsx +0 -280
@@ -1,304 +0,0 @@
1
- import React, { useMemo, useState } from "react";
2
- import { useNavigate } from "react-router-dom";
3
- import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
4
- import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
5
- import { Button } from "@/components/ui/button";
6
- import { Input } from "@/components/ui/input";
7
- import { Label } from "@/components/ui/label";
8
- import { Textarea } from "@/components/ui/textarea";
9
- import { Badge } from "@/components/ui/badge";
10
- import { Loader2, Plus, Truck } from "lucide-react";
11
- import { toast } from "sonner";
12
- import DataTable from "../../components/DataTable";
13
- import ConfirmDialog from "../../components/ConfirmDialog";
14
- import { base44 } from "../../lib/api";
15
- import useAsync from "../../hooks/useAsync";
16
- import { useAdminHref } from "../../context/BasePathContext";
17
- import { slugify } from "../../lib/product-utils";
18
- import { countryName } from "../../lib/geo-data";
19
- import { CONTINENTS } from "../../lib/geo-data";
20
-
21
- /** Human summary of a zone's embedded locations array. */
22
- function regionsSummary(locations) {
23
- const locs = locations || [];
24
- if (locs.length === 0) return "Everywhere not covered by other zones";
25
- const names = [];
26
- let postcodes = 0;
27
- for (const l of locs) {
28
- if (l.type === "postcode") postcodes += 1;
29
- else if (l.type === "continent")
30
- names.push(CONTINENTS.find((c) => c.code === l.code)?.name || l.code);
31
- else if (l.type === "country") names.push(countryName(l.code));
32
- else if (l.type === "state") names.push(l.code);
33
- }
34
- const parts = [];
35
- if (names.length) parts.push(names.slice(0, 4).join(", ") + (names.length > 4 ? `, +${names.length - 4}` : ""));
36
- if (postcodes) parts.push(`${postcodes} postcode${postcodes > 1 ? "s" : ""}`);
37
- return parts.join(" · ") || "—";
38
- }
39
-
40
- function ZonesTab() {
41
- const navigate = useNavigate();
42
- const href = useAdminHref();
43
- const zones = useAsync(() => base44.entities["commerce.ShippingZone"].list("order", 200), []);
44
- const methods = useAsync(() => base44.entities["commerce.ShippingZoneMethod"].list(undefined, 500), []);
45
- const [creating, setCreating] = useState(false);
46
- const [toDelete, setToDelete] = useState(null);
47
- const [deleting, setDeleting] = useState(false);
48
-
49
- const methodsByZone = useMemo(() => {
50
- const map = {};
51
- (methods.data || []).forEach((m) => {
52
- (map[m.zone_id] = map[m.zone_id] || []).push(m);
53
- });
54
- return map;
55
- }, [methods.data]);
56
-
57
- // Sort by order, but push empty-location (rest-of-world) zones to the end.
58
- const rows = useMemo(() => {
59
- return [...(zones.data || [])].sort((a, b) => {
60
- const ae = (a.locations || []).length === 0;
61
- const be = (b.locations || []).length === 0;
62
- if (ae !== be) return ae ? 1 : -1;
63
- return (a.order ?? 0) - (b.order ?? 0);
64
- });
65
- }, [zones.data]);
66
-
67
- const addZone = async () => {
68
- setCreating(true);
69
- try {
70
- const maxOrder = (zones.data || [])
71
- .filter((z) => (z.locations || []).length > 0)
72
- .reduce((m, z) => Math.max(m, z.order ?? 0), 0);
73
- const zone = await base44.entities["commerce.ShippingZone"].create({
74
- name: "New zone",
75
- order: maxOrder + 1,
76
- locations: [],
77
- });
78
- navigate(href(`settings/shipping/zones/${zone.id}`));
79
- } catch (err) {
80
- toast.error(err.message || "Failed to create zone");
81
- setCreating(false);
82
- }
83
- };
84
-
85
- const removeZone = async () => {
86
- setDeleting(true);
87
- try {
88
- // Methods first: a ShippingZoneMethod outlives its zone otherwise, and
89
- // nothing else reaps them.
90
- for (const m of methodsByZone[toDelete.id] || []) {
91
- await base44.entities["commerce.ShippingZoneMethod"].delete(m.id);
92
- }
93
- await base44.entities["commerce.ShippingZone"].delete(toDelete.id);
94
- toast.success("Shipping zone deleted");
95
- setToDelete(null);
96
- zones.refetch();
97
- methods.refetch();
98
- } catch (err) {
99
- toast.error(err.message || "Failed to delete zone");
100
- } finally {
101
- setDeleting(false);
102
- }
103
- };
104
-
105
- const columns = [
106
- {
107
- key: "name",
108
- label: "Zone name",
109
- render: (z) => <span className="font-medium">{z.name}</span>,
110
- },
111
- { key: "regions", label: "Region(s)", render: (z) => regionsSummary(z.locations) },
112
- {
113
- key: "methods",
114
- label: "Shipping method(s)",
115
- render: (z) => {
116
- const ms = methodsByZone[z.id] || [];
117
- if (!ms.length) return <span className="text-muted-foreground">No methods</span>;
118
- return (
119
- <div className="flex flex-wrap gap-1">
120
- {ms.map((m) => (
121
- <Badge key={m.id} variant={m.enabled ? "secondary" : "outline"} className="font-normal">
122
- {m.title || m.method_id}
123
- </Badge>
124
- ))}
125
- </div>
126
- );
127
- },
128
- },
129
- ];
130
-
131
- return (
132
- <div className="space-y-4">
133
- <div className="flex justify-end">
134
- <Button onClick={addZone} disabled={creating}>
135
- {creating ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Plus className="mr-2 h-4 w-4" />}
136
- Add shipping zone
137
- </Button>
138
- </div>
139
- <DataTable
140
- columns={columns}
141
- rows={rows}
142
- loading={zones.loading || methods.loading}
143
- onRowClick={(z) => navigate(href(`settings/shipping/zones/${z.id}`))}
144
- rowActions={(z) => [
145
- { label: "Edit", onClick: () => navigate(href(`settings/shipping/zones/${z.id}`)) },
146
- { label: "Delete", destructive: true, onClick: () => setToDelete(z) },
147
- ]}
148
- empty={{ icon: Truck, title: "No shipping zones", description: "Create a zone to offer shipping methods to your customers." }}
149
- />
150
-
151
- <ConfirmDialog
152
- open={!!toDelete}
153
- onOpenChange={(o) => !o && setToDelete(null)}
154
- title={`Delete ${toDelete?.name || "shipping zone"}?`}
155
- description={
156
- (toDelete?.locations || []).length === 0
157
- ? "This is the catch-all zone. Customers outside your other zones will have no shipping methods until you add another catch-all."
158
- : "The zone and its shipping methods are deleted. Existing orders keep the shipping they were charged."
159
- }
160
- confirmLabel="Delete"
161
- loading={deleting}
162
- onConfirm={removeZone}
163
- />
164
- </div>
165
- );
166
- }
167
-
168
- function ClassesTab() {
169
- const classes = useAsync(() => base44.entities["commerce.ShippingClass"].list("name", 200), []);
170
- const blank = { id: null, name: "", slug: "", description: "" };
171
- const [form, setForm] = useState(blank);
172
- const [slugTouched, setSlugTouched] = useState(false);
173
- const [saving, setSaving] = useState(false);
174
- const [toDelete, setToDelete] = useState(null);
175
- const [deleting, setDeleting] = useState(false);
176
-
177
- const setName = (name) =>
178
- setForm((f) => ({ ...f, name, slug: slugTouched ? f.slug : slugify(name) }));
179
-
180
- const reset = () => {
181
- setForm(blank);
182
- setSlugTouched(false);
183
- };
184
-
185
- const save = async () => {
186
- if (!form.name.trim()) {
187
- toast.error("Name is required");
188
- return;
189
- }
190
- setSaving(true);
191
- try {
192
- const payload = { name: form.name, slug: form.slug || slugify(form.name), description: form.description };
193
- if (form.id) await base44.entities["commerce.ShippingClass"].update(form.id, payload);
194
- else await base44.entities["commerce.ShippingClass"].create(payload);
195
- toast.success("Shipping class saved");
196
- reset();
197
- classes.refetch();
198
- } catch (err) {
199
- toast.error(err.message || "Failed to save");
200
- } finally {
201
- setSaving(false);
202
- }
203
- };
204
-
205
- const remove = async () => {
206
- setDeleting(true);
207
- try {
208
- await base44.entities["commerce.ShippingClass"].delete(toDelete.id);
209
- toast.success("Shipping class deleted");
210
- if (form.id === toDelete.id) reset();
211
- setToDelete(null);
212
- classes.refetch();
213
- } catch (err) {
214
- toast.error(err.message || "Failed to delete");
215
- } finally {
216
- setDeleting(false);
217
- }
218
- };
219
-
220
- const columns = [
221
- { key: "name", label: "Name", render: (c) => <span className="font-medium">{c.name}</span> },
222
- { key: "slug", label: "Slug", render: (c) => <code className="text-xs">{c.slug}</code> },
223
- { key: "description", label: "Description", render: (c) => c.description || "—" },
224
- ];
225
-
226
- return (
227
- <div className="grid gap-4 lg:grid-cols-[320px_1fr]">
228
- <Card className="h-fit">
229
- <CardHeader>
230
- <CardTitle>{form.id ? "Edit shipping class" : "Add shipping class"}</CardTitle>
231
- </CardHeader>
232
- <CardContent className="space-y-3">
233
- <div className="space-y-1.5">
234
- <Label>Name</Label>
235
- <Input value={form.name} onChange={(e) => setName(e.target.value)} />
236
- </div>
237
- <div className="space-y-1.5">
238
- <Label>Slug</Label>
239
- <Input
240
- value={form.slug}
241
- onChange={(e) => {
242
- setSlugTouched(true);
243
- setForm((f) => ({ ...f, slug: e.target.value }));
244
- }}
245
- />
246
- </div>
247
- <div className="space-y-1.5">
248
- <Label>Description</Label>
249
- <Textarea rows={3} value={form.description} onChange={(e) => setForm((f) => ({ ...f, description: e.target.value }))} />
250
- </div>
251
- <div className="flex gap-2">
252
- <Button onClick={save} disabled={saving}>
253
- {saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
254
- {form.id ? "Update" : "Add"}
255
- </Button>
256
- {form.id && (
257
- <Button variant="ghost" onClick={reset}>
258
- Cancel
259
- </Button>
260
- )}
261
- </div>
262
- </CardContent>
263
- </Card>
264
-
265
- <DataTable
266
- columns={columns}
267
- rows={classes.data || []}
268
- loading={classes.loading}
269
- rowActions={(c) => [
270
- { label: "Edit", onClick: () => setForm({ id: c.id, name: c.name, slug: c.slug || "", description: c.description || "" }) },
271
- { label: "Delete", destructive: true, onClick: () => setToDelete(c) },
272
- ]}
273
- empty={{ title: "No shipping classes", description: "Shipping classes group products with similar shipping costs." }}
274
- />
275
-
276
- <ConfirmDialog
277
- open={!!toDelete}
278
- onOpenChange={(o) => !o && setToDelete(null)}
279
- title="Delete shipping class?"
280
- description="Products in this class will not be deleted, but will lose their class assignment."
281
- confirmLabel="Delete"
282
- loading={deleting}
283
- onConfirm={remove}
284
- />
285
- </div>
286
- );
287
- }
288
-
289
- export default function ShippingSettings() {
290
- return (
291
- <Tabs defaultValue="zones" className="space-y-4">
292
- <TabsList>
293
- <TabsTrigger value="zones">Shipping zones</TabsTrigger>
294
- <TabsTrigger value="classes">Shipping classes</TabsTrigger>
295
- </TabsList>
296
- <TabsContent value="zones">
297
- <ZonesTab />
298
- </TabsContent>
299
- <TabsContent value="classes">
300
- <ClassesTab />
301
- </TabsContent>
302
- </Tabs>
303
- );
304
- }