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.
Files changed (88) hide show
  1. package/dist/editor/blocks/defaults/clothing-minimal-home.d.ts.map +1 -1
  2. package/dist/editor/blocks/defaults/clothing-minimal-home.js +0 -17
  3. package/dist/editor/payaza-form/PayazaFormEditor.d.ts.map +1 -1
  4. package/dist/editor/payaza-form/PayazaFormEditor.js +0 -2
  5. package/dist/editor/payaza-form/data-transformer.d.ts.map +1 -1
  6. package/dist/editor/payaza-form/data-transformer.js +1 -8
  7. package/dist/editor/shared/useEditorChromeMetrics.js +1 -2
  8. package/dist/hooks/use-pdp-variation.d.ts +1 -13
  9. package/dist/hooks/use-pdp-variation.d.ts.map +1 -1
  10. package/dist/hooks/use-product-variation-selection.d.ts +1 -13
  11. package/dist/hooks/use-product-variation-selection.d.ts.map +1 -1
  12. package/dist/index.d.ts +8 -4
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +7 -4
  15. package/dist/layouts/booking/pages/ProductDetailPage.d.ts.map +1 -1
  16. package/dist/layouts/booking/pages/ProductDetailPage.js +2 -1
  17. package/dist/layouts/booking-agenda/pages/ProductDetailPage.d.ts.map +1 -1
  18. package/dist/layouts/booking-agenda/pages/ProductDetailPage.js +2 -1
  19. package/dist/layouts/clothing/components/ClothingHomePage.d.ts.map +1 -1
  20. package/dist/layouts/clothing/components/ClothingHomePage.js +7 -11
  21. package/dist/layouts/clothing/pages/ProductDetailPage.d.ts.map +1 -1
  22. package/dist/layouts/clothing/pages/ProductDetailPage.js +1 -1
  23. package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.d.ts.map +1 -1
  24. package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.js +1 -7
  25. package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.d.ts +1 -1
  26. package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.d.ts.map +1 -1
  27. package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.js +5 -21
  28. package/dist/layouts/clothing-minimal/pages/ProductDetailPage.d.ts.map +1 -1
  29. package/dist/layouts/clothing-minimal/pages/ProductDetailPage.js +1 -1
  30. package/dist/layouts/electronics/components/ElectronicsGridProductCard.d.ts.map +1 -1
  31. package/dist/layouts/electronics/components/ElectronicsGridProductCard.js +2 -2
  32. package/dist/layouts/electronics/pages/ProductDetailPage.d.ts.map +1 -1
  33. package/dist/layouts/electronics/pages/ProductDetailPage.js +2 -1
  34. package/dist/layouts/electronics-grid/components/ElectronicsGridProductCard.d.ts.map +1 -1
  35. package/dist/layouts/electronics-grid/components/ElectronicsGridProductCard.js +2 -2
  36. package/dist/layouts/electronics-grid/pages/ProductDetailPage.d.ts.map +1 -1
  37. package/dist/layouts/electronics-grid/pages/ProductDetailPage.js +2 -1
  38. package/dist/layouts/food/pages/MenuPage.d.ts.map +1 -1
  39. package/dist/layouts/food/pages/MenuPage.js +1 -0
  40. package/dist/layouts/food/pages/ProductDetailPage.d.ts.map +1 -1
  41. package/dist/layouts/food/pages/ProductDetailPage.js +2 -1
  42. package/dist/layouts/food-modern/components/FoodHomePageModern.js +1 -1
  43. package/dist/layouts/food-modern/pages/MenuPage.d.ts.map +1 -1
  44. package/dist/layouts/food-modern/pages/MenuPage.js +1 -0
  45. package/dist/layouts/food-modern/pages/ProductDetailPage.d.ts.map +1 -1
  46. package/dist/layouts/food-modern/pages/ProductDetailPage.js +2 -1
  47. package/dist/layouts/shared/components/PromoBanner.d.ts +4 -1
  48. package/dist/layouts/shared/components/PromoBanner.d.ts.map +1 -1
  49. package/dist/layouts/shared/components/PromoBanner.js +2 -2
  50. package/dist/layouts/shared/components/StoreCartSheet.d.ts.map +1 -1
  51. package/dist/layouts/shared/components/StoreCartSheet.js +4 -2
  52. package/dist/layouts/shared/components/StoreFooter.js +2 -2
  53. package/dist/layouts/shared/components/StoreHeader.js +4 -4
  54. package/dist/layouts/shared/pages/CartPage.d.ts.map +1 -1
  55. package/dist/layouts/shared/pages/CartPage.js +4 -28
  56. package/dist/layouts/shared/pages/CheckoutPage.d.ts.map +1 -1
  57. package/dist/layouts/shared/pages/CheckoutPage.js +203 -81
  58. package/dist/layouts/shared/pages/MaintenancePage.js +1 -1
  59. package/dist/layouts/shared/wrappers/ClothingLayout.js +5 -5
  60. package/dist/layouts/shared/wrappers/ClothingMinimalLayout.js +4 -4
  61. package/dist/layouts/shared/wrappers/ElectronicsLayout.js +4 -4
  62. package/dist/layouts/shared/wrappers/FoodModernLayout.js +4 -4
  63. package/dist/layouts/shared/wrappers/MotivationalLayout.js +4 -4
  64. package/dist/layouts/shared/wrappers/StandardLayout.js +4 -4
  65. package/dist/lib/cart-charge-currency.d.ts +16 -0
  66. package/dist/lib/cart-charge-currency.d.ts.map +1 -0
  67. package/dist/lib/cart-charge-currency.js +38 -0
  68. package/dist/lib/checkout-phone-validation.d.ts +3 -0
  69. package/dist/lib/checkout-phone-validation.d.ts.map +1 -0
  70. package/dist/lib/checkout-phone-validation.js +41 -0
  71. package/dist/lib/preview-data.d.ts.map +1 -1
  72. package/dist/lib/preview-data.js +2 -0
  73. package/dist/lib/rich-html.d.ts +1 -2
  74. package/dist/lib/rich-html.d.ts.map +1 -1
  75. package/dist/lib/services/fees.service.d.ts +40 -57
  76. package/dist/lib/services/fees.service.d.ts.map +1 -1
  77. package/dist/lib/services/fees.service.js +24 -120
  78. package/dist/lib/store-types.d.ts +27 -34
  79. package/dist/lib/store-types.d.ts.map +1 -1
  80. package/dist/lib/utils/fee-calculations.d.ts.map +1 -1
  81. package/dist/lib/utils/fee-calculations.js +23 -11
  82. package/dist/preview/LayoutPreview.d.ts.map +1 -1
  83. package/dist/preview/LayoutPreview.js +5 -22
  84. package/dist/styles/index.css +36 -0
  85. package/dist/utils/price-display.d.ts +3 -0
  86. package/dist/utils/price-display.d.ts.map +1 -0
  87. package/dist/utils/price-display.js +8 -0
  88. 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
