mirror of
https://github.com/vercel/commerce.git
synced 2025-05-09 19:27:53 +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 }) {
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="mx-8 max-w-2xl py-20 sm:mx-auto">{children}</div>
|
<div className="mx-8 max-w-2xl py-20 sm:mx-auto">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,73 +1,78 @@
|
|||||||
import type { Metadata } from 'next';
|
|
||||||
import { notFound } from 'next/navigation';
|
|
||||||
|
|
||||||
import { GridTileImage } from 'components/grid/tile';
|
import { GridTileImage } from 'components/grid/tile';
|
||||||
import Footer from 'components/layout/footer';
|
|
||||||
import { Gallery } from 'components/product/gallery';
|
import { Gallery } from 'components/product/gallery';
|
||||||
import { ProductDescription } from 'components/product/product-description';
|
import { ProductDescription } from 'components/product/product-description';
|
||||||
import { HIDDEN_PRODUCT_TAG } from 'lib/constants';
|
import { getProductById, getProductRecommendations } from 'lib/shopify';
|
||||||
import { getProduct, getProductRecommendations } from 'lib/shopify';
|
import { ContentLandingPages, Image, Store } from 'lib/shopify/types';
|
||||||
import { Image } from 'lib/shopify/types';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
export async function generateMetadata({
|
const lookupContentLandingPage = async (contentLandingPageId: string) => {
|
||||||
params
|
const contentLandingPages: ContentLandingPages = {
|
||||||
}: {
|
ABC: {
|
||||||
params: { handle: string };
|
contentLandingPageId: 'ABC',
|
||||||
}): Promise<Metadata> {
|
content: {
|
||||||
const product = await getProduct(params.handle);
|
contentId: 'ABC-123',
|
||||||
|
contentUrl: 'https://vercel.com'
|
||||||
if (!product) return notFound();
|
},
|
||||||
|
brand: {
|
||||||
const { url, width, height, altText: alt } = product.featuredImage || {};
|
brandId: '123456789',
|
||||||
const indexable = !product.tags.includes(HIDDEN_PRODUCT_TAG);
|
companyName: 'Vercel'
|
||||||
|
},
|
||||||
return {
|
store: {
|
||||||
title: product.seo.title || product.title,
|
domain: 'https://test-app-furie.myshopify.com',
|
||||||
description: product.seo.description || product.description,
|
key: '30f0c9b2ee5c69d6c0de2e7a048eb6b4'
|
||||||
robots: {
|
},
|
||||||
index: indexable,
|
productId: 'gid://shopify/Product/8587441176812'
|
||||||
follow: indexable,
|
|
||||||
googleBot: {
|
|
||||||
index: indexable,
|
|
||||||
follow: indexable
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
openGraph: url
|
'123': {
|
||||||
? {
|
contentLandingPageId: '123',
|
||||||
images: [
|
content: {
|
||||||
{
|
contentId: '123-ABC',
|
||||||
url,
|
contentUrl: 'https://vercel.com'
|
||||||
width,
|
},
|
||||||
height,
|
brand: {
|
||||||
alt
|
brandId: '123456789',
|
||||||
}
|
companyName: 'Vercel'
|
||||||
]
|
},
|
||||||
}
|
store: {
|
||||||
: null
|
domain: 'https://test-app-furie.myshopify.com',
|
||||||
|
key: '30f0c9b2ee5c69d6c0de2e7a048eb6b4'
|
||||||
|
},
|
||||||
|
productId: 'gid://shopify/Product/8587440849132'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export default async function ProductPage({ params }: { params: { handle: string } }) {
|
const contentLandingPage = contentLandingPages[contentLandingPageId];
|
||||||
const product = await getProduct(params.handle);
|
|
||||||
|
|
||||||
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 = {
|
const productJsonLd = {
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'Product',
|
'@type': 'Product',
|
||||||
name: product.title,
|
name: instance.product.title,
|
||||||
description: product.description,
|
description: instance.product.description,
|
||||||
image: product.featuredImage.url,
|
image: instance.product.featuredImage.url,
|
||||||
offers: {
|
offers: {
|
||||||
'@type': 'AggregateOffer',
|
'@type': 'AggregateOffer',
|
||||||
availability: product.availableForSale
|
availability: instance.product.availableForSale
|
||||||
? 'https://schema.org/InStock'
|
? 'https://schema.org/InStock'
|
||||||
: 'https://schema.org/OutOfStock',
|
: 'https://schema.org/OutOfStock',
|
||||||
priceCurrency: product.priceRange.minVariantPrice.currencyCode,
|
priceCurrency: instance.product.priceRange.minVariantPrice.currencyCode,
|
||||||
highPrice: product.priceRange.maxVariantPrice.amount,
|
highPrice: instance.product.priceRange.maxVariantPrice.amount,
|
||||||
lowPrice: product.priceRange.minVariantPrice.amount
|
lowPrice: instance.product.priceRange.minVariantPrice.amount
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -88,7 +93,7 @@ export default async function ProductPage({ params }: { params: { handle: string
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Gallery
|
<Gallery
|
||||||
images={product.images.map((image: Image) => ({
|
images={instance.product.images.map((image: Image) => ({
|
||||||
src: image.url,
|
src: image.url,
|
||||||
altText: image.altText
|
altText: image.altText
|
||||||
}))}
|
}))}
|
||||||
@ -97,18 +102,17 @@ export default async function ProductPage({ params }: { params: { handle: string
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="basis-full lg:basis-2/6">
|
<div className="basis-full lg:basis-2/6">
|
||||||
<ProductDescription product={product} />
|
<ProductDescription product={instance.product} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<RelatedProducts id={product.id} />
|
<RelatedProducts id={instance.product.id} store={instance.store} />
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function RelatedProducts({ id }: { id: string }) {
|
async function RelatedProducts({ store, id }: { store: Store; id: string }) {
|
||||||
const relatedProducts = await getProductRecommendations(id);
|
const relatedProducts = await getProductRecommendations(store, id);
|
||||||
|
|
||||||
if (!relatedProducts.length) return null;
|
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 { GeistSans } from 'geist/font/sans';
|
||||||
import { ensureStartsWith } from 'lib/utils';
|
import { ensureStartsWith } from 'lib/utils';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
@ -35,7 +34,6 @@ export default async function RootLayout({ children }: { children: ReactNode })
|
|||||||
return (
|
return (
|
||||||
<html lang="en" className={GeistSans.variable}>
|
<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">
|
<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>
|
<main>{children}</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 { HIDDEN_PRODUCT_TAG, SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/constants';
|
||||||
import { isShopifyError } from 'lib/type-guards';
|
import { isShopifyError } from 'lib/type-guards';
|
||||||
import { ensureStartsWith } from 'lib/utils';
|
|
||||||
import { revalidateTag } from 'next/cache';
|
import { revalidateTag } from 'next/cache';
|
||||||
import { headers } from 'next/headers';
|
import { headers } from 'next/headers';
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
@ -19,6 +18,7 @@ import {
|
|||||||
import { getMenuQuery } from './queries/menu';
|
import { getMenuQuery } from './queries/menu';
|
||||||
import { getPageQuery, getPagesQuery } from './queries/page';
|
import { getPageQuery, getPagesQuery } from './queries/page';
|
||||||
import {
|
import {
|
||||||
|
getProductByIdQuery,
|
||||||
getProductQuery,
|
getProductQuery,
|
||||||
getProductRecommendationsQuery,
|
getProductRecommendationsQuery,
|
||||||
getProductsQuery
|
getProductsQuery
|
||||||
@ -47,36 +47,46 @@ import {
|
|||||||
ShopifyProductRecommendationsOperation,
|
ShopifyProductRecommendationsOperation,
|
||||||
ShopifyProductsOperation,
|
ShopifyProductsOperation,
|
||||||
ShopifyRemoveFromCartOperation,
|
ShopifyRemoveFromCartOperation,
|
||||||
ShopifyUpdateCartOperation
|
ShopifyUpdateCartOperation,
|
||||||
|
Store
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
|
/*
|
||||||
const domain = process.env.SHOPIFY_STORE_DOMAIN
|
const domain = process.env.SHOPIFY_STORE_DOMAIN
|
||||||
? ensureStartsWith(process.env.SHOPIFY_STORE_DOMAIN, 'https://')
|
? 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!;
|
const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
|
||||||
|
*/
|
||||||
|
|
||||||
type ExtractVariables<T> = T extends { variables: object } ? T['variables'] : never;
|
type ExtractVariables<T> = T extends { variables: object } ? T['variables'] : never;
|
||||||
|
|
||||||
export async function shopifyFetch<T>({
|
export async function shopifyFetch<T>({
|
||||||
|
store,
|
||||||
cache = 'force-cache',
|
cache = 'force-cache',
|
||||||
headers,
|
headers,
|
||||||
query,
|
query,
|
||||||
tags,
|
tags,
|
||||||
variables
|
variables
|
||||||
}: {
|
}: {
|
||||||
|
store: Store;
|
||||||
cache?: RequestCache;
|
cache?: RequestCache;
|
||||||
headers?: HeadersInit;
|
headers?: HeadersInit;
|
||||||
query: string;
|
query: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
variables?: ExtractVariables<T>;
|
variables?: ExtractVariables<T>;
|
||||||
}): Promise<{ status: number; body: T } | never> {
|
}): 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 {
|
try {
|
||||||
const result = await fetch(endpoint, {
|
const result = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Shopify-Storefront-Access-Token': key,
|
'X-Shopify-Storefront-Access-Token': store.key,
|
||||||
...headers
|
...headers
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@ -201,8 +211,9 @@ const reshapeProducts = (products: ShopifyProduct[]) => {
|
|||||||
return reshapedProducts;
|
return reshapedProducts;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createCart(): Promise<Cart> {
|
export async function createCart(store: Store): Promise<Cart> {
|
||||||
const res = await shopifyFetch<ShopifyCreateCartOperation>({
|
const res = await shopifyFetch<ShopifyCreateCartOperation>({
|
||||||
|
store: store,
|
||||||
query: createCartMutation,
|
query: createCartMutation,
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
});
|
});
|
||||||
@ -211,10 +222,12 @@ export async function createCart(): Promise<Cart> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function addToCart(
|
export async function addToCart(
|
||||||
|
store: Store,
|
||||||
cartId: string,
|
cartId: string,
|
||||||
lines: { merchandiseId: string; quantity: number }[]
|
lines: { merchandiseId: string; quantity: number }[]
|
||||||
): Promise<Cart> {
|
): Promise<Cart> {
|
||||||
const res = await shopifyFetch<ShopifyAddToCartOperation>({
|
const res = await shopifyFetch<ShopifyAddToCartOperation>({
|
||||||
|
store: store,
|
||||||
query: addToCartMutation,
|
query: addToCartMutation,
|
||||||
variables: {
|
variables: {
|
||||||
cartId,
|
cartId,
|
||||||
@ -225,8 +238,13 @@ export async function addToCart(
|
|||||||
return reshapeCart(res.body.data.cartLinesAdd.cart);
|
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>({
|
const res = await shopifyFetch<ShopifyRemoveFromCartOperation>({
|
||||||
|
store: store,
|
||||||
query: removeFromCartMutation,
|
query: removeFromCartMutation,
|
||||||
variables: {
|
variables: {
|
||||||
cartId,
|
cartId,
|
||||||
@ -239,10 +257,12 @@ export async function removeFromCart(cartId: string, lineIds: string[]): Promise
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateCart(
|
export async function updateCart(
|
||||||
|
store: Store,
|
||||||
cartId: string,
|
cartId: string,
|
||||||
lines: { id: string; merchandiseId: string; quantity: number }[]
|
lines: { id: string; merchandiseId: string; quantity: number }[]
|
||||||
): Promise<Cart> {
|
): Promise<Cart> {
|
||||||
const res = await shopifyFetch<ShopifyUpdateCartOperation>({
|
const res = await shopifyFetch<ShopifyUpdateCartOperation>({
|
||||||
|
store: store,
|
||||||
query: editCartItemsMutation,
|
query: editCartItemsMutation,
|
||||||
variables: {
|
variables: {
|
||||||
cartId,
|
cartId,
|
||||||
@ -254,8 +274,9 @@ export async function updateCart(
|
|||||||
return reshapeCart(res.body.data.cartLinesUpdate.cart);
|
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>({
|
const res = await shopifyFetch<ShopifyCartOperation>({
|
||||||
|
store: store,
|
||||||
query: getCartQuery,
|
query: getCartQuery,
|
||||||
variables: { cartId },
|
variables: { cartId },
|
||||||
tags: [TAGS.cart],
|
tags: [TAGS.cart],
|
||||||
@ -270,8 +291,9 @@ export async function getCart(cartId: string): Promise<Cart | undefined> {
|
|||||||
return reshapeCart(res.body.data.cart);
|
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>({
|
const res = await shopifyFetch<ShopifyCollectionOperation>({
|
||||||
|
store: store,
|
||||||
query: getCollectionQuery,
|
query: getCollectionQuery,
|
||||||
tags: [TAGS.collections],
|
tags: [TAGS.collections],
|
||||||
variables: {
|
variables: {
|
||||||
@ -283,15 +305,18 @@ export async function getCollection(handle: string): Promise<Collection | undefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getCollectionProducts({
|
export async function getCollectionProducts({
|
||||||
|
store,
|
||||||
collection,
|
collection,
|
||||||
reverse,
|
reverse,
|
||||||
sortKey
|
sortKey
|
||||||
}: {
|
}: {
|
||||||
|
store: Store;
|
||||||
collection: string;
|
collection: string;
|
||||||
reverse?: boolean;
|
reverse?: boolean;
|
||||||
sortKey?: string;
|
sortKey?: string;
|
||||||
}): Promise<Product[]> {
|
}): Promise<Product[]> {
|
||||||
const res = await shopifyFetch<ShopifyCollectionProductsOperation>({
|
const res = await shopifyFetch<ShopifyCollectionProductsOperation>({
|
||||||
|
store,
|
||||||
query: getCollectionProductsQuery,
|
query: getCollectionProductsQuery,
|
||||||
tags: [TAGS.collections, TAGS.products],
|
tags: [TAGS.collections, TAGS.products],
|
||||||
variables: {
|
variables: {
|
||||||
@ -309,8 +334,9 @@ export async function getCollectionProducts({
|
|||||||
return reshapeProducts(removeEdgesAndNodes(res.body.data.collection.products));
|
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>({
|
const res = await shopifyFetch<ShopifyCollectionsOperation>({
|
||||||
|
store: store,
|
||||||
query: getCollectionsQuery,
|
query: getCollectionsQuery,
|
||||||
tags: [TAGS.collections]
|
tags: [TAGS.collections]
|
||||||
});
|
});
|
||||||
@ -337,8 +363,9 @@ export async function getCollections(): Promise<Collection[]> {
|
|||||||
return collections;
|
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>({
|
const res = await shopifyFetch<ShopifyMenuOperation>({
|
||||||
|
store: store,
|
||||||
query: getMenuQuery,
|
query: getMenuQuery,
|
||||||
tags: [TAGS.collections],
|
tags: [TAGS.collections],
|
||||||
variables: {
|
variables: {
|
||||||
@ -349,13 +376,17 @@ export async function getMenu(handle: string): Promise<Menu[]> {
|
|||||||
return (
|
return (
|
||||||
res.body?.data?.menu?.items.map((item: { title: string; url: string }) => ({
|
res.body?.data?.menu?.items.map((item: { title: string; url: string }) => ({
|
||||||
title: item.title,
|
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>({
|
const res = await shopifyFetch<ShopifyPageOperation>({
|
||||||
|
store: store,
|
||||||
query: getPageQuery,
|
query: getPageQuery,
|
||||||
cache: 'no-store',
|
cache: 'no-store',
|
||||||
variables: { handle }
|
variables: { handle }
|
||||||
@ -364,8 +395,9 @@ export async function getPage(handle: string): Promise<Page> {
|
|||||||
return res.body.data.pageByHandle;
|
return res.body.data.pageByHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPages(): Promise<Page[]> {
|
export async function getPages(store: Store): Promise<Page[]> {
|
||||||
const res = await shopifyFetch<ShopifyPagesOperation>({
|
const res = await shopifyFetch<ShopifyPagesOperation>({
|
||||||
|
store: store,
|
||||||
query: getPagesQuery,
|
query: getPagesQuery,
|
||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
});
|
});
|
||||||
@ -373,8 +405,9 @@ export async function getPages(): Promise<Page[]> {
|
|||||||
return removeEdgesAndNodes(res.body.data.pages);
|
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>({
|
const res = await shopifyFetch<ShopifyProductOperation>({
|
||||||
|
store,
|
||||||
query: getProductQuery,
|
query: getProductQuery,
|
||||||
tags: [TAGS.products],
|
tags: [TAGS.products],
|
||||||
variables: {
|
variables: {
|
||||||
@ -385,8 +418,28 @@ export async function getProduct(handle: string): Promise<Product | undefined> {
|
|||||||
return reshapeProduct(res.body.data.product, false);
|
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>({
|
const res = await shopifyFetch<ShopifyProductRecommendationsOperation>({
|
||||||
|
store,
|
||||||
query: getProductRecommendationsQuery,
|
query: getProductRecommendationsQuery,
|
||||||
tags: [TAGS.products],
|
tags: [TAGS.products],
|
||||||
variables: {
|
variables: {
|
||||||
@ -398,15 +451,18 @@ export async function getProductRecommendations(productId: string): Promise<Prod
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getProducts({
|
export async function getProducts({
|
||||||
|
store,
|
||||||
query,
|
query,
|
||||||
reverse,
|
reverse,
|
||||||
sortKey
|
sortKey
|
||||||
}: {
|
}: {
|
||||||
|
store: Store;
|
||||||
query?: string;
|
query?: string;
|
||||||
reverse?: boolean;
|
reverse?: boolean;
|
||||||
sortKey?: string;
|
sortKey?: string;
|
||||||
}): Promise<Product[]> {
|
}): Promise<Product[]> {
|
||||||
const res = await shopifyFetch<ShopifyProductsOperation>({
|
const res = await shopifyFetch<ShopifyProductsOperation>({
|
||||||
|
store,
|
||||||
query: getProductsQuery,
|
query: getProductsQuery,
|
||||||
tags: [TAGS.products],
|
tags: [TAGS.products],
|
||||||
variables: {
|
variables: {
|
||||||
|
@ -9,6 +9,15 @@ export const getProductQuery = /* GraphQL */ `
|
|||||||
${productFragment}
|
${productFragment}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const getProductByIdQuery = /* GraphQL */ `
|
||||||
|
query getProduct($id: ID!) {
|
||||||
|
product(id: $id) {
|
||||||
|
...product
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${productFragment}
|
||||||
|
`;
|
||||||
|
|
||||||
export const getProductsQuery = /* GraphQL */ `
|
export const getProductsQuery = /* GraphQL */ `
|
||||||
query getProducts($sortKey: ProductSortKeys, $reverse: Boolean, $query: String) {
|
query getProducts($sortKey: ProductSortKeys, $reverse: Boolean, $query: String) {
|
||||||
products(sortKey: $sortKey, reverse: $reverse, query: $query, first: 100) {
|
products(sortKey: $sortKey, reverse: $reverse, query: $query, first: 100) {
|
||||||
|
@ -8,6 +8,33 @@ export type Edge<T> = {
|
|||||||
node: 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'> & {
|
export type Cart = Omit<ShopifyCart, 'lines'> & {
|
||||||
lines: CartItem[];
|
lines: CartItem[];
|
||||||
};
|
};
|
||||||
@ -239,9 +266,11 @@ export type ShopifyPagesOperation = {
|
|||||||
|
|
||||||
export type ShopifyProductOperation = {
|
export type ShopifyProductOperation = {
|
||||||
data: { product: ShopifyProduct };
|
data: { product: ShopifyProduct };
|
||||||
variables: {
|
variables:
|
||||||
handle: string;
|
| {
|
||||||
};
|
handle: string;
|
||||||
|
}
|
||||||
|
| { id: string };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShopifyProductRecommendationsOperation = {
|
export type ShopifyProductRecommendationsOperation = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user