"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import Image from "next/image"; import { useTranslations, useLocale } from "next-intl"; import Header from "@/components/layout/Header"; import Footer from "@/components/layout/Footer"; import { useSaleorCheckoutStore } from "@/stores/saleorCheckoutStore"; import { formatPrice } from "@/lib/saleor"; import { saleorClient } from "@/lib/saleor/client"; import { useAnalytics } from "@/lib/analytics"; import { CHECKOUT_SHIPPING_ADDRESS_UPDATE, CHECKOUT_BILLING_ADDRESS_UPDATE, CHECKOUT_COMPLETE, CHECKOUT_EMAIL_UPDATE, CHECKOUT_METADATA_UPDATE, CHECKOUT_SHIPPING_METHOD_UPDATE, ORDER_METADATA_UPDATE, CHECKOUT_LANGUAGE_CODE_UPDATE, } from "@/lib/saleor/mutations/Checkout"; import { PaymentSection } from "./components/PaymentSection"; import { DEFAULT_PAYMENT_METHOD } from "@/lib/config/paymentMethods"; import { GET_CHECKOUT_BY_ID } from "@/lib/saleor/queries/Checkout"; import type { Checkout } from "@/types/saleor"; interface ShippingAddressUpdateResponse { checkoutShippingAddressUpdate?: { checkout?: Checkout; errors?: Array<{ message: string }>; }; } interface BillingAddressUpdateResponse { checkoutBillingAddressUpdate?: { checkout?: Checkout; errors?: Array<{ message: string }>; }; } interface CheckoutCompleteResponse { checkoutComplete?: { order?: { id: string; number: string }; errors?: Array<{ message: string }>; }; } interface EmailUpdateResponse { checkoutEmailUpdate?: { checkout?: Checkout; errors?: Array<{ message: string }>; }; } interface MetadataUpdateResponse { updateMetadata?: { item?: { id: string; metadata?: Array<{ key: string; value: string }>; }; errors?: Array<{ message: string }>; }; } interface ShippingMethodUpdateResponse { checkoutShippingMethodUpdate?: { checkout?: Checkout; errors?: Array<{ message: string }>; }; } interface CheckoutQueryResponse { checkout?: Checkout; } interface ShippingMethod { id: string; name: string; price: { amount: number; currency: string; }; } interface AddressForm { firstName: string; lastName: string; streetAddress1: string; streetAddress2: string; city: string; postalCode: string; country: string; phone: string; email: string; } export default function CheckoutPage() { const t = useTranslations("Checkout"); const locale = useLocale(); const router = useRouter(); const { checkout, refreshCheckout, getLines, getTotal } = useSaleorCheckoutStore(); const { trackCheckoutStarted, trackCheckoutStep, trackOrderCompleted, identifyUser } = useAnalytics(); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [orderComplete, setOrderComplete] = useState(false); const [orderNumber, setOrderNumber] = useState(null); const [sameAsShipping, setSameAsShipping] = useState(true); const [selectedPaymentMethod, setSelectedPaymentMethod] = useState(DEFAULT_PAYMENT_METHOD); const [shippingAddress, setShippingAddress] = useState({ firstName: "", lastName: "", streetAddress1: "", streetAddress2: "", city: "", postalCode: "", country: "RS", phone: "", email: "", }); const [billingAddress, setBillingAddress] = useState({ firstName: "", lastName: "", streetAddress1: "", streetAddress2: "", city: "", postalCode: "", country: "RS", phone: "", email: "", }); const [shippingMethods, setShippingMethods] = useState([]); const [selectedShippingMethod, setSelectedShippingMethod] = useState(""); const [isLoadingShipping, setIsLoadingShipping] = useState(false); const lines = getLines(); const total = getTotal(); // Debounced shipping method fetching useEffect(() => { if (!checkout) return; // Check if address is complete enough to fetch shipping methods const isAddressComplete = shippingAddress.firstName && shippingAddress.lastName && shippingAddress.streetAddress1 && shippingAddress.city && shippingAddress.postalCode && shippingAddress.country; if (!isAddressComplete) { setShippingMethods([]); return; } const timer = setTimeout(async () => { setIsLoadingShipping(true); try { console.log("Fetching shipping methods..."); // First update the shipping address await saleorClient.mutate({ mutation: CHECKOUT_SHIPPING_ADDRESS_UPDATE, variables: { checkoutId: checkout.id, shippingAddress: { firstName: shippingAddress.firstName, lastName: shippingAddress.lastName, streetAddress1: shippingAddress.streetAddress1, streetAddress2: shippingAddress.streetAddress2, city: shippingAddress.city, postalCode: shippingAddress.postalCode, country: shippingAddress.country, phone: shippingAddress.phone, }, }, }); // Then query for shipping methods const checkoutQueryResult = await saleorClient.query({ query: GET_CHECKOUT_BY_ID, variables: { id: checkout.id }, fetchPolicy: "network-only", }); const availableMethods = checkoutQueryResult.data?.checkout?.shippingMethods || []; console.log("Available shipping methods:", availableMethods); setShippingMethods(availableMethods); // Auto-select first method if none selected if (availableMethods.length > 0 && !selectedShippingMethod) { setSelectedShippingMethod(availableMethods[0].id); } } catch (err) { console.error("Error fetching shipping methods:", err); } finally { setIsLoadingShipping(false); } }, 500); // 500ms debounce return () => clearTimeout(timer); }, [checkout, shippingAddress]); useEffect(() => { if (!checkout) { refreshCheckout(); } }, [checkout, refreshCheckout]); // Track checkout started when page loads useEffect(() => { if (checkout) { const lines = getLines(); const total = getTotal(); trackCheckoutStarted({ total, currency: "RSD", item_count: lines.reduce((sum, line) => sum + line.quantity, 0), items: lines.map(line => ({ id: line.variant.id, name: line.variant.product.name, quantity: line.quantity, price: line.variant.pricing?.price?.gross?.amount || 0, })), }); } }, [checkout]); // Scroll to top when order is complete useEffect(() => { if (orderComplete) { window.scrollTo({ top: 0, behavior: "smooth" }); } }, [orderComplete]); const handleShippingChange = (field: keyof AddressForm, value: string) => { setShippingAddress((prev) => ({ ...prev, [field]: value })); if (sameAsShipping && field !== "email") { setBillingAddress((prev) => ({ ...prev, [field]: value })); } }; const handleBillingChange = (field: keyof AddressForm, value: string) => { setBillingAddress((prev) => ({ ...prev, [field]: value })); }; const handleEmailChange = (value: string) => { setShippingAddress((prev) => ({ ...prev, email: value })); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!checkout) { setError(t("errorNoCheckout")); return; } // Validate all required fields if (!shippingAddress.email || !shippingAddress.email.includes("@")) { setError(t("errorEmailRequired")); return; } if (!shippingAddress.phone || shippingAddress.phone.length < 8) { setError(t("errorPhoneRequired")); return; } if (!shippingAddress.firstName || !shippingAddress.lastName || !shippingAddress.streetAddress1 || !shippingAddress.city || !shippingAddress.postalCode) { setError(t("errorFieldsRequired")); return; } if (!selectedShippingMethod) { setError(t("errorSelectShipping")); return; } if (!selectedPaymentMethod) { setError(t("errorSelectPayment")); return; } setIsLoading(true); setError(null); try { console.log("Completing order..."); console.log("Step 1: Updating email..."); const emailResult = await saleorClient.mutate({ mutation: CHECKOUT_EMAIL_UPDATE, variables: { checkoutId: checkout.id, email: shippingAddress.email, }, }); if (emailResult.data?.checkoutEmailUpdate?.errors && emailResult.data.checkoutEmailUpdate.errors.length > 0) { const errorMessage = emailResult.data.checkoutEmailUpdate.errors[0].message; if (errorMessage.includes("Couldn't resolve to a node")) { console.error("Checkout not found, clearing cart..."); localStorage.removeItem('cart'); localStorage.removeItem('checkoutId'); window.location.href = `/${locale}/products`; return; } throw new Error(`Email update failed: ${errorMessage}`); } console.log("Step 1: Email updated successfully"); console.log("Step 2: Updating language code..."); await saleorClient.mutate({ mutation: CHECKOUT_LANGUAGE_CODE_UPDATE, variables: { checkoutId: checkout.id, languageCode: locale.toUpperCase(), }, }); console.log("Step 2: Language code updated to", locale.toUpperCase()); console.log("Step 3: Updating billing address..."); const billingResult = await saleorClient.mutate({ mutation: CHECKOUT_BILLING_ADDRESS_UPDATE, variables: { checkoutId: checkout.id, billingAddress: { firstName: billingAddress.firstName, lastName: billingAddress.lastName, streetAddress1: billingAddress.streetAddress1, streetAddress2: billingAddress.streetAddress2, city: billingAddress.city, postalCode: billingAddress.postalCode, country: billingAddress.country, phone: billingAddress.phone, }, }, }); if (billingResult.data?.checkoutBillingAddressUpdate?.errors && billingResult.data.checkoutBillingAddressUpdate.errors.length > 0) { throw new Error(`Billing address update failed: ${billingResult.data.checkoutBillingAddressUpdate.errors[0].message}`); } console.log("Step 3: Billing address updated successfully"); console.log("Step 4: Setting shipping method..."); const shippingMethodResult = await saleorClient.mutate({ mutation: CHECKOUT_SHIPPING_METHOD_UPDATE, variables: { checkoutId: checkout.id, shippingMethodId: selectedShippingMethod, }, }); if (shippingMethodResult.data?.checkoutShippingMethodUpdate?.errors && shippingMethodResult.data.checkoutShippingMethodUpdate.errors.length > 0) { throw new Error(`Shipping method update failed: ${shippingMethodResult.data.checkoutShippingMethodUpdate.errors[0].message}`); } console.log("Step 4: Shipping method set successfully"); console.log("Step 5: Saving metadata..."); const metadataResult = await saleorClient.mutate({ mutation: CHECKOUT_METADATA_UPDATE, variables: { checkoutId: checkout.id, metadata: [ { key: "phone", value: shippingAddress.phone }, { key: "shippingPhone", value: shippingAddress.phone }, { key: "userLanguage", value: locale }, { key: "userLocale", value: locale }, ], }, }); if (metadataResult.data?.updateMetadata?.errors && metadataResult.data.updateMetadata.errors.length > 0) { console.warn("Failed to save phone metadata:", metadataResult.data.updateMetadata.errors); } else { console.log("Step 5: Phone number saved successfully"); } console.log("Step 6: Completing checkout..."); const completeResult = await saleorClient.mutate({ mutation: CHECKOUT_COMPLETE, variables: { checkoutId: checkout.id, }, }); if (completeResult.data?.checkoutComplete?.errors && completeResult.data.checkoutComplete.errors.length > 0) { throw new Error(completeResult.data.checkoutComplete.errors[0].message); } const order = completeResult.data?.checkoutComplete?.order; if (order) { setOrderNumber(order.number); setOrderComplete(true); // Track order completion const lines = getLines(); const total = getTotal(); trackOrderCompleted({ order_id: checkout.id, order_number: order.number, total, currency: "RSD", item_count: lines.reduce((sum, line) => sum + line.quantity, 0), shipping_cost: shippingMethods.find(m => m.id === selectedShippingMethod)?.price.amount, customer_email: shippingAddress.email, }); // Identify the user identifyUser({ profileId: shippingAddress.email, email: shippingAddress.email, firstName: shippingAddress.firstName, lastName: shippingAddress.lastName, }); } else { throw new Error(t("errorCreatingOrder")); } } catch (err: unknown) { console.error("Checkout error:", err); if (err instanceof Error) { if (err.name === "AbortError") { setError("Request timed out. Please check your connection and try again."); } else { setError(err.message || t("errorOccurred")); } } else { setError(t("errorOccurred")); } } finally { setIsLoading(false); } }; if (orderComplete) { return ( <>