- function selectedDisplayCurrency() {
28
- if (typeof document === 'undefined')
29
- return undefined;
30
- const value = document.cookie.match(/(?:^|; )shopaza_currency=([^;]+)/)?.[1]?.toUpperCase();
31
- return value && /^[A-Z]{3}$/.test(value) ? value : undefined;
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
- if (!formData.email)
626
- newErrors.email = 'Email is required';
627
- else if (!/\S+@\S+\.\S+/.test(formData.email))
628
- newErrors.email = 'Invalid email address';
629
- if (!formData.firstName)
630
- newErrors.firstName = 'First name is required';
631
- if (!formData.lastName)
632
- newErrors.lastName = 'Last name is required';
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
- if (!formData.phone)
638
- newErrors.phone = 'Phone is required';
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
- // Validate form data
684
- if (!formData.email) {
685
- return { isValid: false, error: 'Email is required' };
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 (!/\S+@\S+\.\S+/.test(formData.email)) {
688
- return { isValid: false, error: 'Invalid email address' };
689
- }
690
- if (!formData.firstName) {
691
- return { isValid: false, error: 'First name is required' };
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
- // Pre-flight validation
761
+ if (!validateForm()) {
762
+ scrollToFirstCheckoutFieldError();
763
+ return;
764
+ }
714
765
  const validation = validateCheckoutPreFlight();
715
766
  if (!validation.isValid) {
716
- addToast(validation.error || 'Please complete all required fields', 'error');
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
- addToast('Please fix the errors in the form.', 'error');
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", className: fieldClass(!!errors.email), placeholder: "john@example.com", value: formData.email, onChange: (e) => setFormData({ ...formData, email: e.target.value }) }), errors.email && _jsx("p", { className: "text-red-500 text-xs", 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", className: fieldClass(!!errors.firstName), value: formData.firstName, onChange: e => setFormData({ ...formData, firstName: e.target.value }) }), errors.firstName && _jsx("p", { className: "text-red-500 text-xs", children: errors.firstName })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: labelClass, children: "Last Name" }), _jsx("input", { type: "text", className: fieldClass(!!errors.lastName), value: formData.lastName, onChange: e => setFormData({ ...formData, lastName: e.target.value }) }), errors.lastName && _jsx("p", { className: "text-red-500 text-xs", 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: (() => {
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: () => setIsCityDropdownOpen(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: getCitiesByCountry(formData.country).length > 0 ? (getCitiesByCountry(formData.country).map((city) => (_jsx("button", { type: "button", onClick: () => {
1004
- setFormData({ ...formData, city });
1005
- setIsCityDropdownOpen(false);
1006
- }, 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 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", className: fieldClass(!!errors.phone), value: formData.phone, onChange: e => setFormData({ ...formData, phone: e.target.value }) }), errors.phone && _jsx("p", { className: "text-red-500 text-xs", 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() })] }))] }))] }), storeConfig.settings?.shippingEnabled !== false && (formData.city && formData.country) && (_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" })] }), (() => {
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
- : 'Not available' }) })] }, method.code));
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), item.currency || 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) })] }), _jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsxs("span", { children: ["Shipping", selectedShippingMethod
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..." })) : !selectedShippingMethod ? (_jsx("span", { className: "text-gray-400", children: "Not available" })) : shipping === 0 && freeShippingEligible ? (_jsx("span", { className: "text-emerald-600", children: "Free" })) : shipping === 0 ? ('Free') : Number.isFinite(shipping) && shipping > 0 ? (formatCurrency(shipping, currency)) : (_jsx("span", { className: "text-gray-400", children: "Not available" })) })] }), 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) })] })] }), _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, 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' })] })) }) })] }) })] })] }) }));
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?.primaryColor || '#1b0029';
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?.primaryColor);
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?.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,
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?.primaryColor);
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?.primaryColor,
33
- '--store-secondary': storeConfig.branding?.secondaryColor || storeConfig.branding?.primaryColor,
34
- '--store-accent': storeConfig.branding?.accentColor || storeConfig.branding?.primaryColor,
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?.primaryColor);
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?.primaryColor,
25
- '--store-secondary': storeConfig.branding?.secondaryColor || storeConfig.branding?.primaryColor,
26
- '--store-accent': storeConfig.branding?.accentColor || storeConfig.branding?.primaryColor,
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?.primaryColor || '#EA580C'; // orange-600
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?.primaryColor,
33
- '--store-secondary': storeConfig.branding?.secondaryColor || storeConfig.branding?.primaryColor,
34
- '--store-accent': storeConfig.branding?.accentColor || storeConfig.branding?.primaryColor,
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?.primaryColor);
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?.primaryColor,
36
- '--store-secondary': storeConfig.branding?.secondaryColor || storeConfig.branding?.primaryColor,
37
- '--store-accent': storeConfig.branding?.accentColor || storeConfig.branding?.primaryColor,
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?.primaryColor);
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?.primaryColor,
30
- '--store-secondary': storeConfig.branding?.secondaryColor || storeConfig.branding?.primaryColor,
31
- '--store-accent': storeConfig.branding?.accentColor || storeConfig.branding?.primaryColor,
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,