From 3067091fdd59cc041030787fe895b3baf391524d Mon Sep 17 00:00:00 2001
From: "ALMAZROUEI Shamma (2021) WKIS203"
 <shamma.almazrouei.2021@live.rhul.ac.uk>
Date: Tue, 11 Mar 2025 19:58:37 +0530
Subject: [PATCH] Build menu item page

---
 .../src/components/ui/card.jsx                |  50 ++++
 golden-crust-bakery/src/lib/data.js           |   4 +-
 golden-crust-bakery/src/main.jsx              |   5 +
 golden-crust-bakery/src/pages/About.jsx       |   8 +-
 golden-crust-bakery/src/pages/Menu.jsx        |   9 +-
 golden-crust-bakery/src/pages/MenuItem.jsx    | 242 ++++++++++++++++++
 6 files changed, 308 insertions(+), 10 deletions(-)
 create mode 100644 golden-crust-bakery/src/components/ui/card.jsx
 create mode 100644 golden-crust-bakery/src/pages/MenuItem.jsx

diff --git a/golden-crust-bakery/src/components/ui/card.jsx b/golden-crust-bakery/src/components/ui/card.jsx
new file mode 100644
index 0000000..2985cca
--- /dev/null
+++ b/golden-crust-bakery/src/components/ui/card.jsx
@@ -0,0 +1,50 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("rounded-xl border bg-card text-card-foreground shadow", className)}
+    {...props} />
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("flex flex-col space-y-1.5 p-6", className)}
+    {...props} />
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("font-semibold leading-none tracking-tight", className)}
+    {...props} />
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props} />
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef(({ className, ...props }, ref) => (
+  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("flex items-center p-6 pt-0", className)}
+    {...props} />
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/golden-crust-bakery/src/lib/data.js b/golden-crust-bakery/src/lib/data.js
index 0b91f88..2d4c8e8 100644
--- a/golden-crust-bakery/src/lib/data.js
+++ b/golden-crust-bakery/src/lib/data.js
@@ -77,7 +77,7 @@ export const cakeData = [
     price: 149.99,
     category: 'Wedding',
     image:
-      'https://www.warmoven.in/blog/wp-content/uploads/2024/07/Buttercream-Beauty.png',
+      'https://i0.wp.com/coucoucake.com/wp-content/uploads/2022/04/Small-Wedding-Cake-Elegant-Romantic-coucoucake12.webp',
   },
   {
     id: 9,
@@ -97,7 +97,7 @@ export const cakeData = [
     price: 35.99,
     category: 'Birthday',
     image:
-      'https://livforcake.com/wp-content/uploads/2017/05/mint-chocolate-chip-cake-3.jpg',
+      'https://www.bunsenburnerbakery.com/wp-content/uploads/2021/03/mint-chocolate-chip-cake-square-9Q2B2153.jpg',
   },
   {
     id: 11,
diff --git a/golden-crust-bakery/src/main.jsx b/golden-crust-bakery/src/main.jsx
index d41fb02..e394c5f 100644
--- a/golden-crust-bakery/src/main.jsx
+++ b/golden-crust-bakery/src/main.jsx
@@ -14,6 +14,7 @@ import Contact from './pages/Contact';
 import Login from './pages/Login';
 import Register from './pages/Register';
 import Menu from './pages/Menu';
+import MenuItem from './pages/MenuItem';
 
 const router = createBrowserRouter([
   {
@@ -44,6 +45,10 @@ const router = createBrowserRouter([
         path: '/menu',
         element: <Menu />,
       },
+      {
+        path: '/menu/:id',
+        element: <MenuItem />,
+      },
     ],
   },
 ]);
diff --git a/golden-crust-bakery/src/pages/About.jsx b/golden-crust-bakery/src/pages/About.jsx
index 033a62f..0038ef1 100644
--- a/golden-crust-bakery/src/pages/About.jsx
+++ b/golden-crust-bakery/src/pages/About.jsx
@@ -68,7 +68,7 @@ export default function About() {
           </div>
           <div className='relative h-[300px] md:h-[400px] rounded-2xl overflow-hidden shadow-xl order-1 md:order-2'>
             <img
-              src='https://img.freepik.com/free-photo/front-view-friends-barista_23-2148436154.jpg?t=st=1741677372~exp=1741680972~hmac=33f200e859f92c94f56e1621df35f78b4e9cf528dddab61882f34e9d1b4f1eb4&w=1800'
+              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'
               className='object-cover'
             />
@@ -161,19 +161,19 @@ export default function About() {
               name: 'Emma Thompson',
               role: 'Founder & Head Baker',
               bio: 'With over 15 years of experience in pastry arts, Emma leads our team with creativity and passion.',
-              img: 'https://img.freepik.com/free-photo/photo-pleasant-looking-girl-has-healthy-soft-skin-dark-staright-hair_273609-18461.jpg?t=st=1741686287~exp=1741689887~hmac=116de42822039e12d85f23e97e7f8847c335abe7a252dd0dadd666fd6f5b8ff2&w=2000',
+              img: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
             },
             {
               name: 'Michael Chen',
               role: 'Pastry Chef',
               bio: 'A graduate of Le Cordon Bleu, Michael specializes in intricate cake decorations and flavors.',
-              img: 'https://img.freepik.com/free-photo/indoor-picture-cheerful-handsome-young-man-having-folded-hands-looking-directly-smiling-sincerely-wearing-casual-clothes_176532-10257.jpg?t=st=1741686456~exp=1741690056~hmac=ca8481b6e3d92b08c8be06d103df4509d66d147af808c042bafa78e93558eda4&w=2000',
+              img: 'https://images.unsplash.com/photo-1568602471122-7832951cc4c5?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
             },
             {
               name: 'Sophia Rodriguez',
               role: 'Customer Experience Manager',
               bio: 'Sophia ensures that every customer receives exceptional service from order to delivery.',
-              img: 'https://img.freepik.com/free-photo/close-up-portrait-pretty-young-woman-isolated_273609-35589.jpg?t=st=1741686360~exp=1741689960~hmac=1bc751189360c60efb55cd9d98f940487a299bbef5ef9d7fdca0364e34b30dff&w=2000',
+              img: 'https://images.unsplash.com/photo-1512361436605-a484bdb34b5f?q=80&w=2940&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
             },
           ].map((member, index) => (
             <div
diff --git a/golden-crust-bakery/src/pages/Menu.jsx b/golden-crust-bakery/src/pages/Menu.jsx
index 3554c98..f848d65 100644
--- a/golden-crust-bakery/src/pages/Menu.jsx
+++ b/golden-crust-bakery/src/pages/Menu.jsx
@@ -7,6 +7,7 @@ import { cakeData } from '@/lib/data';
 
 import { Button } from '@/components/ui/button';
 import { Input } from '@/components/ui/input';
+import { Link } from 'react-router-dom';
 
 export default function Menu() {
   const { addToCart, initializeCart } = useCartStore();
@@ -65,7 +66,7 @@ export default function Menu() {
             placeholder='Search cakes...'
             value={searchTerm}
             onChange={(e) => setSearchTerm(e.target.value)}
-            className='pl-10 rounded-full border-pink-200 focus:border-pink-400 focus:ring-pink-400'
+            className='pl-10 rounded-full border-pink-200 focus-visible::border-pink-400 focus-visible:ring-pink-400'
           />
         </div>
         <div className='flex flex-wrap gap-2'>
@@ -92,9 +93,9 @@ export default function Menu() {
           {filteredCakes.map((cake) => (
             <div
               key={cake.id}
-              className='bg-white rounded-xl shadow-sm border overflow-hidden transition-transform hover:scale-105 hover:shadow-lg'
+              className='bg-white rounded-xl shadow-sm border overflow-hidden transition-transform hover:scale-105 hover:shadow-md'
             >
-              <div className='relative h-56'>
+              <Link to={`${cake.id}`} className='block relative h-56'>
                 <img
                   src={cake.image}
                   alt={cake.name}
@@ -103,7 +104,7 @@ export default function Menu() {
                 <div className='absolute top-2 right-2 bg-pink-100 text-pink-600 px-2 py-1 rounded-full text-xs font-medium'>
                   {cake.category}
                 </div>
-              </div>
+              </Link>
               <div className='p-6'>
                 <div className='flex justify-between items-start mb-2'>
                   <h3 className='text-xl font-semibold text-gray-800'>
diff --git a/golden-crust-bakery/src/pages/MenuItem.jsx b/golden-crust-bakery/src/pages/MenuItem.jsx
new file mode 100644
index 0000000..7c55a01
--- /dev/null
+++ b/golden-crust-bakery/src/pages/MenuItem.jsx
@@ -0,0 +1,242 @@
+import { useEffect, useState } from 'react';
+import { useLocation, useNavigate } from 'react-router-dom';
+import { toast } from 'sonner';
+import {
+  ArrowLeft,
+  Heart,
+  Minus,
+  Plus,
+  ShoppingCart,
+  Star,
+} from 'lucide-react';
+
+import { useCartStore } from '@/lib/store';
+import { cakeData } from '@/lib/data';
+
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+
+export default function MenuItem() {
+  const navigate = useNavigate();
+  const { addToCart, initializeCart } = useCartStore();
+  const [cake, setCake] = useState(null);
+  const [quantity, setQuantity] = useState(1);
+  const [isLoading, setIsLoading] = useState(true);
+
+  const cakeId = useLocation().pathname.split('/')[2];
+
+  useEffect(() => {
+    initializeCart();
+
+    // Find the cake by ID
+    const foundCake = cakeData.find((c) => c.id === Number.parseInt(cakeId));
+    if (foundCake) {
+      setCake(foundCake);
+    } else {
+      // Cake not found, redirect to menu
+      navigate('/menu');
+      toast({
+        title: 'Product Not Found',
+        description: "The cake you're looking for doesn't exist.",
+        variant: 'destructive',
+      });
+    }
+
+    setIsLoading(false);
+  }, [initializeCart, navigate, cakeId]);
+
+  const handleQuantityChange = (value) => {
+    if (quantity + value > 0) {
+      setQuantity(quantity + value);
+    }
+  };
+
+  const handleAddToCart = () => {
+    if (cake) {
+      addToCart({
+        ...cake,
+        quantity,
+      });
+
+      toast({
+        title: 'Added to cart!',
+        description: `${cake.name} (${quantity}) has been added to your cart.`,
+      });
+    }
+  };
+
+  // Related cakes (same category)
+  const relatedCakes = cake
+    ? cakeData
+        .filter((c) => c.category === cake.category && c.id !== cake.id)
+        .slice(0, 3)
+    : [];
+
+  if (isLoading) {
+    return (
+      <div className='container mx-auto px-4 py-16 text-center'>
+        <p>Loading product details...</p>
+      </div>
+    );
+  }
+
+  if (!cake) {
+    return null;
+  }
+
+  return (
+    <div className='container mx-auto px-4 py-8 md:py-12'>
+      <Button
+        variant='ghost'
+        onClick={() => navigate('/menu')}
+        className='mb-6 text-gray-600 hover:text-gray-800'
+      >
+        <ArrowLeft className='mr-2 h-4 w-4' /> Back to Menu
+      </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'>
+          <img
+            src={cake.image}
+            alt={cake.name}
+            className='object-cover h-full w-full'
+          />
+          <div className='absolute top-4 right-4 bg-pink-100 text-pink-600 px-3 py-1 rounded-full text-sm font-medium'>
+            {cake.category}
+          </div>
+        </div>
+
+        <div className='space-y-6'>
+          <div>
+            <h1 className='text-3xl md:text-4xl font-bold text-gray-800 mb-2'>
+              {cake.name}
+            </h1>
+            <div className='flex items-center mb-4'>
+              <div className='flex text-yellow-400 mr-2'>
+                {[...Array(5)].map((_, i) => (
+                  <Star key={i} className='h-5 w-5 fill-current' />
+                ))}
+              </div>
+              <span className='text-gray-600'>(24 reviews)</span>
+            </div>
+            <p className='text-2xl font-bold text-pink-600 mb-4'>
+              ${cake.price.toFixed(2)}
+            </p>
+            <p className='text-gray-600 mb-6'>{cake.description}</p>
+          </div>
+
+          <div className='border-t border-b border-gray-200 py-6'>
+            <div className='flex items-center mb-6'>
+              <span className='text-gray-700 mr-4'>Quantity:</span>
+              <div className='flex items-center'>
+                <Button
+                  variant='outline'
+                  size='icon'
+                  onClick={() => handleQuantityChange(-1)}
+                  disabled={quantity <= 1}
+                  className='h-10 w-10 rounded-full'
+                >
+                  <Minus className='h-4 w-4' />
+                </Button>
+                <span className='mx-4 text-lg font-medium w-8 text-center'>
+                  {quantity}
+                </span>
+                <Button
+                  variant='outline'
+                  size='icon'
+                  onClick={() => handleQuantityChange(1)}
+                  className='h-10 w-10 rounded-full'
+                >
+                  <Plus className='h-4 w-4' />
+                </Button>
+              </div>
+            </div>
+
+            <div className='flex flex-col sm:flex-row gap-4'>
+              <Button
+                onClick={handleAddToCart}
+                className='bg-pink-500 hover:bg-pink-600 text-white rounded-full py-6 flex-1'
+              >
+                <ShoppingCart className='mr-2 h-5 w-5' /> Add to Cart
+              </Button>
+              <Button
+                variant='outline'
+                className='border-pink-300 text-pink-600 hover:bg-pink-50 rounded-full py-6'
+              >
+                <Heart className='mr-2 h-5 w-5' /> Add to Wishlist
+              </Button>
+            </div>
+          </div>
+
+          <div className='space-y-4'>
+            <h3 className='text-lg font-semibold text-gray-800'>
+              Product Details
+            </h3>
+            <div className='grid grid-cols-2 gap-4'>
+              <div>
+                <p className='text-gray-600 font-medium'>Category</p>
+                <p className='text-gray-800'>{cake.category}</p>
+              </div>
+              <div>
+                <p className='text-gray-600 font-medium'>Serves</p>
+                <p className='text-gray-800'>8-10 people</p>
+              </div>
+              <div>
+                <p className='text-gray-600 font-medium'>Allergens</p>
+                <p className='text-gray-800'>Eggs, Dairy, Wheat</p>
+              </div>
+              <div>
+                <p className='text-gray-600 font-medium'>Storage</p>
+                <p className='text-gray-800'>Refrigerate</p>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      {/* Related Products */}
+      {relatedCakes.length > 0 && (
+        <div className='mt-16'>
+          <h2 className='text-2xl font-bold text-gray-800 mb-6'>
+            You May Also Like
+          </h2>
+          <div className='grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6'>
+            {relatedCakes.map((relatedCake) => (
+              <Card
+                key={relatedCake.id}
+                className='overflow-hidden shadow-sm transition-transform hover:scale-105 hover:shadow-md'
+              >
+                <div className='relative h-56'>
+                  <img
+                    src={relatedCake.image}
+                    alt={relatedCake.name}
+                    className='object-cover h-full w-full'
+                  />
+                </div>
+                <CardContent className='p-6'>
+                  <div className='flex justify-between items-start mb-2'>
+                    <h3 className='text-xl font-semibold text-gray-800'>
+                      {relatedCake.name}
+                    </h3>
+                    <p className='text-pink-600 font-bold'>
+                      ${relatedCake.price.toFixed(2)}
+                    </p>
+                  </div>
+                  <p className='text-gray-600 mb-4 line-clamp-2'>
+                    {relatedCake.description}
+                  </p>
+                  <Button
+                    onClick={() => navigate(`/menu/${relatedCake.id}`)}
+                    className='w-full bg-yellow-100 hover:bg-yellow-200 text-yellow-800 rounded-full'
+                  >
+                    View Details
+                  </Button>
+                </CardContent>
+              </Card>
+            ))}
+          </div>
+        </div>
+      )}
+    </div>
+  );
+}
-- 
GitLab