From f6652da4ab555370cc8ac8c0b93a36db80cffb27 Mon Sep 17 00:00:00 2001 From: Jieren Chen Date: Fri, 20 Sep 2024 09:12:53 -0400 Subject: [PATCH] cleanup types --- components/grid/three-items.tsx | 2 +- lib/fourthwall/reshape.ts | 2 +- lib/fourthwall/types.ts | 1 - lib/shopify/fragments/cart.ts | 53 ------ lib/shopify/fragments/image.ts | 10 -- lib/shopify/fragments/product.ts | 64 ------- lib/shopify/fragments/seo.ts | 8 - lib/shopify/index.ts | 278 ------------------------------ lib/shopify/mutations/cart.ts | 45 ----- lib/shopify/queries/cart.ts | 10 -- lib/shopify/queries/collection.ts | 56 ------ lib/shopify/queries/menu.ts | 10 -- lib/shopify/queries/page.ts | 41 ----- lib/shopify/queries/product.ts | 32 ---- lib/shopify/types.ts | 273 ----------------------------- lib/types.ts | 125 ++++++++++++++ 16 files changed, 127 insertions(+), 883 deletions(-) delete mode 100644 lib/shopify/fragments/cart.ts delete mode 100644 lib/shopify/fragments/image.ts delete mode 100644 lib/shopify/fragments/product.ts delete mode 100644 lib/shopify/fragments/seo.ts delete mode 100644 lib/shopify/index.ts delete mode 100644 lib/shopify/mutations/cart.ts delete mode 100644 lib/shopify/queries/cart.ts delete mode 100644 lib/shopify/queries/collection.ts delete mode 100644 lib/shopify/queries/menu.ts delete mode 100644 lib/shopify/queries/page.ts delete mode 100644 lib/shopify/queries/product.ts delete mode 100644 lib/shopify/types.ts create mode 100644 lib/types.ts diff --git a/components/grid/three-items.tsx b/components/grid/three-items.tsx index 5869405a2..44826c61b 100644 --- a/components/grid/three-items.tsx +++ b/components/grid/three-items.tsx @@ -1,6 +1,6 @@ import { GridTileImage } from 'components/grid/tile'; import { getCollectionProducts } from 'lib/fourthwall'; -import type { Product } from 'lib/shopify/types'; +import type { Product } from 'lib/types'; import Link from 'next/link'; function ThreeItemGridItem({ diff --git a/lib/fourthwall/reshape.ts b/lib/fourthwall/reshape.ts index 3340eef64..1f5e4369d 100644 --- a/lib/fourthwall/reshape.ts +++ b/lib/fourthwall/reshape.ts @@ -1,4 +1,4 @@ -import { Cart, CartItem, Image, Money, Product, ProductVariant } from "lib/shopify/types"; +import { Cart, CartItem, Image, Money, Product, ProductVariant } from "lib/types"; import { FourthwallCart, FourthwallCartItem, FourthwallMoney, FourthwallProduct, FourthwallProductImage, FourthwallProductVariant } from "./types"; /** diff --git a/lib/fourthwall/types.ts b/lib/fourthwall/types.ts index 114b26d4c..72fc4cd52 100644 --- a/lib/fourthwall/types.ts +++ b/lib/fourthwall/types.ts @@ -41,7 +41,6 @@ export type FourthwallProductVariant = { } }; - export type FourthwallCart = { id: string | undefined; items: FourthwallCartItem[]; diff --git a/lib/shopify/fragments/cart.ts b/lib/shopify/fragments/cart.ts deleted file mode 100644 index fc5c838dd..000000000 --- a/lib/shopify/fragments/cart.ts +++ /dev/null @@ -1,53 +0,0 @@ -import productFragment from './product'; - -const cartFragment = /* GraphQL */ ` - fragment cart on Cart { - id - checkoutUrl - cost { - subtotalAmount { - amount - currencyCode - } - totalAmount { - amount - currencyCode - } - totalTaxAmount { - amount - currencyCode - } - } - lines(first: 100) { - edges { - node { - id - quantity - cost { - totalAmount { - amount - currencyCode - } - } - merchandise { - ... on ProductVariant { - id - title - selectedOptions { - name - value - } - product { - ...product - } - } - } - } - } - } - totalQuantity - } - ${productFragment} -`; - -export default cartFragment; diff --git a/lib/shopify/fragments/image.ts b/lib/shopify/fragments/image.ts deleted file mode 100644 index 5d002f175..000000000 --- a/lib/shopify/fragments/image.ts +++ /dev/null @@ -1,10 +0,0 @@ -const imageFragment = /* GraphQL */ ` - fragment image on Image { - url - altText - width - height - } -`; - -export default imageFragment; diff --git a/lib/shopify/fragments/product.ts b/lib/shopify/fragments/product.ts deleted file mode 100644 index be14dedca..000000000 --- a/lib/shopify/fragments/product.ts +++ /dev/null @@ -1,64 +0,0 @@ -import imageFragment from './image'; -import seoFragment from './seo'; - -const productFragment = /* GraphQL */ ` - fragment product on Product { - id - handle - availableForSale - title - description - descriptionHtml - options { - id - name - values - } - priceRange { - maxVariantPrice { - amount - currencyCode - } - minVariantPrice { - amount - currencyCode - } - } - variants(first: 250) { - edges { - node { - id - title - availableForSale - selectedOptions { - name - value - } - price { - amount - currencyCode - } - } - } - } - featuredImage { - ...image - } - images(first: 20) { - edges { - node { - ...image - } - } - } - seo { - ...seo - } - tags - updatedAt - } - ${imageFragment} - ${seoFragment} -`; - -export default productFragment; diff --git a/lib/shopify/fragments/seo.ts b/lib/shopify/fragments/seo.ts deleted file mode 100644 index 2d4786c4f..000000000 --- a/lib/shopify/fragments/seo.ts +++ /dev/null @@ -1,8 +0,0 @@ -const seoFragment = /* GraphQL */ ` - fragment seo on SEO { - description - title - } -`; - -export default seoFragment; diff --git a/lib/shopify/index.ts b/lib/shopify/index.ts deleted file mode 100644 index 8f280ec2d..000000000 --- a/lib/shopify/index.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { HIDDEN_PRODUCT_TAG, SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/constants'; -import { isShopifyError } from 'lib/type-guards'; -import { ensureStartsWith } from 'lib/utils'; -import { revalidateTag } from 'next/cache'; -import { headers } from 'next/headers'; -import { NextRequest, NextResponse } from 'next/server'; -import { - getCollectionQuery, - getCollectionsQuery -} from './queries/collection'; -import { getPageQuery, getPagesQuery } from './queries/page'; -import { - getProductsQuery -} from './queries/product'; -import { - Collection, - Connection, - Image, - Page, - Product, - ShopifyCollection, - ShopifyCollectionOperation, - ShopifyCollectionsOperation, - ShopifyPageOperation, - ShopifyPagesOperation, - ShopifyProduct, - ShopifyProductsOperation -} from './types'; - -const domain = process.env.SHOPIFY_STORE_DOMAIN - ? ensureStartsWith(process.env.SHOPIFY_STORE_DOMAIN, 'https://') - : ''; -const endpoint = `${domain}${SHOPIFY_GRAPHQL_API_ENDPOINT}`; -const key = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN!; - -type ExtractVariables = T extends { variables: object } ? T['variables'] : never; - -export async function shopifyFetch({ - cache = 'force-cache', - headers, - query, - tags, - variables -}: { - cache?: RequestCache; - headers?: HeadersInit; - query: string; - tags?: string[]; - variables?: ExtractVariables; -}): 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 { - cause: e.cause?.toString() || 'unknown', - status: e.status || 500, - message: e.message, - query - }; - } - - throw { - error: e, - query - }; - } -} - -const removeEdgesAndNodes = (array: Connection): T[] => { - return array.edges.map((edge) => edge?.node); -}; - -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, 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 getCollection(handle: string): Promise { - const res = await shopifyFetch({ - query: getCollectionQuery, - tags: [TAGS.collections], - variables: { - handle - } - }); - - return reshapeCollection(res.body.data.collection); -} - -export async function getCollections(): Promise { - const res = await shopifyFetch({ - 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 getPage(handle: string): Promise { - const res = await shopifyFetch({ - query: getPageQuery, - cache: 'no-store', - variables: { handle } - }); - - return res.body.data.pageByHandle; -} - -export async function getPages(): Promise { - const res = await shopifyFetch({ - query: getPagesQuery, - cache: 'no-store' - }); - - return removeEdgesAndNodes(res.body.data.pages); -} - -export async function getProducts({ - query, - reverse, - sortKey -}: { - query?: string; - reverse?: boolean; - sortKey?: string; -}): Promise { - const res = await shopifyFetch({ - 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 { - // 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() }); -} diff --git a/lib/shopify/mutations/cart.ts b/lib/shopify/mutations/cart.ts deleted file mode 100644 index 4cc1b5ac6..000000000 --- a/lib/shopify/mutations/cart.ts +++ /dev/null @@ -1,45 +0,0 @@ -import cartFragment from '../fragments/cart'; - -export const addToCartMutation = /* GraphQL */ ` - mutation addToCart($cartId: ID!, $lines: [CartLineInput!]!) { - cartLinesAdd(cartId: $cartId, lines: $lines) { - cart { - ...cart - } - } - } - ${cartFragment} -`; - -export const createCartMutation = /* GraphQL */ ` - mutation createCart($lineItems: [CartLineInput!]) { - cartCreate(input: { lines: $lineItems }) { - cart { - ...cart - } - } - } - ${cartFragment} -`; - -export const editCartItemsMutation = /* GraphQL */ ` - mutation editCartItems($cartId: ID!, $lines: [CartLineUpdateInput!]!) { - cartLinesUpdate(cartId: $cartId, lines: $lines) { - cart { - ...cart - } - } - } - ${cartFragment} -`; - -export const removeFromCartMutation = /* GraphQL */ ` - mutation removeFromCart($cartId: ID!, $lineIds: [ID!]!) { - cartLinesRemove(cartId: $cartId, lineIds: $lineIds) { - cart { - ...cart - } - } - } - ${cartFragment} -`; diff --git a/lib/shopify/queries/cart.ts b/lib/shopify/queries/cart.ts deleted file mode 100644 index 044e47f66..000000000 --- a/lib/shopify/queries/cart.ts +++ /dev/null @@ -1,10 +0,0 @@ -import cartFragment from '../fragments/cart'; - -export const getCartQuery = /* GraphQL */ ` - query getCart($cartId: ID!) { - cart(id: $cartId) { - ...cart - } - } - ${cartFragment} -`; diff --git a/lib/shopify/queries/collection.ts b/lib/shopify/queries/collection.ts deleted file mode 100644 index 6396ff8eb..000000000 --- a/lib/shopify/queries/collection.ts +++ /dev/null @@ -1,56 +0,0 @@ -import productFragment from '../fragments/product'; -import seoFragment from '../fragments/seo'; - -const collectionFragment = /* GraphQL */ ` - fragment collection on Collection { - handle - title - description - seo { - ...seo - } - updatedAt - } - ${seoFragment} -`; - -export const getCollectionQuery = /* GraphQL */ ` - query getCollection($handle: String!) { - collection(handle: $handle) { - ...collection - } - } - ${collectionFragment} -`; - -export const getCollectionsQuery = /* GraphQL */ ` - query getCollections { - collections(first: 100, sortKey: TITLE) { - edges { - node { - ...collection - } - } - } - } - ${collectionFragment} -`; - -export const getCollectionProductsQuery = /* GraphQL */ ` - query getCollectionProducts( - $handle: String! - $sortKey: ProductCollectionSortKeys - $reverse: Boolean - ) { - collection(handle: $handle) { - products(sortKey: $sortKey, reverse: $reverse, first: 100) { - edges { - node { - ...product - } - } - } - } - } - ${productFragment} -`; diff --git a/lib/shopify/queries/menu.ts b/lib/shopify/queries/menu.ts deleted file mode 100644 index d05b09949..000000000 --- a/lib/shopify/queries/menu.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const getMenuQuery = /* GraphQL */ ` - query getMenu($handle: String!) { - menu(handle: $handle) { - items { - title - url - } - } - } -`; diff --git a/lib/shopify/queries/page.ts b/lib/shopify/queries/page.ts deleted file mode 100644 index ac6f6f986..000000000 --- a/lib/shopify/queries/page.ts +++ /dev/null @@ -1,41 +0,0 @@ -import seoFragment from '../fragments/seo'; - -const pageFragment = /* GraphQL */ ` - fragment page on Page { - ... on Page { - id - title - handle - body - bodySummary - seo { - ...seo - } - createdAt - updatedAt - } - } - ${seoFragment} -`; - -export const getPageQuery = /* GraphQL */ ` - query getPage($handle: String!) { - pageByHandle(handle: $handle) { - ...page - } - } - ${pageFragment} -`; - -export const getPagesQuery = /* GraphQL */ ` - query getPages { - pages(first: 100) { - edges { - node { - ...page - } - } - } - } - ${pageFragment} -`; diff --git a/lib/shopify/queries/product.ts b/lib/shopify/queries/product.ts deleted file mode 100644 index d3f12bd0f..000000000 --- a/lib/shopify/queries/product.ts +++ /dev/null @@ -1,32 +0,0 @@ -import productFragment from '../fragments/product'; - -export const getProductQuery = /* GraphQL */ ` - query getProduct($handle: String!) { - product(handle: $handle) { - ...product - } - } - ${productFragment} -`; - -export const getProductsQuery = /* GraphQL */ ` - query getProducts($sortKey: ProductSortKeys, $reverse: Boolean, $query: String) { - products(sortKey: $sortKey, reverse: $reverse, query: $query, first: 100) { - edges { - node { - ...product - } - } - } - } - ${productFragment} -`; - -export const getProductRecommendationsQuery = /* GraphQL */ ` - query getProductRecommendations($productId: ID!) { - productRecommendations(productId: $productId) { - ...product - } - } - ${productFragment} -`; diff --git a/lib/shopify/types.ts b/lib/shopify/types.ts deleted file mode 100644 index d9c119dc2..000000000 --- a/lib/shopify/types.ts +++ /dev/null @@ -1,273 +0,0 @@ -export type Maybe = T | null; - -export type Connection = { - edges: Array>; -}; - -export type Edge = { - node: T; -}; - -export type Cart = Omit & { - lines: CartItem[]; - currency: string; -}; - -export type CartProduct = { - id: string; - handle: string; - title: string; - featuredImage: Image; -}; - -export type CartItem = { - id: string | undefined; - quantity: number; - cost: { - totalAmount: Money; - }; - merchandise: { - id: string; - title: string; - selectedOptions: { - name: string; - value: string; - }[]; - product: CartProduct; - }; -}; - -export type Collection = ShopifyCollection & { - path: string; -}; - -export type Image = { - url: string; - altText: string; - width: number; - height: number; -}; - -export type Menu = { - title: string; - path: string; -}; - -export type Money = { - amount: string; - currencyCode: string; -}; - -export type Page = { - id: string; - title: string; - handle: string; - body: string; - bodySummary: string; - seo?: SEO; - createdAt: string; - updatedAt: string; -}; - -export type Product = Omit & { - variants: ProductVariant[]; - images: Image[]; -}; - -export type ProductOption = { - id: string; - name: string; - values: string[]; -}; - -export type ProductVariant = { - id: string; - title: string; - availableForSale: boolean; - selectedOptions: { - name: string; - value: string; - }[]; - price: Money; - images: Image[]; -}; - -export type SEO = { - title: string; - description: string; -}; - -export type ShopifyCart = { - id: string | undefined; - checkoutUrl: string; - cost: { - subtotalAmount: Money; - totalAmount: Money; - }; - lines: Connection; - totalQuantity: number; -}; - -export type ShopifyCollection = { - handle: string; - title: string; - description: string; - seo: SEO; - updatedAt: string; -}; - -export type ShopifyProduct = { - id: string; - handle: string; - availableForSale: boolean; - title: string; - description: string; - descriptionHtml: string; - options: ProductOption[]; - priceRange: { - maxVariantPrice: Money; - minVariantPrice: Money; - }; - variants: Connection; - featuredImage: Image; - images: Connection; - seo: SEO; - tags: string[]; - updatedAt: string; -}; - -export type ShopifyCartOperation = { - data: { - cart: ShopifyCart; - }; - variables: { - cartId: string; - }; -}; - -export type ShopifyCreateCartOperation = { - data: { cartCreate: { cart: ShopifyCart } }; -}; - -export type ShopifyAddToCartOperation = { - data: { - cartLinesAdd: { - cart: ShopifyCart; - }; - }; - variables: { - cartId: string; - lines: { - merchandiseId: string; - quantity: number; - }[]; - }; -}; - -export type ShopifyRemoveFromCartOperation = { - data: { - cartLinesRemove: { - cart: ShopifyCart; - }; - }; - variables: { - cartId: string; - lineIds: string[]; - }; -}; - -export type ShopifyUpdateCartOperation = { - data: { - cartLinesUpdate: { - cart: ShopifyCart; - }; - }; - variables: { - cartId: string; - lines: { - id: string; - merchandiseId: string; - quantity: number; - }[]; - }; -}; - -export type ShopifyCollectionOperation = { - data: { - collection: ShopifyCollection; - }; - variables: { - handle: string; - }; -}; - -export type ShopifyCollectionProductsOperation = { - data: { - collection: { - products: Connection; - }; - }; - variables: { - handle: string; - reverse?: boolean; - sortKey?: string; - }; -}; - -export type ShopifyCollectionsOperation = { - data: { - collections: Connection; - }; -}; - -export type ShopifyMenuOperation = { - data: { - menu?: { - items: { - title: string; - url: string; - }[]; - }; - }; - variables: { - handle: string; - }; -}; - -export type ShopifyPageOperation = { - data: { pageByHandle: Page }; - variables: { handle: string }; -}; - -export type ShopifyPagesOperation = { - data: { - pages: Connection; - }; -}; - -export type ShopifyProductOperation = { - data: { product: ShopifyProduct }; - variables: { - handle: string; - }; -}; - -export type ShopifyProductRecommendationsOperation = { - data: { - productRecommendations: ShopifyProduct[]; - }; - variables: { - productId: string; - }; -}; - -export type ShopifyProductsOperation = { - data: { - products: Connection; - }; - variables: { - query?: string; - reverse?: boolean; - sortKey?: string; - }; -}; diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 000000000..70004dee2 --- /dev/null +++ b/lib/types.ts @@ -0,0 +1,125 @@ +export type Maybe = T | null; + +export type Connection = { + edges: Array>; +}; + +export type Edge = { + node: T; +}; + +export type Cart = { + id: string | undefined; + checkoutUrl: string; + cost: { + subtotalAmount: Money; + totalAmount: Money; + }; + totalQuantity: number; + lines: CartItem[]; + currency: string; +}; + +export type CartProduct = { + id: string; + handle: string; + title: string; + featuredImage: Image; +}; + +export type CartItem = { + id: string | undefined; + quantity: number; + cost: { + totalAmount: Money; + }; + merchandise: { + id: string; + title: string; + selectedOptions: { + name: string; + value: string; + }[]; + product: CartProduct; + }; +}; + +export type Collection = { + handle: string; + title: string; + description: string; + seo: SEO; + updatedAt: string; + path: string; +}; + +export type Image = { + url: string; + altText: string; + width: number; + height: number; +}; + +export type Menu = { + title: string; + path: string; +}; + +export type Money = { + amount: string; + currencyCode: string; +}; + +export type Page = { + id: string; + title: string; + handle: string; + body: string; + bodySummary: string; + seo?: SEO; + createdAt: string; + updatedAt: string; +}; + +export type Product = { + id: string; + handle: string; + availableForSale: boolean; + title: string; + description: string; + descriptionHtml: string; + options: ProductOption[]; + priceRange: { + maxVariantPrice: Money; + minVariantPrice: Money; + }; + featuredImage: Image; + seo: SEO; + tags: string[]; + updatedAt: string; + variants: ProductVariant[]; + images: Image[]; +}; + +export type ProductOption = { + id: string; + name: string; + values: string[]; +}; + +export type ProductVariant = { + id: string; + title: string; + availableForSale: boolean; + selectedOptions: { + name: string; + value: string; + }[]; + price: Money; + images: Image[]; +}; + +export type SEO = { + title: string; + description: string; +};