From 1ffd276ddc723b468fd31d3245cfc792fd0e5fa3 Mon Sep 17 00:00:00 2001 From: Kristian Duda Date: Fri, 28 Jun 2024 13:46:17 +0200 Subject: [PATCH] refactoring --- 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 | 209 +++++++++--------------------- 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 ----- 11 files changed, 60 insertions(+), 478 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/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 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 index 6177e4ded..b4c4916d5 100644 --- a/lib/shopify/index.ts +++ b/lib/shopify/index.ts @@ -1,113 +1,32 @@ -import { SHOPIFY_GRAPHQL_API_ENDPOINT, TAGS } from 'lib/constants'; +import { TAGS } from 'lib/constants'; import { AjaxError } from 'lib/shopify/ajax'; import { Where, create, find, findByID, update } from 'lib/shopify/payload'; -import { Cart, Category, Media, Option, Product } from 'lib/shopify/payload-types'; -import { isShopifyError } from 'lib/type-guards'; -import { ensureStartsWith } from 'lib/utils'; +import { + Cart as PayloadCart, + Category as PayloadCategory, + Media as PayloadMedia, + Option as PayloadOption, + Product as PayloadProduct +} from 'lib/shopify/payload-types'; import { revalidateTag } from 'next/cache'; import { headers } from 'next/headers'; import { NextRequest, NextResponse } from 'next/server'; -import { getPageQuery } from './queries/page'; import { + Cart, CartItem, Collection, - Connection, - Cart as ExCart, - Product as ExProduct, + Image, Menu, Money, Page, + Product, ProductOption, - ProductVariant, - ShopifyCart, - ShopifyPageOperation + ProductVariant } 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) => { - return array.edges.map((edge) => edge?.node); -}; - -const reshapeCart = (cart: ShopifyCart): ExCart => { - if (!cart.cost?.totalTaxAmount) { - cart.cost.totalTaxAmount = { - amount: '0.0', - currencyCode: 'USD' - }; - } - - return { - ...cart, - lines: removeEdgesAndNodes(cart.lines) - }; -}; - -const reshapeCartItems = (lines: Cart['lines']): CartItem[] => { +const reshapeCartItems = (lines: PayloadCart['lines']): CartItem[] => { return (lines ?? []).map((item) => { - const product = item.product as Product; + const product = item.product as PayloadProduct; const variant = product.variants.find((v) => v.id === item.variant); return { @@ -126,7 +45,7 @@ const reshapeCartItems = (lines: Cart['lines']): CartItem[] => { }); }; -const reshapeC = (cart: Cart): ExCart => { +const reshapeCart = (cart: PayloadCart): Cart => { return { id: cart.id, checkoutUrl: '/api/checkout', @@ -149,39 +68,39 @@ const reshapeC = (cart: Cart): ExCart => { }; }; -export async function createCart(): Promise { - const cart = await create('carts', { lines: [] }); - return reshapeC(cart.doc); +export async function createCart(): Promise { + const cart = await create('carts', { lines: [] }); + return reshapeCart(cart.doc); } export async function addToCart( cartId: string, lines: { merchandiseId: string; quantity: number }[] -): Promise { - const prevCart = await findByID('carts', cartId); +): Promise { + const prevCart = await findByID('carts', cartId); const cartItems = await mergeItems(prevCart.lines, lines, true); - console.log('ADD'); - const cart = await update('carts', cartId, { + + const cart = await update('carts', cartId, { lines: cartItems }); - return reshapeC(cart.doc); + return reshapeCart(cart.doc); } -export async function removeFromCart(cartId: string, lineIds: string[]): Promise { - const prevCart = await findByID('carts', cartId); +export async function removeFromCart(cartId: string, lineIds: string[]): Promise { + const prevCart = await findByID('carts', cartId); const lines = prevCart?.lines?.filter((lineItem) => !lineIds.includes(lineItem.id!)) ?? []; - const cart = await update('carts', cartId, { lines }); - return reshapeC(cart.doc); + const cart = await update('carts', cartId, { lines }); + return reshapeCart(cart.doc); } const mergeItems = async ( - cartItems: Cart['lines'], + cartItems: PayloadCart['lines'], lines: { merchandiseId: string; quantity: number }[], add: boolean -): Promise => { +): Promise => { const map = new Map((cartItems ?? []).map((item) => [item.variant, item])); - const products = await find('products', { + const products = await find('products', { where: { 'variants.id': { in: lines.map((line) => line.merchandiseId) @@ -199,7 +118,7 @@ const mergeItems = async ( variant: line.merchandiseId, quantity: line.quantity }; - if (add && map.has(line.merchandiseId)) { + if (add) { const added = map.get(line.merchandiseId); if (added) { item = { @@ -217,20 +136,18 @@ const mergeItems = async ( export async function updateCart( cartId: string, lines: { id: string; merchandiseId: string; quantity: number }[] -): Promise { - const cart = await findByID('carts', cartId); - const cartItems = await mergeItems(cart.lines, lines, false); +): Promise { + const prevCart = await findByID('carts', cartId); + const cartItems = await mergeItems(prevCart.lines, lines, false); - console.log(cartItems); - - const c = await update('carts', cartId, { lines: cartItems }); - return reshapeC(c.doc); + const cart = await update('carts', cartId, { lines: cartItems }); + return reshapeCart(cart.doc); } -export async function getCart(cartId: string): Promise { +export async function getCart(cartId: string): Promise { try { - const cart = await findByID('carts', cartId); - return reshapeC(cart); + const cart = await findByID('carts', cartId); + return reshapeCart(cart); } catch (error: unknown) { if (error instanceof AjaxError) { if (error.statusCode === 404) { @@ -243,11 +160,11 @@ export async function getCart(cartId: string): Promise { } export async function getCollection(handle: string): Promise { - const category = await findByID('categories', handle); + const category = await findByID('categories', handle); return reshapeCategory(category); } -const reshapeImage = (media: Media): Image => { +const reshapeImage = (media: PayloadMedia): Image => { return { url: media.url!, altText: media.alt, @@ -268,12 +185,12 @@ const reshapePrice = (price: Price): Money => { }; }; -const reshapeOptions = (variants: Product['variants']): ProductOption[] => { - const options = new Map(); +const reshapeOptions = (variants: PayloadProduct['variants']): ProductOption[] => { + const options = new Map(); variants.forEach((variant) => { variant.selectedOptions?.forEach((selectedOption) => { - const option = selectedOption.option as Option; + const option = selectedOption.option as PayloadOption; options.set(option.id, option); }); }); @@ -286,10 +203,10 @@ const reshapeOptions = (variants: Product['variants']): ProductOption[] => { }; const reshapeSelectedOption = ( - selectedOptions: Product['variants'][0]['selectedOptions'] + selectedOptions: PayloadProduct['variants'][0]['selectedOptions'] ): Array<{ name: string; value: string }> => { return (selectedOptions ?? []).map((selectedOption) => { - const option = selectedOption.option as Option; + const option = selectedOption.option as PayloadOption; return { name: option.name, value: option.values.find(({ value }) => value === selectedOption.value)?.label! @@ -297,7 +214,7 @@ const reshapeSelectedOption = ( }); }; -const reshapeVariants = (variants: Product['variants']): ProductVariant[] => { +const reshapeVariants = (variants: PayloadProduct['variants']): ProductVariant[] => { return variants.map((variant) => ({ id: variant.id!, title: `${variant.price.amount} ${variant.price.currencyCode}`, @@ -307,7 +224,7 @@ const reshapeVariants = (variants: Product['variants']): ProductVariant[] => { })); }; -const reshapeProduct = (product: Product): ExProduct => { +const reshapeProduct = (product: PayloadProduct): Product => { return { id: product.id, handle: product.id, @@ -320,7 +237,7 @@ const reshapeProduct = (product: Product): ExProduct => { maxVariantPrice: reshapePrice(product.variants[0]?.price!), minVariantPrice: reshapePrice(product.variants[0]?.price!) }, - featuredImage: reshapeImage(product.media as Media), + featuredImage: reshapeImage(product.media as PayloadMedia), images: [], seo: { title: product.title, @@ -342,7 +259,7 @@ export async function getCollectionProducts({ tag?: string; reverse?: boolean; sortKey?: string; -}): Promise { +}): Promise { const filters: Where[] = []; if (collection) { filters.push({ @@ -359,7 +276,7 @@ export async function getCollectionProducts({ }); } - const products = await find('products', { + const products = await find('products', { where: { and: filters } @@ -367,7 +284,7 @@ export async function getCollectionProducts({ return products.docs.map(reshapeProduct); } -const reshapeCategory = (category: Category): Collection => { +const reshapeCategory = (category: PayloadCategory): Collection => { return { handle: category.id, title: category.title, @@ -382,7 +299,7 @@ const reshapeCategory = (category: Category): Collection => { }; export async function getCollections(): Promise { - const categories = await find('categories', {}); + const categories = await find('categories', {}); return [ { handle: '', @@ -414,26 +331,20 @@ export async function getMenu(handle: string): Promise { } } -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 getPage(handle: string): Promise { + return undefined; } export async function getPages(): Promise { return []; } -export async function getProduct(handle: string): Promise { - const product = await findByID('products', handle); +export async function getProduct(handle: string): Promise { + const product = await findByID('products', handle); return reshapeProduct(product); } -export async function getProductRecommendations(productId: string): Promise { +export async function getProductRecommendations(productId: string): Promise { return []; } @@ -445,7 +356,7 @@ export async function getProducts({ query?: string; reverse?: boolean; sortKey?: string; -}): Promise { +}): Promise { let where: Where | undefined; if (query) { where = { @@ -464,7 +375,7 @@ export async function getProducts({ }; } - const products = await find('products', { where }); + const products = await find('products', { where }); return products.docs.map(reshapeProduct); } 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} -`;