Skip to content
Snippets Groups Projects
Commit 3067091f authored by ALMAZROUEI Shamma (2021) WKIS203's avatar ALMAZROUEI Shamma (2021) WKIS203
Browse files

Build menu item page

parent f729ccbd
Branches
No related tags found
No related merge requests found
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 }
......@@ -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,
......
......@@ -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 />,
},
],
},
]);
......
......@@ -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
......
......@@ -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'>
......
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>
);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment