diff --git a/src/app/[locale]/checkout/page.tsx b/src/app/[locale]/checkout/page.tsx index c85e866..60b4fd8 100644 --- a/src/app/[locale]/checkout/page.tsx +++ b/src/app/[locale]/checkout/page.tsx @@ -19,6 +19,7 @@ import { DEFAULT_PAYMENT_METHOD } from "@/lib/config/paymentMethods"; import { GET_CHECKOUT_BY_ID } from "@/lib/saleor/queries/Checkout"; import type { Checkout } from "@/types/saleor"; import { createCheckoutService, type Address } from "@/lib/services/checkoutService"; +import { useShippingMethodSelector } from "@/lib/hooks/useShippingMethodSelector"; interface ShippingAddressUpdateResponse { checkoutShippingAddressUpdate?: { @@ -31,6 +32,8 @@ interface CheckoutQueryResponse { checkout?: Checkout; } + + interface ShippingMethod { id: string; name: string; @@ -92,8 +95,16 @@ export default function CheckoutPage() { const [selectedShippingMethod, setSelectedShippingMethod] = useState(""); const [isLoadingShipping, setIsLoadingShipping] = useState(false); + // Hook to manage shipping method selection (both manual and auto) + const { selectShippingMethodWithApi } = useShippingMethodSelector({ + checkoutId: checkout?.id ?? null, + onSelect: setSelectedShippingMethod, + onRefresh: refreshCheckout, + }); + const lines = getLines(); - const total = getTotal(); + // Use checkout.totalPrice directly for reactive updates when shipping method changes + const total = checkout?.totalPrice?.gross?.amount || getTotal(); // Debounced shipping method fetching useEffect(() => { @@ -147,10 +158,12 @@ export default function CheckoutPage() { console.log("Available shipping methods:", availableMethods); setShippingMethods(availableMethods); - + // Auto-select first method if none selected if (availableMethods.length > 0 && !selectedShippingMethod) { - setSelectedShippingMethod(availableMethods[0].id); + const firstMethodId = availableMethods[0].id; + // Use the hook to both update UI and call API + await selectShippingMethodWithApi(firstMethodId); } } catch (err) { console.error("Error fetching shipping methods:", err); @@ -210,6 +223,10 @@ export default function CheckoutPage() { setShippingAddress((prev) => ({ ...prev, email: value })); }; + const handleShippingMethodSelect = async (methodId: string) => { + await selectShippingMethodWithApi(methodId); + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -576,7 +593,7 @@ export default function CheckoutPage() { name="shippingMethod" value={method.id} checked={selectedShippingMethod === method.id} - onChange={(e) => setSelectedShippingMethod(e.target.value)} + onChange={(e) => handleShippingMethodSelect(e.target.value)} className="w-4 h-4" /> {method.name} diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 6a3aa2d..c986cdf 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -55,14 +55,14 @@ export default function Header({ locale: propLocale = "sr" }: HeaderProps) { setLangDropdownOpen(false); }; - // Set language code first, then initialize checkout + // Set language code - checkout initializes lazily when cart is opened useEffect(() => { if (locale) { setLanguageCode(locale); - // Initialize checkout after language code is set - initCheckout(); + // Checkout will initialize lazily when user adds to cart or opens cart drawer + // This prevents blocking page render with unnecessary API calls } - }, [locale, setLanguageCode, initCheckout]); + }, [locale, setLanguageCode]); useEffect(() => { const handleScroll = () => { diff --git a/src/lib/hooks/useShippingMethodSelector.ts b/src/lib/hooks/useShippingMethodSelector.ts new file mode 100644 index 0000000..b907bfc --- /dev/null +++ b/src/lib/hooks/useShippingMethodSelector.ts @@ -0,0 +1,73 @@ +"use client"; + +import { useCallback } from "react"; +import { createCheckoutService } from "@/lib/services/checkoutService"; + +interface UseShippingMethodSelectorOptions { + checkoutId: string | null; + onSelect: (methodId: string) => void; + onRefresh: () => Promise; +} + +interface UseShippingMethodSelectorResult { + selectShippingMethod: (methodId: string) => Promise; + selectShippingMethodWithApi: (methodId: string) => Promise; +} + +/** + * Hook to manage shipping method selection + * Encapsulates both UI state update and API communication + * Used for both manual selection (user click) and auto-selection (default method) + */ +export function useShippingMethodSelector( + options: UseShippingMethodSelectorOptions +): UseShippingMethodSelectorResult { + const { checkoutId, onSelect, onRefresh } = options; + + /** + * Updates UI state only (for initial/pre-selection) + */ + const selectShippingMethod = useCallback( + async (methodId: string) => { + onSelect(methodId); + }, + [onSelect] + ); + + /** + * Updates UI state AND calls Saleor API + * Use this when user manually selects OR when auto-selecting the default + */ + const selectShippingMethodWithApi = useCallback( + async (methodId: string) => { + if (!checkoutId) { + console.warn("[selectShippingMethodWithApi] No checkoutId provided"); + return; + } + + // Update UI immediately for responsiveness + onSelect(methodId); + + // Call API through CheckoutService + const checkoutService = createCheckoutService(checkoutId); + const result = await checkoutService.updateShippingMethod(methodId); + + if (result.success) { + // Refresh checkout to get updated totals including shipping + await onRefresh(); + } else { + console.error( + "[selectShippingMethodWithApi] Failed to update shipping method:", + result.error + ); + // Could add error handling/rollback here + } + }, + [checkoutId, onSelect, onRefresh] + ); + + return { + selectShippingMethod, + selectShippingMethodWithApi, + }; +}