{t("orderConfirmed")}

{t("thankYou")}

{orderNumber && (

{t("orderNumber")}

#{orderNumber}

)}

{t("confirmationEmail")}

{t("continueShoppingBtn")}
); } return ( <>

{t("checkout")}

{error && (
{error}
)}

{t("contactInfo")}

handleEmailChange(e.target.value)} className="w-full border border-border px-4 py-2 rounded" placeholder="email@example.com" />

{t("emailRequired")}

handleShippingChange("phone", e.target.value)} className="w-full border border-border px-4 py-2 rounded" placeholder="+381..." />

{t("phoneRequired")}

{t("shippingAddress")}

handleShippingChange("firstName", e.target.value)} className="w-full border border-border px-4 py-2 rounded" />
handleShippingChange("lastName", e.target.value)} className="w-full border border-border px-4 py-2 rounded" />
handleShippingChange("streetAddress1", e.target.value)} className="w-full border border-border px-4 py-2 rounded" />
handleShippingChange("streetAddress2", e.target.value)} placeholder={t("streetAddressOptional")} className="w-full border border-border px-4 py-2 rounded" />
handleShippingChange("city", e.target.value)} className="w-full border border-border px-4 py-2 rounded" />
handleShippingChange("postalCode", e.target.value)} className="w-full border border-border px-4 py-2 rounded" />
{/* Shipping Method Selection */}

{t("shippingMethod")}

{isLoadingShipping ? (
{t("loadingShippingMethods")}
) : shippingMethods.length > 0 ? (
{shippingMethods.map((method) => ( ))}
) : (

{t("enterAddressForShipping")}

)}
{/* Payment Method Section */} {/* Money Back Guarantee Trust Badge */}
{t("moneyBackGuarantee")}

{t("orderSummary")}

{lines.length === 0 ? (

{t("yourCartEmpty")}

) : ( <>
{lines.map((line) => (
{line.variant.product.media[0]?.url && ( {line.variant.product.name} )}

{line.variant.product.name}

{t("qty")}: {line.quantity}

{formatPrice(line.totalPrice.gross.amount)}

))}
{t("subtotal")} {formatPrice(checkout?.subtotalPrice?.gross?.amount || 0)}
{selectedShippingMethod && (
{t("shipping")} {formatPrice(shippingMethods.find(m => m.id === selectedShippingMethod)?.price.amount || 0)}
)}
{t("total")} {formatPrice(total)}
)}
); }