From 543533d6e050ce512bd47e2ff16185a9dc963c6a Mon Sep 17 00:00:00 2001 From: Amritanshu Date: Fri, 18 Oct 2024 22:20:55 +0530 Subject: [PATCH] Show 3 random products in product page Products page shows 4 columns in large Proper animated underline Resized category slider --- .../products/[handle]/ProductDetails.tsx | 23 +++++---- .../(storefront)/products/[handle]/page.tsx | 48 ++++++++++++++++++- src/app/(storefront)/products/page.tsx | 11 ++--- src/app/globals.css | 23 +++++++++ src/components/category-slider.module.css | 3 +- src/components/category-slider.tsx | 10 ++-- src/components/footer.tsx | 16 +++---- src/components/navbar.tsx | 4 +- 8 files changed, 105 insertions(+), 33 deletions(-) diff --git a/src/app/(storefront)/products/[handle]/ProductDetails.tsx b/src/app/(storefront)/products/[handle]/ProductDetails.tsx index fceb6cb..8fdccd3 100644 --- a/src/app/(storefront)/products/[handle]/ProductDetails.tsx +++ b/src/app/(storefront)/products/[handle]/ProductDetails.tsx @@ -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(); @@ -101,9 +102,11 @@ export default function ProductDetails({ product }: ProductDetailsProps) { {product.material && (

Ingredients

-

+ {/*

*/} +

{product.material} - + {/* */} + {/* */}

)} @@ -119,19 +122,19 @@ export default function ProductDetails({ product }: ProductDetailsProps) {

Related Products

- {[...Array(3)].map((_, i) => ( - + {relatedProducts.map((rel) => ( + {`Related -

Milk Chocolate Truffles

-

Smooth, creamy milk chocolate truffles

-

₹{selectedVariant?.calculated_price?.calculated_amount?.toFixed(2)}

+

{rel.title}

+

{rel.subtitle}

+

₹{rel.variants?.[0].calculated_price?.calculated_amount?.toFixed(2)}

))} diff --git a/src/app/(storefront)/products/[handle]/page.tsx b/src/app/(storefront)/products/[handle]/page.tsx index 0f59a79..fef2077 100644 --- a/src/app/(storefront)/products/[handle]/page.tsx +++ b/src/app/(storefront)/products/[handle]/page.tsx @@ -2,6 +2,13 @@ import { HttpTypes } from '@medusajs/types'; import ProductDetails from './ProductDetails'; +function shuffleArray(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 - + ); } diff --git a/src/app/(storefront)/products/page.tsx b/src/app/(storefront)/products/page.tsx index fc5ee79..8d65b28 100644 --- a/src/app/(storefront)/products/page.tsx +++ b/src/app/(storefront)/products/page.tsx @@ -30,12 +30,12 @@ async function fetchProducts() { export default async function ProductsPage() { const products: HttpTypes.StoreProduct[] = await fetchProducts(); return ( -
+
{/* Products Grid */}
-
-

{product.title as string}

-

Category: {product.title as string}

-

+

+

{product.title as string}

+

Price: ₹{product.variants?.[0].calculated_price?.calculated_amount?.toFixed(2)}

diff --git a/src/app/globals.css b/src/app/globals.css index 4fa8b1d..ebffe3c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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); +} diff --git a/src/components/category-slider.module.css b/src/components/category-slider.module.css index 4f8ff39..9edd29d 100644 --- a/src/components/category-slider.module.css +++ b/src/components/category-slider.module.css @@ -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 */ diff --git a/src/components/category-slider.tsx b/src/components/category-slider.tsx index 7e6da69..01d417f 100644 --- a/src/components/category-slider.tsx +++ b/src/components/category-slider.tsx @@ -20,13 +20,13 @@ export function ChocolateCategories() { const finishClass = 'translate-x-0 delay-300'; return ( -
-
    +
    +
      {categories.map((category) => ( 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() { Chocolate Category diff --git a/src/components/footer.tsx b/src/components/footer.tsx index b2b574b..78a6808 100644 --- a/src/components/footer.tsx +++ b/src/components/footer.tsx @@ -12,7 +12,7 @@ export function Footer() { {/* First Column: Logo and Description */}
      Mozimo Logo -

      India's Premier European style bean-to-bar chocolate experience.

      +

      India's Premier European style bean-to-bar chocolate experience.

      {/* Second Column: Contact Information */} @@ -33,22 +33,22 @@ export function Footer() {

      Know more