From 102807def3289ceaad936ba6cef4a0ed645a4e49 Mon Sep 17 00:00:00 2001 From: "ALMAZROUEI Shamma (2021) WKIS203" <shamma.almazrouei.2021@live.rhul.ac.uk> Date: Tue, 11 Mar 2025 20:36:08 +0530 Subject: [PATCH] Build the cart page --- golden-crust-bakery/src/main.jsx | 5 + golden-crust-bakery/src/pages/About.jsx | 2 +- golden-crust-bakery/src/pages/Cart.jsx | 243 +++++++++++++++++++++ golden-crust-bakery/src/pages/Landing.jsx | 2 +- golden-crust-bakery/src/pages/MenuItem.jsx | 2 +- 5 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 golden-crust-bakery/src/pages/Cart.jsx diff --git a/golden-crust-bakery/src/main.jsx b/golden-crust-bakery/src/main.jsx index bb357ce..f7c36cc 100644 --- a/golden-crust-bakery/src/main.jsx +++ b/golden-crust-bakery/src/main.jsx @@ -16,6 +16,7 @@ import Register from './pages/Register'; import Menu from './pages/Menu'; import MenuItem from './pages/MenuItem'; import ErrorPage from './pages/Error'; +import Cart from './pages/Cart'; const router = createBrowserRouter([ { @@ -43,6 +44,10 @@ const router = createBrowserRouter([ path: '/register', element: <Register />, }, + { + path: '/cart', + element: <Cart />, + }, { path: '/menu', element: <Menu />, diff --git a/golden-crust-bakery/src/pages/About.jsx b/golden-crust-bakery/src/pages/About.jsx index 0038ef1..b9e7c50 100644 --- a/golden-crust-bakery/src/pages/About.jsx +++ b/golden-crust-bakery/src/pages/About.jsx @@ -66,7 +66,7 @@ export default function About() { Get in Touch <ArrowRight className='ml-2 h-4 w-4' /> </Button> </div> - <div className='relative h-[300px] md:h-[400px] rounded-2xl overflow-hidden shadow-xl order-1 md:order-2'> + <div className='relative h-[300px] md:h-[400px] rounded-2xl overflow-hidden shadow-md order-1 md:order-2'> <img src='https://images.unsplash.com/photo-1572054466274-25b4ed6d6899?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D' alt='Our bakery team' diff --git a/golden-crust-bakery/src/pages/Cart.jsx b/golden-crust-bakery/src/pages/Cart.jsx new file mode 100644 index 0000000..f289032 --- /dev/null +++ b/golden-crust-bakery/src/pages/Cart.jsx @@ -0,0 +1,243 @@ +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { toast } from 'sonner'; +import { Minus, Plus, ShoppingCart, Trash2 } from 'lucide-react'; + +import { useCartStore } from '@/lib/store'; +import { useAuthStore } from '@/lib/authStore'; + +import { Button } from '@/components/ui/button'; + +export default function Cart() { + const navigate = useNavigate(); + const { cart, removeFromCart, updateQuantity, clearCart, initializeCart } = + useCartStore(); + const { isAuthenticated } = useAuthStore(); + + useEffect(() => { + initializeCart(); + + // Redirect to login if not authenticated + if (!isAuthenticated) { + toast.error('Authentication required!', { + description: 'Please sign in to view your cart.', + }); + navigate('/login'); + } + }, [initializeCart, isAuthenticated, navigate]); + + // If not authenticated, don't render the cart content + if (!isAuthenticated) { + return null; + } + + const totalItems = cart.reduce((total, item) => total + item.quantity, 0); + const subtotal = cart.reduce( + (total, item) => total + item.price * item.quantity, + 0 + ); + const tax = subtotal * 0.08; // 8% tax + const total = subtotal + tax; + + const handleQuantityChange = (id, newQuantity) => { + if (newQuantity < 1) return; + updateQuantity(id, newQuantity); + }; + + const handleRemoveItem = (id, name) => { + removeFromCart(id); + toast.success('Item removed', { + description: `${name} has been removed from your cart.`, + }); + }; + + const handleCheckout = () => { + if (cart.length === 0) { + toast.error('Cart is empty!', { + description: 'Please add items to your cart before checking out.', + }); + return; + } + navigate('/checkout'); + }; + + return ( + <div className='container mx-auto px-4 py-8 md:py-12'> + <div className='text-center mb-8'> + <h1 className='text-4xl font-bold text-gray-800 mb-4'> + Your Shopping Cart + </h1> + <p className='text-gray-600'> + {totalItems > 0 + ? `You have ${totalItems} item${ + totalItems !== 1 ? 's' : '' + } in your cart` + : 'Your cart is empty'} + </p> + </div> + + {cart.length > 0 ? ( + <div className='grid grid-cols-1 lg:grid-cols-3 gap-8'> + <div className='lg:col-span-2'> + <div className='bg-white rounded-xl shadow-sm border overflow-hidden'> + <div className='p-6'> + <div className='flow-root'> + <ul className='divide-y divide-gray-200'> + {cart.map((item) => ( + <li key={item.id} className='py-6 flex'> + <div className='relative h-24 w-24 rounded-md overflow-hidden'> + <img + src={item.image} + alt={item.name} + className='object-cover h-full w-full' + /> + </div> + <div className='ml-4 flex-1 flex flex-col'> + <div> + <div className='flex justify-between text-base font-medium text-gray-900'> + <h3>{item.name}</h3> + <p className='ml-4'> + ${(item.price * item.quantity).toFixed(2)} + </p> + </div> + <p className='mt-1 text-sm text-gray-500 line-clamp-2'> + {item.description} + </p> + </div> + <div className='flex-1 flex items-end justify-between text-sm'> + <div className='flex items-center space-x-2'> + <Button + variant='outline' + size='icon' + className='h-8 w-8 rounded-full' + onClick={() => + handleQuantityChange( + item.id, + item.quantity - 1 + ) + } + > + <Minus className='h-4 w-4' /> + </Button> + <span className='text-gray-700 w-8 text-center'> + {item.quantity} + </span> + <Button + variant='outline' + size='icon' + className='h-8 w-8 rounded-full' + onClick={() => + handleQuantityChange( + item.id, + item.quantity + 1 + ) + } + > + <Plus className='h-4 w-4' /> + </Button> + </div> + <Button + variant='ghost' + className='text-red-500 hover:text-red-700 hover:bg-red-50' + onClick={() => + handleRemoveItem(item.id, item.name) + } + > + <Trash2 className='h-5 w-5' /> + </Button> + </div> + </div> + </li> + ))} + </ul> + </div> + </div> + </div> + </div> + + <div className='lg:col-span-1'> + <div className='bg-white rounded-xl shadow-sm border overflow-hidden'> + <div className='p-6'> + <h2 className='text-lg font-semibold text-gray-900 mb-4'> + Order Summary + </h2> + <div className='flow-root'> + <div className='border-t border-gray-200 pt-4'> + <div className='flex justify-between py-2'> + <dt className='text-sm text-gray-600'>Subtotal</dt> + <dd className='text-sm font-medium text-gray-900'> + ${subtotal.toFixed(2)} + </dd> + </div> + <div className='flex justify-between py-2'> + <dt className='text-sm text-gray-600'>Tax (8%)</dt> + <dd className='text-sm font-medium text-gray-900'> + ${tax.toFixed(2)} + </dd> + </div> + <div className='flex justify-between py-2 border-t border-gray-200'> + <dt className='text-base font-medium text-gray-900'> + Total + </dt> + <dd className='text-base font-medium text-pink-600'> + ${total.toFixed(2)} + </dd> + </div> + </div> + </div> + <div className='mt-6'> + <Button + onClick={handleCheckout} + className='w-full bg-pink-500 hover:bg-pink-600 text-white rounded-full py-6' + > + Proceed to Checkout + </Button> + <Button + variant='outline' + onClick={() => navigate('/menu')} + className='w-full mt-4 border-pink-300 text-pink-600 hover:bg-pink-50 rounded-full' + > + Continue Shopping + </Button> + {cart.length > 0 && ( + <Button + variant='ghost' + onClick={() => { + clearCart(); + toast.success('Cart cleared!', { + description: + 'All items have been removed from your cart.', + }); + }} + className='w-full mt-2 text-gray-500 hover:text-gray-700 hover:bg-gray-50 rounded-full' + > + Clear Cart + </Button> + )} + </div> + </div> + </div> + </div> + </div> + ) : ( + <div className='text-center py-12'> + <div className='mx-auto w-24 h-24 bg-pink-100 rounded-full flex items-center justify-center mb-6'> + <ShoppingCart className='h-12 w-12 text-pink-500' /> + </div> + <h2 className='text-2xl font-semibold text-gray-800 mb-4'> + Your cart is empty + </h2> + <p className='text-gray-600 mb-8'> + Looks like you haven't added any cakes to your cart yet. + </p> + <Button + onClick={() => navigate('/menu')} + className='bg-pink-500 hover:bg-pink-600 text-white rounded-full px-8 py-6 text-lg' + > + Browse Our Menu + </Button> + </div> + )} + </div> + ); +} diff --git a/golden-crust-bakery/src/pages/Landing.jsx b/golden-crust-bakery/src/pages/Landing.jsx index 6d91362..14e248c 100644 --- a/golden-crust-bakery/src/pages/Landing.jsx +++ b/golden-crust-bakery/src/pages/Landing.jsx @@ -95,7 +95,7 @@ export default function Landing() { </Button> </div> </div> - <div className='relative h-[300px] md:h-[400px] rounded-2xl overflow-hidden shadow-xl'> + <div className='relative h-[300px] md:h-[400px] rounded-2xl overflow-hidden'> <img src='https://images.pexels.com/photos/2536967/pexels-photo-2536967.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2' alt='Delicious cake showcase' diff --git a/golden-crust-bakery/src/pages/MenuItem.jsx b/golden-crust-bakery/src/pages/MenuItem.jsx index cf670b1..84d2e84 100644 --- a/golden-crust-bakery/src/pages/MenuItem.jsx +++ b/golden-crust-bakery/src/pages/MenuItem.jsx @@ -92,7 +92,7 @@ export default function MenuItem() { </Button> <div className='grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 mb-12'> - <div className='relative h-[300px] md:h-[500px] rounded-2xl overflow-hidden shadow-xl'> + <div className='relative h-[300px] md:h-[500px] rounded-2xl overflow-hidden'> <img src={cake.image} alt={cake.name} -- GitLab