E-Commerce-Module/frontend/src/pages/CartPage.jsx
2025-04-25 00:41:30 -05:00

331 lines
No EOL
10 KiB
JavaScript

import React from 'react';
import {
Box,
Typography,
Button,
Paper,
Grid,
Divider,
Card,
CardMedia,
IconButton,
TextField,
CircularProgress,
Alert,
Breadcrumbs,
Link
} from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import RemoveIcon from '@mui/icons-material/Remove';
import DeleteIcon from '@mui/icons-material/Delete';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import { Link as RouterLink, useNavigate } from 'react-router-dom';
import { useAuth } from '@hooks/reduxHooks';
import { useGetCart, useUpdateCartItem, useClearCart } from '.@hooks/apiHooks';
import imageUtils from '@utils/imageUtils';
const CartPage = () => {
const navigate = useNavigate();
const { user } = useAuth();
// Get cart data
const { data: cart, isLoading, error } = useGetCart(user?.id);
// Cart mutations
const updateCartItem = useUpdateCartItem();
const clearCart = useClearCart();
// Handle quantity change
const handleUpdateQuantity = (productId, newQuantity) => {
updateCartItem.mutate({
userId: user.id,
productId,
quantity: newQuantity
});
};
// Handle remove item
const handleRemoveItem = (productId) => {
updateCartItem.mutate({
userId: user.id,
productId,
quantity: 0
});
};
// Handle clear cart
const handleClearCart = () => {
clearCart.mutate(user.id);
};
// Handle proceed to checkout
const handleCheckout = () => {
navigate('/checkout');
};
// Loading state
if (isLoading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', my: 8 }}>
<CircularProgress />
</Box>
);
}
// Error state
if (error) {
return (
<Box sx={{ my: 4 }}>
<Alert severity="error" sx={{ mb: 3 }}>
Error loading your cart. Please try again.
</Alert>
<Button
variant="contained"
component={RouterLink}
to="/products"
>
Continue Shopping
</Button>
</Box>
);
}
// Empty cart state
if (!cart || !cart.items || cart.items.length === 0) {
return (
<Box sx={{ textAlign: 'center', py: 6 }}>
<ShoppingCartIcon sx={{ fontSize: 60, color: 'text.secondary', mb: 2 }} />
<Typography variant="h5" gutterBottom>
Your Cart is Empty
</Typography>
<Typography variant="body1" color="text.secondary" paragraph>
Looks like you haven't added any items to your cart yet.
</Typography>
<Button
variant="contained"
component={RouterLink}
to="/products"
size="large"
sx={{ mt: 2 }}
>
Start Shopping
</Button>
</Box>
);
}
return (
<Box>
{/* Breadcrumbs navigation */}
<Breadcrumbs
separator={<NavigateNextIcon fontSize="small" />}
aria-label="breadcrumb"
sx={{ mb: 3 }}
>
<Link component={RouterLink} to="/" color="inherit">
Home
</Link>
<Typography color="text.primary">Your Cart</Typography>
</Breadcrumbs>
<Typography variant="h4" component="h1" gutterBottom>
Your Shopping Cart
</Typography>
<Grid container spacing={4}>
{/* Cart items */}
<Grid item xs={12} lg={8}>
<Paper variant="outlined" sx={{ mb: { xs: 3, lg: 0 } }}>
<Box sx={{ p: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6">
Cart Items ({cart.itemCount})
</Typography>
<Button
variant="text"
color="error"
onClick={handleClearCart}
disabled={clearCart.isLoading}
startIcon={<DeleteIcon />}
>
Clear Cart
</Button>
</Box>
<Divider />
{cart.items.map((item) => (
<React.Fragment key={item.product_id}>
<Box sx={{ p: 2 }}>
<Grid container spacing={2} alignItems="center">
{/* Product image */}
<Grid item xs={3} sm={2}>
<Card sx={{ height: '100%' }}>
<CardMedia
component="img"
image={imageUtils.getImageUrl(item.image_url || '/images/placeholder.jpg')}
alt={item.name}
sx={{ height: 80, objectFit: 'cover' }}
/>
</Card>
</Grid>
{/* Product details */}
<Grid item xs={9} sm={6}>
<Typography
variant="subtitle1"
component={RouterLink}
to={`/products/${item.product_id}`}
sx={{
textDecoration: 'none',
color: 'inherit',
'&:hover': { color: 'primary.main' },
display: 'block',
mb: 0.5
}}
>
{item.name}
</Typography>
<Typography variant="body2" color="text.secondary">
Category: {item.category_name}
</Typography>
<Typography variant="body2" color="text.secondary">
Price: ${parseFloat(item.price).toFixed(2)}
</Typography>
</Grid>
{/* Quantity controls */}
<Grid item xs={7} sm={2}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<IconButton
size="small"
onClick={() => handleUpdateQuantity(item.product_id, item.quantity - 1)}
disabled={item.quantity <= 1 || updateCartItem.isLoading}
>
<RemoveIcon fontSize="small" />
</IconButton>
<TextField
value={item.quantity}
onChange={(e) => {
const value = parseInt(e.target.value);
if (!isNaN(value) && value > 0) {
handleUpdateQuantity(item.product_id, value);
}
}}
inputProps={{ min: 1, style: { textAlign: 'center' } }}
size="small"
sx={{ width: 40, mx: 1 }}
/>
<IconButton
size="small"
onClick={() => handleUpdateQuantity(item.product_id, item.quantity + 1)}
disabled={updateCartItem.isLoading}
>
<AddIcon fontSize="small" />
</IconButton>
</Box>
</Grid>
{/* Subtotal and remove */}
<Grid item xs={5} sm={2} sx={{ textAlign: 'right' }}>
<Typography variant="subtitle1">
${(parseFloat(item.price) * item.quantity).toFixed(2)}
</Typography>
<IconButton
color="error"
size="small"
onClick={() => handleRemoveItem(item.product_id)}
disabled={updateCartItem.isLoading}
sx={{ ml: 1 }}
>
<DeleteIcon fontSize="small" />
</IconButton>
</Grid>
</Grid>
</Box>
<Divider />
</React.Fragment>
))}
<Box sx={{ p: 2, textAlign: 'right' }}>
<Button
component={RouterLink}
to="/products"
sx={{ mr: 2 }}
>
Continue Shopping
</Button>
</Box>
</Paper>
</Grid>
{/* Order summary */}
<Grid item xs={12} lg={4}>
<Paper variant="outlined" sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
Order Summary
</Typography>
<Box sx={{ my: 2 }}>
<Grid container spacing={1}>
<Grid item xs={8}>
<Typography variant="body1">
Subtotal ({cart.itemCount} items)
</Typography>
</Grid>
<Grid item xs={4} sx={{ textAlign: 'right' }}>
<Typography variant="body1">
${cart.total.toFixed(2)}
</Typography>
</Grid>
<Grid item xs={8}>
<Typography variant="body1">
Shipping
</Typography>
</Grid>
<Grid item xs={4} sx={{ textAlign: 'right' }}>
<Typography variant="body1">
Free
</Typography>
</Grid>
</Grid>
</Box>
<Divider sx={{ my: 2 }} />
<Grid container spacing={1} sx={{ mb: 2 }}>
<Grid item xs={8}>
<Typography variant="h6">
Total
</Typography>
</Grid>
<Grid item xs={4} sx={{ textAlign: 'right' }}>
<Typography variant="h6">
${cart.total.toFixed(2)}
</Typography>
</Grid>
</Grid>
<Button
variant="contained"
fullWidth
size="large"
onClick={handleCheckout}
disabled={cart.items.length === 0}
>
Proceed to Checkout
</Button>
</Paper>
</Grid>
</Grid>
</Box>
);
};
export default CartPage;