@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
|
@@ -21,6 +21,7 @@ import CountrySelect from "../../components/CountrySelect";
|
|
|
21
21
|
import MoneyInput from "../../components/MoneyInput";
|
|
22
22
|
import useMoney from "../../hooks/useMoney";
|
|
23
23
|
import { CONTINENTS } from "../../lib/geo-data";
|
|
24
|
+
import { t } from "../../i18n";
|
|
24
25
|
|
|
25
26
|
const NEW_LOCATION = {
|
|
26
27
|
name: "",
|
|
@@ -62,14 +63,14 @@ export default function LocationEditor() {
|
|
|
62
63
|
}, [locationId, isNew]);
|
|
63
64
|
|
|
64
65
|
useEffect(() => {
|
|
65
|
-
load().catch((e) => toast.error(e.message || "
|
|
66
|
+
load().catch((e) => toast.error(e.message || t("settings.location.load_failed")));
|
|
66
67
|
}, [load]);
|
|
67
68
|
|
|
68
69
|
const up = (patch) => setLocation((l) => ({ ...l, ...patch }));
|
|
69
70
|
|
|
70
71
|
const save = async () => {
|
|
71
72
|
if (!location.name?.trim()) {
|
|
72
|
-
toast.error("
|
|
73
|
+
toast.error(t("settings.location.name_required"));
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
75
76
|
setSaving(true);
|
|
@@ -85,10 +86,10 @@ export default function LocationEditor() {
|
|
|
85
86
|
};
|
|
86
87
|
if (isNew) await base44.entities["commerce.ShippingTaxLocation"].create(payload);
|
|
87
88
|
else await base44.entities["commerce.ShippingTaxLocation"].update(locationId, payload);
|
|
88
|
-
toast.success(isNew ? "
|
|
89
|
+
toast.success(isNew ? t("settings.location.created") : t("settings.location.saved"));
|
|
89
90
|
navigate(href("settings/shipping-tax"));
|
|
90
91
|
} catch (e) {
|
|
91
|
-
toast.error(e.message || "
|
|
92
|
+
toast.error(e.message || t("settings.location.save_failed"));
|
|
92
93
|
} finally {
|
|
93
94
|
setSaving(false);
|
|
94
95
|
}
|
|
@@ -108,23 +109,22 @@ export default function LocationEditor() {
|
|
|
108
109
|
return (
|
|
109
110
|
<div className="max-w-3xl space-y-4">
|
|
110
111
|
<PageHeader
|
|
111
|
-
title={isNew ? "
|
|
112
|
-
description="
|
|
112
|
+
title={isNew ? t("settings.shipping.add_location") : location.name || t("settings.location.edit_location")}
|
|
113
|
+
description={t("settings.location.header_description")}
|
|
113
114
|
backHref={href("settings/shipping-tax")}
|
|
114
115
|
actions={
|
|
115
116
|
<Button onClick={save} disabled={saving}>
|
|
116
117
|
{saving ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Save className="mr-2 h-4 w-4" />}
|
|
117
|
-
|
|
118
|
+
{t("common.save")}
|
|
118
119
|
</Button>
|
|
119
120
|
}
|
|
120
121
|
/>
|
|
121
122
|
|
|
122
123
|
<Card>
|
|
123
124
|
<CardHeader className="pb-2">
|
|
124
|
-
<CardTitle className="text-base">
|
|
125
|
+
<CardTitle className="text-base">{t("settings.shipping.location")}</CardTitle>
|
|
125
126
|
<CardDescription>
|
|
126
|
-
|
|
127
|
-
location covers.
|
|
127
|
+
{t("settings.location.regions_hint")}
|
|
128
128
|
</CardDescription>
|
|
129
129
|
</CardHeader>
|
|
130
130
|
<CardContent className="space-y-3">
|
|
@@ -132,31 +132,31 @@ export default function LocationEditor() {
|
|
|
132
132
|
[1fr | 130px | 130px | 40px icon] — so inputs align across cards. */}
|
|
133
133
|
<div className="grid grid-cols-[1fr_130px_130px_40px] items-center gap-2">
|
|
134
134
|
<div className="col-span-2 space-y-1.5">
|
|
135
|
-
<Label>
|
|
136
|
-
<Input value={location.name} onChange={(e) => up({ name: e.target.value })} placeholder="
|
|
135
|
+
<Label>{t("common.name")}</Label>
|
|
136
|
+
<Input value={location.name} onChange={(e) => up({ name: e.target.value })} placeholder={t("settings.location.name_placeholder")} />
|
|
137
137
|
</div>
|
|
138
138
|
<div className="space-y-1.5">
|
|
139
|
-
<Label>
|
|
139
|
+
<Label>{t("settings.location.match_order")}</Label>
|
|
140
140
|
<Input type="number" value={location.order ?? 0} onChange={(e) => up({ order: Number(e.target.value) || 0 })} />
|
|
141
141
|
</div>
|
|
142
142
|
<div />
|
|
143
143
|
</div>
|
|
144
144
|
|
|
145
145
|
<div className="space-y-2">
|
|
146
|
-
<Label className="block">
|
|
146
|
+
<Label className="block">{t("settings.shipping.regions")}</Label>
|
|
147
147
|
{regions.map((region, i) => (
|
|
148
148
|
<div key={i} className="grid grid-cols-[130px_1fr_40px] items-center gap-2">
|
|
149
149
|
<Select value={region.type || "country"} onValueChange={(v) => setRegion(i, { type: v, code: "" })}>
|
|
150
150
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
151
151
|
<SelectContent>
|
|
152
|
-
<SelectItem value="country">
|
|
153
|
-
<SelectItem value="continent">
|
|
154
|
-
<SelectItem value="state">
|
|
152
|
+
<SelectItem value="country">{t("address.country")}</SelectItem>
|
|
153
|
+
<SelectItem value="continent">{t("settings.location.continent")}</SelectItem>
|
|
154
|
+
<SelectItem value="state">{t("settings.location.state")}</SelectItem>
|
|
155
155
|
</SelectContent>
|
|
156
156
|
</Select>
|
|
157
157
|
{region.type === "continent" ? (
|
|
158
158
|
<Select value={region.code || ""} onValueChange={(v) => setRegion(i, { code: v })}>
|
|
159
|
-
<SelectTrigger><SelectValue placeholder="
|
|
159
|
+
<SelectTrigger><SelectValue placeholder={t("settings.location.select_continent")} /></SelectTrigger>
|
|
160
160
|
<SelectContent>
|
|
161
161
|
{CONTINENTS.map((c) => (
|
|
162
162
|
<SelectItem key={c.code} value={c.code}>{c.name}</SelectItem>
|
|
@@ -165,7 +165,7 @@ export default function LocationEditor() {
|
|
|
165
165
|
</Select>
|
|
166
166
|
) : region.type === "state" ? (
|
|
167
167
|
<Input
|
|
168
|
-
placeholder="
|
|
168
|
+
placeholder={t("settings.location.state_placeholder")}
|
|
169
169
|
value={region.code || ""}
|
|
170
170
|
onChange={(e) => setRegion(i, { code: e.target.value.toUpperCase() })}
|
|
171
171
|
/>
|
|
@@ -179,7 +179,7 @@ export default function LocationEditor() {
|
|
|
179
179
|
))}
|
|
180
180
|
<Button type="button" variant="outline" size="sm" onClick={() => up({ regions: [...regions, { type: "country", code: "" }] })}>
|
|
181
181
|
<Plus className="mr-2 h-4 w-4" />
|
|
182
|
-
|
|
182
|
+
{t("settings.location.add_region")}
|
|
183
183
|
</Button>
|
|
184
184
|
</div>
|
|
185
185
|
</CardContent>
|
|
@@ -187,34 +187,33 @@ export default function LocationEditor() {
|
|
|
187
187
|
|
|
188
188
|
<Card>
|
|
189
189
|
<CardHeader className="pb-2">
|
|
190
|
-
<CardTitle className="text-base">
|
|
190
|
+
<CardTitle className="text-base">{t("settings.shipping.shipping_rates")}</CardTitle>
|
|
191
191
|
<CardDescription>
|
|
192
|
-
|
|
193
|
-
the (discounted) items subtotal reaches it. No rates means nothing ships here.
|
|
192
|
+
{t("settings.location.rates_hint")}
|
|
194
193
|
</CardDescription>
|
|
195
194
|
</CardHeader>
|
|
196
195
|
<CardContent className="space-y-2">
|
|
197
196
|
{rates.length > 0 && (
|
|
198
197
|
<div className="grid grid-cols-[1fr_130px_130px_40px] items-center gap-2 px-1 text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
199
|
-
<span>
|
|
200
|
-
<span>
|
|
201
|
-
<span>
|
|
198
|
+
<span>{t("settings.location.delivery_option")}</span>
|
|
199
|
+
<span>{t("settings.location.cost")}</span>
|
|
200
|
+
<span>{t("settings.location.free_over")}</span>
|
|
202
201
|
<span />
|
|
203
202
|
</div>
|
|
204
203
|
)}
|
|
205
204
|
{rates.map((rate, i) => (
|
|
206
205
|
<div key={rate.id || i} className="grid grid-cols-[1fr_130px_130px_40px] items-center gap-2">
|
|
207
206
|
<Input
|
|
208
|
-
placeholder="
|
|
207
|
+
placeholder={t("settings.location.rate_name_placeholder")}
|
|
209
208
|
value={rate.name || ""}
|
|
210
209
|
onChange={(e) => setRate(i, { name: e.target.value })}
|
|
211
210
|
/>
|
|
212
|
-
<MoneyInput value={rate.cost} onChange={(v) => setRate(i, { cost: v ?? 0 })} placeholder="
|
|
211
|
+
<MoneyInput value={rate.cost} onChange={(v) => setRate(i, { cost: v ?? 0 })} placeholder={t("settings.location.cost")} />
|
|
213
212
|
<Input
|
|
214
213
|
type="number"
|
|
215
214
|
min={0}
|
|
216
|
-
placeholder="
|
|
217
|
-
title="
|
|
215
|
+
placeholder={t("settings.location.never")}
|
|
216
|
+
title={t("settings.location.free_over_title")}
|
|
218
217
|
value={rate.free_over ?? ""}
|
|
219
218
|
onChange={(e) => setRate(i, { free_over: num(e.target.value) })}
|
|
220
219
|
/>
|
|
@@ -230,18 +229,16 @@ export default function LocationEditor() {
|
|
|
230
229
|
onClick={() => up({ shipping_rates: [...rates, { id: crypto.randomUUID(), name: "", cost: 0, free_over: null }] })}
|
|
231
230
|
>
|
|
232
231
|
<Plus className="mr-2 h-4 w-4" />
|
|
233
|
-
|
|
232
|
+
{t("settings.location.add_rate")}
|
|
234
233
|
</Button>
|
|
235
234
|
</CardContent>
|
|
236
235
|
</Card>
|
|
237
236
|
|
|
238
237
|
<Card>
|
|
239
238
|
<CardHeader className="pb-2">
|
|
240
|
-
<CardTitle className="text-base">
|
|
239
|
+
<CardTitle className="text-base">{t("settings.shipping.tax")}</CardTitle>
|
|
241
240
|
<CardDescription>
|
|
242
|
-
|
|
243
|
-
charge the <strong>Products</strong> group unless their own Tax group names another;
|
|
244
|
-
add groups for goods taxed differently. Tax on shipping is set separately below.
|
|
241
|
+
{t("settings.location.tax_hint", { group: "Products" })}
|
|
245
242
|
</CardDescription>
|
|
246
243
|
</CardHeader>
|
|
247
244
|
<CardContent className="space-y-4">
|
|
@@ -255,12 +252,12 @@ export default function LocationEditor() {
|
|
|
255
252
|
<Input
|
|
256
253
|
className="h-8 max-w-xs bg-background font-medium"
|
|
257
254
|
value={group.name}
|
|
258
|
-
placeholder="
|
|
255
|
+
placeholder={t("settings.location.group_name_placeholder")}
|
|
259
256
|
disabled={isDefault}
|
|
260
257
|
onChange={(e) => setGroup(i, { name: e.target.value })}
|
|
261
258
|
/>
|
|
262
259
|
<span className="text-xs text-muted-foreground">
|
|
263
|
-
{isDefault ? "
|
|
260
|
+
{isDefault ? t("settings.location.default_group_hint") : t("settings.location.tax_group")}
|
|
264
261
|
</span>
|
|
265
262
|
</div>
|
|
266
263
|
{!isDefault && (
|
|
@@ -272,13 +269,17 @@ export default function LocationEditor() {
|
|
|
272
269
|
<div className="space-y-2 p-3">
|
|
273
270
|
{!groupRates.length && (
|
|
274
271
|
<p className="text-xs text-muted-foreground">
|
|
275
|
-
|
|
272
|
+
{isDefault
|
|
273
|
+
? t("settings.location.no_tax_rates_default")
|
|
274
|
+
: group.name.trim()
|
|
275
|
+
? t("settings.location.no_tax_rates", { name: group.name.trim() })
|
|
276
|
+
: t("settings.location.no_tax_rates_unnamed")}
|
|
276
277
|
</p>
|
|
277
278
|
)}
|
|
278
279
|
{groupRates.map((r, j) => (
|
|
279
280
|
<div key={j} className="grid grid-cols-[1fr_130px_40px] items-center gap-2">
|
|
280
281
|
<Input
|
|
281
|
-
placeholder="
|
|
282
|
+
placeholder={t("settings.location.rate_placeholder")}
|
|
282
283
|
value={r.name || ""}
|
|
283
284
|
onChange={(e) => setGroup(i, { rates: groupRates.map((x, k) => (k === j ? { ...x, name: e.target.value } : x)) })}
|
|
284
285
|
/>
|
|
@@ -309,7 +310,7 @@ export default function LocationEditor() {
|
|
|
309
310
|
onClick={() => setGroup(i, { rates: [...groupRates, { name: "", rate: 0 }] })}
|
|
310
311
|
>
|
|
311
312
|
<Plus className="mr-2 h-4 w-4" />
|
|
312
|
-
|
|
313
|
+
{t("settings.location.add_rate")}
|
|
313
314
|
</Button>
|
|
314
315
|
</div>
|
|
315
316
|
</div>
|
|
@@ -323,16 +324,16 @@ export default function LocationEditor() {
|
|
|
323
324
|
onClick={() => up({ tax_groups: [...groups, { name: "", rates: [{ name: "", rate: 0 }] }] })}
|
|
324
325
|
>
|
|
325
326
|
<Plus className="mr-2 h-4 w-4" />
|
|
326
|
-
|
|
327
|
+
{t("settings.location.add_tax_group")}
|
|
327
328
|
</Button>
|
|
328
329
|
|
|
329
330
|
<div className="rounded-lg border">
|
|
330
331
|
<div className="border-b bg-muted/40 px-3 py-2">
|
|
331
|
-
<p className="text-sm font-medium">
|
|
332
|
+
<p className="text-sm font-medium">{t("settings.location.tax_on_shipping")}</p>
|
|
332
333
|
</div>
|
|
333
334
|
<div className="grid grid-cols-[1fr_130px_130px_40px] items-center gap-2 p-3">
|
|
334
335
|
<p className="text-xs text-muted-foreground">
|
|
335
|
-
|
|
336
|
+
{t("settings.location.tax_on_shipping_hint")}
|
|
336
337
|
</p>
|
|
337
338
|
<Select
|
|
338
339
|
value={shippingTax?.type || "none"}
|
|
@@ -340,9 +341,9 @@ export default function LocationEditor() {
|
|
|
340
341
|
>
|
|
341
342
|
<SelectTrigger><SelectValue /></SelectTrigger>
|
|
342
343
|
<SelectContent>
|
|
343
|
-
<SelectItem value="none">
|
|
344
|
-
<SelectItem value="percent">
|
|
345
|
-
<SelectItem value="fixed">
|
|
344
|
+
<SelectItem value="none">{t("settings.location.not_taxed")}</SelectItem>
|
|
345
|
+
<SelectItem value="percent">{t("settings.location.rate_percent")}</SelectItem>
|
|
346
|
+
<SelectItem value="fixed">{t("settings.location.exact_amount")}</SelectItem>
|
|
346
347
|
</SelectContent>
|
|
347
348
|
</Select>
|
|
348
349
|
{shippingTax ? (
|
|
@@ -368,7 +369,7 @@ export default function LocationEditor() {
|
|
|
368
369
|
</div>
|
|
369
370
|
|
|
370
371
|
<p className="text-xs text-muted-foreground">
|
|
371
|
-
|
|
372
|
+
{t("settings.location.tax_example", { base: money.format(100), tax: money.format(18) })}
|
|
372
373
|
</p>
|
|
373
374
|
</CardContent>
|
|
374
375
|
</Card>
|
|
@@ -16,6 +16,7 @@ import { ArrowDown, ArrowUp, Loader2, Plus, Settings2, Trash2 } from "lucide-rea
|
|
|
16
16
|
import { toast } from "sonner";
|
|
17
17
|
import { base44 } from "../../lib/api";
|
|
18
18
|
import useAsync from "../../hooks/useAsync";
|
|
19
|
+
import { t } from "../../i18n";
|
|
19
20
|
|
|
20
21
|
const emptyBank = () => ({ account_name: "", account_number: "", bank_name: "", sort_code: "", iban: "", bic: "" });
|
|
21
22
|
|
|
@@ -42,7 +43,7 @@ export default function PaymentsSettings() {
|
|
|
42
43
|
await base44.entities["commerce.PaymentGateway"].update(g.id, { enabled });
|
|
43
44
|
gateways.refetch();
|
|
44
45
|
} catch (err) {
|
|
45
|
-
toast.error(err.message || "
|
|
46
|
+
toast.error(err.message || t("settings.payments.update_failed"));
|
|
46
47
|
}
|
|
47
48
|
};
|
|
48
49
|
|
|
@@ -59,21 +60,21 @@ export default function PaymentsSettings() {
|
|
|
59
60
|
]);
|
|
60
61
|
gateways.refetch();
|
|
61
62
|
} catch (err) {
|
|
62
|
-
toast.error(err.message || "
|
|
63
|
+
toast.error(err.message || t("settings.payments.reorder_failed"));
|
|
63
64
|
} finally {
|
|
64
65
|
setBusy(false);
|
|
65
66
|
}
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
const remove = async (g) => {
|
|
69
|
-
if (!window.confirm(
|
|
70
|
+
if (!window.confirm(t("settings.payments.delete_confirm", { title: g.title || g.slug }))) return;
|
|
70
71
|
setBusy(true);
|
|
71
72
|
try {
|
|
72
73
|
await base44.entities["commerce.PaymentGateway"].delete(g.id);
|
|
73
|
-
toast.success("
|
|
74
|
+
toast.success(t("settings.payments.deleted"));
|
|
74
75
|
gateways.refetch();
|
|
75
76
|
} catch (err) {
|
|
76
|
-
toast.error(err.message || "
|
|
77
|
+
toast.error(err.message || t("settings.payments.delete_failed"));
|
|
77
78
|
} finally {
|
|
78
79
|
setBusy(false);
|
|
79
80
|
}
|
|
@@ -88,7 +89,7 @@ export default function PaymentsSettings() {
|
|
|
88
89
|
const saveGateway = async () => {
|
|
89
90
|
const g = dialog.gateway;
|
|
90
91
|
if (!String(g.title || "").trim()) {
|
|
91
|
-
toast.error("
|
|
92
|
+
toast.error(t("settings.payments.title_required"));
|
|
92
93
|
return;
|
|
93
94
|
}
|
|
94
95
|
setSaving(true);
|
|
@@ -104,19 +105,19 @@ export default function PaymentsSettings() {
|
|
|
104
105
|
method_description: g.description || "",
|
|
105
106
|
settings: g.settings || {},
|
|
106
107
|
});
|
|
107
|
-
toast.success("
|
|
108
|
+
toast.success(t("settings.payments.added"));
|
|
108
109
|
} else {
|
|
109
110
|
await base44.entities["commerce.PaymentGateway"].update(g.id, {
|
|
110
111
|
title: g.title,
|
|
111
112
|
description: g.description,
|
|
112
113
|
settings: g.settings || {},
|
|
113
114
|
});
|
|
114
|
-
toast.success("
|
|
115
|
+
toast.success(t("settings.payments.saved"));
|
|
115
116
|
}
|
|
116
117
|
setDialog(null);
|
|
117
118
|
gateways.refetch();
|
|
118
119
|
} catch (err) {
|
|
119
|
-
toast.error(err.message || "
|
|
120
|
+
toast.error(err.message || t("settings.save_failed"));
|
|
120
121
|
} finally {
|
|
121
122
|
setSaving(false);
|
|
122
123
|
}
|
|
@@ -135,16 +136,14 @@ export default function PaymentsSettings() {
|
|
|
135
136
|
<Card>
|
|
136
137
|
<CardHeader className="flex-row items-start justify-between gap-4 space-y-0 pb-3">
|
|
137
138
|
<div className="space-y-1.5">
|
|
138
|
-
<CardTitle>
|
|
139
|
+
<CardTitle>{t("settings.payments.checkout_options")}</CardTitle>
|
|
139
140
|
<CardDescription>
|
|
140
|
-
|
|
141
|
-
they see. Every option except Credit card settles outside the store: the order goes
|
|
142
|
-
on-hold with the option's description as instructions, and you move it on once paid.
|
|
141
|
+
{t("settings.payments.checkout_options_description")}
|
|
143
142
|
</CardDescription>
|
|
144
143
|
</div>
|
|
145
144
|
<Button size="sm" onClick={openNew}>
|
|
146
145
|
<Plus className="mr-2 h-4 w-4" />
|
|
147
|
-
|
|
146
|
+
{t("settings.payments.add_method")}
|
|
148
147
|
</Button>
|
|
149
148
|
</CardHeader>
|
|
150
149
|
<CardContent className="divide-y border-t p-0">
|
|
@@ -187,7 +186,7 @@ export default function PaymentsSettings() {
|
|
|
187
186
|
onClick={() => setDialog({ gateway: { ...g, settings: { ...(g.settings || {}) } } })}
|
|
188
187
|
>
|
|
189
188
|
<Settings2 className="mr-2 h-4 w-4" />
|
|
190
|
-
|
|
189
|
+
{t("common.edit")}
|
|
191
190
|
</Button>
|
|
192
191
|
{!BUILT_IN_SLUGS.has(g.slug) && (
|
|
193
192
|
<Button
|
|
@@ -195,7 +194,7 @@ export default function PaymentsSettings() {
|
|
|
195
194
|
size="icon"
|
|
196
195
|
className="h-8 w-8 text-muted-foreground"
|
|
197
196
|
disabled={busy}
|
|
198
|
-
title="
|
|
197
|
+
title={t("common.delete")}
|
|
199
198
|
onClick={() => remove(g)}
|
|
200
199
|
>
|
|
201
200
|
<Trash2 className="h-3.5 w-3.5" />
|
|
@@ -224,20 +223,20 @@ export default function PaymentsSettings() {
|
|
|
224
223
|
<DialogContent className="max-h-[85vh] overflow-y-auto">
|
|
225
224
|
<DialogHeader>
|
|
226
225
|
<DialogTitle>
|
|
227
|
-
{dialog.isNew ? "
|
|
226
|
+
{dialog.isNew ? t("settings.payments.add_method") : dialog.gateway.method_title || dialog.gateway.title}
|
|
228
227
|
</DialogTitle>
|
|
229
228
|
</DialogHeader>
|
|
230
229
|
<div className="space-y-4">
|
|
231
230
|
<div className="space-y-1.5">
|
|
232
|
-
<Label>
|
|
231
|
+
<Label>{t("settings.payments.title_label")}</Label>
|
|
233
232
|
<Input
|
|
234
233
|
value={dialog.gateway.title || ""}
|
|
235
234
|
onChange={(e) => setDialog({ ...dialog, gateway: { ...dialog.gateway, title: e.target.value } })}
|
|
236
235
|
/>
|
|
237
|
-
<p className="text-xs text-muted-foreground">
|
|
236
|
+
<p className="text-xs text-muted-foreground">{t("settings.payments.title_hint")}</p>
|
|
238
237
|
</div>
|
|
239
238
|
<div className="space-y-1.5">
|
|
240
|
-
<Label>
|
|
239
|
+
<Label>{t("common.description")}</Label>
|
|
241
240
|
<Textarea
|
|
242
241
|
rows={2}
|
|
243
242
|
value={dialog.gateway.description || ""}
|
|
@@ -245,7 +244,7 @@ export default function PaymentsSettings() {
|
|
|
245
244
|
/>
|
|
246
245
|
{dialog.gateway.slug !== "card" && (
|
|
247
246
|
<p className="text-xs text-muted-foreground">
|
|
248
|
-
|
|
247
|
+
{t("settings.payments.description_hint")}
|
|
249
248
|
</p>
|
|
250
249
|
)}
|
|
251
250
|
</div>
|
|
@@ -260,10 +259,10 @@ export default function PaymentsSettings() {
|
|
|
260
259
|
)}
|
|
261
260
|
</div>
|
|
262
261
|
<DialogFooter>
|
|
263
|
-
<Button variant="outline" onClick={() => setDialog(null)}>
|
|
262
|
+
<Button variant="outline" onClick={() => setDialog(null)}>{t("common.cancel")}</Button>
|
|
264
263
|
<Button onClick={saveGateway} disabled={saving}>
|
|
265
264
|
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
|
|
266
|
-
{dialog.isNew ? "
|
|
265
|
+
{dialog.isNew ? t("common.add") : t("common.save")}
|
|
267
266
|
</Button>
|
|
268
267
|
</DialogFooter>
|
|
269
268
|
</DialogContent>
|
|
@@ -280,28 +279,28 @@ function BankAccountsEditor({ accounts, onChange }) {
|
|
|
280
279
|
|
|
281
280
|
return (
|
|
282
281
|
<div className="space-y-3">
|
|
283
|
-
<Label>
|
|
282
|
+
<Label>{t("settings.payments.bank_details")}</Label>
|
|
284
283
|
{accounts.length === 0 && (
|
|
285
284
|
<p className="text-xs text-muted-foreground">
|
|
286
|
-
|
|
285
|
+
{t("settings.payments.bank_none")}
|
|
287
286
|
</p>
|
|
288
287
|
)}
|
|
289
288
|
{accounts.map((a, i) => (
|
|
290
289
|
<div key={i} className="space-y-2 rounded-md border p-3">
|
|
291
290
|
<div className="flex items-center justify-between">
|
|
292
|
-
<span className="text-sm font-medium">
|
|
291
|
+
<span className="text-sm font-medium">{t("settings.payments.account_n", { n: i + 1 })}</span>
|
|
293
292
|
<Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => remove(i)}>
|
|
294
293
|
<Trash2 className="h-3.5 w-3.5" />
|
|
295
294
|
</Button>
|
|
296
295
|
</div>
|
|
297
296
|
<div className="grid gap-2 sm:grid-cols-2">
|
|
298
297
|
{[
|
|
299
|
-
["account_name", "
|
|
300
|
-
["account_number", "
|
|
301
|
-
["bank_name", "
|
|
302
|
-
["sort_code", "
|
|
303
|
-
["iban", "
|
|
304
|
-
["bic", "
|
|
298
|
+
["account_name", t("settings.payments.account_name")],
|
|
299
|
+
["account_number", t("settings.payments.account_number")],
|
|
300
|
+
["bank_name", t("settings.payments.bank_name")],
|
|
301
|
+
["sort_code", t("settings.payments.sort_code")],
|
|
302
|
+
["iban", t("settings.payments.iban")],
|
|
303
|
+
["bic", t("settings.payments.bic")],
|
|
305
304
|
].map(([key, label]) => (
|
|
306
305
|
<div key={key} className="space-y-1">
|
|
307
306
|
<Label className="text-xs">{label}</Label>
|
|
@@ -313,7 +312,7 @@ function BankAccountsEditor({ accounts, onChange }) {
|
|
|
313
312
|
))}
|
|
314
313
|
<Button variant="outline" size="sm" onClick={add}>
|
|
315
314
|
<Plus className="mr-2 h-4 w-4" />
|
|
316
|
-
|
|
315
|
+
{t("settings.payments.add_account")}
|
|
317
316
|
</Button>
|
|
318
317
|
</div>
|
|
319
318
|
);
|
|
@@ -2,14 +2,15 @@ import React from "react";
|
|
|
2
2
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
|
3
3
|
import { useAdminHref } from "../../context/BasePathContext";
|
|
4
4
|
import PageHeader from "../../components/PageHeader";
|
|
5
|
+
import { t } from "../../i18n";
|
|
5
6
|
|
|
6
7
|
const TABS = [
|
|
7
|
-
{ seg: "general", label: "
|
|
8
|
-
{ seg: "inventory", label: "
|
|
9
|
-
{ seg: "shipping-tax", label: "
|
|
10
|
-
{ seg: "payments", label: "
|
|
11
|
-
{ seg: "emails", label: "
|
|
12
|
-
{ seg: "webhooks", label: "
|
|
8
|
+
{ seg: "general", label: "routes.settings_general" },
|
|
9
|
+
{ seg: "inventory", label: "routes.settings_inventory" },
|
|
10
|
+
{ seg: "shipping-tax", label: "routes.settings_shipping_tax" },
|
|
11
|
+
{ seg: "payments", label: "routes.settings_payments" },
|
|
12
|
+
{ seg: "emails", label: "routes.settings_emails" },
|
|
13
|
+
{ seg: "webhooks", label: "routes.settings_webhooks" },
|
|
13
14
|
];
|
|
14
15
|
|
|
15
16
|
/** Layout route for /settings/*: horizontal group tabs + nested page via <Outlet/>. */
|
|
@@ -24,20 +25,20 @@ export default function SettingsLayout() {
|
|
|
24
25
|
|
|
25
26
|
return (
|
|
26
27
|
<div className="space-y-4">
|
|
27
|
-
<PageHeader title="
|
|
28
|
+
<PageHeader title={t("routes.settings")} description={t("settings.description")} />
|
|
28
29
|
<div className="flex flex-wrap gap-1 border-b">
|
|
29
|
-
{TABS.map((
|
|
30
|
+
{TABS.map((tab) => (
|
|
30
31
|
<button
|
|
31
|
-
key={
|
|
32
|
+
key={tab.seg}
|
|
32
33
|
type="button"
|
|
33
|
-
onClick={() => navigate(href(`settings/${
|
|
34
|
+
onClick={() => navigate(href(`settings/${tab.seg}`))}
|
|
34
35
|
className={`-mb-px border-b-2 px-3 py-2 text-sm font-medium transition-colors ${
|
|
35
|
-
current ===
|
|
36
|
+
current === tab.seg
|
|
36
37
|
? "border-primary text-foreground"
|
|
37
38
|
: "border-transparent text-muted-foreground hover:text-foreground"
|
|
38
39
|
}`}
|
|
39
40
|
>
|
|
40
|
-
{t.label}
|
|
41
|
+
{t(tab.label)}
|
|
41
42
|
</button>
|
|
42
43
|
))}
|
|
43
44
|
</div>
|