mirror of
https://github.com/vercel/commerce.git
synced 2025-05-09 03:07:50 +00:00
Merge branch 'main' into develop
This commit is contained in:
commit
659d7cf255
@ -1,12 +1,9 @@
|
||||
import Footer from 'components/layout/footer';
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full">
|
||||
<div className="mx-8 max-w-2xl py-20 sm:mx-auto">{children}</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,73 +1,78 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import { GridTileImage } from 'components/grid/tile';
|
||||
import Footer from 'components/layout/footer';
|
||||
import { Gallery } from 'components/product/gallery';
|
||||
import { ProductDescription } from 'components/product/product-description';
|
||||
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
||||
import { getProduct, getProductRecommendations } from 'lib/shopify';
|
||||
import { Image } from 'lib/shopify/types';
|
||||
import { getProductById, getProductRecommendations } from 'lib/shopify';
|
||||
import { ContentLandingPages, Image, Store } from 'lib/shopify/types';
|
||||
import Link from 'next/link';
|
||||
import { Suspense } from 'react';
|
||||
|
||||
export async function generateMetadata({
|
||||
params
|
||||
}: {
|
||||
params: { handle: string };
|
||||
}): Promise<Metadata> {
|
||||
const product = await getProduct(params.handle);
|
||||
|
||||
if (!product) return notFound();
|
||||
|
||||
const { url, width, height, altText: alt } = product.featuredImage || {};
|
||||
const indexable = !product.tags.includes(HIDDEN_PRODUCT_TAG);
|
||||
|
||||
return {
|
||||
title: product.seo.title || product.title,
|
||||
description: product.seo.description || product.description,
|
||||
robots: {
|
||||
index: indexable,
|
||||
follow: indexable,
|
||||
googleBot: {
|
||||
index: indexable,
|
||||
follow: indexable
|
||||
}
|
||||
const lookupContentLandingPage = async (contentLandingPageId: string) => {
|
||||
const contentLandingPages: ContentLandingPages = {
|
||||
ABC: {
|
||||
contentLandingPageId: 'ABC',
|
||||
content: {
|
||||
contentId: 'ABC-123',
|
||||
contentUrl: 'https://vercel.com'
|
||||
},
|
||||
brand: {
|
||||
brandId: '123456789',
|
||||
companyName: 'Vercel'
|
||||
},
|
||||
store: {
|
||||
domain: 'https://test-app-furie.myshopify.com',
|
||||
key: '30f0c9b2ee5c69d6c0de2e7a048eb6b4'
|
||||
},
|
||||
productId: 'gid://shopify/Product/8587441176812'
|
||||
},
|
||||
openGraph: url
|
||||
? {
|
||||
images: [
|
||||
{
|
||||
url,
|
||||
width,
|
||||
height,
|
||||
alt
|
||||
}
|
||||
]
|
||||
}
|
||||
: null
|
||||
'123': {
|
||||
contentLandingPageId: '123',
|
||||
content: {
|
||||
contentId: '123-ABC',
|
||||
contentUrl: 'https://vercel.com'
|
||||
},
|
||||
brand: {
|
||||
brandId: '123456789',
|
||||
companyName: 'Vercel'
|
||||
},
|
||||
store: {
|
||||
domain: 'https://test-app-furie.myshopify.com',
|
||||
key: '30f0c9b2ee5c69d6c0de2e7a048eb6b4'
|
||||
},
|
||||
productId: 'gid://shopify/Product/8587440849132'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default async function ProductPage({ params }: { params: { handle: string } }) {
|
||||
const product = await getProduct(params.handle);
|
||||
const contentLandingPage = contentLandingPages[contentLandingPageId];
|
||||
|
||||
if (!product) return notFound();
|
||||
if (!contentLandingPage) {
|
||||
throw new Error('Content Landing Page not found');
|
||||
}
|
||||
|
||||
const product = await getProductById(contentLandingPage.store, contentLandingPage?.productId);
|
||||
return { ...contentLandingPage, product };
|
||||
};
|
||||
|
||||
export default async function Page({ params }: { params: { ContentLandingPage: string } }) {
|
||||
const instance = await lookupContentLandingPage(params.ContentLandingPage);
|
||||
|
||||
if (!instance.product) {
|
||||
return <div>Product not found</div>;
|
||||
}
|
||||
|
||||
const productJsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Product',
|
||||
name: product.title,
|
||||
description: product.description,
|
||||
image: product.featuredImage.url,
|
||||
name: instance.product.title,
|
||||
description: instance.product.description,
|
||||
image: instance.product.featuredImage.url,
|
||||
offers: {
|
||||
'@type': 'AggregateOffer',
|
||||
availability: product.availableForSale
|
||||
availability: instance.product.availableForSale
|
||||
? 'https://schema.org/InStock'
|
||||
: 'https://schema.org/OutOfStock',
|
||||
priceCurrency: product.priceRange.minVariantPrice.currencyCode,
|
||||
highPrice: product.priceRange.maxVariantPrice.amount,
|
||||
lowPrice: product.priceRange.minVariantPrice.amount
|
||||
priceCurrency: instance.product.priceRange.minVariantPrice.currencyCode,
|
||||
highPrice: instance.product.priceRange.maxVariantPrice.amount,
|
||||
lowPrice: instance.product.priceRange.minVariantPrice.amount
|
||||
}
|
||||
};
|
||||
|
||||
@ -88,7 +93,7 @@ export default async function ProductPage({ params }: { params: { handle: string
|
||||
}
|
||||
>
|
||||
<Gallery
|
||||
images={product.images.map((image: Image) => ({
|
||||
images={instance.product.images.map((image: Image) => ({
|
||||
src: image.url,
|
||||
altText: image.altText
|
||||
}))}
|
||||
@ -97,18 +102,17 @@ export default async function ProductPage({ params }: { params: { handle: string
|
||||
</div>
|
||||
|
||||
<div className="basis-full lg:basis-2/6">
|
||||
<ProductDescription product={product} />
|
||||
<ProductDescription product={instance.product} />
|
||||
</div>
|
||||
</div>
|
||||
<RelatedProducts id={product.id} />
|
||||
<RelatedProducts id={instance.product.id} store={instance.store} />
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async function RelatedProducts({ id }: { id: string }) {
|
||||
const relatedProducts = await getProductRecommendations(id);
|
||||
async function RelatedProducts({ store, id }: { store: Store; id: string }) {
|
||||
const relatedProducts = await getProductRecommendations(store, id);
|
||||
|
||||
if (!relatedProducts.length) return null;
|
||||
|
@ -1,11 +0,0 @@
|
||||
import OpengraphImage from 'components/opengraph-image';
|
||||
import { getPage } from 'lib/shopify';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export default async function Image({ params }: { params: { page: string } }) {
|
||||
const page = await getPage(params.page);
|
||||
const title = page.seo?.title || page.title;
|
||||
|
||||
return await OpengraphImage({ title });
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
import Prose from 'components/prose';
|
||||
import { getPage } from 'lib/shopify';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
export async function generateMetadata({
|
||||
params
|
||||
}: {
|
||||
params: { page: string };
|
||||
}): Promise<Metadata> {
|
||||
const page = await getPage(params.page);
|
||||
|
||||
if (!page) return notFound();
|
||||
|
||||
return {
|
||||
title: page.seo?.title || page.title,
|
||||
description: page.seo?.description || page.bodySummary,
|
||||
openGraph: {
|
||||
publishedTime: page.createdAt,
|
||||
modifiedTime: page.updatedAt,
|
||||
type: 'article'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: { page: string } }) {
|
||||
const page = await getPage(params.page);
|
||||
|
||||
if (!page) return notFound();
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="mb-8 text-5xl font-bold">{page.title}</h1>
|
||||
<Prose className="mb-8" html={page.body as string} />
|
||||
<p className="text-sm italic">
|
||||
{`This document was last updated on ${new Intl.DateTimeFormat(undefined, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
}).format(new Date(page.updatedAt))}.`}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import Navbar from 'components/layout/navbar';
|
||||
import { GeistSans } from 'geist/font/sans';
|
||||
import { ensureStartsWith } from 'lib/utils';
|
||||
import { ReactNode } from 'react';
|
||||
@ -35,7 +34,6 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
||||
return (
|
||||
<html lang="en" className={GeistSans.variable}>
|
||||
<body className="bg-neutral-50 text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
|
||||
<Navbar />
|
||||
<main>{children}</main>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,11 +0,0 @@
|
||||
import OpengraphImage from 'components/opengraph-image';
|
||||
import { getCollection } from 'lib/shopify';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export default async function Image({ params }: { params: { collection: string } }) {
|
||||
const collection = await getCollection(params.collection);
|
||||
const title = collection?.seo?.title || collection?.title;
|
||||
|
||||
return await OpengraphImage({ title });
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import { getCollection, getCollectionProducts } from 'lib/shopify';
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
|
||||
import Grid from 'components/grid';
|
||||
import ProductGridItems from 'components/layout/product-grid-items';
|
||||
import { defaultSort, sorting } from 'lib/constants';
|
||||
|
||||
export async function generateMetadata({
|
||||
params
|
||||
}: {
|
||||
params: { collection: string };
|
||||
}): Promise<Metadata> {
|
||||
const collection = await getCollection(params.collection);
|
||||
|
||||
if (!collection) return notFound();
|
||||
|
||||
return {
|
||||
title: collection.seo?.title || collection.title,
|
||||
description:
|
||||
collection.seo?.description || collection.description || `${collection.title} products`
|
||||
};
|
||||
}
|
||||
|
||||
export default async function CategoryPage({
|
||||
params,
|
||||
searchParams
|
||||
}: {
|
||||
params: { collection: string };
|
||||
searchParams?: { [key: string]: string | string[] | undefined };
|
||||
}) {
|
||||
const { sort } = searchParams as { [key: string]: string };
|
||||
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
|
||||
const products = await getCollectionProducts({ collection: params.collection, sortKey, reverse });
|
||||
|
||||
return (
|
||||
<section>
|
||||
{products.length === 0 ? (
|
||||
<p className="py-3 text-lg">{`No products found in this collection`}</p>
|
||||
) : (
|
||||
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<ProductGridItems products={products} />
|
||||
</Grid>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import Footer from 'components/layout/footer';
|
||||
import Collections from 'components/layout/search/collections';
|
||||
import FilterList from 'components/layout/search/filter';
|
||||
import { sorting } from 'lib/constants';
|
||||
|
||||
export default function SearchLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto flex max-w-screen-2xl flex-col gap-8 px-4 pb-4 text-black md:flex-row dark:text-white">
|
||||
<div className="order-first w-full flex-none md:max-w-[125px]">
|
||||
<Collections />
|
||||
</div>
|
||||
<div className="order-last min-h-screen w-full md:order-none">{children}</div>
|
||||
<div className="order-none flex-none md:order-last md:w-[125px]">
|
||||
<FilterList list={sorting} title="Sort by" />
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import Grid from 'components/grid';
|
||||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<Grid className="grid-cols-2 lg:grid-cols-3">
|
||||
{Array(12)
|
||||
.fill(0)
|
||||
.map((_, index) => {
|
||||
return (
|
||||
<Grid.Item key={index} className="animate-pulse bg-neutral-100 dark:bg-neutral-900" />
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import Grid from 'components/grid';
|
||||
import ProductGridItems from 'components/layout/product-grid-items';
|
||||
import { defaultSort, sorting } from 'lib/constants';
|
||||
import { getProducts } from 'lib/shopify';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Search',
|
||||
description: 'Search for products in the store.'
|
||||
};
|
||||
|
||||
export default async function SearchPage({
|
||||
searchParams
|
||||
}: {
|
||||
searchParams?: { [key: string]: string | string[] | undefined };
|
||||
}) {
|
||||
const { sort, q: searchValue } = searchParams as { [key: string]: string };
|
||||
const { sortKey, reverse } = sorting.find((item) => item.slug === sort) || defaultSort;
|
||||
|
||||
const products = await getProducts({ sortKey, reverse, query: searchValue });
|
||||
const resultsText = products.length > 1 ? 'results' : 'result';
|
||||
|
||||
return (
|
||||
<>
|
||||
{searchValue ? (
|
||||
<p className="mb-4">
|
||||
{products.length === 0
|
||||
? 'There are no products that match '
|
||||
: `Showing ${products.length} ${resultsText} for `}
|
||||
<span className="font-bold">"{searchValue}"</span>
|
||||
</p>
|
||||
) : null}
|
||||
{products.length > 0 ? (
|
||||
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<ProductGridItems products={products} />
|
||||
</Grid>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import { HIDDEN_PRODUCT_TAG, SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/constants';
|
||||
import { isShopifyError } from 'lib/type-guards';
|
||||
import { ensureStartsWith } from 'lib/utils';
|
||||
import { revalidateTag } from 'next/cache';
|
||||
import { headers } from 'next/headers';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
@ -19,6 +18,7 @@ import {
|
||||
import { getMenuQuery } from './queries/menu';
|
||||
import { getPageQuery, getPagesQuery } from './queries/page';
|
||||
import {
|
||||
getProductByIdQuery,
|
||||
getProductQuery,
|
||||
getProductRecommendationsQuery,
|
||||
getProductsQuery
|
||||
@ -47,36 +47,46 @@ import {
|
||||
ShopifyProductRecommendationsOperation,
|
||||
ShopifyProductsOperation,
|
||||
ShopifyRemoveFromCartOperation,
|
||||
ShopifyUpdateCartOperation
|
||||
ShopifyUpdateCartOperation,
|
||||
Store
|
||||
} from './types';
|
||||
|
||||
/*
|
||||
const domain = process.env.SHOPIFY_STORE_DOMAIN
|
||||
? ensureStartsWith(process.env.SHOPIFY_STORE_DOMAIN, 'https://')
|
||||
: '';
|
||||
const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`;
|
||||
const endpoint = `${domain}`;
|
||||
const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
|
||||
*/
|
||||
|
||||
type ExtractVariables<T> = T extends { variables: object } ? T['variables'] : never;
|
||||
|
||||
export async function shopifyFetch<T>({
|
||||
store,
|
||||
cache = 'force-cache',
|
||||
headers,
|
||||
query,
|
||||
tags,
|
||||
variables
|
||||
}: {
|
||||
store: Store;
|
||||
cache?: RequestCache;
|
||||
headers?: HeadersInit;
|
||||
query: string;
|
||||
tags?: string[];
|
||||
variables?: ExtractVariables<T>;
|
||||
}): Promise<{ status: number; body: T } | never> {
|
||||
if (!store) {
|
||||
throw new Error('Missing Shopify store configuration.');
|
||||
}
|
||||
const url = `${store.domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`;
|
||||
console.log({ url, query });
|
||||
try {
|
||||
const result = await fetch(endpoint, {
|
||||
const result = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Shopify-Storefront-Access-Token': key,
|
||||
'X-Shopify-Storefront-Access-Token': store.key,
|
||||
...headers
|
||||
},
|
||||
body: JSON.stringify({
|
||||
@ -201,8 +211,9 @@ const reshapeProducts = (products: ShopifyProduct[]) => {
|
||||
return reshapedProducts;
|
||||
};
|
||||
|
||||
export async function createCart(): Promise<Cart> {
|
||||
export async function createCart(store: Store): Promise<Cart> {
|
||||
const res = await shopifyFetch<ShopifyCreateCartOperation>({
|
||||
store: store,
|
||||
query: createCartMutation,
|
||||
cache: 'no-store'
|
||||
});
|
||||
@ -211,10 +222,12 @@ export async function createCart(): Promise<Cart> {
|
||||
}
|
||||
|
||||
export async function addToCart(
|
||||
store: Store,
|
||||
cartId: string,
|
||||
lines: { merchandiseId: string; quantity: number }[]
|
||||
): Promise<Cart> {
|
||||
const res = await shopifyFetch<ShopifyAddToCartOperation>({
|
||||
store: store,
|
||||
query: addToCartMutation,
|
||||
variables: {
|
||||
cartId,
|
||||
@ -225,8 +238,13 @@ export async function addToCart(
|
||||
return reshapeCart(res.body.data.cartLinesAdd.cart);
|
||||
}
|
||||
|
||||
export async function removeFromCart(cartId: string, lineIds: string[]): Promise<Cart> {
|
||||
export async function removeFromCart(
|
||||
store: Store,
|
||||
cartId: string,
|
||||
lineIds: string[]
|
||||
): Promise<Cart> {
|
||||
const res = await shopifyFetch<ShopifyRemoveFromCartOperation>({
|
||||
store: store,
|
||||
query: removeFromCartMutation,
|
||||
variables: {
|
||||
cartId,
|
||||
@ -239,10 +257,12 @@ export async function removeFromCart(cartId: string, lineIds: string[]): Promise
|
||||
}
|
||||
|
||||
export async function updateCart(
|
||||
store: Store,
|
||||
cartId: string,
|
||||
lines: { id: string; merchandiseId: string; quantity: number }[]
|
||||
): Promise<Cart> {
|
||||
const res = await shopifyFetch<ShopifyUpdateCartOperation>({
|
||||
store: store,
|
||||
query: editCartItemsMutation,
|
||||
variables: {
|
||||
cartId,
|
||||
@ -254,8 +274,9 @@ export async function updateCart(
|
||||
return reshapeCart(res.body.data.cartLinesUpdate.cart);
|
||||
}
|
||||
|
||||
export async function getCart(cartId: string): Promise<Cart | undefined> {
|
||||
export async function getCart(store: Store, cartId: string): Promise<Cart | undefined> {
|
||||
const res = await shopifyFetch<ShopifyCartOperation>({
|
||||
store: store,
|
||||
query: getCartQuery,
|
||||
variables: { cartId },
|
||||
tags: [TAGS.cart],
|
||||
@ -270,8 +291,9 @@ export async function getCart(cartId: string): Promise<Cart | undefined> {
|
||||
return reshapeCart(res.body.data.cart);
|
||||
}
|
||||
|
||||
export async function getCollection(handle: string): Promise<Collection | undefined> {
|
||||
export async function getCollection(store: Store, handle: string): Promise<Collection | undefined> {
|
||||
const res = await shopifyFetch<ShopifyCollectionOperation>({
|
||||
store: store,
|
||||
query: getCollectionQuery,
|
||||
tags: [TAGS.collections],
|
||||
variables: {
|
||||
@ -283,15 +305,18 @@ export async function getCollection(handle: string): Promise<Collection | undefi
|
||||
}
|
||||
|
||||
export async function getCollectionProducts({
|
||||
store,
|
||||
collection,
|
||||
reverse,
|
||||
sortKey
|
||||
}: {
|
||||
store: Store;
|
||||
collection: string;
|
||||
reverse?: boolean;
|
||||
sortKey?: string;
|
||||
}): Promise<Product[]> {
|
||||
const res = await shopifyFetch<ShopifyCollectionProductsOperation>({
|
||||
store,
|
||||
query: getCollectionProductsQuery,
|
||||
tags: [TAGS.collections, TAGS.products],
|
||||
variables: {
|
||||
@ -309,8 +334,9 @@ export async function getCollectionProducts({
|
||||
return reshapeProducts(removeEdgesAndNodes(res.body.data.collection.products));
|
||||
}
|
||||
|
||||
export async function getCollections(): Promise<Collection[]> {
|
||||
export async function getCollections(store: Store): Promise<Collection[]> {
|
||||
const res = await shopifyFetch<ShopifyCollectionsOperation>({
|
||||
store: store,
|
||||
query: getCollectionsQuery,
|
||||
tags: [TAGS.collections]
|
||||
});
|
||||
@ -337,8 +363,9 @@ export async function getCollections(): Promise<Collection[]> {
|
||||
return collections;
|
||||
}
|
||||
|
||||
export async function getMenu(handle: string): Promise<Menu[]> {
|
||||
export async function getMenu(store: Store, handle: string): Promise<Menu[]> {
|
||||
const res = await shopifyFetch<ShopifyMenuOperation>({
|
||||
store: store,
|
||||
query: getMenuQuery,
|
||||
tags: [TAGS.collections],
|
||||
variables: {
|
||||
@ -349,13 +376,17 @@ export async function getMenu(handle: string): Promise<Menu[]> {
|
||||
return (
|
||||
res.body?.data?.menu?.items.map((item: { title: string; url: string }) => ({
|
||||
title: item.title,
|
||||
path: item.url.replace(domain, '').replace('/collections', '/search').replace('/pages', '')
|
||||
path: item.url
|
||||
.replace(store.domain, '')
|
||||
.replace('/collections', '/search')
|
||||
.replace('/pages', '')
|
||||
})) || []
|
||||
);
|
||||
}
|
||||
|
||||
export async function getPage(handle: string): Promise<Page> {
|
||||
export async function getPage(store: Store, handle: string): Promise<Page> {
|
||||
const res = await shopifyFetch<ShopifyPageOperation>({
|
||||
store: store,
|
||||
query: getPageQuery,
|
||||
cache: 'no-store',
|
||||
variables: { handle }
|
||||
@ -364,8 +395,9 @@ export async function getPage(handle: string): Promise<Page> {
|
||||
return res.body.data.pageByHandle;
|
||||
}
|
||||
|
||||
export async function getPages(): Promise<Page[]> {
|
||||
export async function getPages(store: Store): Promise<Page[]> {
|
||||
const res = await shopifyFetch<ShopifyPagesOperation>({
|
||||
store: store,
|
||||
query: getPagesQuery,
|
||||
cache: 'no-store'
|
||||
});
|
||||
@ -373,8 +405,9 @@ export async function getPages(): Promise<Page[]> {
|
||||
return removeEdgesAndNodes(res.body.data.pages);
|
||||
}
|
||||
|
||||
export async function getProduct(handle: string): Promise<Product | undefined> {
|
||||
export async function getProduct(store: Store, handle: string): Promise<Product | undefined> {
|
||||
const res = await shopifyFetch<ShopifyProductOperation>({
|
||||
store,
|
||||
query: getProductQuery,
|
||||
tags: [TAGS.products],
|
||||
variables: {
|
||||
@ -385,8 +418,28 @@ export async function getProduct(handle: string): Promise<Product | undefined> {
|
||||
return reshapeProduct(res.body.data.product, false);
|
||||
}
|
||||
|
||||
export async function getProductRecommendations(productId: string): Promise<Product[]> {
|
||||
export async function getProductById(
|
||||
store: Store,
|
||||
productId: string
|
||||
): Promise<Product | undefined> {
|
||||
const res = await shopifyFetch<ShopifyProductOperation>({
|
||||
store,
|
||||
query: getProductByIdQuery,
|
||||
tags: [TAGS.products],
|
||||
variables: {
|
||||
id: productId
|
||||
}
|
||||
});
|
||||
|
||||
return reshapeProduct(res.body.data.product, false);
|
||||
}
|
||||
|
||||
export async function getProductRecommendations(
|
||||
store: Store,
|
||||
productId: string
|
||||
): Promise<Product[]> {
|
||||
const res = await shopifyFetch<ShopifyProductRecommendationsOperation>({
|
||||
store,
|
||||
query: getProductRecommendationsQuery,
|
||||
tags: [TAGS.products],
|
||||
variables: {
|
||||
@ -398,15 +451,18 @@ export async function getProductRecommendations(productId: string): Promise<Prod
|
||||
}
|
||||
|
||||
export async function getProducts({
|
||||
store,
|
||||
query,
|
||||
reverse,
|
||||
sortKey
|
||||
}: {
|
||||
store: Store;
|
||||
query?: string;
|
||||
reverse?: boolean;
|
||||
sortKey?: string;
|
||||
}): Promise<Product[]> {
|
||||
const res = await shopifyFetch<ShopifyProductsOperation>({
|
||||
store,
|
||||
query: getProductsQuery,
|
||||
tags: [TAGS.products],
|
||||
variables: {
|
||||
|
@ -9,6 +9,15 @@ export const getProductQuery = /* GraphQL */ `
|
||||
${productFragment}
|
||||
`;
|
||||
|
||||
export const getProductByIdQuery = /* GraphQL */ `
|
||||
query getProduct($id: ID!) {
|
||||
product(id: $id) {
|
||||
...product
|
||||
}
|
||||
}
|
||||
${productFragment}
|
||||
`;
|
||||
|
||||
export const getProductsQuery = /* GraphQL */ `
|
||||
query getProducts($sortKey: ProductSortKeys, $reverse: Boolean, $query: String) {
|
||||
products(sortKey: $sortKey, reverse: $reverse, query: $query, first: 100) {
|
||||
|
@ -8,6 +8,33 @@ export type Edge<T> = {
|
||||
node: T;
|
||||
};
|
||||
|
||||
export type Brand = {
|
||||
brandId: string;
|
||||
companyName: string;
|
||||
};
|
||||
|
||||
export type Content = {
|
||||
contentId: string;
|
||||
contentUrl: string;
|
||||
};
|
||||
|
||||
export type ContentLandingPage = {
|
||||
contentLandingPageId: string;
|
||||
content: Content;
|
||||
brand: Brand;
|
||||
store: Store;
|
||||
productId: string;
|
||||
};
|
||||
|
||||
export type ContentLandingPages = {
|
||||
[key: string]: ContentLandingPage;
|
||||
};
|
||||
|
||||
export type Store = {
|
||||
domain: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
export type Cart = Omit<ShopifyCart, 'lines'> & {
|
||||
lines: CartItem[];
|
||||
};
|
||||
@ -239,9 +266,11 @@ export type ShopifyPagesOperation = {
|
||||
|
||||
export type ShopifyProductOperation = {
|
||||
data: { product: ShopifyProduct };
|
||||
variables: {
|
||||
handle: string;
|
||||
};
|
||||
variables:
|
||||
| {
|
||||
handle: string;
|
||||
}
|
||||
| { id: string };
|
||||
};
|
||||
|
||||
export type ShopifyProductRecommendationsOperation = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user