import React, { useState, useEffect } from 'react'; import { Box, Typography, Paper, Grid, Stepper, Step, StepLabel, Button, Divider, TextField, FormControlLabel, Checkbox, CircularProgress, List, ListItem, ListItemText, Alert, Radio, RadioGroup, FormControl, FormLabel } from '@mui/material'; import { useNavigate, Link as RouterLink } from 'react-router-dom'; import { useAuth, useCart } from '../hooks/reduxHooks'; import { useCheckout } from '../hooks/apiHooks'; import { useStripe, StripeElementsProvider } from '../context/StripeContext'; import apiClient from '../services/api'; // Checkout steps const steps = ['Shipping Address', 'Shipping Method', 'Review Order', 'Payment', 'Confirmation']; const CheckoutPage = () => { const navigate = useNavigate(); const { user, userData } = useAuth(); const { items, total, itemCount } = useCart(); const checkout = useCheckout(); const { createCheckoutSession, isLoading: isStripeLoading } = useStripe(); // State for checkout steps const [activeStep, setActiveStep] = useState(0); const [isProcessing, setIsProcessing] = useState(false); const [isLoadingShipping, setIsLoadingShipping] = useState(false); const [error, setError] = useState(null); const [orderId, setOrderId] = useState(null); const [checkoutUrl, setCheckoutUrl] = useState(null); // State for shipping options const [shippingRates, setShippingRates] = useState([]); const [selectedShippingMethod, setSelectedShippingMethod] = useState(null); const [shippingCost, setShippingCost] = useState(0); const [shipmentId, setShipmentId] = useState(null); // State for form data const [formData, setFormData] = useState({ firstName: userData?.first_name || '', lastName: userData?.last_name || '', email: userData?.email || '', address: '', city: '', province: '', postalCode: '', country: '', saveAddress: false, }); // Handle form changes const handleChange = (e) => { const { name, value, checked } = e.target; setFormData({ ...formData, [name]: name === 'saveAddress' ? checked : value, }); // Clear validation error when field is edited if (error) { setError(null); } }; // Handle next step const handleNext = () => { if (activeStep === steps.length - 1) { // Complete checkout - placeholder return; } // If on shipping address step, validate form if (activeStep === 0) { if (!validateShippingForm()) { return; } fetchShippingRates(); return } // If on shipping method step, validate selection if (activeStep === 1) { if (!selectedShippingMethod) { setError('Please select a shipping method'); return; } setError(null); } // If on review step, process checkout if (activeStep === 2) { handlePlaceOrder(); return; } setActiveStep((prevStep) => prevStep + 1); }; // Handle back step const handleBack = () => { setActiveStep((prevStep) => prevStep - 1); }; // Validate shipping form const validateShippingForm = () => { const requiredFields = ['firstName', 'lastName', 'email', 'address', 'city', 'province', 'postalCode', 'country']; for (const field of requiredFields) { if (!formData[field]) { // In a real app, you'd set specific errors for each field setError(`Please fill in all required fields`); return false; } } // Basic email validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(formData.email)) { setError('Please enter a valid email address'); return false; } setError(null); return true; }; // Fetch shipping rates based on address const fetchShippingRates = async () => { try { setIsLoadingShipping(true); setError(null); // Format shipping address const shippingAddress = { name: `${formData.firstName} ${formData.lastName}`, street: formData.address, city: formData.city, state: formData.province, zip: formData.postalCode, country: formData.country, email: formData.email }; // Call API to get shipping rates const response = await apiClient.post('/cart/shipping-rates', { userId: user, shippingAddress }); if (response.data.rates && response.data.rates.length > 0) { setShippingRates(response.data.rates); // Store shipment_id if provided (needed for actual shipment creation) if (response.data.shipment_id) { setShipmentId(response.data.shipment_id); } // Default to lowest cost option const lowestCostOption = response.data.rates.reduce( (lowest, current) => current.rate < lowest.rate ? current : lowest, response.data.rates[0] ); setSelectedShippingMethod(lowestCostOption); setShippingCost(lowestCostOption.rate); } else { setShippingRates([]); setSelectedShippingMethod(null); setShipmentId(null); setShippingCost(0); setError('No shipping options available for this address'); } // Move to next step setActiveStep((prevStep) => prevStep + 1); } catch (error) { console.error('Error fetching shipping rates:', error); setError('Failed to retrieve shipping options. Please try again.'); } finally { setIsLoadingShipping(false); } }; // Handle shipping method selection const handleShippingMethodChange = (event) => { const selectedMethodId = event.target.value; const method = shippingRates.find(rate => rate.id === selectedMethodId); if (method) { setSelectedShippingMethod(method); setShippingCost(method.rate); } }; // Handle place order const handlePlaceOrder = async () => { if (!user || !items || items.length === 0) { return; } setIsProcessing(true); setError(null); try { // Format shipping address const shippingAddress = `${formData.firstName} ${formData.lastName} ${formData.address} ${formData.city}, ${formData.province} ${formData.postalCode} ${formData.country} ${formData.email}`; // Call the checkout API to create the order const orderResponse = await checkout.mutateAsync({ userId: user, shippingAddress, shippingMethod: selectedShippingMethod }); // Store the order ID for later use setOrderId(orderResponse.orderId); // Use the shipping data from the response if available const shippingDetails = orderResponse.shipmentData ? { shipping_cost: orderResponse.shippingCost, shipping_method: `${orderResponse.shipmentData.carrier} - ${orderResponse.shipmentData.service}`, tracking_code: orderResponse.shipmentData.tracking_code, label_url: orderResponse.shipmentData.label_url } : { shipping_cost: orderResponse.shippingCost, shipping_method: selectedShippingMethod ? `${selectedShippingMethod.carrier} - ${selectedShippingMethod.service}` : 'Standard Shipping' }; // Proceed to payment step setActiveStep(3); // Create a Stripe checkout session const session = await createCheckoutSession( orderResponse.cartItems, orderResponse.orderId, shippingAddress, user, shippingDetails ); // Redirect to Stripe Checkout if (session.url) { setCheckoutUrl(session.url); } } catch (error) { console.error('Checkout error:', error); setError(error.message || 'An error occurred during checkout'); } finally { setIsProcessing(false); } }; // Redirect to Stripe checkout when the URL is available useEffect(() => { if (checkoutUrl) { window.location.href = checkoutUrl; } }, [checkoutUrl]); // If no items in cart, redirect to cart page if (!items || items.length === 0) { return ( Your cart is empty You need to add items to your cart before checkout. ); } // Render different step content based on active step const getStepContent = (step) => { switch (step) { case 0: return ( } label="Save this address for future orders" /> ); case 1: return ( Shipping Method {isLoadingShipping ? ( ) : shippingRates.length > 0 ? ( {shippingRates.map((rate) => ( } label={ {rate.carrier} - {rate.service} Estimated delivery: {rate.delivery_days} days {rate.rate > 0 ? `$${rate.rate.toFixed(2)}` : 'FREE'} } sx={{ width: '100%', m: 0 }} /> ))} ) : ( No shipping options available for this address. Please check your shipping address or contact support. )} ); case 2: return ( {/* Order summary */} Order Summary {items.map((item) => ( ${(parseFloat(item.price) * item.quantity).toFixed(2)} ))} ${total.toFixed(2)} {shippingCost > 0 ? `$${shippingCost.toFixed(2)}` : 'Free'} ${(total + shippingCost).toFixed(2)} {/* Shipping address */} Shipping Address {formData.firstName} {formData.lastName} {formData.address} {formData.city}, {formData.province} {formData.postalCode} {formData.country} {formData.email} ); case 3: return ( {isStripeLoading || isProcessing ? ( Preparing secure payment... ) : ( You will be redirected to our secure payment processor. )} {error && ( {error} )} ); case 4: return ( Your order has been placed successfully! Thank you for your order Your order number is: #{orderId?.substring(0, 8) || Math.floor(100000 + Math.random() * 900000)} We will send you a confirmation email with your order details. ); default: return Unknown step; } }; return ( Checkout {steps.map((label) => ( {label} ))} {error && ( {error} )} {getStepContent(activeStep)} {activeStep !== 0 && activeStep !== 3 && activeStep !== 4 && !isProcessing && !isLoadingShipping && ( )} {activeStep !== 3 && activeStep !== 4 && ( )} {activeStep === 4 && ( )} ); }; export default CheckoutPage;