Merge branch 'main' into develop

This commit is contained in:
Thomas Frost 2024-07-20 13:56:15 -07:00
commit 659d7cf255
13 changed files with 175 additions and 271 deletions

View File

@ -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 />
</> </>
); );
} }

View File

@ -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;

View File

@ -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 });
}

View File

@ -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>
</>
);
}

View File

@ -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>

View File

@ -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 });
}

View File

@ -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>
);
}

View File

@ -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 />
</>
);
}

View File

@ -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>
);
}

View File

@ -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">&quot;{searchValue}&quot;</span>
</p>
) : null}
{products.length > 0 ? (
<Grid className="grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
<ProductGridItems products={products} />
</Grid>
) : null}
</>
);
}

View File

@ -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: {

View File

@ -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) {

View File

@ -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 = {