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:
parent
7e1fa65cba
commit
543533d6e0
@ -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>
|
||||
))}
|
||||
|
@ -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} />
|
||||
);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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'
|
||||
/>
|
||||
|
@ -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's Premier European style bean-to-bar chocolate experience.</p>
|
||||
<p className='text-base'>India'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>
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user