@base44/app-plugin-commerce 0.7.1 → 0.8.0
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 +4 -10
- package/base44/entities/commerce.ProductReview.jsonc +2 -2
- package/base44/functions/commerce/storefront-catalog/entry.ts +7 -3
- package/package.json +1 -1
- package/scripts/install.js +2 -4
- package/skills/commerce/SKILL.md +9 -3
- package/skills/commerce/docs/api-storefront.md +3 -3
- package/skills/commerce/install/01-install.md +8 -24
- package/skills/commerce/install/02-storefront.md +21 -9
- package/skills/commerce/references/admin-localization.md +95 -0
- package/skills/commerce/references/reviews.md +7 -6
- package/src/commerce/admin/README.md +36 -14
- package/src/commerce/admin/bot/StoreAdminBot.jsx +12 -11
- package/src/commerce/admin/components/AddressForm.jsx +13 -12
- package/src/commerce/admin/components/ConfirmDialog.jsx +5 -4
- package/src/commerce/admin/components/CountrySelect.jsx +12 -8
- package/src/commerce/admin/components/DataTable.jsx +3 -2
- package/src/commerce/admin/components/DateRangePicker.jsx +11 -10
- package/src/commerce/admin/components/EmptyState.jsx +2 -1
- package/src/commerce/admin/components/MediaUploader.jsx +3 -2
- package/src/commerce/admin/components/MetaDataEditor.jsx +4 -3
- package/src/commerce/admin/components/SearchSelect.jsx +4 -3
- package/src/commerce/admin/context/SettingsContext.jsx +12 -25
- package/src/commerce/admin/hooks/useMoney.js +3 -2
- package/src/commerce/admin/i18n/index.js +54 -0
- package/src/commerce/admin/i18n/locales/de.js +896 -0
- package/src/commerce/admin/i18n/locales/en.js +900 -0
- package/src/commerce/admin/i18n/locales/es.js +896 -0
- package/src/commerce/admin/i18n/locales/fr.js +896 -0
- package/src/commerce/admin/i18n/locales/ja.js +896 -0
- package/src/commerce/admin/i18n/locales/pt.js +896 -0
- package/src/commerce/admin/index.jsx +6 -30
- package/src/commerce/admin/layout/AccessDenied.jsx +8 -9
- package/src/commerce/admin/layout/AuthGuard.jsx +2 -1
- package/src/commerce/admin/layout/Sidebar.jsx +29 -18
- package/src/commerce/admin/layout/Topbar.jsx +4 -3
- package/src/commerce/admin/lib/api.js +3 -2
- package/src/commerce/admin/lib/constants.js +44 -43
- package/src/commerce/admin/lib/format.js +4 -3
- package/src/commerce/admin/lib/paths.js +2 -3
- package/src/commerce/admin/pages/Dashboard.jsx +25 -24
- package/src/commerce/admin/pages/coupons/CouponEditor.jsx +55 -57
- package/src/commerce/admin/pages/coupons/CouponsList.jsx +22 -21
- package/src/commerce/admin/pages/customers/CustomerEditor.jsx +31 -30
- package/src/commerce/admin/pages/customers/CustomersList.jsx +19 -18
- package/src/commerce/admin/pages/orders/OrderEditor.jsx +75 -71
- package/src/commerce/admin/pages/orders/OrdersList.jsx +16 -15
- package/src/commerce/admin/pages/orders/components/AddProductDialog.jsx +12 -11
- package/src/commerce/admin/pages/orders/components/DownloadPermissionsPanel.jsx +14 -13
- package/src/commerce/admin/pages/orders/components/LineItemsTable.jsx +12 -11
- package/src/commerce/admin/pages/orders/components/OrderNotesPanel.jsx +11 -10
- package/src/commerce/admin/pages/orders/components/PaymentPanel.jsx +22 -22
- package/src/commerce/admin/pages/orders/components/RefundPanel.jsx +18 -17
- package/src/commerce/admin/pages/orders/components/TotalsBox.jsx +18 -17
- package/src/commerce/admin/pages/products/Categories.jsx +14 -13
- package/src/commerce/admin/pages/products/ProductEditor.jsx +16 -15
- package/src/commerce/admin/pages/products/ProductsList.jsx +41 -36
- package/src/commerce/admin/pages/products/Reviews.jsx +54 -46
- package/src/commerce/admin/pages/products/components/AttributesSection.jsx +45 -39
- package/src/commerce/admin/pages/products/components/ProductDataPanel.jsx +8 -7
- package/src/commerce/admin/pages/products/components/PublishBox.jsx +10 -9
- package/src/commerce/admin/pages/products/components/TaxonomyPanel.jsx +18 -17
- package/src/commerce/admin/pages/products/components/tabs/DownloadsTab.jsx +11 -10
- package/src/commerce/admin/pages/products/components/tabs/LinkedTab.jsx +9 -10
- package/src/commerce/admin/pages/products/components/tabs/ModifiersTab.jsx +3 -9
- package/src/commerce/admin/pages/products/components/tabs/PriceInventoryTab.jsx +76 -78
- package/src/commerce/admin/pages/reports/Reports.jsx +33 -32
- package/src/commerce/admin/pages/settings/EmailsSettings.jsx +36 -39
- package/src/commerce/admin/pages/settings/GeneralSettings.jsx +13 -14
- package/src/commerce/admin/pages/settings/InventorySettings.jsx +12 -13
- package/src/commerce/admin/pages/settings/LocationEditor.jsx +48 -47
- package/src/commerce/admin/pages/settings/PaymentsSettings.jsx +32 -33
- package/src/commerce/admin/pages/settings/SettingsLayout.jsx +13 -12
- package/src/commerce/admin/pages/settings/ShippingTaxSettings.jsx +31 -31
- package/src/commerce/admin/pages/settings/useGroupForm.jsx +4 -3
- package/src/commerce/admin/pages/status/WebhookEditor.jsx +34 -31
- package/src/commerce/admin/pages/status/Webhooks.jsx +11 -10
- package/src/commerce/admin/routes.jsx +31 -43
- package/src/commerce/storefront/pickers.jsx +19 -5
- package/src/commerce/storefront/useCheckout.jsx +28 -3
- package/src/commerce/utils/storefront.js +49 -2
|
@@ -16,6 +16,7 @@ import { Loader2, Plus } from "lucide-react";
|
|
|
16
16
|
import { toast } from "sonner";
|
|
17
17
|
|
|
18
18
|
import { base44 } from "../../../lib/api";
|
|
19
|
+
import { t } from "../../../i18n";
|
|
19
20
|
import SearchSelect from "../../../components/SearchSelect";
|
|
20
21
|
import { slugify } from "../../../lib/product-utils";
|
|
21
22
|
|
|
@@ -87,14 +88,14 @@ function CategoriesCard({ categories, selected, onChange, refresh }) {
|
|
|
87
88
|
slug: slugify(name),
|
|
88
89
|
parent_id: parent === NO_PARENT ? "" : parent,
|
|
89
90
|
});
|
|
90
|
-
toast.success("
|
|
91
|
+
toast.success(t("products.category_created"));
|
|
91
92
|
setName("");
|
|
92
93
|
setParent(NO_PARENT);
|
|
93
94
|
setAdding(false);
|
|
94
95
|
await refresh?.();
|
|
95
96
|
if (created?.id) onChange([...selected, created.id]);
|
|
96
97
|
} catch (e) {
|
|
97
|
-
toast.error(e.message || "
|
|
98
|
+
toast.error(e.message || t("products.category_create_failed"));
|
|
98
99
|
} finally {
|
|
99
100
|
setSaving(false);
|
|
100
101
|
}
|
|
@@ -103,7 +104,7 @@ function CategoriesCard({ categories, selected, onChange, refresh }) {
|
|
|
103
104
|
return (
|
|
104
105
|
<Card>
|
|
105
106
|
<CardHeader className="pb-2">
|
|
106
|
-
<CardTitle className="text-base">
|
|
107
|
+
<CardTitle className="text-base">{t("routes.categories")}</CardTitle>
|
|
107
108
|
</CardHeader>
|
|
108
109
|
<CardContent className="space-y-2">
|
|
109
110
|
{tree.length > 0 ? (
|
|
@@ -122,21 +123,21 @@ function CategoriesCard({ categories, selected, onChange, refresh }) {
|
|
|
122
123
|
</div>
|
|
123
124
|
</ScrollArea>
|
|
124
125
|
) : (
|
|
125
|
-
<p className="text-sm text-muted-foreground">
|
|
126
|
+
<p className="text-sm text-muted-foreground">{t("products.no_categories")}</p>
|
|
126
127
|
)}
|
|
127
128
|
|
|
128
129
|
{adding ? (
|
|
129
130
|
<div className="space-y-2 rounded-md border p-2">
|
|
130
131
|
<div className="space-y-1">
|
|
131
|
-
<Label className="text-xs">
|
|
132
|
-
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="
|
|
132
|
+
<Label className="text-xs">{t("common.name")}</Label>
|
|
133
|
+
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder={t("products.category_name_placeholder")} />
|
|
133
134
|
</div>
|
|
134
135
|
<div className="space-y-1">
|
|
135
|
-
<Label className="text-xs">
|
|
136
|
+
<Label className="text-xs">{t("products.parent")}</Label>
|
|
136
137
|
<Select value={parent} onValueChange={setParent}>
|
|
137
138
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
138
139
|
<SelectContent>
|
|
139
|
-
<SelectItem value={NO_PARENT}
|
|
140
|
+
<SelectItem value={NO_PARENT}>{t("products.no_parent")}</SelectItem>
|
|
140
141
|
{categories.map((c) => (
|
|
141
142
|
<SelectItem key={c.id} value={c.id}>{c.name}</SelectItem>
|
|
142
143
|
))}
|
|
@@ -145,16 +146,16 @@ function CategoriesCard({ categories, selected, onChange, refresh }) {
|
|
|
145
146
|
</div>
|
|
146
147
|
<div className="flex gap-2">
|
|
147
148
|
<Button size="sm" onClick={create} disabled={saving || !name.trim()}>
|
|
148
|
-
{saving && <Loader2 className="mr-2 h-3 w-3 animate-spin" />}
|
|
149
|
+
{saving && <Loader2 className="mr-2 h-3 w-3 animate-spin" />}{t("common.add")}
|
|
149
150
|
</Button>
|
|
150
151
|
<Button size="sm" variant="ghost" onClick={() => setAdding(false)} disabled={saving}>
|
|
151
|
-
|
|
152
|
+
{t("common.cancel")}
|
|
152
153
|
</Button>
|
|
153
154
|
</div>
|
|
154
155
|
</div>
|
|
155
156
|
) : (
|
|
156
157
|
<Button variant="link" size="sm" className="h-auto p-0 text-xs" onClick={() => setAdding(true)}>
|
|
157
|
-
<Plus className="mr-1 h-3 w-3" />
|
|
158
|
+
<Plus className="mr-1 h-3 w-3" /> {t("products.add_new_category")}
|
|
158
159
|
</Button>
|
|
159
160
|
)}
|
|
160
161
|
</CardContent>
|
|
@@ -196,12 +197,12 @@ function RibbonsCard({ ribbons, selected, onChange, refresh }) {
|
|
|
196
197
|
setSaving(true);
|
|
197
198
|
try {
|
|
198
199
|
const created = await base44.entities["commerce.ProductRibbon"].create({ name: label, count: 0 });
|
|
199
|
-
toast.success("
|
|
200
|
+
toast.success(t("products.ribbon_created"));
|
|
200
201
|
setNewRibbon("");
|
|
201
202
|
await refresh?.();
|
|
202
203
|
if (created?.id) onChange([...selected, created.id]);
|
|
203
204
|
} catch (e) {
|
|
204
|
-
toast.error(e.message || "
|
|
205
|
+
toast.error(e.message || t("products.ribbon_create_failed"));
|
|
205
206
|
} finally {
|
|
206
207
|
setSaving(false);
|
|
207
208
|
}
|
|
@@ -210,12 +211,12 @@ function RibbonsCard({ ribbons, selected, onChange, refresh }) {
|
|
|
210
211
|
return (
|
|
211
212
|
<Card>
|
|
212
213
|
<CardHeader className="pb-2">
|
|
213
|
-
<CardTitle className="text-base">
|
|
214
|
+
<CardTitle className="text-base">{t("products.ribbons")}</CardTitle>
|
|
214
215
|
</CardHeader>
|
|
215
216
|
<CardContent className="space-y-2">
|
|
216
217
|
<SearchSelect
|
|
217
218
|
multiple
|
|
218
|
-
placeholder="
|
|
219
|
+
placeholder={t("products.select_ribbons")}
|
|
219
220
|
value={value}
|
|
220
221
|
onChange={(next) => onChange(next.map((o) => o.value))}
|
|
221
222
|
search={search}
|
|
@@ -230,11 +231,11 @@ function RibbonsCard({ ribbons, selected, onChange, refresh }) {
|
|
|
230
231
|
createRibbon();
|
|
231
232
|
}
|
|
232
233
|
}}
|
|
233
|
-
placeholder="
|
|
234
|
+
placeholder={t("products.add_new_ribbon")}
|
|
234
235
|
className="h-8 text-sm"
|
|
235
236
|
/>
|
|
236
237
|
<Button size="sm" variant="outline" onClick={createRibbon} disabled={saving || !newRibbon.trim()}>
|
|
237
|
-
{saving ? <Loader2 className="h-3 w-3 animate-spin" /> : "
|
|
238
|
+
{saving ? <Loader2 className="h-3 w-3 animate-spin" /> : t("common.add")}
|
|
238
239
|
</Button>
|
|
239
240
|
</div>
|
|
240
241
|
</CardContent>
|
|
@@ -5,6 +5,7 @@ import { Label } from "@/components/ui/label";
|
|
|
5
5
|
import { Plus, Trash2 } from "lucide-react";
|
|
6
6
|
|
|
7
7
|
import MediaUploader from "../../../../components/MediaUploader";
|
|
8
|
+
import { t } from "../../../../i18n";
|
|
8
9
|
|
|
9
10
|
const num = (v, fallback) => (v === "" ? fallback : Number(v));
|
|
10
11
|
|
|
@@ -18,19 +19,19 @@ export default function DownloadsTab({ product, up }) {
|
|
|
18
19
|
return (
|
|
19
20
|
<div className="max-w-xl space-y-4">
|
|
20
21
|
<div className="space-y-3">
|
|
21
|
-
<Label>
|
|
22
|
+
<Label>{t("products.downloadable_files")}</Label>
|
|
22
23
|
{downloads.map((d, i) => (
|
|
23
24
|
<div key={i} className="grid gap-2 rounded-md border p-3 sm:grid-cols-[1fr_1fr_auto]">
|
|
24
25
|
<div className="space-y-1.5">
|
|
25
|
-
<Label className="text-xs">
|
|
26
|
+
<Label className="text-xs">{t("common.name")}</Label>
|
|
26
27
|
<Input
|
|
27
|
-
placeholder="
|
|
28
|
+
placeholder={t("products.download_name_placeholder")}
|
|
28
29
|
value={d.name || ""}
|
|
29
30
|
onChange={(e) => setRow(i, { name: e.target.value })}
|
|
30
31
|
/>
|
|
31
32
|
</div>
|
|
32
33
|
<div className="space-y-1.5">
|
|
33
|
-
<Label className="text-xs">
|
|
34
|
+
<Label className="text-xs">{t("products.file")}</Label>
|
|
34
35
|
{d.file_url ? (
|
|
35
36
|
<div className="flex items-center gap-2">
|
|
36
37
|
<Input value={d.file_url} onChange={(e) => setRow(i, { file_url: e.target.value })} />
|
|
@@ -38,7 +39,7 @@ export default function DownloadsTab({ product, up }) {
|
|
|
38
39
|
) : (
|
|
39
40
|
<MediaUploader
|
|
40
41
|
accept="*/*"
|
|
41
|
-
label="
|
|
42
|
+
label={t("products.upload_file")}
|
|
42
43
|
value={null}
|
|
43
44
|
onChange={(img) => img && setRow(i, { file_url: img.src, name: d.name || img.name })}
|
|
44
45
|
/>
|
|
@@ -62,28 +63,28 @@ export default function DownloadsTab({ product, up }) {
|
|
|
62
63
|
size="sm"
|
|
63
64
|
onClick={() => up({ downloads: [...downloads, { name: "", file_url: "" }] })}
|
|
64
65
|
>
|
|
65
|
-
<Plus className="mr-1 h-4 w-4" />
|
|
66
|
+
<Plus className="mr-1 h-4 w-4" /> {t("products.add_file")}
|
|
66
67
|
</Button>
|
|
67
68
|
</div>
|
|
68
69
|
|
|
69
70
|
<div className="grid max-w-md grid-cols-2 gap-2">
|
|
70
71
|
<div className="space-y-1.5">
|
|
71
|
-
<Label>
|
|
72
|
+
<Label>{t("products.download_limit")}</Label>
|
|
72
73
|
<Input
|
|
73
74
|
type="number"
|
|
74
75
|
value={product.download_limit ?? -1}
|
|
75
76
|
onChange={(e) => up({ download_limit: num(e.target.value, -1) })}
|
|
76
77
|
/>
|
|
77
|
-
<p className="text-xs text-muted-foreground"
|
|
78
|
+
<p className="text-xs text-muted-foreground">{t("products.download_limit_hint")}</p>
|
|
78
79
|
</div>
|
|
79
80
|
<div className="space-y-1.5">
|
|
80
|
-
<Label>
|
|
81
|
+
<Label>{t("products.download_expiry")}</Label>
|
|
81
82
|
<Input
|
|
82
83
|
type="number"
|
|
83
84
|
value={product.download_expiry ?? -1}
|
|
84
85
|
onChange={(e) => up({ download_expiry: num(e.target.value, -1) })}
|
|
85
86
|
/>
|
|
86
|
-
<p className="text-xs text-muted-foreground"
|
|
87
|
+
<p className="text-xs text-muted-foreground">{t("products.download_expiry_hint")}</p>
|
|
87
88
|
</div>
|
|
88
89
|
</div>
|
|
89
90
|
</div>
|
|
@@ -3,6 +3,7 @@ import { Label } from "@/components/ui/label";
|
|
|
3
3
|
|
|
4
4
|
import { base44, call } from "../../../../lib/api";
|
|
5
5
|
import SearchSelect from "../../../../components/SearchSelect";
|
|
6
|
+
import { t } from "../../../../i18n";
|
|
6
7
|
|
|
7
8
|
/** Search products server-side for the pickers. */
|
|
8
9
|
const searchProducts = async (q) => {
|
|
@@ -29,8 +30,8 @@ function ProductIdsPicker({ ids, onChange, placeholder }) {
|
|
|
29
30
|
Promise.all(
|
|
30
31
|
missing.map((id) =>
|
|
31
32
|
base44.entities["commerce.Product"].get(id)
|
|
32
|
-
.then((p) => ({ value: id, label: p?.name || "
|
|
33
|
-
.catch(() => ({ value: id, label: "
|
|
33
|
+
.then((p) => ({ value: id, label: p?.name || t("products.deleted_product") }))
|
|
34
|
+
.catch(() => ({ value: id, label: t("products.deleted_product") }))
|
|
34
35
|
)
|
|
35
36
|
).then((resolved) => {
|
|
36
37
|
if (!cancelled) setOptions([...(options || []), ...resolved]);
|
|
@@ -64,25 +65,23 @@ export default function LinkedTab({ product, up }) {
|
|
|
64
65
|
return (
|
|
65
66
|
<div className="max-w-md space-y-4">
|
|
66
67
|
<div className="space-y-1.5">
|
|
67
|
-
<Label>
|
|
68
|
+
<Label>{t("products.upsells")}</Label>
|
|
68
69
|
<ProductIdsPicker
|
|
69
70
|
ids={product.upsell_ids}
|
|
70
71
|
onChange={(upsell_ids) => up({ upsell_ids })}
|
|
71
|
-
placeholder="
|
|
72
|
+
placeholder={t("products.upsells_placeholder")}
|
|
72
73
|
/>
|
|
73
|
-
<p className="text-xs text-muted-foreground">
|
|
74
|
-
Recommended instead of this product, e.g. premium alternatives.
|
|
75
|
-
</p>
|
|
74
|
+
<p className="text-xs text-muted-foreground">{t("products.upsells_hint")}</p>
|
|
76
75
|
</div>
|
|
77
76
|
|
|
78
77
|
<div className="space-y-1.5">
|
|
79
|
-
<Label>
|
|
78
|
+
<Label>{t("products.cross_sells")}</Label>
|
|
80
79
|
<ProductIdsPicker
|
|
81
80
|
ids={product.cross_sell_ids}
|
|
82
81
|
onChange={(cross_sell_ids) => up({ cross_sell_ids })}
|
|
83
|
-
placeholder="
|
|
82
|
+
placeholder={t("products.cross_sells_placeholder")}
|
|
84
83
|
/>
|
|
85
|
-
<p className="text-xs text-muted-foreground">
|
|
84
|
+
<p className="text-xs text-muted-foreground">{t("products.cross_sells_hint")}</p>
|
|
86
85
|
</div>
|
|
87
86
|
</div>
|
|
88
87
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
3
|
import MetaDataEditor from "../../../../components/MetaDataEditor";
|
|
4
|
+
import { t } from "../../../../i18n";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Descriptive properties — Material, Care, GTIN. They are `meta_data`, never
|
|
@@ -10,16 +11,9 @@ export default function ModifiersTab({ product, up }) {
|
|
|
10
11
|
const empty = !(product.meta_data || []).length;
|
|
11
12
|
return (
|
|
12
13
|
<div className="max-w-xl space-y-2">
|
|
13
|
-
<p className="text-xs text-muted-foreground">
|
|
14
|
-
Descriptive details shown on the product page (e.g. ingredients, allergens, care
|
|
15
|
-
instructions).
|
|
16
|
-
</p>
|
|
14
|
+
<p className="text-xs text-muted-foreground">{t("products.modifiers_hint")}</p>
|
|
17
15
|
<MetaDataEditor value={product.meta_data || []} onChange={(meta_data) => up({ meta_data })} />
|
|
18
|
-
{empty && (
|
|
19
|
-
<p className="text-xs text-muted-foreground">
|
|
20
|
-
Want the product to vary by something? Add it as an option under Price & Inventory.
|
|
21
|
-
</p>
|
|
22
|
-
)}
|
|
16
|
+
{empty && <p className="text-xs text-muted-foreground">{t("products.modifiers_empty_hint")}</p>}
|
|
23
17
|
</div>
|
|
24
18
|
);
|
|
25
19
|
}
|