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:
Amritanshu Agrawal 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 {
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 [selectedVariant, setSelectedVariant] = useState<HttpTypes.StoreProductVariant>();
@ -101,9 +102,11 @@ export default function ProductDetails({ product }: ProductDetailsProps) {
{product.material && (
<div className='mt-6'>
<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}
<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>
</div>
)}
@ -119,19 +122,19 @@ export default function ProductDetails({ product }: ProductDetailsProps) {
<div className='mt-12'>
<h2 className='text-2xl font-bold mb-4'>Related Products</h2>
<div className='grid grid-cols-1 md:grid-cols-3 gap-6'>
{[...Array(3)].map((_, i) => (
<Card key={i}>
{relatedProducts.map((rel) => (
<Card key={rel.id}>
<CardContent className='p-4'>
<Image
width={200}
height={192}
src={product.images?.[0].url as string}
alt={`Related product ${i + 1}`}
src={rel.images?.[0].url as string}
alt={`Related product ${rel.title}`}
className='w-full h-48 object-cover mb-4 rounded'
/>
<h3 className='font-bold mb-2'>Milk Chocolate Truffles</h3>
<p className='text-gray-600 mb-2'>Smooth, creamy milk chocolate truffles</p>
<p className='font-bold'>{selectedVariant?.calculated_price?.calculated_amount?.toFixed(2)}</p>
<h3 className='font-bold mb-2'>{rel.title}</h3>
<p className='text-gray-600 mb-2'>{rel.subtitle}</p>
<p className='font-bold'>{rel.variants?.[0].calculated_price?.calculated_amount?.toFixed(2)}</p>
</CardContent>
</Card>
))}

View File

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

View File

@ -218,3 +218,26 @@ animation-range-end: contain; */
.pillLink:hover {
@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 {
width: 100px; /* Set the width of the image */
width: 60px; /* Set the width of the image */
aspect-ratio: 1;
height: 60px;
border-radius: 50%; /* Make the image circular */
object-fit: cover; /* Ensure the image covers the container */
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';
return (
<section className='flex flex-col md:flex-row bg-white py-12 px-4 sm:px-6 lg:px-8 h-[90vh]'>
<ul className='w-full md:w-1/2 bg-white h-auto m-8 text-[#5a352a] text-left'>
<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 flex flex-col justify-between'>
{categories.map((category) => (
<AnimatedLi
key={category.name}
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}
finishClass={finishClass}
>
@ -41,8 +41,8 @@ export function ChocolateCategories() {
<Image
src={currentImage}
alt='Chocolate Category'
width={300}
height={500}
width={250}
height={400}
className='w-full h-auto'
sizes='100vw'
/>

View File

@ -12,7 +12,7 @@ export function Footer() {
{/* First Column: Logo and Description */}
<div className='space-y-4 flex flex-col'>
<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>
{/* Second Column: Contact Information */}
@ -33,22 +33,22 @@ export function Footer() {
<h3 className='font-semibold'>Know more</h3>
<ul className='space-y-2'>
<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
</a>
</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
</a>
</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
</a>
</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
</a>
</li>
@ -59,17 +59,17 @@ export function Footer() {
<div className='space-y-4 flex flex-col'>
<ul className='space-y-2'>
<li>
<a href='/faq' className='text-sm text-brown-600 hover:underline'>
<a href='/faq' className='text-sm text-brown-600 animated-underline'>
FAQ
</a>
</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
</a>
</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
</a>
</li>

View File

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