From 8d70c0cdb5f21676c2ecba83206a07bb54b5f675 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands <victorgerbrands@gmail.com> Date: Wed, 10 May 2023 16:47:59 +0200 Subject: [PATCH 1/3] feat: add categories and menus --- app/search/[collection]/page.tsx | 8 ++- app/sitemap.ts | 4 +- components/carousel.tsx | 4 +- components/grid/three-items.tsx | 4 +- components/layout/footer.tsx | 4 +- components/layout/navbar/index.tsx | 4 +- components/layout/search/collections.tsx | 4 +- lib/medusa/index.ts | 92 +++++++++++++++++------- lib/medusa/types.ts | 5 +- 9 files changed, 88 insertions(+), 41 deletions(-) diff --git a/app/search/[collection]/page.tsx b/app/search/[collection]/page.tsx index 60f73f280..85d7726fc 100644 --- a/app/search/[collection]/page.tsx +++ b/app/search/[collection]/page.tsx @@ -1,4 +1,4 @@ -import { getCollection, getCollectionProducts } from 'lib/medusa'; +import { getCategory, getCategoryProducts } from 'lib/medusa'; import { Metadata } from 'next'; import { notFound } from 'next/navigation'; @@ -12,7 +12,8 @@ export async function generateMetadata({ }: { params: { collection: string }; }): Promise<Metadata> { - const collection = await getCollection(params.collection); + console.log({ params }); + const collection = await getCategory(params.collection); if (!collection) return notFound(); @@ -33,7 +34,8 @@ export async function generateMetadata({ } export default async function CategoryPage({ params }: { params: { collection: string } }) { - const products = await getCollectionProducts(params.collection); + console.log({ collection: params.collection }); + const products = await getCategoryProducts(params.collection); return ( <section> diff --git a/app/sitemap.ts b/app/sitemap.ts index f2789dba2..5c079a789 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,4 +1,4 @@ -import { getCollections, getProducts } from 'lib/medusa'; +import { getCategories, getProducts } from 'lib/medusa'; import { MetadataRoute } from 'next'; const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL @@ -11,7 +11,7 @@ export default async function sitemap(): Promise<Promise<Promise<MetadataRoute.S lastModified: new Date().toISOString() })); - const collections = await getCollections(); + const collections = await getCategories(); const collectionsMap = collections.map((collection) => ({ url: `${baseUrl}${collection.path}`, lastModified: collection.updatedAt diff --git a/components/carousel.tsx b/components/carousel.tsx index a1ddcfda7..669ef15f1 100644 --- a/components/carousel.tsx +++ b/components/carousel.tsx @@ -1,10 +1,10 @@ -import { getCollectionProducts } from 'lib/medusa'; +import { getCategoryProducts } from 'lib/medusa'; import Image from 'next/image'; import Link from 'next/link'; export async function Carousel() { // Collections that start with `hidden-*` are hidden from the search page. - const products = await getCollectionProducts('hidden-homepage-carousel'); + const products = await getCategoryProducts('hidden-homepage-carousel'); if (!products?.length) return null; diff --git a/components/grid/three-items.tsx b/components/grid/three-items.tsx index 8bbf671e6..1cc93b271 100644 --- a/components/grid/three-items.tsx +++ b/components/grid/three-items.tsx @@ -1,5 +1,5 @@ import { GridTileImage } from 'components/grid/tile'; -import { getCollectionProducts } from 'lib/medusa'; +import { getCategoryProducts } from 'lib/medusa'; import type { Product } from 'lib/medusa/types'; import Link from 'next/link'; @@ -37,7 +37,7 @@ function ThreeItemGridItem({ export async function ThreeItemGrid() { // Collections that start with `hidden-*` are hidden from the search page. - const homepageItems = await getCollectionProducts('hidden-homepage-featured-items'); + const homepageItems = await getCategoryProducts('hidden-homepage-featured-items'); if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null; diff --git a/components/layout/footer.tsx b/components/layout/footer.tsx index d9d8afb7e..e6a68febd 100644 --- a/components/layout/footer.tsx +++ b/components/layout/footer.tsx @@ -3,6 +3,7 @@ import Link from 'next/link'; import GitHubIcon from 'components/icons/github'; import LogoIcon from 'components/icons/logo'; import VercelIcon from 'components/icons/vercel'; +import { getMenu } from 'lib/medusa'; import { Menu } from 'lib/medusa/types'; const { SITE_NAME } = process.env; @@ -10,8 +11,7 @@ const { SITE_NAME } = process.env; export default async function Footer() { const currentYear = new Date().getFullYear(); const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : ''); - // const menu = await getMenu('next-js-frontend-footer-menu'); - const menu: any[] = []; + const menu = await getMenu('next-js-frontend-footer-menu'); return ( <footer className="border-t border-gray-700 bg-white text-black dark:bg-black dark:text-white"> diff --git a/components/layout/navbar/index.tsx b/components/layout/navbar/index.tsx index 554da5180..f6c121514 100644 --- a/components/layout/navbar/index.tsx +++ b/components/layout/navbar/index.tsx @@ -3,14 +3,14 @@ import Link from 'next/link'; import Cart from 'components/cart'; import CartIcon from 'components/icons/cart'; import LogoIcon from 'components/icons/logo'; +import { getMenu } from 'lib/medusa'; import { Menu } from 'lib/medusa/types'; import { Suspense } from 'react'; import MobileMenu from './mobile-menu'; import Search from './search'; export default async function Navbar() { - const menu: any[] = []; - // const menu = await getMenu('next-js-frontend-header-menu'); + const menu = await getMenu('next-js-frontend-header-menu'); return ( <nav className="relative flex items-center justify-between bg-white p-4 dark:bg-black lg:px-6"> diff --git a/components/layout/search/collections.tsx b/components/layout/search/collections.tsx index 5f3f8920e..33e9706bf 100644 --- a/components/layout/search/collections.tsx +++ b/components/layout/search/collections.tsx @@ -1,11 +1,11 @@ import clsx from 'clsx'; import { Suspense } from 'react'; -import { getCollections } from 'lib/medusa'; +import { getCategories } from 'lib/medusa'; import FilterList from './filter'; async function CollectionList() { - const collections = await getCollections(); + const collections = await getCategories(); return <FilterList list={collections} title="Collections" />; } diff --git a/lib/medusa/index.ts b/lib/medusa/index.ts index cd994ce7d..acccee51e 100644 --- a/lib/medusa/index.ts +++ b/lib/medusa/index.ts @@ -12,6 +12,7 @@ import { MedusaProductOption, MedusaProductVariant, Product, + ProductCategory, ProductCollection, ProductOption, ProductVariant, @@ -184,6 +185,7 @@ const reshapeProduct = (product: MedusaProduct): Product => { altText: product.images?.[0]?.id ?? '' }; const availableForSale = product.variants?.[0]?.purchasable || true; + const variants = product.variants.map((variant) => reshapeProductVariant(variant, product.options) ); @@ -243,18 +245,40 @@ const reshapeProductVariant = ( }; const reshapeCollection = (collection: MedusaProductCollection): ProductCollection => { - const description = collection.metadata?.description?.toString() ?? ''; + const description = collection.description || collection.metadata?.description?.toString() || ''; const seo = { - title: collection?.metadata?.seo_title?.toString() ?? '', - description: collection?.metadata?.seo_description?.toString() ?? '' + title: collection?.metadata?.seo_title?.toString() || collection.title || '', + description: collection?.metadata?.seo_description?.toString() || collection.description || '' }; const path = `/${collection.handle}`; const updatedAt = collection.updated_at; + const title = collection.name; return { ...collection, description, seo, + title, + path, + updatedAt + }; +}; + +const reshapeCategory = (category: ProductCategory): ProductCollection => { + const description = category.description || category.metadata?.description?.toString() || ''; + const seo = { + title: category?.metadata?.seo_title?.toString() || category.name || '', + description: category?.metadata?.seo_description?.toString() || category.description || '' + }; + const path = `/search/${category.handle}`; + const updatedAt = category.updated_at; + const title = category.name; + + return { + ...category, + description, + seo, + title, path, updatedAt }; @@ -302,42 +326,40 @@ export async function getCart(cartId: string): Promise<Cart | null> { return reshapeCart(cart); } -export async function getCollection(handle: string): Promise<ProductCollection | undefined> { - const res = await medusaRequest('GET', `/collections?handle[]=${handle}&limit=1`); - return res.body.collections[0]; +export async function getCategories(): Promise<ProductCollection[]> { + const res = await medusaRequest('GET', '/product-categories'); + + // Reshape categories and hide categories starting with 'hidden' + const categories = res.body.product_categories + .map((collection: ProductCategory) => reshapeCategory(collection)) + .filter((collection: MedusaProductCollection) => !collection.handle.startsWith('hidden')); + + return categories; } -export async function getCollectionProducts(handle: string): Promise<Product[]> { - const collection = await getCollection(handle); +export async function getCategory(handle: string): Promise<ProductCollection | undefined> { + const res = await medusaRequest('GET', `/product-categories?handle=${handle}&expand=products`); + return res.body.product_categories[0]; +} - if (!collection) { +export async function getCategoryProducts(handle: string): Promise<Product[]> { + const res = await medusaRequest('GET', `/product-categories?handle=${handle}`); + + if (!res) { return []; } - const res = await medusaRequest('GET', `/products?collection_id[]=${collection.id}`); + const category = res.body.product_categories[0]; - if (!res.body?.products) { - return []; - } + const category_products = await medusaRequest('GET', `/products?category_id[]=${category.id}`); - const products: Product[] = res.body.products.map((product: MedusaProduct) => + const products: Product[] = category_products.body.products.map((product: MedusaProduct) => reshapeProduct(product) ); return products; } -export async function getCollections(): Promise<ProductCollection[]> { - const res = await medusaRequest('GET', '/collections'); - - // Reshape collections and hide collections starting with 'hidden' - const collections = res.body.collections - .map((collection: MedusaProductCollection) => reshapeCollection(collection)) - .filter((collection: MedusaProductCollection) => !collection.handle.startsWith('hidden')); - - return collections; -} - export async function getProduct(handle: string): Promise<Product> { const res = await medusaRequest('GET', `/products?handle=${handle}&limit=1`); const product = res.body.products[0]; @@ -372,3 +394,23 @@ export async function getProducts({ return products; } + +export async function getMenu(menu: string): Promise<any[]> { + if (menu === 'next-js-frontend-header-menu') { + const categories = await getCategories(); + return categories.map((cat) => ({ + title: cat.title, + path: cat.path + })); + } + + if (menu === 'next-js-frontend-footer-menu') { + return [ + { title: 'About', path: 'https://medusajs.com/' }, + { title: 'Docs', path: 'https://docs.medusajs.com/' }, + { title: 'Blog', path: 'https://medusajs.com/blog' } + ]; + } + + return []; +} diff --git a/lib/medusa/types.ts b/lib/medusa/types.ts index 587c36a55..514654ae3 100644 --- a/lib/medusa/types.ts +++ b/lib/medusa/types.ts @@ -1,4 +1,6 @@ export type MedusaProductCollection = { + name: any; + description: string | undefined; id: string; title: string; handle: string; @@ -10,7 +12,6 @@ export type MedusaProductCollection = { }; export type ProductCollection = MedusaProductCollection & { - description?: string; seo?: { title?: string; description?: string; @@ -115,6 +116,7 @@ export type ShippingProfile = { export type ProductCategory = { id: string; name: string; + description: string; handle: string; mpath: string | null; is_internal?: boolean; @@ -126,6 +128,7 @@ export type ProductCategory = { products?: Product[]; created_at: string; updated_at: string; + metadata?: { [key: string]: string } | null; }; export type MedusaProductVariant = { From a05a6ac65c361f26af66873c86637c32b58674e6 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands <victorgerbrands@gmail.com> Date: Wed, 10 May 2023 16:51:43 +0200 Subject: [PATCH 2/3] fix: remove console.logs --- app/search/[collection]/page.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/search/[collection]/page.tsx b/app/search/[collection]/page.tsx index 85d7726fc..3f950b9d0 100644 --- a/app/search/[collection]/page.tsx +++ b/app/search/[collection]/page.tsx @@ -12,7 +12,6 @@ export async function generateMetadata({ }: { params: { collection: string }; }): Promise<Metadata> { - console.log({ params }); const collection = await getCategory(params.collection); if (!collection) return notFound(); @@ -34,7 +33,6 @@ export async function generateMetadata({ } export default async function CategoryPage({ params }: { params: { collection: string } }) { - console.log({ collection: params.collection }); const products = await getCategoryProducts(params.collection); return ( From 904f9d7ccee9acc39085fd95f7d0ee94d5026dd9 Mon Sep 17 00:00:00 2001 From: Victor Gerbrands <victorgerbrands@gmail.com> Date: Thu, 11 May 2023 11:10:34 +0200 Subject: [PATCH 3/3] chore: add aws host to next config --- next.config.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/next.config.js b/next.config.js index cdf28ca54..0848f2ab8 100644 --- a/next.config.js +++ b/next.config.js @@ -11,6 +11,11 @@ module.exports = { protocol: 'https', hostname: 'medusa-public-images.s3.eu-west-1.amazonaws.com', pathname: '/**' + }, + { + protocol: 'https', + hostname: 'medusa-server-testing.s3.amazonaws.com', + pathname: '/**' } ] }