storefront-layouts 1.0.13 → 1.0.15
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/editor/blocks/defaults/clothing-minimal-home.d.ts.map +1 -1
- package/dist/editor/blocks/defaults/clothing-minimal-home.js +0 -17
- package/dist/editor/payaza-form/PayazaFormEditor.d.ts.map +1 -1
- package/dist/editor/payaza-form/PayazaFormEditor.js +0 -2
- package/dist/editor/payaza-form/data-transformer.d.ts.map +1 -1
- package/dist/editor/payaza-form/data-transformer.js +1 -8
- package/dist/editor/shared/useEditorChromeMetrics.js +1 -2
- package/dist/hooks/use-pdp-variation.d.ts +1 -13
- package/dist/hooks/use-pdp-variation.d.ts.map +1 -1
- package/dist/hooks/use-product-variation-selection.d.ts +1 -13
- package/dist/hooks/use-product-variation-selection.d.ts.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/layouts/booking/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/booking/pages/ProductDetailPage.js +2 -1
- package/dist/layouts/booking-agenda/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/booking-agenda/pages/ProductDetailPage.js +2 -1
- package/dist/layouts/clothing/components/ClothingHomePage.d.ts.map +1 -1
- package/dist/layouts/clothing/components/ClothingHomePage.js +7 -11
- package/dist/layouts/clothing/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/clothing/pages/ProductDetailPage.js +1 -1
- package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.d.ts.map +1 -1
- package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.js +1 -7
- package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.d.ts +1 -1
- package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.d.ts.map +1 -1
- package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.js +5 -21
- package/dist/layouts/clothing-minimal/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/clothing-minimal/pages/ProductDetailPage.js +1 -1
- package/dist/layouts/electronics/components/ElectronicsGridProductCard.d.ts.map +1 -1
- package/dist/layouts/electronics/components/ElectronicsGridProductCard.js +2 -2
- package/dist/layouts/electronics/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/electronics/pages/ProductDetailPage.js +2 -1
- package/dist/layouts/electronics-grid/components/ElectronicsGridProductCard.d.ts.map +1 -1
- package/dist/layouts/electronics-grid/components/ElectronicsGridProductCard.js +2 -2
- package/dist/layouts/electronics-grid/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/electronics-grid/pages/ProductDetailPage.js +2 -1
- package/dist/layouts/food/pages/MenuPage.d.ts.map +1 -1
- package/dist/layouts/food/pages/MenuPage.js +1 -0
- package/dist/layouts/food/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/food/pages/ProductDetailPage.js +2 -1
- package/dist/layouts/food-modern/components/FoodHomePageModern.js +1 -1
- package/dist/layouts/food-modern/pages/MenuPage.d.ts.map +1 -1
- package/dist/layouts/food-modern/pages/MenuPage.js +1 -0
- package/dist/layouts/food-modern/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/food-modern/pages/ProductDetailPage.js +2 -1
- package/dist/layouts/shared/components/PromoBanner.d.ts +4 -1
- package/dist/layouts/shared/components/PromoBanner.d.ts.map +1 -1
- package/dist/layouts/shared/components/PromoBanner.js +2 -2
- package/dist/layouts/shared/components/StoreCartSheet.d.ts.map +1 -1
- package/dist/layouts/shared/components/StoreCartSheet.js +4 -2
- package/dist/layouts/shared/components/StoreFooter.js +2 -2
- package/dist/layouts/shared/components/StoreHeader.js +4 -4
- package/dist/layouts/shared/pages/CartPage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/CartPage.js +4 -28
- package/dist/layouts/shared/pages/CheckoutPage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/CheckoutPage.js +203 -81
- package/dist/layouts/shared/pages/MaintenancePage.js +1 -1
- package/dist/layouts/shared/wrappers/ClothingLayout.js +5 -5
- package/dist/layouts/shared/wrappers/ClothingMinimalLayout.js +4 -4
- package/dist/layouts/shared/wrappers/ElectronicsLayout.js +4 -4
- package/dist/layouts/shared/wrappers/FoodModernLayout.js +4 -4
- package/dist/layouts/shared/wrappers/MotivationalLayout.js +4 -4
- package/dist/layouts/shared/wrappers/StandardLayout.js +4 -4
- package/dist/lib/cart-charge-currency.d.ts +16 -0
- package/dist/lib/cart-charge-currency.d.ts.map +1 -0
- package/dist/lib/cart-charge-currency.js +38 -0
- package/dist/lib/checkout-phone-validation.d.ts +3 -0
- package/dist/lib/checkout-phone-validation.d.ts.map +1 -0
- package/dist/lib/checkout-phone-validation.js +41 -0
- package/dist/lib/preview-data.d.ts.map +1 -1
- package/dist/lib/preview-data.js +2 -0
- package/dist/lib/rich-html.d.ts +1 -2
- package/dist/lib/rich-html.d.ts.map +1 -1
- package/dist/lib/services/fees.service.d.ts +40 -57
- package/dist/lib/services/fees.service.d.ts.map +1 -1
- package/dist/lib/services/fees.service.js +24 -120
- package/dist/lib/store-types.d.ts +27 -34
- package/dist/lib/store-types.d.ts.map +1 -1
- package/dist/lib/utils/fee-calculations.d.ts.map +1 -1
- package/dist/lib/utils/fee-calculations.js +23 -11
- package/dist/preview/LayoutPreview.d.ts.map +1 -1
- package/dist/preview/LayoutPreview.js +5 -22
- package/dist/styles/index.css +36 -0
- package/dist/utils/price-display.d.ts +3 -0
- package/dist/utils/price-display.d.ts.map +1 -0
- package/dist/utils/price-display.js +8 -0
- package/package.json +2 -1
|
@@ -4,7 +4,7 @@ import { Button } from '../../../components/ui/button.js';
|
|
|
4
4
|
import { ArrowLeft, CheckCircle, Lock, ChevronDown, Trash2 } from 'lucide-react';
|
|
5
5
|
import Link from 'next/link';
|
|
6
6
|
import Image from 'next/image';
|
|
7
|
-
import { useState, useEffect, useRef } from 'react';
|
|
7
|
+
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
8
8
|
import { cn } from '../../../lib/utils/index.js';
|
|
9
9
|
import { usePayazaCheckout } from '../../../hooks/use-payaza-checkout.js';
|
|
10
10
|
import { generateCheckoutConfig } from '../../../lib/payaza-checkout.js';
|
|
@@ -24,11 +24,50 @@ import { shippingService } from '../../../lib/services/shipping.service.js';
|
|
|
24
24
|
import { feesService } from '../../../lib/services/fees.service.js';
|
|
25
25
|
import { getCartLineImage } from '../../../lib/product-variation-utils.js';
|
|
26
26
|
import { useAnalytics } from '../../../hooks/use-analytics.js';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
import { resolveCartChargeCurrency } from '../../../lib/cart-charge-currency.js';
|
|
28
|
+
import { getCheckoutPhoneValidationError, sanitizeCheckoutPhoneInput, } from '../../../lib/checkout-phone-validation.js';
|
|
29
|
+
const CHECKOUT_PERSON_NAME_MIN_LENGTH = 2;
|
|
30
|
+
const CHECKOUT_PERSON_NAME_MAX_LENGTH = 30;
|
|
31
|
+
function getCheckoutPersonNameValidationError(value, field) {
|
|
32
|
+
const trimmed = value.trim();
|
|
33
|
+
const label = field === 'first' ? 'First name' : 'Last name';
|
|
34
|
+
if (!trimmed)
|
|
35
|
+
return `${label} is required`;
|
|
36
|
+
if (trimmed.length < CHECKOUT_PERSON_NAME_MIN_LENGTH) {
|
|
37
|
+
return `${label} must be at least ${CHECKOUT_PERSON_NAME_MIN_LENGTH} characters`;
|
|
38
|
+
}
|
|
39
|
+
if (trimmed.length > CHECKOUT_PERSON_NAME_MAX_LENGTH) {
|
|
40
|
+
return `${label} must be at most ${CHECKOUT_PERSON_NAME_MAX_LENGTH} characters`;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function getCheckoutEmailValidationError(email) {
|
|
45
|
+
const trimmed = email.trim();
|
|
46
|
+
if (!trimmed)
|
|
47
|
+
return 'Email is required';
|
|
48
|
+
const atIndex = trimmed.indexOf('@');
|
|
49
|
+
if (atIndex < 1) {
|
|
50
|
+
return 'Enter an email with @ and a domain (e.g. name@example.com)';
|
|
51
|
+
}
|
|
52
|
+
const domain = trimmed.slice(atIndex + 1);
|
|
53
|
+
if (!domain.includes('.')) {
|
|
54
|
+
return 'Enter an email with a domain (e.g. name@example.com)';
|
|
55
|
+
}
|
|
56
|
+
const [host, ...rest] = domain.split('.');
|
|
57
|
+
const tld = rest.join('.');
|
|
58
|
+
if (!host?.trim() || !tld?.trim() || tld.length < 2) {
|
|
59
|
+
return 'Enter a valid email domain (e.g. name@example.com)';
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
function scrollToFirstCheckoutFieldError() {
|
|
64
|
+
const firstErrorField = document.querySelector('.border-red-400, .border-red-500');
|
|
65
|
+
if (firstErrorField) {
|
|
66
|
+
firstErrorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
70
|
+
}
|
|
32
71
|
}
|
|
33
72
|
function ContactInformationIcon({ className }) {
|
|
34
73
|
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 90 90", className: className, "aria-hidden": true, children: [_jsx("path", { fill: "#000", d: "M 81.353 50.79 L 63.143 36.799 l 18.302 -15.87 c 0.834 -0.723 0.924 -1.986 0.2 -2.82 c -0.723 -0.834 -1.985 -0.925 -2.82 -0.201 L 58.682 35.374 c -0.005 0.004 -0.009 0.008 -0.014 0.012 l -4.921 4.267 L 28.669 17.907 c -0.834 -0.724 -2.096 -0.633 -2.82 0.201 c -0.723 0.834 -0.634 2.097 0.201 2.82 l 18.44 15.99 l -12.442 9.912 c -0.864 0.688 -1.006 1.946 -0.318 2.81 c 0.395 0.496 0.977 0.754 1.565 0.754 c 0.436 0 0.876 -0.143 1.244 -0.435 l 13.024 -10.375 l 4.874 4.226 c 0.376 0.326 0.843 0.489 1.31 0.489 c 0.467 0 0.934 -0.163 1.31 -0.489 l 5.002 -4.337 l 18.858 14.488 c 0.363 0.279 0.792 0.414 1.217 0.414 c 0.599 0 1.193 -0.268 1.586 -0.781 C 82.393 52.718 82.229 51.463 81.353 50.79 z" }), _jsx("path", { fill: "#000", d: "M 84.004 10.598 H 23.49 c -3.306 0 -5.996 2.69 -5.996 5.996 v 1.616 c -1.125 -0.827 -2.636 -1.044 -3.982 -0.453 c -0.87 0.383 -2.006 0.88 -3.19 1.394 l -0.032 0.014 c -2.526 1.095 -4.385 1.88 -5.378 2.269 c -0.741 0.29 -1.362 0.72 -1.845 1.278 c -8.173 9.429 1.178 26.658 14.071 39.551 c 9.539 9.539 21.449 17.14 30.82 17.139 c 3.296 -0.001 6.278 -0.941 8.731 -3.066 c 0.557 -0.483 0.987 -1.104 1.278 -1.846 c 0.395 -1.009 1.171 -2.847 2.304 -5.456 c 0.507 -1.168 0.996 -2.286 1.373 -3.144 c 0.621 -1.413 0.355 -3.01 -0.578 -4.148 h 22.939 c 3.307 0 5.996 -2.69 5.996 -5.996 v -39.15 C 90 13.288 87.31 10.598 84.004 10.598 z M 56.603 67.439 c -1.152 2.656 -1.947 4.538 -2.36 5.592 c -0.05 0.127 -0.113 0.23 -0.173 0.282 c -7.118 6.171 -22.543 -2.315 -34.105 -13.877 C 8.402 47.872 -0.078 32.445 6.088 25.331 c 0.053 -0.061 0.156 -0.124 0.283 -0.174 c 1.063 -0.416 2.944 -1.21 5.592 -2.359 v 0 c 1.173 -0.509 2.296 -1 3.168 -1.373 l 7.266 12.256 l -4.505 8.393 c -0.843 1.57 -0.562 3.471 0.698 4.732 l 14.007 14.007 c 1.262 1.26 3.164 1.541 4.731 0.696 l 8.404 -4.505 l 12.245 7.267 l 0.006 0.01 C 57.603 65.144 57.11 66.268 56.603 67.439 z M 86.001 55.744 c 0 1.101 -0.896 1.997 -1.997 1.997 H 54.809 l -7.038 -4.177 c -1.201 -0.712 -2.711 -0.744 -3.942 -0.083 l -8.406 4.503 l -14.008 -14.02 l 4.505 -8.392 c 0.66 -1.231 0.628 -2.741 -0.084 -3.943 l -4.343 -7.319 v -7.716 c 0 -1.101 0.896 -1.997 1.997 -1.997 h 60.514 c 1.101 0 1.997 0.896 1.997 1.997 V 55.744 z" })] }));
|
|
@@ -108,6 +147,13 @@ function estimatedDeliveryText(method) {
|
|
|
108
147
|
}
|
|
109
148
|
return null;
|
|
110
149
|
}
|
|
150
|
+
function shouldShowOrderSummaryShippingLine(options) {
|
|
151
|
+
if (!options.shippingEnabled)
|
|
152
|
+
return false;
|
|
153
|
+
if (options.isCalculating)
|
|
154
|
+
return true;
|
|
155
|
+
return options.selectedMethod != null;
|
|
156
|
+
}
|
|
111
157
|
function readCachedCartForCheckout(slug) {
|
|
112
158
|
if (typeof window === 'undefined')
|
|
113
159
|
return [];
|
|
@@ -168,6 +214,7 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
168
214
|
: cart.length > 0
|
|
169
215
|
? cartTotal
|
|
170
216
|
: cartForCheckout.reduce((total, item) => total + normalizePrice(item.price) * item.quantity, 0);
|
|
217
|
+
const { currency, mixed: hasMixedCartCurrencies } = useMemo(() => resolveCartChargeCurrency(checkoutItems, storeConfig.settings?.currency), [checkoutItems, storeConfig.settings?.currency]);
|
|
171
218
|
const handleRemoveCheckoutItem = async (itemId) => {
|
|
172
219
|
if (effectiveBuyNowItem && effectiveBuyNowItem.id === itemId) {
|
|
173
220
|
clearBuyNowItem();
|
|
@@ -194,6 +241,33 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
194
241
|
const [freeShippingEnabled, setFreeShippingEnabled] = useState(false);
|
|
195
242
|
const [feePreview, setFeePreview] = useState(null);
|
|
196
243
|
const [isCalculatingFees, setIsCalculatingFees] = useState(false);
|
|
244
|
+
const [formData, setFormData] = useState({
|
|
245
|
+
email: '',
|
|
246
|
+
firstName: '',
|
|
247
|
+
lastName: '',
|
|
248
|
+
address: '',
|
|
249
|
+
city: '',
|
|
250
|
+
country: 'Nigeria',
|
|
251
|
+
postalCode: '',
|
|
252
|
+
phone: '',
|
|
253
|
+
});
|
|
254
|
+
const [errors, setErrors] = useState({});
|
|
255
|
+
const [isCountryDropdownOpen, setIsCountryDropdownOpen] = useState(false);
|
|
256
|
+
const [isCityDropdownOpen, setIsCityDropdownOpen] = useState(false);
|
|
257
|
+
const [citySearchQuery, setCitySearchQuery] = useState('');
|
|
258
|
+
const citySearchInputRef = useRef(null);
|
|
259
|
+
const cityOptions = useMemo(() => getCitiesByCountry(formData.country), [formData.country]);
|
|
260
|
+
const filteredCities = useMemo(() => {
|
|
261
|
+
const q = citySearchQuery.trim().toLowerCase();
|
|
262
|
+
if (!q)
|
|
263
|
+
return cityOptions;
|
|
264
|
+
return cityOptions.filter((city) => city.toLowerCase().includes(q));
|
|
265
|
+
}, [cityOptions, citySearchQuery]);
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (isCityDropdownOpen) {
|
|
268
|
+
citySearchInputRef.current?.focus();
|
|
269
|
+
}
|
|
270
|
+
}, [isCityDropdownOpen]);
|
|
197
271
|
const { checkout: initiateCheckout, isLoading: isPayazaLoading } = usePayazaCheckout({
|
|
198
272
|
onSuccess: async (response) => {
|
|
199
273
|
try {
|
|
@@ -514,19 +588,6 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
514
588
|
setIsCalculatingShipping(false);
|
|
515
589
|
}
|
|
516
590
|
};
|
|
517
|
-
// Determine currency from checkout items or store config (avoid switching to USD at checkout)
|
|
518
|
-
const getCurrency = () => {
|
|
519
|
-
const selected = selectedDisplayCurrency();
|
|
520
|
-
if (selected)
|
|
521
|
-
return selected;
|
|
522
|
-
if (checkoutItems.length > 0) {
|
|
523
|
-
const itemCurrency = checkoutItems[0]?.currency ?? checkoutItems[0]?.product?.currency;
|
|
524
|
-
if (itemCurrency)
|
|
525
|
-
return itemCurrency;
|
|
526
|
-
}
|
|
527
|
-
return storeConfig.settings?.currency || 'USD';
|
|
528
|
-
};
|
|
529
|
-
const currency = getCurrency();
|
|
530
591
|
// Calculations
|
|
531
592
|
const subtotal = checkoutTotal;
|
|
532
593
|
// Only use shipping cost if it's a valid number and from a properly selected method
|
|
@@ -547,6 +608,15 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
547
608
|
const fallbackTotal = Math.max(safeSubtotal, calculatedTotal);
|
|
548
609
|
const total = feePreview?.totalPayable ?? fallbackTotal;
|
|
549
610
|
const customerFeeLines = (feePreview?.customerLines ?? []).filter((line) => line.key !== 'shipping');
|
|
611
|
+
const shippingEnabledForCheckout = storeConfig.settings?.shippingEnabled !== false;
|
|
612
|
+
const showOrderSummaryShippingLine = shouldShowOrderSummaryShippingLine({
|
|
613
|
+
shippingEnabled: shippingEnabledForCheckout,
|
|
614
|
+
isCalculating: isCalculatingShipping,
|
|
615
|
+
selectedMethod: selectedShippingMethod,
|
|
616
|
+
});
|
|
617
|
+
const showShippingMethodSection = shippingEnabledForCheckout &&
|
|
618
|
+
Boolean(formData.city && formData.country) &&
|
|
619
|
+
(isCalculatingShipping || Boolean(shippingError) || shippingMethods.length > 0);
|
|
550
620
|
useEffect(() => {
|
|
551
621
|
if (!storeConfig.id || safeSubtotal <= 0 || !currency) {
|
|
552
622
|
setFeePreview(null);
|
|
@@ -595,20 +665,6 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
595
665
|
effectiveBuyNowItem: !!effectiveBuyNowItem,
|
|
596
666
|
});
|
|
597
667
|
}
|
|
598
|
-
// Mock Form Data
|
|
599
|
-
const [formData, setFormData] = useState({
|
|
600
|
-
email: '',
|
|
601
|
-
firstName: '',
|
|
602
|
-
lastName: '',
|
|
603
|
-
address: '',
|
|
604
|
-
city: '',
|
|
605
|
-
country: 'Nigeria',
|
|
606
|
-
postalCode: '',
|
|
607
|
-
phone: ''
|
|
608
|
-
});
|
|
609
|
-
const [errors, setErrors] = useState({});
|
|
610
|
-
const [isCountryDropdownOpen, setIsCountryDropdownOpen] = useState(false);
|
|
611
|
-
const [isCityDropdownOpen, setIsCityDropdownOpen] = useState(false);
|
|
612
668
|
// Debounced shipping calculation when address changes
|
|
613
669
|
useEffect(() => {
|
|
614
670
|
if (!formData.city || !formData.country || checkoutItems.length === 0) {
|
|
@@ -622,20 +678,22 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
622
678
|
}, [formData.city, formData.country, formData.postalCode, checkoutItems.length, subtotal, currency]);
|
|
623
679
|
const validateForm = () => {
|
|
624
680
|
const newErrors = {};
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if (
|
|
630
|
-
newErrors.firstName =
|
|
631
|
-
|
|
632
|
-
|
|
681
|
+
const emailError = getCheckoutEmailValidationError(formData.email);
|
|
682
|
+
if (emailError)
|
|
683
|
+
newErrors.email = emailError;
|
|
684
|
+
const firstNameError = getCheckoutPersonNameValidationError(formData.firstName, 'first');
|
|
685
|
+
if (firstNameError)
|
|
686
|
+
newErrors.firstName = firstNameError;
|
|
687
|
+
const lastNameError = getCheckoutPersonNameValidationError(formData.lastName, 'last');
|
|
688
|
+
if (lastNameError)
|
|
689
|
+
newErrors.lastName = lastNameError;
|
|
633
690
|
if (!formData.address)
|
|
634
691
|
newErrors.address = 'Address is required';
|
|
635
692
|
if (!formData.city)
|
|
636
693
|
newErrors.city = 'City is required';
|
|
637
|
-
|
|
638
|
-
|
|
694
|
+
const phoneError = getCheckoutPhoneValidationError(formData.phone, getCountryByName(formData.country)?.code);
|
|
695
|
+
if (phoneError)
|
|
696
|
+
newErrors.phone = phoneError;
|
|
639
697
|
setErrors(newErrors);
|
|
640
698
|
return Object.keys(newErrors).length === 0;
|
|
641
699
|
};
|
|
@@ -680,27 +738,17 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
680
738
|
if (checkoutItems.length === 0) {
|
|
681
739
|
return { isValid: false, error: 'No items to checkout' };
|
|
682
740
|
}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
741
|
+
if (hasMixedCartCurrencies) {
|
|
742
|
+
return {
|
|
743
|
+
isValid: false,
|
|
744
|
+
error: 'Your cart contains items in different currencies. Remove items until only one currency remains.',
|
|
745
|
+
};
|
|
686
746
|
}
|
|
687
|
-
if (
|
|
688
|
-
return {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
if (!formData.lastName) {
|
|
694
|
-
return { isValid: false, error: 'Last name is required' };
|
|
695
|
-
}
|
|
696
|
-
if (!formData.address) {
|
|
697
|
-
return { isValid: false, error: 'Address is required' };
|
|
698
|
-
}
|
|
699
|
-
if (!formData.city) {
|
|
700
|
-
return { isValid: false, error: 'City is required' };
|
|
701
|
-
}
|
|
702
|
-
if (!formData.phone) {
|
|
703
|
-
return { isValid: false, error: 'Phone is required' };
|
|
747
|
+
if (hasMixedCartCurrencies) {
|
|
748
|
+
return {
|
|
749
|
+
isValid: false,
|
|
750
|
+
error: 'Your cart contains items in different currencies. Remove items until only one currency remains.',
|
|
751
|
+
};
|
|
704
752
|
}
|
|
705
753
|
// Validate currency availability
|
|
706
754
|
const hasCurrency = storeConfig.settings?.currency || checkoutItems.some(item => item.product.currency);
|
|
@@ -710,10 +758,13 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
710
758
|
return { isValid: true };
|
|
711
759
|
};
|
|
712
760
|
const handlePayazaCheckout = async () => {
|
|
713
|
-
|
|
761
|
+
if (!validateForm()) {
|
|
762
|
+
scrollToFirstCheckoutFieldError();
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
714
765
|
const validation = validateCheckoutPreFlight();
|
|
715
766
|
if (!validation.isValid) {
|
|
716
|
-
addToast(validation.error || 'Please complete
|
|
767
|
+
addToast(validation.error || 'Please complete checkout requirements', 'error');
|
|
717
768
|
return;
|
|
718
769
|
}
|
|
719
770
|
setLoading(true);
|
|
@@ -962,14 +1013,7 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
962
1013
|
const handleDetailsSubmit = async (e) => {
|
|
963
1014
|
e.preventDefault();
|
|
964
1015
|
if (!validateForm()) {
|
|
965
|
-
|
|
966
|
-
const firstErrorField = document.querySelector('.border-red-400, .border-red-500');
|
|
967
|
-
if (firstErrorField) {
|
|
968
|
-
firstErrorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
969
|
-
}
|
|
970
|
-
else {
|
|
971
|
-
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
972
|
-
}
|
|
1016
|
+
scrollToFirstCheckoutFieldError();
|
|
973
1017
|
return;
|
|
974
1018
|
}
|
|
975
1019
|
await handlePayazaCheckout();
|
|
@@ -986,24 +1030,102 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
986
1030
|
if (!checkoutItems || checkoutItems.length === 0) {
|
|
987
1031
|
return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-10 px-4 sm:px-6 lg:px-8 font-sans", "data-checkout-ready": true, children: _jsx("div", { className: "container mx-auto max-w-6xl", children: _jsx("div", { className: "bg-white rounded-sm shadow-sm border border-gray-200/90 p-12 text-center", children: _jsxs("div", { className: "max-w-md mx-auto space-y-6", children: [_jsx("div", { className: "w-20 h-20 mx-auto rounded-sm bg-gray-100 flex items-center justify-center", children: _jsx("svg", { className: "w-10 h-10 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" }) }) }), _jsx("h2", { className: "text-2xl font-bold text-gray-900", children: "Your cart is empty" }), _jsx("p", { className: "text-gray-600", children: "Add some items to your cart before proceeding to checkout." }), _jsx("div", { className: "pt-4", children: _jsx(Link, { href: `/${storeConfig.slug}`, children: _jsxs(Button, { size: "lg", className: "bg-black hover:bg-gray-800 text-white rounded-sm px-8 py-6 h-auto font-semibold flex items-center gap-2 mx-auto", children: [_jsx(ArrowLeft, { className: "w-5 h-5" }), "Continue Shopping"] }) }) })] }) }) }) }));
|
|
988
1032
|
}
|
|
989
|
-
return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-8 sm:py-10 px-4 sm:px-6 lg:px-8 font-sans", "data-checkout-ready": true, children: _jsxs("div", { className: "container mx-auto max-w-6xl", children: [_jsx("div", { className: "mb-8", children: _jsxs(Link, { href: `/${storeConfig.slug}/cart`, className: "group inline-flex items-center gap-2 text-sm text-gray-500 hover:text-gray-900 transition-colors font-medium", children: [_jsx(ArrowLeft, { className: "w-4 h-4 transition-transform group-hover:-translate-x-0.5" }), "Back to Cart"] }) }), _jsxs("div", { className: "flex flex-col lg:flex-row gap-6 lg:gap-8 lg:items-start", children: [_jsx("div", { className: "order-1 flex-1 min-w-0 lg:order-1", children: _jsxs("form", { id: "checkout-details-form", onSubmit: handleDetailsSubmit, className: "space-y-5 sm:space-y-6 animate-fade-in", children: [_jsxs("section", { className: sectionClass, children: [_jsxs("div", { className: sectionHeaderClass, children: [_jsx(ContactInformationIcon, { className: "h-6 w-6 shrink-0" }), _jsx("h2", { className: "text-lg font-semibold text-gray-900 tracking-tight", children: "Contact Information" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "Email Address" }), _jsx("input", { type: "email",
|
|
1033
|
+
return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-8 sm:py-10 px-4 sm:px-6 lg:px-8 font-sans", "data-checkout-ready": true, children: _jsxs("div", { className: "container mx-auto max-w-6xl", children: [_jsx("div", { className: "mb-8", children: _jsxs(Link, { href: `/${storeConfig.slug}/cart`, className: "group inline-flex items-center gap-2 text-sm text-gray-500 hover:text-gray-900 transition-colors font-medium", children: [_jsx(ArrowLeft, { className: "w-4 h-4 transition-transform group-hover:-translate-x-0.5" }), "Back to Cart"] }) }), _jsxs("div", { className: "flex flex-col lg:flex-row gap-6 lg:gap-8 lg:items-start", children: [_jsx("div", { className: "order-1 flex-1 min-w-0 lg:order-1", children: _jsxs("form", { id: "checkout-details-form", onSubmit: handleDetailsSubmit, className: "space-y-5 sm:space-y-6 animate-fade-in", children: [_jsxs("section", { className: sectionClass, children: [_jsxs("div", { className: sectionHeaderClass, children: [_jsx(ContactInformationIcon, { className: "h-6 w-6 shrink-0" }), _jsx("h2", { className: "text-lg font-semibold text-gray-900 tracking-tight", children: "Contact Information" })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "Email Address" }), _jsx("input", { type: "email", autoComplete: "email", inputMode: "email", className: fieldClass(!!errors.email), placeholder: "john@example.com", value: formData.email, onChange: (e) => {
|
|
1034
|
+
const email = e.target.value;
|
|
1035
|
+
setFormData({ ...formData, email });
|
|
1036
|
+
if (errors.email) {
|
|
1037
|
+
setErrors((prev) => {
|
|
1038
|
+
const next = { ...prev };
|
|
1039
|
+
delete next.email;
|
|
1040
|
+
return next;
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
}, onBlur: () => {
|
|
1044
|
+
const emailError = getCheckoutEmailValidationError(formData.email);
|
|
1045
|
+
if (emailError) {
|
|
1046
|
+
setErrors((prev) => ({ ...prev, email: emailError }));
|
|
1047
|
+
}
|
|
1048
|
+
}, "aria-invalid": !!errors.email, "aria-describedby": errors.email ? 'checkout-email-error' : undefined }), errors.email && (_jsx("p", { id: "checkout-email-error", className: "text-red-500 text-xs", role: "alert", children: errors.email }))] })] }), _jsxs("section", { className: sectionClass, children: [_jsxs("div", { className: sectionHeaderClass, children: [_jsx(ShippingAddressIcon, { className: "h-6 w-6 shrink-0" }), _jsx("h2", { className: "text-lg font-semibold text-gray-900 tracking-tight", children: storeConfig.settings?.shippingEnabled === false ? 'Billing Address' : 'Shipping Address' })] }), isAuthenticated && (_jsx(AddressSelector, { addresses: savedAddresses, selectedAddressId: selectedAddressId, onSelect: handleAddressSelect, isLoading: loadingAddresses, isAuthenticated: isAuthenticated })), _jsxs("div", { className: cn('grid grid-cols-1 md:grid-cols-2 gap-4', isAuthenticated && 'pt-1'), children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "First Name" }), _jsx("input", { type: "text", autoComplete: "given-name", minLength: CHECKOUT_PERSON_NAME_MIN_LENGTH, maxLength: CHECKOUT_PERSON_NAME_MAX_LENGTH, className: fieldClass(!!errors.firstName), value: formData.firstName, onChange: (e) => {
|
|
1049
|
+
setFormData({ ...formData, firstName: e.target.value });
|
|
1050
|
+
if (errors.firstName) {
|
|
1051
|
+
setErrors((prev) => {
|
|
1052
|
+
const next = { ...prev };
|
|
1053
|
+
delete next.firstName;
|
|
1054
|
+
return next;
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}, onBlur: () => {
|
|
1058
|
+
const err = getCheckoutPersonNameValidationError(formData.firstName, 'first');
|
|
1059
|
+
if (err)
|
|
1060
|
+
setErrors((prev) => ({ ...prev, firstName: err }));
|
|
1061
|
+
}, "aria-invalid": !!errors.firstName, "aria-describedby": errors.firstName ? 'checkout-first-name-error' : undefined }), errors.firstName && (_jsx("p", { id: "checkout-first-name-error", className: "text-red-500 text-xs", role: "alert", children: errors.firstName }))] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "Last Name" }), _jsx("input", { type: "text", autoComplete: "family-name", minLength: CHECKOUT_PERSON_NAME_MIN_LENGTH, maxLength: CHECKOUT_PERSON_NAME_MAX_LENGTH, className: fieldClass(!!errors.lastName), value: formData.lastName, onChange: (e) => {
|
|
1062
|
+
setFormData({ ...formData, lastName: e.target.value });
|
|
1063
|
+
if (errors.lastName) {
|
|
1064
|
+
setErrors((prev) => {
|
|
1065
|
+
const next = { ...prev };
|
|
1066
|
+
delete next.lastName;
|
|
1067
|
+
return next;
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
}, onBlur: () => {
|
|
1071
|
+
const err = getCheckoutPersonNameValidationError(formData.lastName, 'last');
|
|
1072
|
+
if (err)
|
|
1073
|
+
setErrors((prev) => ({ ...prev, lastName: err }));
|
|
1074
|
+
}, "aria-invalid": !!errors.lastName, "aria-describedby": errors.lastName ? 'checkout-last-name-error' : undefined }), errors.lastName && (_jsx("p", { id: "checkout-last-name-error", className: "text-red-500 text-xs", role: "alert", children: errors.lastName }))] }), _jsxs("div", { className: "space-y-1.5 md:col-span-2", children: [_jsx("label", { className: labelClass, children: "Address" }), _jsx("input", { type: "text", className: fieldClass(!!errors.address), placeholder: "123 Main St", value: formData.address, onChange: e => setFormData({ ...formData, address: e.target.value }) }), errors.address && _jsx("p", { className: "text-red-500 text-xs", children: errors.address })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "Country" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsCountryDropdownOpen(!isCountryDropdownOpen), className: cn(fieldClass(!!errors.country), 'text-left flex items-center justify-between'), children: [_jsx("span", { className: "flex items-center gap-2", children: (() => {
|
|
990
1075
|
const selectedCountry = getCountryByName(formData.country);
|
|
991
1076
|
return selectedCountry ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-lg", children: selectedCountry.flag }), _jsx("span", { children: selectedCountry.name })] })) : (_jsx("span", { className: "text-gray-400", children: "Select country" }));
|
|
992
1077
|
})() }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isCountryDropdownOpen && "transform rotate-180") })] }), isCountryDropdownOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsCountryDropdownOpen(false) }), _jsx("div", { className: "absolute z-20 w-full mt-1.5 bg-white border border-gray-200 rounded-sm shadow-lg max-h-64 overflow-y-auto", children: countries.map((country) => (_jsxs("button", { type: "button", onClick: () => {
|
|
993
1078
|
setFormData({ ...formData, country: country.name, city: '' }); // Clear city when country changes
|
|
994
1079
|
setIsCountryDropdownOpen(false);
|
|
995
1080
|
setIsCityDropdownOpen(false);
|
|
1081
|
+
setCitySearchQuery('');
|
|
1082
|
+
if (formData.phone.trim()) {
|
|
1083
|
+
const phoneErr = getCheckoutPhoneValidationError(formData.phone, country.code);
|
|
1084
|
+
setErrors((prev) => {
|
|
1085
|
+
const next = { ...prev };
|
|
1086
|
+
if (phoneErr)
|
|
1087
|
+
next.phone = phoneErr;
|
|
1088
|
+
else
|
|
1089
|
+
delete next.phone;
|
|
1090
|
+
return next;
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
996
1093
|
}, className: cn("w-full px-4 py-3 text-left flex items-center gap-3 hover:bg-gray-50 transition-colors", formData.country === country.name && "bg-gray-50 font-medium"), children: [_jsx("span", { className: "text-lg", children: country.flag }), _jsx("span", { children: country.name })] }, country.code))) })] }))] }), errors.country && _jsx("p", { className: "text-red-500 text-xs", children: errors.country })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "City" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => {
|
|
997
1094
|
if (formData.country) {
|
|
1095
|
+
if (!isCityDropdownOpen)
|
|
1096
|
+
setCitySearchQuery('');
|
|
998
1097
|
setIsCityDropdownOpen(!isCityDropdownOpen);
|
|
999
1098
|
}
|
|
1000
1099
|
else {
|
|
1001
1100
|
addToast('Please select a country first', 'error');
|
|
1002
1101
|
}
|
|
1003
|
-
}, disabled: !formData.country, className: cn(fieldClass(!!errors.city), 'text-left flex items-center justify-between', !formData.country && 'bg-gray-50 text-gray-400 cursor-not-allowed'), children: [_jsx("span", { className: formData.city ? "text-gray-900" : "text-gray-400", children: formData.city || 'Select city' }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isCityDropdownOpen && "transform rotate-180") })] }), isCityDropdownOpen && formData.country && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () =>
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1102
|
+
}, disabled: !formData.country, className: cn(fieldClass(!!errors.city), 'text-left flex items-center justify-between', !formData.country && 'bg-gray-50 text-gray-400 cursor-not-allowed'), children: [_jsx("span", { className: formData.city ? "text-gray-900" : "text-gray-400", children: formData.city || 'Select city' }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isCityDropdownOpen && "transform rotate-180") })] }), isCityDropdownOpen && formData.country && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => {
|
|
1103
|
+
setIsCityDropdownOpen(false);
|
|
1104
|
+
setCitySearchQuery('');
|
|
1105
|
+
} }), _jsxs("div", { className: "absolute z-20 w-full mt-1.5 bg-white border border-gray-200 rounded-sm shadow-lg overflow-hidden", children: [_jsx("div", { className: "p-2 border-b border-gray-100 bg-white", children: _jsx("input", { ref: citySearchInputRef, type: "search", value: citySearchQuery, onChange: (e) => setCitySearchQuery(e.target.value), placeholder: "Search city...", className: cn(fieldClass(false), 'h-10 text-sm'), onClick: (e) => e.stopPropagation(), onKeyDown: (e) => {
|
|
1106
|
+
if (e.key === 'Escape') {
|
|
1107
|
+
setIsCityDropdownOpen(false);
|
|
1108
|
+
setCitySearchQuery('');
|
|
1109
|
+
}
|
|
1110
|
+
} }) }), _jsx("div", { className: "max-h-56 overflow-y-auto", children: cityOptions.length > 0 ? (filteredCities.length > 0 ? (filteredCities.map((city) => (_jsx("button", { type: "button", onClick: () => {
|
|
1111
|
+
setFormData({ ...formData, city });
|
|
1112
|
+
setIsCityDropdownOpen(false);
|
|
1113
|
+
setCitySearchQuery('');
|
|
1114
|
+
}, className: cn("w-full px-4 py-3 text-left hover:bg-gray-50 transition-colors", formData.city === city && "bg-gray-50 font-medium"), children: city }, city)))) : (_jsx("div", { className: "px-4 py-3 text-gray-500 text-sm", children: "No cities match your search" }))) : (_jsx("div", { className: "px-4 py-3 text-gray-500 text-sm", children: "No cities available for this country" })) })] })] }))] }), errors.city && _jsx("p", { className: "text-red-500 text-xs", children: errors.city })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "Postal/Zip Code" }), _jsx("input", { type: "text", className: fieldClass(!!errors.postalCode), placeholder: "Optional", value: formData.postalCode, onChange: e => setFormData({ ...formData, postalCode: e.target.value }) }), errors.postalCode && _jsx("p", { className: "text-red-500 text-xs", children: errors.postalCode })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "Phone" }), _jsx("input", { type: "tel", autoComplete: "tel", inputMode: "tel", className: fieldClass(!!errors.phone), value: formData.phone, onChange: (e) => {
|
|
1115
|
+
const phone = sanitizeCheckoutPhoneInput(e.target.value);
|
|
1116
|
+
setFormData({ ...formData, phone });
|
|
1117
|
+
if (errors.phone) {
|
|
1118
|
+
setErrors((prev) => {
|
|
1119
|
+
const next = { ...prev };
|
|
1120
|
+
delete next.phone;
|
|
1121
|
+
return next;
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
}, onBlur: () => {
|
|
1125
|
+
const err = getCheckoutPhoneValidationError(formData.phone, getCountryByName(formData.country)?.code);
|
|
1126
|
+
if (err)
|
|
1127
|
+
setErrors((prev) => ({ ...prev, phone: err }));
|
|
1128
|
+
}, "aria-invalid": !!errors.phone, "aria-describedby": errors.phone ? 'checkout-phone-error' : undefined }), errors.phone && (_jsx("p", { id: "checkout-phone-error", className: "text-red-500 text-xs", role: "alert", children: errors.phone }))] })] }), isAuthenticated && !selectedAddressId && (_jsxs("div", { className: "mt-5 pt-5 border-t border-gray-100 space-y-3", children: [_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group", children: [_jsx("input", { type: "checkbox", checked: saveNewAddress, onChange: (e) => setSaveNewAddress(e.target.checked), className: "w-4 h-4 rounded border-gray-300 text-black focus:ring-gray-900 cursor-pointer" }), _jsx("span", { className: "text-sm font-medium text-gray-700 group-hover:text-gray-900 transition-colors", children: "Save this address for future use" })] }), saveNewAddress && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "Address Label (optional)" }), _jsx("input", { type: "text", value: addressLabel, onChange: (e) => setAddressLabel(e.target.value), placeholder: "e.g., Home, Work, Office", className: fieldClass() })] }))] }))] }), showShippingMethodSection && (_jsxs("section", { className: sectionClass, children: [_jsxs("div", { className: sectionHeaderClass, children: [_jsx(ShippingMethodIcon, { className: "h-6 w-6 shrink-0" }), _jsx("h2", { className: "text-lg font-semibold text-gray-900 tracking-tight", children: "Shipping Method" })] }), (() => {
|
|
1007
1129
|
const remainingForFreeShipping = freeShippingEnabled &&
|
|
1008
1130
|
freeShippingThreshold !== undefined &&
|
|
1009
1131
|
!freeShippingEligible
|
|
@@ -1024,10 +1146,10 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1024
1146
|
? 'FREE'
|
|
1025
1147
|
: Number.isFinite(method.cost) && method.cost >= 0
|
|
1026
1148
|
? formatCurrency(method.cost, currency)
|
|
1027
|
-
:
|
|
1149
|
+
: null }) })] }, method.code));
|
|
1028
1150
|
}) })) : (_jsx("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-sm text-center", children: _jsx("p", { className: "text-sm text-gray-600", children: "No shipping methods available. Please check your address." }) }))] }))] }) }), _jsx("div", { className: "order-2 w-full flex-shrink-0 lg:order-2 lg:w-[380px] lg:sticky lg:top-6 lg:self-start", children: _jsxs("div", { className: "bg-white p-6 sm:p-8 rounded-sm border border-gray-200/90 shadow-sm", children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900 tracking-tight mb-5 pb-4 border-b border-gray-100", children: "Order Summary" }), _jsx("div", { className: "space-y-4 mb-6 max-h-[360px] overflow-y-auto scrollbar-hide", children: checkoutItems.map((item) => (_jsxs("div", { className: "flex gap-3.5 group", children: [_jsxs("div", { className: "w-16 h-16 sm:w-[72px] sm:h-[72px] bg-gray-100 rounded-sm overflow-hidden flex-shrink-0 relative border border-gray-200", children: [_jsx(Image, { src: getCartLineImage(item) || item.product.images[0], alt: item.product.name, fill: true, className: "object-cover group-hover:scale-105 transition-transform duration-500", unoptimized: true }), _jsxs("span", { className: "absolute top-0 right-0 bg-gray-900 text-white text-[10px] font-semibold px-1.5 py-0.5 rounded-sm z-10", children: ["x", item.quantity] })] }), _jsxs("div", { className: "flex-1 min-w-0 py-0.5", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsx("p", { className: "font-medium text-gray-900 text-sm line-clamp-2 leading-snug", children: item.product.name }), _jsx("button", { type: "button", onClick: () => void handleRemoveCheckoutItem(item.id), className: "shrink-0 rounded-sm p-1.5 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-900", "aria-label": `Remove ${item.product.name} from cart`, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), item.variantName && _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: item.variantName }), _jsx("p", { className: "text-gray-600 text-sm font-medium mt-1", children: normalizePrice(item.price) <= 0
|
|
1029
1151
|
? 'Free'
|
|
1030
|
-
: formatCurrency(normalizePrice(item.price),
|
|
1152
|
+
: formatCurrency(normalizePrice(item.price), currency) })] })] }, item.id))) }), _jsxs("div", { className: "space-y-3 pt-5 border-t border-gray-100", children: [_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: "Subtotal" }), _jsx("span", { className: "text-gray-900 font-semibold", children: isFreeOrder ? 'Free' : formatCurrency(subtotal, currency) })] }), showOrderSummaryShippingLine ? (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsxs("span", { children: ["Shipping", selectedShippingMethod
|
|
1031
1153
|
? ` (${shippingMethodSummaryLabel(selectedShippingMethod.name)})`
|
|
1032
|
-
: ''] }), _jsx("span", { className: "text-gray-900 font-semibold", children: isCalculatingShipping ? (_jsx("span", { className: "text-gray-400", children: "Calculating..." })) :
|
|
1154
|
+
: ''] }), _jsx("span", { className: "text-gray-900 font-semibold", children: isCalculatingShipping ? (_jsx("span", { className: "text-gray-400", children: "Calculating..." })) : shipping === 0 && freeShippingEligible ? (_jsx("span", { className: "text-emerald-600", children: "Free" })) : shipping === 0 ? ('Free') : (formatCurrency(shipping, currency)) })] })) : null, customerFeeLines.map((line) => (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: line.label }), _jsx("span", { className: "text-gray-900 font-semibold", children: formatCurrency(line.amount, currency) })] }, line.key))), !feePreview && vatAmount > 0 && (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: vatLabel }), _jsx("span", { className: "text-gray-900 font-semibold", children: formatCurrency(vatAmount, currency) })] })), _jsxs("div", { className: "flex justify-between items-end pt-4 mt-1 border-t border-gray-100", children: [_jsx("span", { className: "text-sm font-semibold text-gray-900", children: "Total to pay" }), _jsx("span", { className: "text-2xl font-bold text-gray-900 tracking-tight", children: isCalculatingFees ? 'Calculating…' : isFreeOrder ? 'Free' : formatCurrency(total, currency) })] })] }), hasMixedCartCurrencies ? (_jsx("p", { className: "mt-4 text-sm text-red-600 font-medium", role: "alert", children: "Your cart mixes currencies. Remove items until everything uses the same currency to checkout." })) : null, _jsx("div", { className: "mt-6 pt-5 border-t border-gray-100", children: _jsx(Button, { type: "submit", form: "checkout-details-form", size: "lg", className: "w-full h-12 text-base font-semibold rounded-sm shadow-none transition-all hover:opacity-95 active:scale-[0.99]", style: { backgroundColor: isFreeOrder ? '#059669' : primaryColor }, disabled: loading || isPayazaLoading || hasMixedCartCurrencies, children: loading || isPayazaLoading ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" }), _jsx("span", { children: isFreeOrder ? 'Completing order...' : 'Processing Securely...' })] })) : (_jsxs("div", { className: "flex items-center gap-2", children: [isFreeOrder ? _jsx(CheckCircle, { className: "w-5 h-5" }) : _jsx(Lock, { className: "w-5 h-5" }), _jsx("span", { children: isFreeOrder ? 'Complete Free Order' : 'Pay Now' })] })) }) })] }) })] })] }) }));
|
|
1033
1155
|
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { formatStoreDisplayName } from '../../../lib/utils/index.js';
|
|
4
4
|
import { RefreshCw, Store } from 'lucide-react';
|
|
5
5
|
export function MaintenancePage({ storeConfig }) {
|
|
6
|
-
const primaryColor = storeConfig.branding
|
|
6
|
+
const primaryColor = storeConfig.branding.primaryColor || '#1b0029';
|
|
7
7
|
const storeDisplayName = formatStoreDisplayName(storeConfig.name);
|
|
8
8
|
return (_jsxs("div", { className: "relative flex min-h-[100dvh] min-h-screen items-center justify-center overflow-hidden bg-[#f7f5f0] px-4 py-6 pb-[max(1.5rem,env(safe-area-inset-bottom))] pt-[max(1.5rem,env(safe-area-inset-top))] selection:bg-[#f7f5f0] selection:text-[#1b0029] sm:px-6 sm:py-10", style: { backgroundColor: '#f7f5f0' }, children: [_jsxs("div", { className: "pointer-events-none absolute inset-0 overflow-hidden", children: [_jsx("div", { className: "absolute right-[-20%] top-[-15%] h-[280px] w-[280px] rounded-full bg-[#f7f5f0] opacity-90 blur-[80px] sm:h-[480px] sm:w-[480px] sm:blur-[100px]" }), _jsx("div", { className: "absolute bottom-0 left-[-10%] h-[240px] w-[320px] bg-gradient-to-t from-[#d16253]/12 to-transparent sm:h-[480px] sm:w-[640px]" }), _jsx("div", { className: "absolute inset-0 bg-[radial-gradient(circle_at_20%_20%,rgba(68,0,102,0.05),transparent_40%),radial-gradient(circle_at_80%_70%,rgba(209,98,83,0.08),transparent_45%)]" })] }), _jsxs("div", { className: "relative z-10 w-full max-w-md text-center sm:max-w-2xl lg:max-w-3xl", children: [_jsxs("div", { className: "mb-5 flex flex-col items-center gap-3 sm:mb-7 sm:gap-4", children: [_jsx("span", { className: "inline-flex items-center rounded-full border border-[#1b0029]/10 bg-white px-3 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-[#1b0029] shadow-sm sm:text-xs sm:tracking-wide", children: "Coming Soon" }), _jsxs("div", { className: "relative flex h-20 w-20 items-center justify-center sm:h-24 sm:w-24 md:h-28 md:w-28", children: [_jsx("div", { className: "absolute inset-0 rounded-full opacity-20 blur-lg animate-pulse-slow sm:blur-xl", style: { backgroundColor: primaryColor } }), _jsx("div", { className: "relative flex h-full w-full rotate-2 transform items-center justify-center rounded-2xl border border-[#1b0029]/10 bg-white p-3 shadow-xl transition-transform duration-500 hover:rotate-0 sm:rounded-[28px] sm:p-3.5 md:rounded-[32px]", children: storeConfig.branding?.logo ? (_jsx("img", { src: storeConfig.branding.logo, alt: storeDisplayName, className: "max-h-full max-w-full object-contain" })) : (_jsx(Store, { className: "h-9 w-9 text-[#1b0029] sm:h-11 sm:w-11 md:h-12 md:w-12" })) })] })] }), _jsxs("div", { className: "animate-fade-in-up space-y-3 sm:space-y-4", children: [_jsxs("h1", { className: "text-[1.65rem] font-semibold leading-tight tracking-tight text-[#1b0029] sm:text-3xl md:text-4xl lg:text-5xl", children: ["Something good is", ' ', _jsx("span", { className: "font-normal italic text-[#d16253]", children: "on the way" }), "."] }), _jsxs("p", { className: "mx-auto max-w-lg text-sm font-medium leading-snug text-[#1b0029] sm:text-base md:text-lg", children: [storeDisplayName, " isn't open for shopping right now."] })] }), _jsx("div", { className: "mt-5 flex animate-fade-in-up justify-center sm:mt-7", style: { animationDelay: '0.1s' }, children: _jsxs("button", { type: "button", onClick: () => window.location.reload(), className: "group inline-flex h-9 items-center justify-center rounded-full bg-[#1b0029] px-4 text-xs font-semibold text-white shadow-md transition-all hover:bg-[#220033] hover:shadow-lg sm:h-10 sm:px-5 sm:text-sm", children: [_jsx(RefreshCw, { className: "mr-1.5 h-3.5 w-3.5 transition-transform group-hover:rotate-180 sm:h-4 sm:w-4" }), "Try Again"] }) })] })] }));
|
|
9
9
|
}
|
|
@@ -22,13 +22,13 @@ export function ClothingLayout({ children, storeConfig }) {
|
|
|
22
22
|
const textInverse = getThemeColor(storeConfig, 'text', 'inverse', '#FFFFFF');
|
|
23
23
|
const borderPrimary = getThemeColor(storeConfig, 'border', 'primary', '#E5E7EB');
|
|
24
24
|
const borderSecondary = getThemeColor(storeConfig, 'border', 'secondary', '#F3F4F6');
|
|
25
|
-
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding
|
|
25
|
+
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding.primaryColor);
|
|
26
26
|
const accentFocus = getThemeColor(storeConfig, 'accent', 'focus', '');
|
|
27
27
|
const cssVariables = {
|
|
28
|
-
'--store-primary': storeConfig.branding
|
|
29
|
-
'--store-secondary': storeConfig.branding
|
|
30
|
-
'--store-accent': storeConfig.branding
|
|
31
|
-
'--layout-clothing-primary': storeConfig.branding
|
|
28
|
+
'--store-primary': storeConfig.branding.primaryColor,
|
|
29
|
+
'--store-secondary': storeConfig.branding.secondaryColor || storeConfig.branding.primaryColor,
|
|
30
|
+
'--store-accent': storeConfig.branding.accentColor || storeConfig.branding.primaryColor,
|
|
31
|
+
'--layout-clothing-primary': storeConfig.branding.primaryColor,
|
|
32
32
|
'--theme-bg-primary': bgPrimary,
|
|
33
33
|
'--theme-bg-secondary': bgSecondary,
|
|
34
34
|
'--theme-bg-dark': bgDark,
|
|
@@ -24,14 +24,14 @@ export function ClothingMinimalLayout({ children, storeConfig }) {
|
|
|
24
24
|
const textInverse = getThemeColor(storeConfig, 'text', 'inverse', '#FFFFFF');
|
|
25
25
|
const borderPrimary = getThemeColor(storeConfig, 'border', 'primary', '#E5E7EB');
|
|
26
26
|
const borderSecondary = getThemeColor(storeConfig, 'border', 'secondary', '#F3F4F6');
|
|
27
|
-
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding
|
|
27
|
+
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding.primaryColor);
|
|
28
28
|
const accentFocus = getThemeColor(storeConfig, 'accent', 'focus', '');
|
|
29
29
|
// Build CSS variables for theme colors
|
|
30
30
|
const cssVariables = {
|
|
31
31
|
// Branding colors (existing)
|
|
32
|
-
'--store-primary': storeConfig.branding
|
|
33
|
-
'--store-secondary': storeConfig.branding
|
|
34
|
-
'--store-accent': storeConfig.branding
|
|
32
|
+
'--store-primary': storeConfig.branding.primaryColor,
|
|
33
|
+
'--store-secondary': storeConfig.branding.secondaryColor || storeConfig.branding.primaryColor,
|
|
34
|
+
'--store-accent': storeConfig.branding.accentColor || storeConfig.branding.primaryColor,
|
|
35
35
|
// Theme colors
|
|
36
36
|
'--theme-bg-primary': bgPrimary,
|
|
37
37
|
'--theme-bg-secondary': bgSecondary,
|
|
@@ -16,14 +16,14 @@ export function ElectronicsLayout({ children, storeConfig }) {
|
|
|
16
16
|
const textInverse = getThemeColor(storeConfig, 'text', 'inverse', '#FFFFFF');
|
|
17
17
|
const borderPrimary = getThemeColor(storeConfig, 'border', 'primary', '#E5E7EB');
|
|
18
18
|
const borderSecondary = getThemeColor(storeConfig, 'border', 'secondary', '#F3F4F6');
|
|
19
|
-
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding
|
|
19
|
+
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding.primaryColor);
|
|
20
20
|
const accentFocus = getThemeColor(storeConfig, 'accent', 'focus', '');
|
|
21
21
|
// Build CSS variables for theme colors
|
|
22
22
|
const cssVariables = {
|
|
23
23
|
// Branding colors (existing)
|
|
24
|
-
'--store-primary': storeConfig.branding
|
|
25
|
-
'--store-secondary': storeConfig.branding
|
|
26
|
-
'--store-accent': storeConfig.branding
|
|
24
|
+
'--store-primary': storeConfig.branding.primaryColor,
|
|
25
|
+
'--store-secondary': storeConfig.branding.secondaryColor || storeConfig.branding.primaryColor,
|
|
26
|
+
'--store-accent': storeConfig.branding.accentColor || storeConfig.branding.primaryColor,
|
|
27
27
|
// Theme colors
|
|
28
28
|
'--theme-bg-primary': bgPrimary,
|
|
29
29
|
'--theme-bg-secondary': bgSecondary,
|
|
@@ -24,14 +24,14 @@ export function FoodModernLayout({ children, storeConfig }) {
|
|
|
24
24
|
const textInverse = '#000000';
|
|
25
25
|
const borderPrimary = 'rgba(255, 255, 255, 0.1)';
|
|
26
26
|
const borderSecondary = 'rgba(255, 255, 255, 0.05)';
|
|
27
|
-
const accentHover = storeConfig.branding
|
|
27
|
+
const accentHover = storeConfig.branding.primaryColor || '#EA580C'; // orange-600
|
|
28
28
|
const accentFocus = '#C2410C'; // orange-700
|
|
29
29
|
// Build CSS variables for theme colors
|
|
30
30
|
const cssVariables = {
|
|
31
31
|
// Branding colors (existing)
|
|
32
|
-
'--store-primary': storeConfig.branding
|
|
33
|
-
'--store-secondary': storeConfig.branding
|
|
34
|
-
'--store-accent': storeConfig.branding
|
|
32
|
+
'--store-primary': storeConfig.branding.primaryColor,
|
|
33
|
+
'--store-secondary': storeConfig.branding.secondaryColor || storeConfig.branding.primaryColor,
|
|
34
|
+
'--store-accent': storeConfig.branding.accentColor || storeConfig.branding.primaryColor,
|
|
35
35
|
// Theme colors
|
|
36
36
|
'--theme-bg-primary': bgPrimary,
|
|
37
37
|
'--theme-bg-secondary': bgSecondary,
|
|
@@ -27,14 +27,14 @@ export function MotivationalLayout({ children, storeConfig }) {
|
|
|
27
27
|
const textInverse = getThemeColor(storeConfig, 'text', 'inverse', '#FFFFFF');
|
|
28
28
|
const borderPrimary = getThemeColor(storeConfig, 'border', 'primary', '#E5E7EB');
|
|
29
29
|
const borderSecondary = getThemeColor(storeConfig, 'border', 'secondary', '#F3F4F6');
|
|
30
|
-
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding
|
|
30
|
+
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding.primaryColor);
|
|
31
31
|
const accentFocus = getThemeColor(storeConfig, 'accent', 'focus', '');
|
|
32
32
|
// Build CSS variables for theme colors
|
|
33
33
|
const cssVariables = {
|
|
34
34
|
// Branding colors (existing)
|
|
35
|
-
'--store-primary': storeConfig.branding
|
|
36
|
-
'--store-secondary': storeConfig.branding
|
|
37
|
-
'--store-accent': storeConfig.branding
|
|
35
|
+
'--store-primary': storeConfig.branding.primaryColor,
|
|
36
|
+
'--store-secondary': storeConfig.branding.secondaryColor || storeConfig.branding.primaryColor,
|
|
37
|
+
'--store-accent': storeConfig.branding.accentColor || storeConfig.branding.primaryColor,
|
|
38
38
|
// Theme colors
|
|
39
39
|
'--theme-bg-primary': bgPrimary,
|
|
40
40
|
'--theme-bg-secondary': bgSecondary,
|
|
@@ -21,14 +21,14 @@ export function StandardLayout({ children, storeConfig }) {
|
|
|
21
21
|
const textInverse = getThemeColor(storeConfig, 'text', 'inverse', '#FFFFFF');
|
|
22
22
|
const borderPrimary = getThemeColor(storeConfig, 'border', 'primary', '#E5E7EB');
|
|
23
23
|
const borderSecondary = getThemeColor(storeConfig, 'border', 'secondary', '#F3F4F6');
|
|
24
|
-
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding
|
|
24
|
+
const accentHover = getThemeColor(storeConfig, 'accent', 'hover', storeConfig.branding.primaryColor);
|
|
25
25
|
const accentFocus = getThemeColor(storeConfig, 'accent', 'focus', '');
|
|
26
26
|
// Build CSS variables for theme colors
|
|
27
27
|
const cssVariables = {
|
|
28
28
|
// Branding colors (existing)
|
|
29
|
-
'--store-primary': storeConfig.branding
|
|
30
|
-
'--store-secondary': storeConfig.branding
|
|
31
|
-
'--store-accent': storeConfig.branding
|
|
29
|
+
'--store-primary': storeConfig.branding.primaryColor,
|
|
30
|
+
'--store-secondary': storeConfig.branding.secondaryColor || storeConfig.branding.primaryColor,
|
|
31
|
+
'--store-accent': storeConfig.branding.accentColor || storeConfig.branding.primaryColor,
|
|
32
32
|
// Theme colors
|
|
33
33
|
'--theme-bg-primary': bgPrimary,
|
|
34
34
|
'--theme-bg-secondary': bgSecondary,
|