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;