create-brainerce-store 1.14.5 → 1.15.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/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "create-brainerce-store",
|
|
34
|
-
version: "1.
|
|
34
|
+
version: "1.15.0",
|
|
35
35
|
description: "Scaffold a production-ready e-commerce storefront connected to Brainerce",
|
|
36
36
|
bin: {
|
|
37
37
|
"create-brainerce-store": "dist/index.js"
|
package/messages/en.json
CHANGED
|
@@ -116,6 +116,8 @@
|
|
|
116
116
|
"pickUpInStore": "Pick up in store",
|
|
117
117
|
"pickUpInStoreDesc": "Collect from a pickup location",
|
|
118
118
|
"shippingAddress": "Shipping Address",
|
|
119
|
+
"contactInfo": "Contact Information",
|
|
120
|
+
"stepContactInfo": "Contact",
|
|
119
121
|
"changeMethod": "Change method",
|
|
120
122
|
"editAddress": "Edit address",
|
|
121
123
|
"shippingMethod": "Shipping Method",
|
|
@@ -168,6 +170,7 @@
|
|
|
168
170
|
"phone": "Phone",
|
|
169
171
|
"phonePlaceholder": "+1234567890 (optional)",
|
|
170
172
|
"continueToShipping": "Continue to Shipping",
|
|
173
|
+
"continueToPayment": "Continue to Payment",
|
|
171
174
|
"countryPlaceholder": "e.g. US, IL, GB",
|
|
172
175
|
"emailRequired": "Email is required",
|
|
173
176
|
"emailInvalid": "Please enter a valid email",
|
|
@@ -180,7 +183,8 @@
|
|
|
180
183
|
"privacyAcceptPrefix": "I have read and agree to the",
|
|
181
184
|
"privacyPolicyLink": "Privacy Policy",
|
|
182
185
|
"privacyRequired": "You must accept the privacy policy to continue",
|
|
183
|
-
"acceptsMarketing": "Send me news, promotions, and updates by email"
|
|
186
|
+
"acceptsMarketing": "Send me news, promotions, and updates by email",
|
|
187
|
+
"saveDetailsForNextTime": "Save my details for faster checkout next time"
|
|
184
188
|
},
|
|
185
189
|
"auth": {
|
|
186
190
|
"loginPageTitle": "Sign In",
|
package/messages/he.json
CHANGED
|
@@ -116,6 +116,8 @@
|
|
|
116
116
|
"pickUpInStore": "איסוף מהחנות",
|
|
117
117
|
"pickUpInStoreDesc": "איסוף מנקודת איסוף",
|
|
118
118
|
"shippingAddress": "כתובת למשלוח",
|
|
119
|
+
"contactInfo": "פרטי קשר",
|
|
120
|
+
"stepContactInfo": "פרטי קשר",
|
|
119
121
|
"changeMethod": "שנה שיטה",
|
|
120
122
|
"editAddress": "ערוך כתובת",
|
|
121
123
|
"shippingMethod": "שיטת משלוח",
|
|
@@ -168,6 +170,7 @@
|
|
|
168
170
|
"phone": "טלפון",
|
|
169
171
|
"phonePlaceholder": "+972501234567 (אופציונלי)",
|
|
170
172
|
"continueToShipping": "המשך למשלוח",
|
|
173
|
+
"continueToPayment": "המשך לתשלום",
|
|
171
174
|
"countryPlaceholder": "לדוגמה: IL, US, GB",
|
|
172
175
|
"emailRequired": "אימייל הוא שדה חובה",
|
|
173
176
|
"emailInvalid": "כתובת אימייל לא תקינה",
|
|
@@ -180,7 +183,8 @@
|
|
|
180
183
|
"privacyAcceptPrefix": "קראתי ואני מסכים/ה ל",
|
|
181
184
|
"privacyPolicyLink": "מדיניות הפרטיות",
|
|
182
185
|
"privacyRequired": "יש לאשר את מדיניות הפרטיות כדי להמשיך",
|
|
183
|
-
"acceptsMarketing": "שלחו לי חדשות, מבצעים ועדכונים במייל"
|
|
186
|
+
"acceptsMarketing": "שלחו לי חדשות, מבצעים ועדכונים במייל",
|
|
187
|
+
"saveDetailsForNextTime": "שמרו את הפרטים שלי לתשלום מהיר בפעם הבאה"
|
|
184
188
|
},
|
|
185
189
|
"auth": {
|
|
186
190
|
"loginPageTitle": "התחברות",
|
package/package.json
CHANGED
|
@@ -36,7 +36,6 @@ function CheckoutContent() {
|
|
|
36
36
|
const currency = storeInfo?.currency || 'USD';
|
|
37
37
|
const t = useTranslations('checkout');
|
|
38
38
|
const tc = useTranslations('common');
|
|
39
|
-
const tAddr = useTranslations('checkoutAddress');
|
|
40
39
|
|
|
41
40
|
const [step, setStep] = useState<CheckoutStep>('address');
|
|
42
41
|
const [checkout, setCheckout] = useState<Checkout | null>(null);
|
|
@@ -50,23 +49,29 @@ function CheckoutContent() {
|
|
|
50
49
|
const [deliveryType, setDeliveryType] = useState<'shipping' | 'pickup'>('shipping');
|
|
51
50
|
const [isAllDigital, setIsAllDigital] = useState(false);
|
|
52
51
|
const [prefillAddress, setPrefillAddress] = useState<SetShippingAddressDto | null>(null);
|
|
53
|
-
const [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
const [prefillCustomer, setPrefillCustomer] = useState<{
|
|
53
|
+
email: string;
|
|
54
|
+
firstName?: string;
|
|
55
|
+
lastName?: string;
|
|
56
|
+
phone?: string;
|
|
57
|
+
} | null>(null);
|
|
58
|
+
const [hasSavedAddress, setHasSavedAddress] = useState(false);
|
|
58
59
|
|
|
59
60
|
// Check for returning from canceled payment
|
|
60
61
|
const canceled = searchParams.get('canceled') === 'true';
|
|
61
62
|
const existingCheckoutId = searchParams.get('checkout_id');
|
|
62
63
|
|
|
63
|
-
// Pre-fill address
|
|
64
|
+
// Pre-fill address and customer data from profile when logged in
|
|
64
65
|
useEffect(() => {
|
|
65
66
|
if (!isLoggedIn) return;
|
|
66
67
|
getClient()
|
|
67
68
|
.getCheckoutPrefillData()
|
|
68
69
|
.then((data) => {
|
|
69
|
-
if (data.
|
|
70
|
+
if (data.customer) setPrefillCustomer(data.customer);
|
|
71
|
+
if (data.shippingAddress) {
|
|
72
|
+
setPrefillAddress(data.shippingAddress);
|
|
73
|
+
setHasSavedAddress(true);
|
|
74
|
+
}
|
|
70
75
|
})
|
|
71
76
|
.catch(() => {});
|
|
72
77
|
}, [isLoggedIn]);
|
|
@@ -98,7 +103,8 @@ function CheckoutContent() {
|
|
|
98
103
|
);
|
|
99
104
|
setIsAllDigital(allDigital);
|
|
100
105
|
if (allDigital) {
|
|
101
|
-
|
|
106
|
+
// Digital products: show contact info step if email not set, else payment
|
|
107
|
+
setStep(existing.email ? 'payment' : 'address');
|
|
102
108
|
} else if (existing.deliveryType === 'pickup' && existing.pickupLocation) {
|
|
103
109
|
setDeliveryType('pickup');
|
|
104
110
|
setStep('payment');
|
|
@@ -120,13 +126,13 @@ function CheckoutContent() {
|
|
|
120
126
|
const newCheckout = await client.createCheckout({ cartId: cart.id });
|
|
121
127
|
setCheckout(newCheckout);
|
|
122
128
|
|
|
123
|
-
// If all items are downloadable, skip shipping
|
|
129
|
+
// If all items are downloadable, skip shipping — show contact info step
|
|
124
130
|
const allDigital = newCheckout.lineItems.every(
|
|
125
131
|
(i) => (i.product as unknown as { isDownloadable?: boolean }).isDownloadable
|
|
126
132
|
);
|
|
127
133
|
setIsAllDigital(allDigital);
|
|
128
134
|
if (allDigital) {
|
|
129
|
-
setStep('
|
|
135
|
+
setStep('address');
|
|
130
136
|
return;
|
|
131
137
|
}
|
|
132
138
|
} else {
|
|
@@ -155,7 +161,7 @@ function CheckoutContent() {
|
|
|
155
161
|
// Handle shipping address submission
|
|
156
162
|
async function handleAddressSubmit(
|
|
157
163
|
address: SetShippingAddressDto,
|
|
158
|
-
consent: { acceptsMarketing: boolean }
|
|
164
|
+
consent: { acceptsMarketing: boolean; saveDetails: boolean }
|
|
159
165
|
) {
|
|
160
166
|
if (!checkout) return;
|
|
161
167
|
|
|
@@ -164,9 +170,23 @@ function CheckoutContent() {
|
|
|
164
170
|
setError(null);
|
|
165
171
|
const client = getClient();
|
|
166
172
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
if (isAllDigital) {
|
|
174
|
+
// Digital products: set customer info only, skip shipping
|
|
175
|
+
const updated = await client.setCheckoutCustomer(checkout.id, {
|
|
176
|
+
email: address.email,
|
|
177
|
+
firstName: address.firstName,
|
|
178
|
+
lastName: address.lastName,
|
|
179
|
+
phone: address.phone,
|
|
180
|
+
acceptsMarketing: consent.acceptsMarketing,
|
|
181
|
+
});
|
|
182
|
+
setCheckout(updated);
|
|
183
|
+
setStep('payment');
|
|
184
|
+
} else {
|
|
185
|
+
const response = await client.setShippingAddress(checkout.id, address);
|
|
186
|
+
setCheckout(response.checkout);
|
|
187
|
+
setShippingRates(response.rates);
|
|
188
|
+
setStep('shipping');
|
|
189
|
+
}
|
|
170
190
|
|
|
171
191
|
// Update marketing preference for logged-in users
|
|
172
192
|
if (isLoggedIn) {
|
|
@@ -177,13 +197,25 @@ function CheckoutContent() {
|
|
|
177
197
|
}
|
|
178
198
|
}
|
|
179
199
|
|
|
180
|
-
//
|
|
181
|
-
if (isLoggedIn && !
|
|
182
|
-
|
|
183
|
-
|
|
200
|
+
// Save address to profile if checkbox was checked and no existing saved address
|
|
201
|
+
if (isLoggedIn && consent.saveDetails && !hasSavedAddress && !isAllDigital) {
|
|
202
|
+
try {
|
|
203
|
+
await client.addMyAddress({
|
|
204
|
+
firstName: address.firstName,
|
|
205
|
+
lastName: address.lastName,
|
|
206
|
+
line1: address.line1,
|
|
207
|
+
line2: address.line2,
|
|
208
|
+
city: address.city,
|
|
209
|
+
region: address.region,
|
|
210
|
+
postalCode: address.postalCode,
|
|
211
|
+
country: address.country,
|
|
212
|
+
phone: address.phone,
|
|
213
|
+
isDefault: true,
|
|
214
|
+
});
|
|
215
|
+
} catch {
|
|
216
|
+
// non-critical
|
|
217
|
+
}
|
|
184
218
|
}
|
|
185
|
-
|
|
186
|
-
setStep('shipping');
|
|
187
219
|
} catch (err) {
|
|
188
220
|
const message = err instanceof Error ? err.message : t('failedToSaveAddress');
|
|
189
221
|
setError(message);
|
|
@@ -192,30 +224,6 @@ function CheckoutContent() {
|
|
|
192
224
|
}
|
|
193
225
|
}
|
|
194
226
|
|
|
195
|
-
async function handleSaveAddressToProfile() {
|
|
196
|
-
if (!lastSubmittedAddress) return;
|
|
197
|
-
setSavingAddress(true);
|
|
198
|
-
try {
|
|
199
|
-
await getClient().addMyAddress({
|
|
200
|
-
firstName: lastSubmittedAddress.firstName,
|
|
201
|
-
lastName: lastSubmittedAddress.lastName,
|
|
202
|
-
line1: lastSubmittedAddress.line1,
|
|
203
|
-
line2: lastSubmittedAddress.line2,
|
|
204
|
-
city: lastSubmittedAddress.city,
|
|
205
|
-
region: lastSubmittedAddress.region,
|
|
206
|
-
postalCode: lastSubmittedAddress.postalCode,
|
|
207
|
-
country: lastSubmittedAddress.country,
|
|
208
|
-
phone: lastSubmittedAddress.phone,
|
|
209
|
-
isDefault: true,
|
|
210
|
-
});
|
|
211
|
-
} catch {
|
|
212
|
-
// ignore
|
|
213
|
-
} finally {
|
|
214
|
-
setSavingAddress(false);
|
|
215
|
-
setShowSavePrompt(false);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
227
|
// Handle shipping method selection
|
|
220
228
|
async function handleShippingSelect(rateId: string) {
|
|
221
229
|
if (!checkout) return;
|
|
@@ -345,7 +353,10 @@ function CheckoutContent() {
|
|
|
345
353
|
}
|
|
346
354
|
|
|
347
355
|
const steps: { key: CheckoutStep; label: string }[] = isAllDigital
|
|
348
|
-
? [
|
|
356
|
+
? [
|
|
357
|
+
{ key: 'address', label: t('stepContactInfo') },
|
|
358
|
+
{ key: 'payment', label: t('stepPayment') },
|
|
359
|
+
]
|
|
349
360
|
: pickupLocations.length > 0
|
|
350
361
|
? deliveryType === 'pickup'
|
|
351
362
|
? [
|
|
@@ -456,8 +467,10 @@ function CheckoutContent() {
|
|
|
456
467
|
{step === 'address' && (
|
|
457
468
|
<div>
|
|
458
469
|
<div className="mb-4 flex items-center justify-between">
|
|
459
|
-
<h2 className="text-foreground text-lg font-semibold">
|
|
460
|
-
|
|
470
|
+
<h2 className="text-foreground text-lg font-semibold">
|
|
471
|
+
{isAllDigital ? t('contactInfo') : t('shippingAddress')}
|
|
472
|
+
</h2>
|
|
473
|
+
{!isAllDigital && pickupLocations.length > 0 && (
|
|
461
474
|
<button
|
|
462
475
|
type="button"
|
|
463
476
|
onClick={() => setStep('method')}
|
|
@@ -467,33 +480,12 @@ function CheckoutContent() {
|
|
|
467
480
|
</button>
|
|
468
481
|
)}
|
|
469
482
|
</div>
|
|
470
|
-
{/* Save-to-profile prompt */}
|
|
471
|
-
{showSavePrompt && (
|
|
472
|
-
<div className="border-border bg-muted/50 mb-4 flex items-center justify-between gap-3 rounded-lg border px-4 py-3 text-sm">
|
|
473
|
-
<span className="text-foreground">{tAddr('saveToProfile')}</span>
|
|
474
|
-
<div className="flex gap-2">
|
|
475
|
-
<button
|
|
476
|
-
type="button"
|
|
477
|
-
onClick={handleSaveAddressToProfile}
|
|
478
|
-
disabled={savingAddress}
|
|
479
|
-
className="bg-primary text-primary-foreground rounded px-3 py-1 text-xs font-medium transition-opacity hover:opacity-90 disabled:opacity-50"
|
|
480
|
-
>
|
|
481
|
-
{savingAddress ? '...' : tAddr('saveYes')}
|
|
482
|
-
</button>
|
|
483
|
-
<button
|
|
484
|
-
type="button"
|
|
485
|
-
onClick={() => setShowSavePrompt(false)}
|
|
486
|
-
className="text-muted-foreground hover:text-foreground rounded px-3 py-1 text-xs transition-colors"
|
|
487
|
-
>
|
|
488
|
-
{tAddr('saveNo')}
|
|
489
|
-
</button>
|
|
490
|
-
</div>
|
|
491
|
-
</div>
|
|
492
|
-
)}
|
|
493
483
|
<CheckoutForm
|
|
494
484
|
onSubmit={handleAddressSubmit}
|
|
495
485
|
loading={loading}
|
|
496
|
-
destinations={destinations}
|
|
486
|
+
destinations={isAllDigital ? null : destinations}
|
|
487
|
+
showSaveDetails={isLoggedIn && !hasSavedAddress && !isAllDigital}
|
|
488
|
+
emailOnly={isAllDigital}
|
|
497
489
|
initialValues={
|
|
498
490
|
checkout?.shippingAddress
|
|
499
491
|
? {
|
|
@@ -521,7 +513,14 @@ function CheckoutContent() {
|
|
|
521
513
|
country: prefillAddress.country,
|
|
522
514
|
phone: prefillAddress.phone || '',
|
|
523
515
|
}
|
|
524
|
-
:
|
|
516
|
+
: prefillCustomer
|
|
517
|
+
? {
|
|
518
|
+
email: prefillCustomer.email,
|
|
519
|
+
firstName: prefillCustomer.firstName || '',
|
|
520
|
+
lastName: prefillCustomer.lastName || '',
|
|
521
|
+
phone: prefillCustomer.phone || '',
|
|
522
|
+
}
|
|
523
|
+
: undefined
|
|
525
524
|
}
|
|
526
525
|
/>
|
|
527
526
|
</div>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState } from 'react';
|
|
3
|
+
import { useState, useEffect, useRef } from 'react';
|
|
4
4
|
import type { SetShippingAddressDto, ShippingDestinations } from 'brainerce';
|
|
5
5
|
import { useTranslations } from '@/lib/translations';
|
|
6
6
|
import { cn } from '@/lib/utils';
|
|
7
7
|
|
|
8
8
|
interface CheckoutConsent {
|
|
9
9
|
acceptsMarketing: boolean;
|
|
10
|
+
saveDetails: boolean;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
interface CheckoutFormProps {
|
|
@@ -15,6 +16,8 @@ interface CheckoutFormProps {
|
|
|
15
16
|
initialValues?: Partial<SetShippingAddressDto>;
|
|
16
17
|
destinations?: ShippingDestinations | null;
|
|
17
18
|
className?: string;
|
|
19
|
+
showSaveDetails?: boolean;
|
|
20
|
+
emailOnly?: boolean;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export function CheckoutForm({
|
|
@@ -23,6 +26,8 @@ export function CheckoutForm({
|
|
|
23
26
|
initialValues,
|
|
24
27
|
destinations,
|
|
25
28
|
className,
|
|
29
|
+
showSaveDetails = false,
|
|
30
|
+
emailOnly = false,
|
|
26
31
|
}: CheckoutFormProps) {
|
|
27
32
|
const [formData, setFormData] = useState<SetShippingAddressDto>({
|
|
28
33
|
email: initialValues?.email || '',
|
|
@@ -39,8 +44,28 @@ export function CheckoutForm({
|
|
|
39
44
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
40
45
|
const [privacyAccepted, setPrivacyAccepted] = useState(false);
|
|
41
46
|
const [acceptsMarketing, setAcceptsMarketing] = useState(false);
|
|
47
|
+
const [saveDetails, setSaveDetails] = useState(true);
|
|
42
48
|
const t = useTranslations('checkoutForm');
|
|
43
49
|
const tc = useTranslations('common');
|
|
50
|
+
const hasAppliedPrefill = useRef(!!initialValues);
|
|
51
|
+
|
|
52
|
+
// Sync prefill data when it arrives async (e.g. from getCheckoutPrefillData)
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!initialValues || hasAppliedPrefill.current) return;
|
|
55
|
+
hasAppliedPrefill.current = true;
|
|
56
|
+
setFormData((prev) => ({
|
|
57
|
+
email: initialValues.email || prev.email,
|
|
58
|
+
firstName: initialValues.firstName || prev.firstName,
|
|
59
|
+
lastName: initialValues.lastName || prev.lastName,
|
|
60
|
+
line1: initialValues.line1 || prev.line1,
|
|
61
|
+
line2: initialValues.line2 || prev.line2 || '',
|
|
62
|
+
city: initialValues.city || prev.city,
|
|
63
|
+
region: initialValues.region || prev.region || '',
|
|
64
|
+
postalCode: initialValues.postalCode || prev.postalCode,
|
|
65
|
+
country: initialValues.country || prev.country,
|
|
66
|
+
phone: initialValues.phone || prev.phone || '',
|
|
67
|
+
}));
|
|
68
|
+
}, [initialValues]);
|
|
44
69
|
|
|
45
70
|
const hasCountryOptions = destinations && destinations.countries.length > 0;
|
|
46
71
|
const countryRegions = destinations?.regions[formData.country];
|
|
@@ -61,17 +86,19 @@ export function CheckoutForm({
|
|
|
61
86
|
if (!formData.lastName.trim()) {
|
|
62
87
|
newErrors.lastName = t('lastNameRequired');
|
|
63
88
|
}
|
|
64
|
-
if (!
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
89
|
+
if (!emailOnly) {
|
|
90
|
+
if (!formData.line1.trim()) {
|
|
91
|
+
newErrors.line1 = t('addressRequired');
|
|
92
|
+
}
|
|
93
|
+
if (!formData.city.trim()) {
|
|
94
|
+
newErrors.city = t('cityRequired');
|
|
95
|
+
}
|
|
96
|
+
if (!formData.postalCode.trim()) {
|
|
97
|
+
newErrors.postalCode = t('postalCodeRequired');
|
|
98
|
+
}
|
|
99
|
+
if (!formData.country.trim()) {
|
|
100
|
+
newErrors.country = t('countryRequired');
|
|
101
|
+
}
|
|
75
102
|
}
|
|
76
103
|
if (!privacyAccepted) {
|
|
77
104
|
newErrors.privacy = t('privacyRequired');
|
|
@@ -84,7 +111,7 @@ export function CheckoutForm({
|
|
|
84
111
|
function handleSubmit(e: React.FormEvent) {
|
|
85
112
|
e.preventDefault();
|
|
86
113
|
if (validate()) {
|
|
87
|
-
onSubmit(formData, { acceptsMarketing });
|
|
114
|
+
onSubmit(formData, { acceptsMarketing, saveDetails: showSaveDetails && saveDetails });
|
|
88
115
|
}
|
|
89
116
|
}
|
|
90
117
|
|
|
@@ -160,6 +187,8 @@ export function CheckoutForm({
|
|
|
160
187
|
</div>
|
|
161
188
|
</div>
|
|
162
189
|
|
|
190
|
+
{!emailOnly && (
|
|
191
|
+
<>
|
|
163
192
|
{/* Country + Region row */}
|
|
164
193
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
165
194
|
<div>
|
|
@@ -301,6 +330,8 @@ export function CheckoutForm({
|
|
|
301
330
|
placeholder={t('phonePlaceholder')}
|
|
302
331
|
/>
|
|
303
332
|
</div>
|
|
333
|
+
</>
|
|
334
|
+
)}
|
|
304
335
|
|
|
305
336
|
{/* Privacy Policy (required) */}
|
|
306
337
|
<div>
|
|
@@ -347,12 +378,25 @@ export function CheckoutForm({
|
|
|
347
378
|
<span className="text-muted-foreground text-sm">{t('acceptsMarketing')}</span>
|
|
348
379
|
</label>
|
|
349
380
|
|
|
381
|
+
{/* Save details for next time (logged-in users only) */}
|
|
382
|
+
{showSaveDetails && (
|
|
383
|
+
<label className="flex cursor-pointer items-start gap-2">
|
|
384
|
+
<input
|
|
385
|
+
type="checkbox"
|
|
386
|
+
checked={saveDetails}
|
|
387
|
+
onChange={(e) => setSaveDetails(e.target.checked)}
|
|
388
|
+
className="accent-primary mt-0.5"
|
|
389
|
+
/>
|
|
390
|
+
<span className="text-muted-foreground text-sm">{t('saveDetailsForNextTime')}</span>
|
|
391
|
+
</label>
|
|
392
|
+
)}
|
|
393
|
+
|
|
350
394
|
<button
|
|
351
395
|
type="submit"
|
|
352
396
|
disabled={loading}
|
|
353
397
|
className="bg-primary text-primary-foreground w-full rounded px-6 py-3 text-sm font-medium transition-opacity hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
|
|
354
398
|
>
|
|
355
|
-
{loading ? tc('saving') : t('continueToShipping')}
|
|
399
|
+
{loading ? tc('saving') : emailOnly ? t('continueToPayment') : t('continueToShipping')}
|
|
356
400
|
</button>
|
|
357
401
|
</form>
|
|
358
402
|
);
|