mirror of
https://github.com/vercel/commerce.git
synced 2025-05-16 06:26:58 +00:00
feat: add categories and menus
This commit is contained in:
parent
52a1dddfc6
commit
8d70c0cdb5
@ -1,4 +1,4 @@
|
|||||||
import { getCollection, getCollectionProducts } from 'lib/medusa';
|
import { getCategory, getCategoryProducts } from 'lib/medusa';
|
||||||
import { Metadata } from 'next';
|
import { Metadata } from 'next';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
@ -12,7 +12,8 @@ export async function generateMetadata({
|
|||||||
}: {
|
}: {
|
||||||
params: { collection: string };
|
params: { collection: string };
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const collection = await getCollection(params.collection);
|
console.log({ params });
|
||||||
|
const collection = await getCategory(params.collection);
|
||||||
|
|
||||||
if (!collection) return notFound();
|
if (!collection) return notFound();
|
||||||
|
|
||||||
@ -33,7 +34,8 @@ export async function generateMetadata({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function CategoryPage({ params }: { params: { collection: string } }) {
|
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 (
|
return (
|
||||||
<section>
|
<section>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getCollections, getProducts } from 'lib/medusa';
|
import { getCategories, getProducts } from 'lib/medusa';
|
||||||
import { MetadataRoute } from 'next';
|
import { MetadataRoute } from 'next';
|
||||||
|
|
||||||
const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
|
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()
|
lastModified: new Date().toISOString()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const collections = await getCollections();
|
const collections = await getCategories();
|
||||||
const collectionsMap = collections.map((collection) => ({
|
const collectionsMap = collections.map((collection) => ({
|
||||||
url: `${baseUrl}${collection.path}`,
|
url: `${baseUrl}${collection.path}`,
|
||||||
lastModified: collection.updatedAt
|
lastModified: collection.updatedAt
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { getCollectionProducts } from 'lib/medusa';
|
import { getCategoryProducts } from 'lib/medusa';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
export async function Carousel() {
|
export async function Carousel() {
|
||||||
// Collections that start with `hidden-*` are hidden from the search page.
|
// 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;
|
if (!products?.length) return null;
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { GridTileImage } from 'components/grid/tile';
|
import { GridTileImage } from 'components/grid/tile';
|
||||||
import { getCollectionProducts } from 'lib/medusa';
|
import { getCategoryProducts } from 'lib/medusa';
|
||||||
import type { Product } from 'lib/medusa/types';
|
import type { Product } from 'lib/medusa/types';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ function ThreeItemGridItem({
|
|||||||
|
|
||||||
export async function ThreeItemGrid() {
|
export async function ThreeItemGrid() {
|
||||||
// Collections that start with `hidden-*` are hidden from the search page.
|
// 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;
|
if (!homepageItems[0] || !homepageItems[1] || !homepageItems[2]) return null;
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import Link from 'next/link';
|
|||||||
import GitHubIcon from 'components/icons/github';
|
import GitHubIcon from 'components/icons/github';
|
||||||
import LogoIcon from 'components/icons/logo';
|
import LogoIcon from 'components/icons/logo';
|
||||||
import VercelIcon from 'components/icons/vercel';
|
import VercelIcon from 'components/icons/vercel';
|
||||||
|
import { getMenu } from 'lib/medusa';
|
||||||
import { Menu } from 'lib/medusa/types';
|
import { Menu } from 'lib/medusa/types';
|
||||||
|
|
||||||
const { SITE_NAME } = process.env;
|
const { SITE_NAME } = process.env;
|
||||||
@ -10,8 +11,7 @@ const { SITE_NAME } = process.env;
|
|||||||
export default async function Footer() {
|
export default async function Footer() {
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
|
const copyrightDate = 2023 + (currentYear > 2023 ? `-${currentYear}` : '');
|
||||||
// const menu = await getMenu('next-js-frontend-footer-menu');
|
const menu = await getMenu('next-js-frontend-footer-menu');
|
||||||
const menu: any[] = [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="border-t border-gray-700 bg-white text-black dark:bg-black dark:text-white">
|
<footer className="border-t border-gray-700 bg-white text-black dark:bg-black dark:text-white">
|
||||||
|
@ -3,14 +3,14 @@ import Link from 'next/link';
|
|||||||
import Cart from 'components/cart';
|
import Cart from 'components/cart';
|
||||||
import CartIcon from 'components/icons/cart';
|
import CartIcon from 'components/icons/cart';
|
||||||
import LogoIcon from 'components/icons/logo';
|
import LogoIcon from 'components/icons/logo';
|
||||||
|
import { getMenu } from 'lib/medusa';
|
||||||
import { Menu } from 'lib/medusa/types';
|
import { Menu } from 'lib/medusa/types';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import MobileMenu from './mobile-menu';
|
import MobileMenu from './mobile-menu';
|
||||||
import Search from './search';
|
import Search from './search';
|
||||||
|
|
||||||
export default async function Navbar() {
|
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 (
|
return (
|
||||||
<nav className="relative flex items-center justify-between bg-white p-4 dark:bg-black lg:px-6">
|
<nav className="relative flex items-center justify-between bg-white p-4 dark:bg-black lg:px-6">
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
import { getCollections } from 'lib/medusa';
|
import { getCategories } from 'lib/medusa';
|
||||||
import FilterList from './filter';
|
import FilterList from './filter';
|
||||||
|
|
||||||
async function CollectionList() {
|
async function CollectionList() {
|
||||||
const collections = await getCollections();
|
const collections = await getCategories();
|
||||||
return <FilterList list={collections} title="Collections" />;
|
return <FilterList list={collections} title="Collections" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
MedusaProductOption,
|
MedusaProductOption,
|
||||||
MedusaProductVariant,
|
MedusaProductVariant,
|
||||||
Product,
|
Product,
|
||||||
|
ProductCategory,
|
||||||
ProductCollection,
|
ProductCollection,
|
||||||
ProductOption,
|
ProductOption,
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
@ -184,6 +185,7 @@ const reshapeProduct = (product: MedusaProduct): Product => {
|
|||||||
altText: product.images?.[0]?.id ?? ''
|
altText: product.images?.[0]?.id ?? ''
|
||||||
};
|
};
|
||||||
const availableForSale = product.variants?.[0]?.purchasable || true;
|
const availableForSale = product.variants?.[0]?.purchasable || true;
|
||||||
|
|
||||||
const variants = product.variants.map((variant) =>
|
const variants = product.variants.map((variant) =>
|
||||||
reshapeProductVariant(variant, product.options)
|
reshapeProductVariant(variant, product.options)
|
||||||
);
|
);
|
||||||
@ -243,18 +245,40 @@ const reshapeProductVariant = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const reshapeCollection = (collection: MedusaProductCollection): ProductCollection => {
|
const reshapeCollection = (collection: MedusaProductCollection): ProductCollection => {
|
||||||
const description = collection.metadata?.description?.toString() ?? '';
|
const description = collection.description || collection.metadata?.description?.toString() || '';
|
||||||
const seo = {
|
const seo = {
|
||||||
title: collection?.metadata?.seo_title?.toString() ?? '',
|
title: collection?.metadata?.seo_title?.toString() || collection.title || '',
|
||||||
description: collection?.metadata?.seo_description?.toString() ?? ''
|
description: collection?.metadata?.seo_description?.toString() || collection.description || ''
|
||||||
};
|
};
|
||||||
const path = `/${collection.handle}`;
|
const path = `/${collection.handle}`;
|
||||||
const updatedAt = collection.updated_at;
|
const updatedAt = collection.updated_at;
|
||||||
|
const title = collection.name;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...collection,
|
...collection,
|
||||||
description,
|
description,
|
||||||
seo,
|
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,
|
path,
|
||||||
updatedAt
|
updatedAt
|
||||||
};
|
};
|
||||||
@ -302,42 +326,40 @@ export async function getCart(cartId: string): Promise<Cart | null> {
|
|||||||
return reshapeCart(cart);
|
return reshapeCart(cart);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCollection(handle: string): Promise<ProductCollection | undefined> {
|
export async function getCategories(): Promise<ProductCollection[]> {
|
||||||
const res = await medusaRequest('GET', `/collections?handle[]=${handle}&limit=1`);
|
const res = await medusaRequest('GET', '/product-categories');
|
||||||
return res.body.collections[0];
|
|
||||||
|
// 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[]> {
|
export async function getCategory(handle: string): Promise<ProductCollection | undefined> {
|
||||||
const collection = await getCollection(handle);
|
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 [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await medusaRequest('GET', `/products?collection_id[]=${collection.id}`);
|
const category = res.body.product_categories[0];
|
||||||
|
|
||||||
if (!res.body?.products) {
|
const category_products = await medusaRequest('GET', `/products?category_id[]=${category.id}`);
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const products: Product[] = res.body.products.map((product: MedusaProduct) =>
|
const products: Product[] = category_products.body.products.map((product: MedusaProduct) =>
|
||||||
reshapeProduct(product)
|
reshapeProduct(product)
|
||||||
);
|
);
|
||||||
|
|
||||||
return products;
|
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> {
|
export async function getProduct(handle: string): Promise<Product> {
|
||||||
const res = await medusaRequest('GET', `/products?handle=${handle}&limit=1`);
|
const res = await medusaRequest('GET', `/products?handle=${handle}&limit=1`);
|
||||||
const product = res.body.products[0];
|
const product = res.body.products[0];
|
||||||
@ -372,3 +394,23 @@ export async function getProducts({
|
|||||||
|
|
||||||
return products;
|
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 [];
|
||||||
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
export type MedusaProductCollection = {
|
export type MedusaProductCollection = {
|
||||||
|
name: any;
|
||||||
|
description: string | undefined;
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
handle: string;
|
handle: string;
|
||||||
@ -10,7 +12,6 @@ export type MedusaProductCollection = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type ProductCollection = MedusaProductCollection & {
|
export type ProductCollection = MedusaProductCollection & {
|
||||||
description?: string;
|
|
||||||
seo?: {
|
seo?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@ -115,6 +116,7 @@ export type ShippingProfile = {
|
|||||||
export type ProductCategory = {
|
export type ProductCategory = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
description: string;
|
||||||
handle: string;
|
handle: string;
|
||||||
mpath: string | null;
|
mpath: string | null;
|
||||||
is_internal?: boolean;
|
is_internal?: boolean;
|
||||||
@ -126,6 +128,7 @@ export type ProductCategory = {
|
|||||||
products?: Product[];
|
products?: Product[];
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_at: string;
|
updated_at: string;
|
||||||
|
metadata?: { [key: string]: string } | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MedusaProductVariant = {
|
export type MedusaProductVariant = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user