Show 3 random products in product page

Products page shows 4 columns in large
Proper animated underline
Resized category slider
This commit is contained in:
2024-10-18 22:20:55 +05:30
parent 7e1fa65cba
commit 543533d6e0
8 changed files with 105 additions and 33 deletions

View File

@ -13,9 +13,10 @@ import { useCart } from '@/providers/cart';
interface ProductDetailsProps { interface ProductDetailsProps {
product: HttpTypes.StoreProduct; product: HttpTypes.StoreProduct;
relatedProducts: HttpTypes.StoreProduct[];
} }
export default function ProductDetails({ product }: ProductDetailsProps) { export default function ProductDetails({ product, relatedProducts }: ProductDetailsProps) {
const [quantity, setQuantity] = useState(1); const [quantity, setQuantity] = useState(1);
const [selectedVariant, setSelectedVariant] = useState<HttpTypes.StoreProductVariant>(); const [selectedVariant, setSelectedVariant] = useState<HttpTypes.StoreProductVariant>();
@ -101,9 +102,11 @@ export default function ProductDetails({ product }: ProductDetailsProps) {
{product.material && ( {product.material && (
<div className='mt-6'> <div className='mt-6'>
<h2 className='text-lg font-medium mb-4'>Ingredients</h2> <h2 className='text-lg font-medium mb-4'>Ingredients</h2>
<p className='text-gray-600 relative overflow-hidden group'> {/* <p className='text-gray-600 relative overflow-hidden group'> */}
<p className='text-gray-600 animated-underline'>
{product.material} {product.material}
<span className='absolute bottom-0 left-0 h-[2px] bg-black w-full transform -translate-x-full transition-transform duration-500 group-hover:translate-x-0'></span> {/* <span className='absolute bottom-0 left-0 h-[2px] bg-black w-full transform -translate-x-full transition-transform duration-500 group-hover:translate-x-0'></span> */}
{/* <span className='absolute bottom-0 left-0 h-[2px] bg-black origin-right w-0 group-hover:w-full transition-all duration-500'></span> */}
</p> </p>
</div> </div>
)} )}
@ -119,19 +122,19 @@ export default function ProductDetails({ product }: ProductDetailsProps) {
<div className='mt-12'> <div className='mt-12'>
<h2 className='text-2xl font-bold mb-4'>Related Products</h2> <h2 className='text-2xl font-bold mb-4'>Related Products</h2>
<div className='grid grid-cols-1 md:grid-cols-3 gap-6'> <div className='grid grid-cols-1 md:grid-cols-3 gap-6'>
{[...Array(3)].map((_, i) => ( {relatedProducts.map((rel) => (
<Card key={i}> <Card key={rel.id}>
<CardContent className='p-4'> <CardContent className='p-4'>
<Image <Image
width={200} width={200}
height={192} height={192}
src={product.images?.[0].url as string} src={rel.images?.[0].url as string}
alt={`Related product ${i + 1}`} alt={`Related product ${rel.title}`}
className='w-full h-48 object-cover mb-4 rounded' className='w-full h-48 object-cover mb-4 rounded'
/> />
<h3 className='font-bold mb-2'>Milk Chocolate Truffles</h3> <h3 className='font-bold mb-2'>{rel.title}</h3>
<p className='text-gray-600 mb-2'>Smooth, creamy milk chocolate truffles</p> <p className='text-gray-600 mb-2'>{rel.subtitle}</p>
<p className='font-bold'>{selectedVariant?.calculated_price?.calculated_amount?.toFixed(2)}</p> <p className='font-bold'>{rel.variants?.[0].calculated_price?.calculated_amount?.toFixed(2)}</p>
</CardContent> </CardContent>
</Card> </Card>
))} ))}

View File

@ -2,6 +2,13 @@ import { HttpTypes } from '@medusajs/types';
import ProductDetails from './ProductDetails'; import ProductDetails from './ProductDetails';
function shuffleArray<T>(array: T[]) {
for (let i = array.length - 1; i >= 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
interface ProductPageProps { interface ProductPageProps {
params: { params: {
handle: string; handle: string;
@ -45,13 +52,52 @@ async function fetchProduct(handle: string, region: HttpTypes.StoreRegion | unde
} }
} }
async function fetchProducts(region: HttpTypes.StoreRegion | undefined, count: number) {
try {
const params = new URLSearchParams({
fields: `*variants.prices.*`,
region_id: region?.id || (process.env.NEXT_PUBLIC_DEFAULT_REGION_ID as string),
});
const response = await fetch(`${process.env.NEXT_PRIVATE_BASE_URL}/store/products?${params}`, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'x-publishable-api-key': process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY as string,
},
next: { revalidate: 0 },
});
if (!response.ok) {
console.error('Failed to fetch product:', response.statusText);
return null;
}
const data = await response.json();
if (!data.products || !data.products.length) {
console.error('No products found');
return null;
}
shuffleArray(data.products);
if (count > 0) {
return data.products.splice(0, count);
} else {
return data.products;
}
} catch (error) {
console.error('Error fetching product:', error);
return null;
}
}
export default async function ProductPage({ params }: ProductPageProps) { export default async function ProductPage({ params }: ProductPageProps) {
// const { region } = useRegion(); // const { region } = useRegion();
const { handle } = params; // Extract the handle from the params const { handle } = params; // Extract the handle from the params
const product: HttpTypes.StoreProduct = await fetchProduct(handle, undefined); const product: HttpTypes.StoreProduct = await fetchProduct(handle, undefined);
const products: HttpTypes.StoreProduct[] = await fetchProducts(undefined, 3);
return ( return (
// Render the client component and pass necessary props // Render the client component and pass necessary props
<ProductDetails product={product} /> <ProductDetails product={product} relatedProducts={products} />
); );
} }

View File

@ -30,12 +30,12 @@ async function fetchProducts() {
export default async function ProductsPage() { export default async function ProductsPage() {
const products: HttpTypes.StoreProduct[] = await fetchProducts(); const products: HttpTypes.StoreProduct[] = await fetchProducts();
return ( return (
<div className='container mx-auto py-12 pt-28'> <div className='py-12 pt-28'>
{/* Products Grid */} {/* Products Grid */}
<div <div
className='grid gap-8 mb-8' className='grid gap-8 mb-8'
style={{ style={{
gridTemplateColumns: 'repeat(auto-fit, clamp(200px, 20vw, 300px))', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
marginLeft: 'clamp(10px, 5vw, 30px)', marginLeft: 'clamp(10px, 5vw, 30px)',
marginRight: 'clamp(10px, 5vw, 30px)', marginRight: 'clamp(10px, 5vw, 30px)',
}} }}
@ -56,10 +56,9 @@ export default async function ProductsPage() {
Add to Cart Add to Cart
</button> </button>
</div> </div>
<div className='p-4 text-left flex-shrink-0'> <div className='text-left flex-shrink-0'>
<h3 className='text-lg font-bold mb-2'>{product.title as string}</h3> <h3 className='text-lg'>{product.title as string}</h3>
<p className='text-sm text-gray-600 mb-1'>Category: {product.title as string}</p> <p className='text-sm text-gray-800 mb-4'>
<p className='text-sm text-gray-800 font-semibold mb-4'>
Price: {product.variants?.[0].calculated_price?.calculated_amount?.toFixed(2)} Price: {product.variants?.[0].calculated_price?.calculated_amount?.toFixed(2)}
</p> </p>
</div> </div>

View File

@ -218,3 +218,26 @@ animation-range-end: contain; */
.pillLink:hover { .pillLink:hover {
@apply shadow-lg transform duration-300 transition-transform ease-in-out; @apply shadow-lg transform duration-300 transition-transform ease-in-out;
} }
.animated-underline {
position: relative;
/* cursor: pointer; */
background-image: linear-gradient(180deg, currentColor, currentColor);
background-size: 100% 1px;
background-origin: content-box;
background-repeat: no-repeat;
background-position: 0 100%;
display: inline;
background-size: 0 1px;
background-position: 100% calc(100% + 0.2rem);
line-height: 1;
padding-bottom: 0.2rem;
transition: background-size 0.8s cubic-bezier(0.165, 0.84, 0.44, 1);
}
/* On hover: expand the underline from left to right */
.animated-underline:hover {
background-size: 100% 1px;
background-position: 0 calc(100% + 0.2rem);
}

View File

@ -1,6 +1,7 @@
.productImage { .productImage {
width: 100px; /* Set the width of the image */ width: 60px; /* Set the width of the image */
aspect-ratio: 1; aspect-ratio: 1;
height: 60px;
border-radius: 50%; /* Make the image circular */ border-radius: 50%; /* Make the image circular */
object-fit: cover; /* Ensure the image covers the container */ object-fit: cover; /* Ensure the image covers the container */
margin-right: 1.5rem; /* Space between image and product name */ margin-right: 1.5rem; /* Space between image and product name */

View File

@ -20,13 +20,13 @@ export function ChocolateCategories() {
const finishClass = 'translate-x-0 delay-300'; const finishClass = 'translate-x-0 delay-300';
return ( return (
<section className='flex flex-col md:flex-row bg-white py-12 px-4 sm:px-6 lg:px-8 h-[90vh]'> <section className='flex flex-col md:flex-row bg-white py-12 px-4 sm:px-6 lg:px-8'>
<ul className='w-full md:w-1/2 bg-white h-auto m-8 text-[#5a352a] text-left'> <ul className='w-full md:w-1/2 bg-white h-auto m-8 text-[#5a352a] text-left flex flex-col justify-between'>
{categories.map((category) => ( {categories.map((category) => (
<AnimatedLi <AnimatedLi
key={category.name} key={category.name}
onMouseEnter={() => setCurrentImage(category.image)} onMouseEnter={() => setCurrentImage(category.image)}
className='hover:text-[#c19a6b] cursor-pointer font-montera font-normal text-s-headline' className='flex hover:text-[#c19a6b] cursor-pointer font-montera font-normal text-s-title md:text-6xl'
startClass={startClass} startClass={startClass}
finishClass={finishClass} finishClass={finishClass}
> >
@ -41,8 +41,8 @@ export function ChocolateCategories() {
<Image <Image
src={currentImage} src={currentImage}
alt='Chocolate Category' alt='Chocolate Category'
width={300} width={250}
height={500} height={400}
className='w-full h-auto' className='w-full h-auto'
sizes='100vw' sizes='100vw'
/> />

View File

@ -12,7 +12,7 @@ export function Footer() {
{/* First Column: Logo and Description */} {/* First Column: Logo and Description */}
<div className='space-y-4 flex flex-col'> <div className='space-y-4 flex flex-col'>
<Image src={logoPic} alt='Mozimo Logo' width={100} height={100} className='h-full logo' /> <Image src={logoPic} alt='Mozimo Logo' width={100} height={100} className='h-full logo' />
<p className='text-sm'>India&apos;s Premier European style bean-to-bar chocolate experience.</p> <p className='text-base'>India&apos;s Premier European style bean-to-bar chocolate experience.</p>
</div> </div>
{/* Second Column: Contact Information */} {/* Second Column: Contact Information */}
@ -33,22 +33,22 @@ export function Footer() {
<h3 className='font-semibold'>Know more</h3> <h3 className='font-semibold'>Know more</h3>
<ul className='space-y-2'> <ul className='space-y-2'>
<li> <li>
<a href='/privacy-policy' className='text-sm text-brown-600 hover:underline'> <a href='/privacy-policy' className='text-sm text-brown-600 animated-underline'>
Privacy Policy Privacy Policy
</a> </a>
</li> </li>
<li> <li>
<a href='/refund-policy' className='text-sm text-brown-600 hover:underline'> <a href='/refund-policy' className='text-sm text-brown-600 animated-underline'>
Refund & Return Policy Refund & Return Policy
</a> </a>
</li> </li>
<li> <li>
<a href='/terms' className='text-sm text-brown-600 hover:underline'> <a href='/terms' className='text-sm text-brown-600 animated-underline'>
Terms & Conditions Terms & Conditions
</a> </a>
</li> </li>
<li> <li>
<a href='/shipping' className='text-sm text-brown-600 hover:underline'> <a href='/shipping' className='text-sm text-brown-600 animated-underline'>
Shipping Policy Shipping Policy
</a> </a>
</li> </li>
@ -59,17 +59,17 @@ export function Footer() {
<div className='space-y-4 flex flex-col'> <div className='space-y-4 flex flex-col'>
<ul className='space-y-2'> <ul className='space-y-2'>
<li> <li>
<a href='/faq' className='text-sm text-brown-600 hover:underline'> <a href='/faq' className='text-sm text-brown-600 animated-underline'>
FAQ FAQ
</a> </a>
</li> </li>
<li> <li>
<a href='/locate' className='text-sm text-brown-600 hover:underline'> <a href='/locate' className='text-sm text-brown-600 animated-underline'>
Locate our Store Locate our Store
</a> </a>
</li> </li>
<li> <li>
<a href='/bulk-ordering' className='text-sm text-brown-600 hover:underline'> <a href='/bulk-ordering' className='text-sm text-brown-600 animated-underline'>
Bulk Ordering Bulk Ordering
</a> </a>
</li> </li>

View File

@ -69,10 +69,10 @@ export function Navbar() {
<nav data-visible={isVisible} className={navClasses}> <nav data-visible={isVisible} className={navClasses}>
{/* Left side */} {/* Left side */}
<div> <div>
<Link href='/about-us' className='font-light hover:underline'> <Link href='/about-us' className='font-light animated-underline'>
About Us About Us
</Link> </Link>
<Link href='/products' className='font-light hover:underline'> <Link href='/products' className='font-light animated-underline'>
Shop Shop
</Link> </Link>
</div> </div>