mirror of
https://github.com/vercel/commerce.git
synced 2025-05-15 05:56:59 +00:00
mocked backed api
This commit is contained in:
parent
e8c0ee04fc
commit
84ff7da090
@ -3,7 +3,7 @@ module.exports = {
|
|||||||
plugins: ['unicorn'],
|
plugins: ['unicorn'],
|
||||||
rules: {
|
rules: {
|
||||||
'no-unused-vars': [
|
'no-unused-vars': [
|
||||||
'error',
|
'off',
|
||||||
{
|
{
|
||||||
args: 'after-used',
|
args: 'after-used',
|
||||||
caughtErrors: 'none',
|
caughtErrors: 'none',
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { revalidate } from 'lib/shopify';
|
// import { revalidate } from 'lib/shopify';
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
export async function POST(req: NextRequest): Promise<NextResponse> {
|
export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||||
return revalidate(req);
|
// return revalidate(req);
|
||||||
|
return NextResponse.json({ status: 200, revalidated: true, now: Date.now() });
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,15 @@
|
|||||||
import { HIDDEN_PRODUCT_TAG, SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/constants';
|
// @ts-ignore
|
||||||
import { isShopifyError } from 'lib/type-guards';
|
|
||||||
import { revalidateTag } from 'next/cache';
|
|
||||||
import { headers } from 'next/headers';
|
|
||||||
import { NextRequest, NextResponse } from 'next/server';
|
|
||||||
import {
|
import {
|
||||||
addToCartMutation,
|
mockMoney,
|
||||||
createCartMutation,
|
mockImage,
|
||||||
editCartItemsMutation,
|
mockProductOption,
|
||||||
removeFromCartMutation
|
mockProductVariant,
|
||||||
} from './mutations/cart';
|
mockShopifyProduct,
|
||||||
import { getCartQuery } from './queries/cart';
|
mockCartItem,
|
||||||
import {
|
mockShopifyCart,
|
||||||
getCollectionProductsQuery,
|
mockShopifyCollection,
|
||||||
getCollectionQuery,
|
mockPage
|
||||||
getCollectionsQuery
|
} from './mock';
|
||||||
} from './queries/collection';
|
|
||||||
import { getMenuQuery } from './queries/menu';
|
|
||||||
import { getPageQuery, getPagesQuery } from './queries/page';
|
|
||||||
import {
|
|
||||||
getProductQuery,
|
|
||||||
getProductRecommendationsQuery,
|
|
||||||
getProductsQuery
|
|
||||||
} from './queries/product';
|
|
||||||
import {
|
import {
|
||||||
Cart,
|
Cart,
|
||||||
Collection,
|
Collection,
|
||||||
@ -30,88 +18,195 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
Page,
|
Page,
|
||||||
Product,
|
Product,
|
||||||
ShopifyAddToCartOperation,
|
|
||||||
ShopifyCart,
|
ShopifyCart,
|
||||||
ShopifyCartOperation,
|
|
||||||
ShopifyCollection,
|
ShopifyCollection,
|
||||||
ShopifyCollectionOperation,
|
|
||||||
ShopifyCollectionProductsOperation,
|
|
||||||
ShopifyCollectionsOperation,
|
ShopifyCollectionsOperation,
|
||||||
ShopifyCreateCartOperation,
|
ShopifyProduct
|
||||||
ShopifyMenuOperation,
|
|
||||||
ShopifyPageOperation,
|
|
||||||
ShopifyPagesOperation,
|
|
||||||
ShopifyProduct,
|
|
||||||
ShopifyProductOperation,
|
|
||||||
ShopifyProductRecommendationsOperation,
|
|
||||||
ShopifyProductsOperation,
|
|
||||||
ShopifyRemoveFromCartOperation,
|
|
||||||
ShopifyUpdateCartOperation
|
|
||||||
} from './types';
|
} from './types';
|
||||||
|
import { getCollectionsQuery } from './queries/collection';
|
||||||
|
import { TAGS } from '../constants';
|
||||||
|
import { shopifyFetch } from './index_old'; // Import the mock data from wherever it's located
|
||||||
|
|
||||||
const domain = `https://${process.env.SHOPIFY_STORE_DOMAIN!}`;
|
const HIDDEN_PRODUCT_TAG = 'hidden';
|
||||||
const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`;
|
|
||||||
const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
|
|
||||||
|
|
||||||
type ExtractVariables<T> = T extends { variables: object } ? T['variables'] : never;
|
// Helper function to simulate a fetch response
|
||||||
|
// @ts-ignore
|
||||||
export async function shopifyFetch<T>({
|
const mockFetchResponse = (data) => ({
|
||||||
cache = 'force-cache',
|
body: {
|
||||||
headers,
|
data
|
||||||
query,
|
}
|
||||||
tags,
|
|
||||||
variables
|
|
||||||
}: {
|
|
||||||
cache?: RequestCache;
|
|
||||||
headers?: HeadersInit;
|
|
||||||
query: string;
|
|
||||||
tags?: string[];
|
|
||||||
variables?: ExtractVariables<T>;
|
|
||||||
}): Promise<{ status: number; body: T } | never> {
|
|
||||||
try {
|
|
||||||
const result = await fetch(endpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Shopify-Storefront-Access-Token': key,
|
|
||||||
...headers
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
...(query && { query }),
|
|
||||||
...(variables && { variables })
|
|
||||||
}),
|
|
||||||
cache,
|
|
||||||
...(tags && { next: { tags } })
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const body = await result.json();
|
// @ts-ignore
|
||||||
|
const removeEdgesAndNodes = (connection) => connection.edges.map((edge) => edge.node);
|
||||||
|
|
||||||
if (body.errors) {
|
export const createCart = async (): Promise<Cart> => {
|
||||||
throw body.errors[0];
|
const res = mockFetchResponse({
|
||||||
|
cartCreate: {
|
||||||
|
cart: mockShopifyCart
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return {
|
return reshapeCart(res.body.data.cartCreate.cart);
|
||||||
status: result.status,
|
|
||||||
body
|
|
||||||
};
|
};
|
||||||
} catch (e) {
|
|
||||||
if (isShopifyError(e)) {
|
export const addToCart = async (
|
||||||
throw {
|
cartId: string,
|
||||||
status: e.status || 500,
|
lines: { merchandiseId: string; quantity: number }[]
|
||||||
message: e.message,
|
): Promise<Cart> => {
|
||||||
query
|
const res = mockFetchResponse({
|
||||||
|
cartLinesAdd: {
|
||||||
|
cart: mockShopifyCart
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return reshapeCart(res.body.data.cartLinesAdd.cart);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const removeFromCart = async (cartId: string, lineIds: string[]): Promise<Cart> => {
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
cartLinesRemove: {
|
||||||
|
cart: mockShopifyCart
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return reshapeCart(res.body.data.cartLinesRemove.cart);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateCart = async (
|
||||||
|
cartId: string,
|
||||||
|
lines: { id: string; merchandiseId: string; quantity: number }[]
|
||||||
|
): Promise<Cart> => {
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
cartLinesUpdate: {
|
||||||
|
cart: mockShopifyCart
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return reshapeCart(res.body.data.cartLinesUpdate.cart);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCart = async (cartId: string): Promise<Cart | undefined> => {
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
cart: mockShopifyCart
|
||||||
|
});
|
||||||
|
return reshapeCart(res.body.data.cart);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCollection = async (handle: string): Promise<Collection | undefined> => {
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
collection: mockShopifyCollection
|
||||||
|
});
|
||||||
|
return reshapeCollection(res.body.data.collection);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCollectionProducts = async ({
|
||||||
|
collection,
|
||||||
|
reverse,
|
||||||
|
sortKey
|
||||||
|
}: {
|
||||||
|
collection: string;
|
||||||
|
reverse?: boolean;
|
||||||
|
sortKey?: string;
|
||||||
|
}): Promise<Product[]> => {
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
collection: {
|
||||||
|
products: {
|
||||||
|
edges: [{ node: mockShopifyProduct }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return reshapeProducts(removeEdgesAndNodes(res.body.data.collection.products));
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getCollections(): Promise<Collection[]> {
|
||||||
|
// const res = await shopifyFetch<ShopifyCollectionsOperation>({
|
||||||
|
// query: getCollectionsQuery,
|
||||||
|
// tags: [TAGS.collections]
|
||||||
|
// });
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
collections: {
|
||||||
|
edges: [{ node: mockShopifyCollection }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const shopifyCollections = removeEdgesAndNodes(res.body?.data?.collections);
|
||||||
|
const collections = [
|
||||||
|
{
|
||||||
|
handle: '',
|
||||||
|
title: 'All',
|
||||||
|
description: 'All products',
|
||||||
|
seo: {
|
||||||
|
title: 'All',
|
||||||
|
description: 'All products'
|
||||||
|
},
|
||||||
|
path: '/search',
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
},
|
||||||
|
// Filter out the `hidden` collections.
|
||||||
|
// Collections that start with `hidden-*` need to be hidden on the search page.
|
||||||
|
...reshapeCollections(shopifyCollections).filter(
|
||||||
|
(collection) => !collection.handle.startsWith('hidden')
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
return collections;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw {
|
export const getMenu = async (handle: string): Promise<Menu[]> => {
|
||||||
error: e,
|
const res = mockFetchResponse({
|
||||||
query
|
menu: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: 'Sample Menu',
|
||||||
|
path: 'https://example.com/sample-menu'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return res.body.data.menu.items;
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeEdgesAndNodes = (array: Connection<any>) => {
|
export const getPage = async (handle: string): Promise<Page> => {
|
||||||
return array.edges.map((edge) => edge?.node);
|
const res = mockFetchResponse({
|
||||||
|
pageByHandle: mockPage
|
||||||
|
});
|
||||||
|
return res.body.data.pageByHandle;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPages = async (): Promise<Page[]> => {
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
pages: {
|
||||||
|
edges: [{ node: mockPage }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return removeEdgesAndNodes(res.body.data.pages);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProduct = async (handle: string): Promise<Product | undefined> => {
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
product: mockShopifyProduct
|
||||||
|
});
|
||||||
|
return reshapeProduct(res.body.data.product, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProductRecommendations = async (productId: string): Promise<Product[]> => {
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
productRecommendations: [mockShopifyProduct]
|
||||||
|
});
|
||||||
|
return reshapeProducts(res.body.data.productRecommendations);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProducts = async ({
|
||||||
|
query,
|
||||||
|
reverse,
|
||||||
|
sortKey
|
||||||
|
}: {
|
||||||
|
query?: string;
|
||||||
|
reverse?: boolean;
|
||||||
|
sortKey?: string;
|
||||||
|
}): Promise<Product[]> => {
|
||||||
|
const res = mockFetchResponse({
|
||||||
|
products: {
|
||||||
|
edges: [{ node: mockShopifyProduct }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return reshapeProducts(removeEdgesAndNodes(res.body.data.products));
|
||||||
};
|
};
|
||||||
|
|
||||||
const reshapeCart = (cart: ShopifyCart): Cart => {
|
const reshapeCart = (cart: ShopifyCart): Cart => {
|
||||||
@ -158,6 +253,7 @@ const reshapeCollections = (collections: ShopifyCollection[]) => {
|
|||||||
const reshapeImages = (images: Connection<Image>, productTitle: string) => {
|
const reshapeImages = (images: Connection<Image>, productTitle: string) => {
|
||||||
const flattened = removeEdgesAndNodes(images);
|
const flattened = removeEdgesAndNodes(images);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
return flattened.map((image) => {
|
return flattened.map((image) => {
|
||||||
const filename = image.url.match(/.*\/(.*)\..*/)[1];
|
const filename = image.url.match(/.*\/(.*)\..*/)[1];
|
||||||
return {
|
return {
|
||||||
@ -196,250 +292,3 @@ const reshapeProducts = (products: ShopifyProduct[]) => {
|
|||||||
|
|
||||||
return reshapedProducts;
|
return reshapedProducts;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function createCart(): Promise<Cart> {
|
|
||||||
const res = await shopifyFetch<ShopifyCreateCartOperation>({
|
|
||||||
query: createCartMutation,
|
|
||||||
cache: 'no-store'
|
|
||||||
});
|
|
||||||
|
|
||||||
return reshapeCart(res.body.data.cartCreate.cart);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function addToCart(
|
|
||||||
cartId: string,
|
|
||||||
lines: { merchandiseId: string; quantity: number }[]
|
|
||||||
): Promise<Cart> {
|
|
||||||
const res = await shopifyFetch<ShopifyAddToCartOperation>({
|
|
||||||
query: addToCartMutation,
|
|
||||||
variables: {
|
|
||||||
cartId,
|
|
||||||
lines
|
|
||||||
},
|
|
||||||
cache: 'no-store'
|
|
||||||
});
|
|
||||||
return reshapeCart(res.body.data.cartLinesAdd.cart);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function removeFromCart(cartId: string, lineIds: string[]): Promise<Cart> {
|
|
||||||
const res = await shopifyFetch<ShopifyRemoveFromCartOperation>({
|
|
||||||
query: removeFromCartMutation,
|
|
||||||
variables: {
|
|
||||||
cartId,
|
|
||||||
lineIds
|
|
||||||
},
|
|
||||||
cache: 'no-store'
|
|
||||||
});
|
|
||||||
|
|
||||||
return reshapeCart(res.body.data.cartLinesRemove.cart);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateCart(
|
|
||||||
cartId: string,
|
|
||||||
lines: { id: string; merchandiseId: string; quantity: number }[]
|
|
||||||
): Promise<Cart> {
|
|
||||||
const res = await shopifyFetch<ShopifyUpdateCartOperation>({
|
|
||||||
query: editCartItemsMutation,
|
|
||||||
variables: {
|
|
||||||
cartId,
|
|
||||||
lines
|
|
||||||
},
|
|
||||||
cache: 'no-store'
|
|
||||||
});
|
|
||||||
|
|
||||||
return reshapeCart(res.body.data.cartLinesUpdate.cart);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCart(cartId: string): Promise<Cart | undefined> {
|
|
||||||
const res = await shopifyFetch<ShopifyCartOperation>({
|
|
||||||
query: getCartQuery,
|
|
||||||
variables: { cartId },
|
|
||||||
cache: 'no-store'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Old carts becomes `null` when you checkout.
|
|
||||||
if (!res.body.data.cart) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return reshapeCart(res.body.data.cart);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCollection(handle: string): Promise<Collection | undefined> {
|
|
||||||
const res = await shopifyFetch<ShopifyCollectionOperation>({
|
|
||||||
query: getCollectionQuery,
|
|
||||||
tags: [TAGS.collections],
|
|
||||||
variables: {
|
|
||||||
handle
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return reshapeCollection(res.body.data.collection);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCollectionProducts({
|
|
||||||
collection,
|
|
||||||
reverse,
|
|
||||||
sortKey
|
|
||||||
}: {
|
|
||||||
collection: string;
|
|
||||||
reverse?: boolean;
|
|
||||||
sortKey?: string;
|
|
||||||
}): Promise<Product[]> {
|
|
||||||
const res = await shopifyFetch<ShopifyCollectionProductsOperation>({
|
|
||||||
query: getCollectionProductsQuery,
|
|
||||||
tags: [TAGS.collections, TAGS.products],
|
|
||||||
variables: {
|
|
||||||
handle: collection,
|
|
||||||
reverse,
|
|
||||||
sortKey: sortKey === 'CREATED_AT' ? 'CREATED' : sortKey
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.body.data.collection) {
|
|
||||||
console.log(`No collection found for \`${collection}\``);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return reshapeProducts(removeEdgesAndNodes(res.body.data.collection.products));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getCollections(): Promise<Collection[]> {
|
|
||||||
const res = await shopifyFetch<ShopifyCollectionsOperation>({
|
|
||||||
query: getCollectionsQuery,
|
|
||||||
tags: [TAGS.collections]
|
|
||||||
});
|
|
||||||
const shopifyCollections = removeEdgesAndNodes(res.body?.data?.collections);
|
|
||||||
const collections = [
|
|
||||||
{
|
|
||||||
handle: '',
|
|
||||||
title: 'All',
|
|
||||||
description: 'All products',
|
|
||||||
seo: {
|
|
||||||
title: 'All',
|
|
||||||
description: 'All products'
|
|
||||||
},
|
|
||||||
path: '/search',
|
|
||||||
updatedAt: new Date().toISOString()
|
|
||||||
},
|
|
||||||
// Filter out the `hidden` collections.
|
|
||||||
// Collections that start with `hidden-*` need to be hidden on the search page.
|
|
||||||
...reshapeCollections(shopifyCollections).filter(
|
|
||||||
(collection) => !collection.handle.startsWith('hidden')
|
|
||||||
)
|
|
||||||
];
|
|
||||||
|
|
||||||
return collections;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMenu(handle: string): Promise<Menu[]> {
|
|
||||||
const res = await shopifyFetch<ShopifyMenuOperation>({
|
|
||||||
query: getMenuQuery,
|
|
||||||
tags: [TAGS.collections],
|
|
||||||
variables: {
|
|
||||||
handle
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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', '')
|
|
||||||
})) || []
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPage(handle: string): Promise<Page> {
|
|
||||||
const res = await shopifyFetch<ShopifyPageOperation>({
|
|
||||||
query: getPageQuery,
|
|
||||||
variables: { handle }
|
|
||||||
});
|
|
||||||
|
|
||||||
return res.body.data.pageByHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPages(): Promise<Page[]> {
|
|
||||||
const res = await shopifyFetch<ShopifyPagesOperation>({
|
|
||||||
query: getPagesQuery
|
|
||||||
});
|
|
||||||
|
|
||||||
return removeEdgesAndNodes(res.body.data.pages);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getProduct(handle: string): Promise<Product | undefined> {
|
|
||||||
const res = await shopifyFetch<ShopifyProductOperation>({
|
|
||||||
query: getProductQuery,
|
|
||||||
tags: [TAGS.products],
|
|
||||||
variables: {
|
|
||||||
handle
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return reshapeProduct(res.body.data.product, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getProductRecommendations(productId: string): Promise<Product[]> {
|
|
||||||
const res = await shopifyFetch<ShopifyProductRecommendationsOperation>({
|
|
||||||
query: getProductRecommendationsQuery,
|
|
||||||
tags: [TAGS.products],
|
|
||||||
variables: {
|
|
||||||
productId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return reshapeProducts(res.body.data.productRecommendations);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getProducts({
|
|
||||||
query,
|
|
||||||
reverse,
|
|
||||||
sortKey
|
|
||||||
}: {
|
|
||||||
query?: string;
|
|
||||||
reverse?: boolean;
|
|
||||||
sortKey?: string;
|
|
||||||
}): Promise<Product[]> {
|
|
||||||
const res = await shopifyFetch<ShopifyProductsOperation>({
|
|
||||||
query: getProductsQuery,
|
|
||||||
tags: [TAGS.products],
|
|
||||||
variables: {
|
|
||||||
query,
|
|
||||||
reverse,
|
|
||||||
sortKey
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return reshapeProducts(removeEdgesAndNodes(res.body.data.products));
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is called from `app/api/revalidate.ts` so providers can control revalidation logic.
|
|
||||||
export async function revalidate(req: NextRequest): Promise<NextResponse> {
|
|
||||||
// We always need to respond with a 200 status code to Shopify,
|
|
||||||
// otherwise it will continue to retry the request.
|
|
||||||
const collectionWebhooks = ['collections/create', 'collections/delete', 'collections/update'];
|
|
||||||
const productWebhooks = ['products/create', 'products/delete', 'products/update'];
|
|
||||||
const topic = headers().get('x-shopify-topic') || 'unknown';
|
|
||||||
const secret = req.nextUrl.searchParams.get('secret');
|
|
||||||
const isCollectionUpdate = collectionWebhooks.includes(topic);
|
|
||||||
const isProductUpdate = productWebhooks.includes(topic);
|
|
||||||
|
|
||||||
if (!secret || secret !== process.env.SHOPIFY_REVALIDATION_SECRET) {
|
|
||||||
console.error('Invalid revalidation secret.');
|
|
||||||
return NextResponse.json({ status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCollectionUpdate && !isProductUpdate) {
|
|
||||||
// We don't need to revalidate anything for any other topics.
|
|
||||||
return NextResponse.json({ status: 200 });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCollectionUpdate) {
|
|
||||||
revalidateTag(TAGS.collections);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isProductUpdate) {
|
|
||||||
revalidateTag(TAGS.products);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ status: 200, revalidated: true, now: Date.now() });
|
|
||||||
}
|
|
||||||
|
445
lib/shopify/index_old.ts
Normal file
445
lib/shopify/index_old.ts
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
import { HIDDEN_PRODUCT_TAG, SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/constants';
|
||||||
|
import { isShopifyError } from 'lib/type-guards';
|
||||||
|
import { revalidateTag } from 'next/cache';
|
||||||
|
import { headers } from 'next/headers';
|
||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import {
|
||||||
|
addToCartMutation,
|
||||||
|
createCartMutation,
|
||||||
|
editCartItemsMutation,
|
||||||
|
removeFromCartMutation
|
||||||
|
} from './mutations/cart';
|
||||||
|
import { getCartQuery } from './queries/cart';
|
||||||
|
import {
|
||||||
|
getCollectionProductsQuery,
|
||||||
|
getCollectionQuery,
|
||||||
|
getCollectionsQuery
|
||||||
|
} from './queries/collection';
|
||||||
|
import { getMenuQuery } from './queries/menu';
|
||||||
|
import { getPageQuery, getPagesQuery } from './queries/page';
|
||||||
|
import {
|
||||||
|
getProductQuery,
|
||||||
|
getProductRecommendationsQuery,
|
||||||
|
getProductsQuery
|
||||||
|
} from './queries/product';
|
||||||
|
import {
|
||||||
|
Cart,
|
||||||
|
Collection,
|
||||||
|
Connection,
|
||||||
|
Image,
|
||||||
|
Menu,
|
||||||
|
Page,
|
||||||
|
Product,
|
||||||
|
ShopifyAddToCartOperation,
|
||||||
|
ShopifyCart,
|
||||||
|
ShopifyCartOperation,
|
||||||
|
ShopifyCollection,
|
||||||
|
ShopifyCollectionOperation,
|
||||||
|
ShopifyCollectionProductsOperation,
|
||||||
|
ShopifyCollectionsOperation,
|
||||||
|
ShopifyCreateCartOperation,
|
||||||
|
ShopifyMenuOperation,
|
||||||
|
ShopifyPageOperation,
|
||||||
|
ShopifyPagesOperation,
|
||||||
|
ShopifyProduct,
|
||||||
|
ShopifyProductOperation,
|
||||||
|
ShopifyProductRecommendationsOperation,
|
||||||
|
ShopifyProductsOperation,
|
||||||
|
ShopifyRemoveFromCartOperation,
|
||||||
|
ShopifyUpdateCartOperation
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
const domain = `https://${process.env.SHOPIFY_STORE_DOMAIN!}`;
|
||||||
|
const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`;
|
||||||
|
const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!;
|
||||||
|
|
||||||
|
type ExtractVariables<T> = T extends { variables: object } ? T['variables'] : never;
|
||||||
|
|
||||||
|
export async function shopifyFetch<T>({
|
||||||
|
cache = 'force-cache',
|
||||||
|
headers,
|
||||||
|
query,
|
||||||
|
tags,
|
||||||
|
variables
|
||||||
|
}: {
|
||||||
|
cache?: RequestCache;
|
||||||
|
headers?: HeadersInit;
|
||||||
|
query: string;
|
||||||
|
tags?: string[];
|
||||||
|
variables?: ExtractVariables<T>;
|
||||||
|
}): Promise<{ status: number; body: T } | never> {
|
||||||
|
try {
|
||||||
|
const result = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Shopify-Storefront-Access-Token': key,
|
||||||
|
...headers
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...(query && { query }),
|
||||||
|
...(variables && { variables })
|
||||||
|
}),
|
||||||
|
cache,
|
||||||
|
...(tags && { next: { tags } })
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = await result.json();
|
||||||
|
|
||||||
|
if (body.errors) {
|
||||||
|
throw body.errors[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: result.status,
|
||||||
|
body
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
if (isShopifyError(e)) {
|
||||||
|
throw {
|
||||||
|
status: e.status || 500,
|
||||||
|
message: e.message,
|
||||||
|
query
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
throw {
|
||||||
|
error: e,
|
||||||
|
query
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeEdgesAndNodes = (array: Connection<any>) => {
|
||||||
|
return array.edges.map((edge) => edge?.node);
|
||||||
|
};
|
||||||
|
|
||||||
|
const reshapeCart = (cart: ShopifyCart): Cart => {
|
||||||
|
if (!cart.cost?.totalTaxAmount) {
|
||||||
|
cart.cost.totalTaxAmount = {
|
||||||
|
amount: '0.0',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...cart,
|
||||||
|
lines: removeEdgesAndNodes(cart.lines)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const reshapeCollection = (collection: ShopifyCollection): Collection | undefined => {
|
||||||
|
if (!collection) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...collection,
|
||||||
|
path: `/search/${collection.handle}`
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const reshapeCollections = (collections: ShopifyCollection[]) => {
|
||||||
|
const reshapedCollections = [];
|
||||||
|
|
||||||
|
for (const collection of collections) {
|
||||||
|
if (collection) {
|
||||||
|
const reshapedCollection = reshapeCollection(collection);
|
||||||
|
|
||||||
|
if (reshapedCollection) {
|
||||||
|
reshapedCollections.push(reshapedCollection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reshapedCollections;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reshapeImages = (images: Connection<Image>, productTitle: string) => {
|
||||||
|
const flattened = removeEdgesAndNodes(images);
|
||||||
|
|
||||||
|
return flattened.map((image) => {
|
||||||
|
const filename = image.url.match(/.*\/(.*)\..*/)[1];
|
||||||
|
return {
|
||||||
|
...image,
|
||||||
|
altText: image.altText || `${productTitle} - ${filename}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const reshapeProduct = (product: ShopifyProduct, filterHiddenProducts: boolean = true) => {
|
||||||
|
if (!product || (filterHiddenProducts && product.tags.includes(HIDDEN_PRODUCT_TAG))) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { images, variants, ...rest } = product;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
images: reshapeImages(images, product.title),
|
||||||
|
variants: removeEdgesAndNodes(variants)
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const reshapeProducts = (products: ShopifyProduct[]) => {
|
||||||
|
const reshapedProducts = [];
|
||||||
|
|
||||||
|
for (const product of products) {
|
||||||
|
if (product) {
|
||||||
|
const reshapedProduct = reshapeProduct(product);
|
||||||
|
|
||||||
|
if (reshapedProduct) {
|
||||||
|
reshapedProducts.push(reshapedProduct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reshapedProducts;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function createCart(): Promise<Cart> {
|
||||||
|
const res = await shopifyFetch<ShopifyCreateCartOperation>({
|
||||||
|
query: createCartMutation,
|
||||||
|
cache: 'no-store'
|
||||||
|
});
|
||||||
|
|
||||||
|
return reshapeCart(res.body.data.cartCreate.cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addToCart(
|
||||||
|
cartId: string,
|
||||||
|
lines: { merchandiseId: string; quantity: number }[]
|
||||||
|
): Promise<Cart> {
|
||||||
|
const res = await shopifyFetch<ShopifyAddToCartOperation>({
|
||||||
|
query: addToCartMutation,
|
||||||
|
variables: {
|
||||||
|
cartId,
|
||||||
|
lines
|
||||||
|
},
|
||||||
|
cache: 'no-store'
|
||||||
|
});
|
||||||
|
return reshapeCart(res.body.data.cartLinesAdd.cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeFromCart(cartId: string, lineIds: string[]): Promise<Cart> {
|
||||||
|
const res = await shopifyFetch<ShopifyRemoveFromCartOperation>({
|
||||||
|
query: removeFromCartMutation,
|
||||||
|
variables: {
|
||||||
|
cartId,
|
||||||
|
lineIds
|
||||||
|
},
|
||||||
|
cache: 'no-store'
|
||||||
|
});
|
||||||
|
|
||||||
|
return reshapeCart(res.body.data.cartLinesRemove.cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateCart(
|
||||||
|
cartId: string,
|
||||||
|
lines: { id: string; merchandiseId: string; quantity: number }[]
|
||||||
|
): Promise<Cart> {
|
||||||
|
const res = await shopifyFetch<ShopifyUpdateCartOperation>({
|
||||||
|
query: editCartItemsMutation,
|
||||||
|
variables: {
|
||||||
|
cartId,
|
||||||
|
lines
|
||||||
|
},
|
||||||
|
cache: 'no-store'
|
||||||
|
});
|
||||||
|
|
||||||
|
return reshapeCart(res.body.data.cartLinesUpdate.cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCart(cartId: string): Promise<Cart | undefined> {
|
||||||
|
const res = await shopifyFetch<ShopifyCartOperation>({
|
||||||
|
query: getCartQuery,
|
||||||
|
variables: { cartId },
|
||||||
|
cache: 'no-store'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Old carts becomes `null` when you checkout.
|
||||||
|
if (!res.body.data.cart) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reshapeCart(res.body.data.cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCollection(handle: string): Promise<Collection | undefined> {
|
||||||
|
const res = await shopifyFetch<ShopifyCollectionOperation>({
|
||||||
|
query: getCollectionQuery,
|
||||||
|
tags: [TAGS.collections],
|
||||||
|
variables: {
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return reshapeCollection(res.body.data.collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCollectionProducts({
|
||||||
|
collection,
|
||||||
|
reverse,
|
||||||
|
sortKey
|
||||||
|
}: {
|
||||||
|
collection: string;
|
||||||
|
reverse?: boolean;
|
||||||
|
sortKey?: string;
|
||||||
|
}): Promise<Product[]> {
|
||||||
|
const res = await shopifyFetch<ShopifyCollectionProductsOperation>({
|
||||||
|
query: getCollectionProductsQuery,
|
||||||
|
tags: [TAGS.collections, TAGS.products],
|
||||||
|
variables: {
|
||||||
|
handle: collection,
|
||||||
|
reverse,
|
||||||
|
sortKey: sortKey === 'CREATED_AT' ? 'CREATED' : sortKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.body.data.collection) {
|
||||||
|
console.log(`No collection found for \`${collection}\``);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return reshapeProducts(removeEdgesAndNodes(res.body.data.collection.products));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCollections(): Promise<Collection[]> {
|
||||||
|
const res = await shopifyFetch<ShopifyCollectionsOperation>({
|
||||||
|
query: getCollectionsQuery,
|
||||||
|
tags: [TAGS.collections]
|
||||||
|
});
|
||||||
|
const shopifyCollections = removeEdgesAndNodes(res.body?.data?.collections);
|
||||||
|
const collections = [
|
||||||
|
{
|
||||||
|
handle: '',
|
||||||
|
title: 'All',
|
||||||
|
description: 'All products',
|
||||||
|
seo: {
|
||||||
|
title: 'All',
|
||||||
|
description: 'All products'
|
||||||
|
},
|
||||||
|
path: '/search',
|
||||||
|
updatedAt: new Date().toISOString()
|
||||||
|
},
|
||||||
|
// Filter out the `hidden` collections.
|
||||||
|
// Collections that start with `hidden-*` need to be hidden on the search page.
|
||||||
|
...reshapeCollections(shopifyCollections).filter(
|
||||||
|
(collection) => !collection.handle.startsWith('hidden')
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
return collections;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMenu(handle: string): Promise<Menu[]> {
|
||||||
|
const res = await shopifyFetch<ShopifyMenuOperation>({
|
||||||
|
query: getMenuQuery,
|
||||||
|
tags: [TAGS.collections],
|
||||||
|
variables: {
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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', '')
|
||||||
|
})) || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPage(handle: string): Promise<Page> {
|
||||||
|
const res = await shopifyFetch<ShopifyPageOperation>({
|
||||||
|
query: getPageQuery,
|
||||||
|
variables: { handle }
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.body.data.pageByHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPages(): Promise<Page[]> {
|
||||||
|
const res = await shopifyFetch<ShopifyPagesOperation>({
|
||||||
|
query: getPagesQuery
|
||||||
|
});
|
||||||
|
|
||||||
|
return removeEdgesAndNodes(res.body.data.pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProduct(handle: string): Promise<Product | undefined> {
|
||||||
|
const res = await shopifyFetch<ShopifyProductOperation>({
|
||||||
|
query: getProductQuery,
|
||||||
|
tags: [TAGS.products],
|
||||||
|
variables: {
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return reshapeProduct(res.body.data.product, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProductRecommendations(productId: string): Promise<Product[]> {
|
||||||
|
const res = await shopifyFetch<ShopifyProductRecommendationsOperation>({
|
||||||
|
query: getProductRecommendationsQuery,
|
||||||
|
tags: [TAGS.products],
|
||||||
|
variables: {
|
||||||
|
productId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return reshapeProducts(res.body.data.productRecommendations);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getProducts({
|
||||||
|
query,
|
||||||
|
reverse,
|
||||||
|
sortKey
|
||||||
|
}: {
|
||||||
|
query?: string;
|
||||||
|
reverse?: boolean;
|
||||||
|
sortKey?: string;
|
||||||
|
}): Promise<Product[]> {
|
||||||
|
const res = await shopifyFetch<ShopifyProductsOperation>({
|
||||||
|
query: getProductsQuery,
|
||||||
|
tags: [TAGS.products],
|
||||||
|
variables: {
|
||||||
|
query,
|
||||||
|
reverse,
|
||||||
|
sortKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return reshapeProducts(removeEdgesAndNodes(res.body.data.products));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is called from `app/api/revalidate.ts` so providers can control revalidation logic.
|
||||||
|
export async function revalidate(req: NextRequest): Promise<NextResponse> {
|
||||||
|
// We always need to respond with a 200 status code to Shopify,
|
||||||
|
// otherwise it will continue to retry the request.
|
||||||
|
const collectionWebhooks = ['collections/create', 'collections/delete', 'collections/update'];
|
||||||
|
const productWebhooks = ['products/create', 'products/delete', 'products/update'];
|
||||||
|
const topic = headers().get('x-shopify-topic') || 'unknown';
|
||||||
|
const secret = req.nextUrl.searchParams.get('secret');
|
||||||
|
const isCollectionUpdate = collectionWebhooks.includes(topic);
|
||||||
|
const isProductUpdate = productWebhooks.includes(topic);
|
||||||
|
|
||||||
|
if (!secret || secret !== process.env.SHOPIFY_REVALIDATION_SECRET) {
|
||||||
|
console.error('Invalid revalidation secret.');
|
||||||
|
return NextResponse.json({ status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCollectionUpdate && !isProductUpdate) {
|
||||||
|
// We don't need to revalidate anything for any other topics.
|
||||||
|
return NextResponse.json({ status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCollectionUpdate) {
|
||||||
|
revalidateTag(TAGS.collections);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isProductUpdate) {
|
||||||
|
revalidateTag(TAGS.products);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({ status: 200, revalidated: true, now: Date.now() });
|
||||||
|
}
|
123
lib/shopify/mock.js
Normal file
123
lib/shopify/mock.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// Mock data for the defined types
|
||||||
|
|
||||||
|
const mockMoney = {
|
||||||
|
amount: '100.00',
|
||||||
|
currencyCode: 'USD'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockImage = {
|
||||||
|
url: 'https://example.com/image.jpg',
|
||||||
|
altText: 'Sample Image',
|
||||||
|
width: 500,
|
||||||
|
height: 500
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockProductOption = {
|
||||||
|
id: 'option1',
|
||||||
|
name: 'Color',
|
||||||
|
values: ['Red', 'Blue', 'Green']
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockProductVariant = {
|
||||||
|
id: 'variant1',
|
||||||
|
title: 'Red Variant',
|
||||||
|
availableForSale: true,
|
||||||
|
selectedOptions: [
|
||||||
|
{
|
||||||
|
name: 'Color',
|
||||||
|
value: 'Red'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
price: mockMoney
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockShopifyProduct = {
|
||||||
|
id: 'product1',
|
||||||
|
handle: 'sample-product',
|
||||||
|
availableForSale: true,
|
||||||
|
title: 'Sample Product',
|
||||||
|
description: 'This is a sample product.',
|
||||||
|
descriptionHtml: '<p>This is a sample product.</p>',
|
||||||
|
options: [mockProductOption],
|
||||||
|
priceRange: {
|
||||||
|
maxVariantPrice: mockMoney,
|
||||||
|
minVariantPrice: mockMoney
|
||||||
|
},
|
||||||
|
variants: { edges: [{ node: mockProductVariant }] },
|
||||||
|
featuredImage: mockImage,
|
||||||
|
images: { edges: [{ node: mockImage }] },
|
||||||
|
seo: {
|
||||||
|
title: 'Sample Product',
|
||||||
|
description: 'This is a sample product.'
|
||||||
|
},
|
||||||
|
tags: ['sample', 'product'],
|
||||||
|
updatedAt: '2023-08-10T00:00:00Z'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCartItem = {
|
||||||
|
id: 'item1',
|
||||||
|
quantity: 1,
|
||||||
|
cost: { totalAmount: mockMoney },
|
||||||
|
merchandise: {
|
||||||
|
id: 'merchandise1',
|
||||||
|
title: 'Sample Merchandise',
|
||||||
|
selectedOptions: [
|
||||||
|
{
|
||||||
|
name: 'Color',
|
||||||
|
value: 'Red'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
product: mockShopifyProduct
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockShopifyCart = {
|
||||||
|
id: 'cart1',
|
||||||
|
checkoutUrl: 'https://example.com/checkout',
|
||||||
|
cost: {
|
||||||
|
subtotalAmount: mockMoney,
|
||||||
|
totalAmount: mockMoney,
|
||||||
|
totalTaxAmount: mockMoney
|
||||||
|
},
|
||||||
|
lines: { edges: [{ node: mockCartItem }] },
|
||||||
|
totalQuantity: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockShopifyCollection = {
|
||||||
|
handle: 'sample-collection',
|
||||||
|
title: 'Sample Collection',
|
||||||
|
description: 'This is a sample collection.',
|
||||||
|
seo: {
|
||||||
|
title: 'Sample Collection',
|
||||||
|
description: 'This is a sample collection.'
|
||||||
|
},
|
||||||
|
updatedAt: '2023-08-10T00:00:00Z'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockPage = {
|
||||||
|
id: 'page1',
|
||||||
|
title: 'Sample Page',
|
||||||
|
handle: 'sample-page',
|
||||||
|
body: 'This is a sample page.',
|
||||||
|
bodySummary: 'Sample summary.',
|
||||||
|
seo: {
|
||||||
|
title: 'Sample Page',
|
||||||
|
description: 'This is a sample page.'
|
||||||
|
},
|
||||||
|
createdAt: '2023-08-01T00:00:00Z',
|
||||||
|
updatedAt: '2023-08-10T00:00:00Z'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Exporting the mock data
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mockMoney,
|
||||||
|
mockImage,
|
||||||
|
mockProductOption,
|
||||||
|
mockProductVariant,
|
||||||
|
mockShopifyProduct,
|
||||||
|
mockCartItem,
|
||||||
|
mockShopifyCart,
|
||||||
|
mockShopifyCollection,
|
||||||
|
mockPage
|
||||||
|
};
|
@ -14,6 +14,11 @@ module.exports = {
|
|||||||
protocol: 'https',
|
protocol: 'https',
|
||||||
hostname: 'cdn.shopify.com',
|
hostname: 'cdn.shopify.com',
|
||||||
pathname: '/s/files/**'
|
pathname: '/s/files/**'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'example.com',
|
||||||
|
pathname: '/**'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user