diff --git a/packages/shopify/src/api/operations/get-site-info.ts b/packages/shopify/src/api/operations/get-site-info.ts index 6bb6de4b0..074501a75 100644 --- a/packages/shopify/src/api/operations/get-site-info.ts +++ b/packages/shopify/src/api/operations/get-site-info.ts @@ -6,7 +6,16 @@ import { GetSiteInfoQueryVariables } from '../../../schema' import type { ShopifyConfig, Provider } from '..' import { GetSiteInfoOperation } from '../../types/site' -import { getCategories, getBrands, getSiteInfoQuery } from '../../utils' +import { getBrands } from '../utils/get-brands' +import { getCategories } from '../utils/get-categories' + +export const getSiteInfoQuery = /* GraphQL */ ` + query getSiteInfo { + shop { + name + } + } +` export default function getSiteInfoOperation({ commerce, @@ -37,20 +46,6 @@ export default function getSiteInfoOperation({ const categoriesPromise = getCategories(cfg) const brandsPromise = getBrands(cfg) - /* - const { fetch, locale } = cfg - const { data } = await fetch( - query, - { variables }, - { - ...(locale && { - headers: { - 'Accept-Language': locale, - }, - }), - } - ) - */ return { categories: await categoriesPromise, diff --git a/packages/shopify/src/api/utils/fetch-graphql-api.ts b/packages/shopify/src/api/utils/fetch-graphql-api.ts index 1970db572..e8fe8bda3 100644 --- a/packages/shopify/src/api/utils/fetch-graphql-api.ts +++ b/packages/shopify/src/api/utils/fetch-graphql-api.ts @@ -3,6 +3,7 @@ import fetch from './fetch' import { API_URL, API_TOKEN } from '../../const' import { getError } from '../../utils/handle-fetch-response' +import { CommerceError } from '@vercel/commerce/utils/errors' const fetchGraphqlApi: GraphQLFetcher = async ( query: string, @@ -32,14 +33,20 @@ const fetchGraphqlApi: GraphQLFetcher = async ( return { data, res } } catch (err) { - throw getError( - [ - { - message: `${err} \n Most likely related to an unexpected output. e.g the store might be protected with password or not available.`, - }, - ], - 500 - ) + // if the error is a CommerceError, we can use it as-is + if (err instanceof CommerceError) { + throw err + } else { + // otherwise, we'll wrap unknown errors in a CommerceError + throw getError( + [ + { + message: `${err} \n Most likely related to an unexpected output. e.g the store might be protected with password or not available.`, + }, + ], + 500 + ) + } } } export default fetchGraphqlApi diff --git a/packages/shopify/src/utils/get-brands.ts b/packages/shopify/src/api/utils/get-brands.ts similarity index 87% rename from packages/shopify/src/utils/get-brands.ts rename to packages/shopify/src/api/utils/get-brands.ts index 48da73a35..5f5eb5b8a 100644 --- a/packages/shopify/src/utils/get-brands.ts +++ b/packages/shopify/src/api/utils/get-brands.ts @@ -1,11 +1,11 @@ -import { ShopifyConfig } from '../api' +import { ShopifyConfig } from '..' import { GetAllProductVendorsQuery, GetAllProductVendorsQueryVariables, -} from '../../schema' +} from '../../../schema' -import { getAllProductVendors } from './queries' +import { getAllProductVendors } from '../../utils/queries' export type Brand = { entityId: string diff --git a/packages/shopify/src/utils/get-categories.ts b/packages/shopify/src/api/utils/get-categories.ts similarity index 64% rename from packages/shopify/src/utils/get-categories.ts rename to packages/shopify/src/api/utils/get-categories.ts index ff90b6ef4..b7d2c2619 100644 --- a/packages/shopify/src/utils/get-categories.ts +++ b/packages/shopify/src/api/utils/get-categories.ts @@ -1,8 +1,7 @@ -import type { Category } from '../types/site' -import { ShopifyConfig } from '../api' -import { CollectionEdge } from '../../schema' -import { normalizeCategory } from './normalize' -import { getSiteCollectionsQuery } from './queries/get-all-collections-query' +import { ShopifyConfig } from '..' +import type { Category } from '../../types/site' +import { CollectionEdge } from '../../../schema' +import { normalizeCategory, getSiteCollectionsQuery } from '../../utils' export const getCategories = async ({ fetch, diff --git a/packages/shopify/src/cart/use-cart.tsx b/packages/shopify/src/cart/use-cart.tsx index 844db8ec1..f78d4ddbd 100644 --- a/packages/shopify/src/cart/use-cart.tsx +++ b/packages/shopify/src/cart/use-cart.tsx @@ -1,15 +1,13 @@ import { useMemo } from 'react' import useCommerceCart, { UseCart } from '@vercel/commerce/cart/use-cart' - import { SWRHook } from '@vercel/commerce/utils/types' -import { getCartQuery, normalizeCart } from '../utils' -import { GetCartHook } from '../types/cart' import Cookies from 'js-cookie' -import { GetCartQueryVariables, QueryRoot } from '../../schema' +import { getCartQuery, normalizeCart, setCartUrlCookie } from '../utils' +import { GetCartHook } from '../types/cart' +import { GetCartQueryVariables, QueryRoot } from '../../schema' import { SHOPIFY_CART_ID_COOKIE } from '../const' -import { setCartUrlCookie } from '../utils/set-cart-url-cookie' export default useCommerceCart as UseCart export const handler: SWRHook = { diff --git a/packages/shopify/src/cart/use-remove-item.tsx b/packages/shopify/src/cart/use-remove-item.tsx index 4806822c1..c36d5cbb3 100644 --- a/packages/shopify/src/cart/use-remove-item.tsx +++ b/packages/shopify/src/cart/use-remove-item.tsx @@ -10,6 +10,18 @@ import useRemoveItem, { import type { Cart, LineItem, RemoveItemHook } from '../types/cart' import useCart from './use-cart' +import { + getCartId, + normalizeCart, + throwUserErrors, + cartLinesRemoveMutation, +} from '../utils' + +import { + CartLinesRemoveMutation, + CartLinesRemoveMutationVariables, +} from '../../schema' + export type RemoveItemFn = T extends LineItem ? (input?: RemoveItemActionInput) => Promise : (input: RemoveItemActionInput) => Promise @@ -20,17 +32,6 @@ export type RemoveItemActionInput = T extends LineItem export default useRemoveItem as UseRemoveItem -import { - cartLinesRemoveMutation, - getCartId, - normalizeCart, - throwUserErrors, -} from '../utils' -import { - CartLinesRemoveMutation, - CartLinesRemoveMutationVariables, -} from '../../schema' - export const handler = { fetchOptions: { query: cartLinesRemoveMutation, diff --git a/packages/shopify/src/cart/use-update-item.tsx b/packages/shopify/src/cart/use-update-item.tsx index 0a4ecd78f..b0a366350 100644 --- a/packages/shopify/src/cart/use-update-item.tsx +++ b/packages/shopify/src/cart/use-update-item.tsx @@ -11,9 +11,12 @@ import useUpdateItem, { import useCart from './use-cart' import { handler as removeItemHandler } from './use-remove-item' + +import { getCartId, normalizeCart, cartLinesUpdateMutation } from '../utils' + import type { UpdateItemHook, LineItem } from '../types/cart' -import { getCartId, cartLinesUpdateMutation, normalizeCart } from '../utils' -import { + +import type { CartLinesUpdateMutation, CartLinesUpdateMutationVariables, } from '../../schema' diff --git a/packages/shopify/src/const.ts b/packages/shopify/src/const.ts index e9d56594c..77bb340bb 100644 --- a/packages/shopify/src/const.ts +++ b/packages/shopify/src/const.ts @@ -1,13 +1,16 @@ +export const API_VERSION = '2022-04' + export const SHOPIFY_CART_ID_COOKIE = 'shopify_cartId' export const SHOPIFY_CART_URL_COOKIE = 'shopify_cartUrl' export const SHOPIFY_CUSTOMER_TOKEN_COOKIE = 'shopify_customerToken' -export const STORE_DOMAIN = process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN +export const STORE_DOMAIN = + process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN?.replace(/(^\w+:|^)\/\//, '') export const SHOPIFY_COOKIE_EXPIRE = 30 -export const API_URL = `https://${STORE_DOMAIN}/api/2022-04/graphql.json` +export const API_URL = `https://${STORE_DOMAIN}/api/${API_VERSION}/graphql.json` export const API_TOKEN = process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN diff --git a/packages/shopify/src/utils/cart-create.ts b/packages/shopify/src/utils/cart-create.ts index 49f3ca7f3..e14d9bb1b 100644 --- a/packages/shopify/src/utils/cart-create.ts +++ b/packages/shopify/src/utils/cart-create.ts @@ -1,6 +1,6 @@ import Cookies from 'js-cookie' import { SHOPIFY_CART_ID_COOKIE, SHOPIFY_COOKIE_EXPIRE } from '../const' -import { cartCreateMutation } from './mutations/cart-create' +import { cartCreateMutation } from './mutations/cart-mutations' import { CartCreateMutation, @@ -9,9 +9,9 @@ import { CartLineInput, } from '../../schema' +import { setCartUrlCookie } from './helpers' import { throwUserErrors } from './throw-user-errors' -import { setCartUrlCookie } from './set-cart-url-cookie' -import { FetcherOptions } from '@vercel/commerce/utils/types' +import type { FetcherOptions } from '@vercel/commerce/utils/types' export const cartCreate = async ( fetch: (options: FetcherOptions) => Promise, @@ -44,5 +44,3 @@ export const cartCreate = async ( return cart } - -export default cartCreate diff --git a/packages/shopify/src/utils/customer-token.ts b/packages/shopify/src/utils/customer-token.ts deleted file mode 100644 index 85454cb83..000000000 --- a/packages/shopify/src/utils/customer-token.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Cookies, { CookieAttributes } from 'js-cookie' -import { SHOPIFY_COOKIE_EXPIRE, SHOPIFY_CUSTOMER_TOKEN_COOKIE } from '../const' - -export const getCustomerToken = () => Cookies.get(SHOPIFY_CUSTOMER_TOKEN_COOKIE) - -export const setCustomerToken = ( - token: string | null, - options?: CookieAttributes -) => { - if (!token) { - Cookies.remove(SHOPIFY_CUSTOMER_TOKEN_COOKIE) - } else { - Cookies.set( - SHOPIFY_CUSTOMER_TOKEN_COOKIE, - token, - options ?? { - expires: SHOPIFY_COOKIE_EXPIRE, - } - ) - } -} diff --git a/packages/shopify/src/utils/fragments/cart-details-fragment.ts b/packages/shopify/src/utils/fragments/cart-details-fragment.ts new file mode 100644 index 000000000..711639566 --- /dev/null +++ b/packages/shopify/src/utils/fragments/cart-details-fragment.ts @@ -0,0 +1,82 @@ +export const cartDetailsFragment = /* GraphQL */ ` + fragment cartDetails on Cart { + id + checkoutUrl + createdAt + updatedAt + lines(first: 10) { + edges { + node { + id + quantity + merchandise { + ... on ProductVariant { + id + id + sku + title + selectedOptions { + name + value + } + image { + url + altText + width + height + } + priceV2 { + amount + currencyCode + } + compareAtPriceV2 { + amount + currencyCode + } + product { + title + handle + } + } + } + } + } + } + attributes { + key + value + } + buyerIdentity { + email + customer { + id + } + } + estimatedCost { + totalAmount { + amount + currencyCode + } + subtotalAmount { + amount + currencyCode + } + totalTaxAmount { + amount + currencyCode + } + totalDutyAmount { + amount + currencyCode + } + } + } +` + +export const userErrorsFragment = /* GraphQL */ ` + fragment userErrors on UserError { + code + field + message + } +` diff --git a/packages/shopify/src/utils/fragments/customer-fragments.ts b/packages/shopify/src/utils/fragments/customer-fragments.ts new file mode 100644 index 000000000..9bddcb49c --- /dev/null +++ b/packages/shopify/src/utils/fragments/customer-fragments.ts @@ -0,0 +1,15 @@ +export const customerUserErrorsFragment = /* GraphQL */ ` + fragment customerUserErrors on CustomerUserErrors { + code + field + message + } +` + +export const customerAccessTokenFragment = /* GraphQL */ ` + fragment customerAccessToken on CustomerAccessToken { + code + field + message + } +` diff --git a/packages/shopify/src/utils/fragments/media-fragment.ts b/packages/shopify/src/utils/fragments/media-fragment.ts new file mode 100644 index 000000000..ad5c96439 --- /dev/null +++ b/packages/shopify/src/utils/fragments/media-fragment.ts @@ -0,0 +1,36 @@ +export const mediaFragment = /* GraphQL */ ` + fragment Media on Media { + mediaContentType + alt + previewImage { + url + } + ... on MediaImage { + id + image { + url + width + height + } + } + # ... on Video { + # id + # sources { + # mimeType + # url + # } + # } + # ... on Model3d { + # id + # sources { + # mimeType + # url + # } + # } + # ... on ExternalVideo { + # id + # embedUrl + # host + # } + } +` diff --git a/packages/shopify/src/utils/fragments/product-card-fragment.ts b/packages/shopify/src/utils/fragments/product-card-fragment.ts new file mode 100644 index 000000000..8f37543d8 --- /dev/null +++ b/packages/shopify/src/utils/fragments/product-card-fragment.ts @@ -0,0 +1,36 @@ +export const productCardFragment = /* GraphQL */ ` + fragment productCard on Product { + id + handle + availableForSale + title + productType + vendor + variants(first: 1) { + nodes { + id + title + requiresShipping + availableForSale + selectedOptions { + name + value + } + image { + url + altText + width + height + } + priceV2 { + amount + currencyCode + } + compareAtPriceV2 { + amount + currencyCode + } + } + } + } +` diff --git a/packages/shopify/src/utils/get-cart-id.ts b/packages/shopify/src/utils/get-cart-id.ts deleted file mode 100644 index 56a6b888c..000000000 --- a/packages/shopify/src/utils/get-cart-id.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Cookies from 'js-cookie' -import { SHOPIFY_CART_ID_COOKIE } from '../const' - -export const getCartId = (id?: string) => { - return id || Cookies.get(SHOPIFY_CART_ID_COOKIE) -} diff --git a/packages/shopify/src/utils/get-search-variables.ts b/packages/shopify/src/utils/get-search-variables.ts deleted file mode 100644 index f2b93a46c..000000000 --- a/packages/shopify/src/utils/get-search-variables.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { getSortVariables } from './get-sort-variables' -import { SearchProductsBody } from '../types/product' - -export const getSearchVariables = ({ - brandId, - search, - categoryId, - sort, - locale, -}: SearchProductsBody) => { - let query = '' - - if (search) { - query += `product_type:${search} OR title:${search} OR tag:${search} ` - } - - if (brandId) { - query += `${search ? 'AND ' : ''}vendor:${brandId}` - } - - return { - categoryId, - query, - ...getSortVariables(sort, !!categoryId), - ...(locale && { - locale, - }), - } -} diff --git a/packages/shopify/src/utils/get-sort-variables.ts b/packages/shopify/src/utils/get-sort-variables.ts deleted file mode 100644 index 076a982cb..000000000 --- a/packages/shopify/src/utils/get-sort-variables.ts +++ /dev/null @@ -1,33 +0,0 @@ -export const getSortVariables = ( - sort?: string, - isCategory: boolean = false -) => { - let output = {} - switch (sort) { - case 'price-asc': - output = { - sortKey: 'PRICE', - reverse: false, - } - break - case 'price-desc': - output = { - sortKey: 'PRICE', - reverse: true, - } - break - case 'trending-desc': - output = { - sortKey: 'BEST_SELLING', - reverse: false, - } - break - case 'latest-desc': - output = { - sortKey: isCategory ? 'CREATED' : 'CREATED_AT', - reverse: true, - } - break - } - return output -} diff --git a/packages/shopify/src/utils/handle-fetch-response.ts b/packages/shopify/src/utils/handle-fetch-response.ts index 72f2f216e..3f9e5e6fd 100644 --- a/packages/shopify/src/utils/handle-fetch-response.ts +++ b/packages/shopify/src/utils/handle-fetch-response.ts @@ -1,12 +1,17 @@ import { FetcherError } from '@vercel/commerce/utils/errors' -export function getError(errors: any[] | null, status: number) { - errors = errors ?? [{ message: 'Failed to fetch Shopify API' }] +export function getError(err: any[] | string | null, status: number) { + console.log(JSON.stringify(err, null, 2)) + + const errors = Array.isArray(err) + ? err + : [{ message: err || 'Failed to fetch Shopify API' }] return new FetcherError({ errors, status }) } export async function getAsyncError(res: Response) { const data = await res.json() + return getError(data.errors, res.status) } diff --git a/packages/shopify/src/utils/handle-login.ts b/packages/shopify/src/utils/handle-login.ts index 5daffd425..2eb0c911c 100644 --- a/packages/shopify/src/utils/handle-login.ts +++ b/packages/shopify/src/utils/handle-login.ts @@ -1,6 +1,6 @@ import { FetcherOptions } from '@vercel/commerce/utils/types' import { CustomerAccessTokenCreateInput } from '../../schema' -import { setCustomerToken } from './customer-token' +import { setCustomerToken } from './helpers' import { customerAccessTokenCreateMutation } from './mutations' import { throwUserErrors } from './throw-user-errors' diff --git a/packages/shopify/src/utils/helpers.ts b/packages/shopify/src/utils/helpers.ts new file mode 100644 index 000000000..30267a8f6 --- /dev/null +++ b/packages/shopify/src/utils/helpers.ts @@ -0,0 +1,104 @@ +import Cookies, { CookieAttributes } from 'js-cookie' +import { SearchProductsBody } from '../types/product' + +import { + SHOPIFY_CART_URL_COOKIE, + SHOPIFY_COOKIE_EXPIRE, + SHOPIFY_CART_ID_COOKIE, + SHOPIFY_CUSTOMER_TOKEN_COOKIE, +} from '../const' + +export const setCartUrlCookie = (cartUrl: string) => { + if (cartUrl) { + const oldCookie = Cookies.get(SHOPIFY_CART_URL_COOKIE) + if (oldCookie !== cartUrl) { + Cookies.set(SHOPIFY_CART_URL_COOKIE, cartUrl, { + expires: SHOPIFY_COOKIE_EXPIRE, + }) + } + } +} + +export const getCartId = (id?: string) => { + return id || Cookies.get(SHOPIFY_CART_ID_COOKIE) +} + +export const getSortVariables = ( + sort?: string, + isCategory: boolean = false +) => { + let output = {} + switch (sort) { + case 'price-asc': + output = { + sortKey: 'PRICE', + reverse: false, + } + break + case 'price-desc': + output = { + sortKey: 'PRICE', + reverse: true, + } + break + case 'trending-desc': + output = { + sortKey: 'BEST_SELLING', + reverse: false, + } + break + case 'latest-desc': + output = { + sortKey: isCategory ? 'CREATED' : 'CREATED_AT', + reverse: true, + } + break + } + return output +} + +export const getSearchVariables = ({ + brandId, + search, + categoryId, + sort, + locale, +}: SearchProductsBody) => { + let query = '' + + if (search) { + query += `product_type:${search} OR title:${search} OR tag:${search} ` + } + + if (brandId) { + query += `${search ? 'AND ' : ''}vendor:${brandId}` + } + + return { + categoryId, + query, + ...getSortVariables(sort, !!categoryId), + ...(locale && { + locale, + }), + } +} + +export const getCustomerToken = () => Cookies.get(SHOPIFY_CUSTOMER_TOKEN_COOKIE) + +export const setCustomerToken = ( + token: string | null, + options?: CookieAttributes +) => { + if (!token) { + Cookies.remove(SHOPIFY_CUSTOMER_TOKEN_COOKIE) + } else { + Cookies.set( + SHOPIFY_CUSTOMER_TOKEN_COOKIE, + token, + options ?? { + expires: SHOPIFY_COOKIE_EXPIRE, + } + ) + } +} diff --git a/packages/shopify/src/utils/index.ts b/packages/shopify/src/utils/index.ts index f4b173cec..dddb40b57 100644 --- a/packages/shopify/src/utils/index.ts +++ b/packages/shopify/src/utils/index.ts @@ -1,15 +1,10 @@ +export { throwUserErrors } from './throw-user-errors' export { handleFetchResponse } from './handle-fetch-response' -export { getSearchVariables } from './get-search-variables' -export { getSortVariables } from './get-sort-variables' -export { getBrands } from './get-brands' -export { getCategories } from './get-categories' -export { getCartId } from './get-cart-id' export { cartCreate } from './cart-create' export { handleLogin, handleAutomaticLogin } from './handle-login' export { handleAccountActivation } from './handle-account-activation' -export { throwUserErrors } from './throw-user-errors' +export * from './helpers' export * from './queries' export * from './mutations' export * from './normalize' -export * from './customer-token' diff --git a/packages/shopify/src/utils/mutations/associate-customer-with-checkout.ts b/packages/shopify/src/utils/mutations/associate-customer-with-checkout.ts deleted file mode 100644 index 96d71ef24..000000000 --- a/packages/shopify/src/utils/mutations/associate-customer-with-checkout.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const associateCustomerWithCheckoutMutation = /* GraphQl */ ` -mutation associateCustomerWithCheckout($checkoutId: ID!, $customerAccessToken: String!) { - checkoutCustomerAssociateV2(checkoutId: $checkoutId, customerAccessToken: $customerAccessToken) { - checkout { - id - } - checkoutUserErrors { - code - field - message - } - customer { - id - } - } - } -` diff --git a/packages/shopify/src/utils/mutations/cart-create.ts b/packages/shopify/src/utils/mutations/cart-create.ts deleted file mode 100644 index bfc51bfd2..000000000 --- a/packages/shopify/src/utils/mutations/cart-create.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { cartDetailsFragment } from '../queries/get-cart-query' - -export const cartCreateMutation = /* GraphQL */ ` - mutation cartCreate($input: CartInput = {}) { - cartCreate(input: $input) { - cart { - ...cartDetails - } - userErrors { - code - field - message - } - } - } - ${cartDetailsFragment} -` diff --git a/packages/shopify/src/utils/mutations/cart-lines-item-add.ts b/packages/shopify/src/utils/mutations/cart-lines-item-add.ts deleted file mode 100644 index b85ab6d80..000000000 --- a/packages/shopify/src/utils/mutations/cart-lines-item-add.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { cartDetailsFragment } from '../queries/get-cart-query' - -export const cartLinesAddMutation = /* GraphQL */ ` - mutation cartLinesAdd($lines: [CartLineInput!]!, $cartId: ID!) { - cartLinesAdd(lines: $lines, cartId: $cartId) { - cart { - ...cartDetails - } - userErrors { - code - field - message - } - } - } - ${cartDetailsFragment} -` diff --git a/packages/shopify/src/utils/mutations/cart-lines-item-remove.ts b/packages/shopify/src/utils/mutations/cart-lines-item-remove.ts deleted file mode 100644 index dcea958d2..000000000 --- a/packages/shopify/src/utils/mutations/cart-lines-item-remove.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { cartDetailsFragment } from '../queries/get-cart-query' - -export const cartLinesRemoveMutation = /* GraphQL */ ` - mutation cartLinesRemove($cartId: ID!, $lineIds: [ID!]!) { - cartLinesRemove(cartId: $cartId, lineIds: $lineIds) { - cart { - ...cartDetails - } - userErrors { - code - field - message - } - } - } - ${cartDetailsFragment} -` diff --git a/packages/shopify/src/utils/mutations/cart-lines-item-update.ts b/packages/shopify/src/utils/mutations/cart-lines-item-update.ts deleted file mode 100644 index b6d6812eb..000000000 --- a/packages/shopify/src/utils/mutations/cart-lines-item-update.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { cartDetailsFragment } from '../queries/get-cart-query' - -export const cartLinesUpdateMutation = /* GraphQL */ ` - mutation cartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) { - cartLinesUpdate(cartId: $cartId, lines: $lines) { - cart { - ...cartDetails - } - userErrors { - code - field - message - } - } - } - ${cartDetailsFragment} -` diff --git a/packages/shopify/src/utils/mutations/cart-mutations.ts b/packages/shopify/src/utils/mutations/cart-mutations.ts new file mode 100644 index 000000000..77de6a694 --- /dev/null +++ b/packages/shopify/src/utils/mutations/cart-mutations.ts @@ -0,0 +1,64 @@ +import { + cartDetailsFragment, + userErrorsFragment, +} from '../fragments/cart-details-fragment' + +export const cartCreateMutation = /* GraphQL */ ` + mutation cartCreate($input: CartInput = {}) { + cartCreate(input: $input) { + cart { + ...cartDetails + } + userErrors { + ...userErrors + } + } + } + ${cartDetailsFragment} + ${userErrorsFragment} +` + +export const cartLinesAddMutation = /* GraphQL */ ` + mutation cartLinesAdd($lines: [CartLineInput!]!, $cartId: ID!) { + cartLinesAdd(lines: $lines, cartId: $cartId) { + cart { + ...cartDetails + } + userErrors { + ...userErrors + } + } + } + ${cartDetailsFragment} + ${userErrorsFragment} +` + +export const cartLinesRemoveMutation = /* GraphQL */ ` + mutation cartLinesRemove($cartId: ID!, $lineIds: [ID!]!) { + cartLinesRemove(cartId: $cartId, lineIds: $lineIds) { + cart { + ...cartDetails + } + userErrors { + ...userErrors + } + } + } + ${cartDetailsFragment} + ${userErrorsFragment} +` + +export const cartLinesUpdateMutation = /* GraphQL */ ` + mutation cartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) { + cartLinesUpdate(cartId: $cartId, lines: $lines) { + cart { + ...cartDetails + } + userErrors { + ...userErrors + } + } + } + ${cartDetailsFragment} + ${userErrorsFragment} +` diff --git a/packages/shopify/src/utils/mutations/customer-access-token-create.ts b/packages/shopify/src/utils/mutations/customer-access-token-create.ts deleted file mode 100644 index a294d9c74..000000000 --- a/packages/shopify/src/utils/mutations/customer-access-token-create.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const customerAccessTokenCreateMutation = /* GraphQL */ ` - mutation customerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) { - customerAccessTokenCreate(input: $input) { - customerAccessToken { - accessToken - expiresAt - } - customerUserErrors { - code - field - message - } - } - } -` diff --git a/packages/shopify/src/utils/mutations/customer-access-token-delete.ts b/packages/shopify/src/utils/mutations/customer-access-token-delete.ts deleted file mode 100644 index 5edd7b6d6..000000000 --- a/packages/shopify/src/utils/mutations/customer-access-token-delete.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const customerAccessTokenDeleteMutation = /* GraphQL */ ` - mutation customerAccessTokenDelete($customerAccessToken: String!) { - customerAccessTokenDelete(customerAccessToken: $customerAccessToken) { - deletedAccessToken - deletedCustomerAccessTokenId - userErrors { - field - message - } - } - } -` diff --git a/packages/shopify/src/utils/mutations/customer-activate-by-url.ts b/packages/shopify/src/utils/mutations/customer-activate-by-url.ts deleted file mode 100644 index 5e2566cb8..000000000 --- a/packages/shopify/src/utils/mutations/customer-activate-by-url.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const customerActivateByUrlMutation = /* GraphQL */ ` - mutation customerActivateByUrl($activationUrl: URL!, $password: String!) { - customerActivateByUrl(activationUrl: $activationUrl, password: $password) { - customer { - id - } - customerAccessToken { - accessToken - expiresAt - } - customerUserErrors { - code - field - message - } - } - } -` diff --git a/packages/shopify/src/utils/mutations/customer-activate.ts b/packages/shopify/src/utils/mutations/customer-activate.ts deleted file mode 100644 index 7a07d5ad8..000000000 --- a/packages/shopify/src/utils/mutations/customer-activate.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const customerActivateMutation = /* GraphQL */ ` - mutation customerActivate($id: ID!, $input: CustomerActivateInput!) { - customerActivate(id: $id, input: $input) { - customer { - id - } - customerAccessToken { - accessToken - expiresAt - } - customerUserErrors { - code - field - message - } - } - } -` diff --git a/packages/shopify/src/utils/mutations/customer-create.ts b/packages/shopify/src/utils/mutations/customer-create.ts deleted file mode 100644 index ad8c24238..000000000 --- a/packages/shopify/src/utils/mutations/customer-create.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const customerCreateMutation = /* GraphQL */ ` - mutation customerCreate($input: CustomerCreateInput!) { - customerCreate(input: $input) { - customerUserErrors { - code - field - message - } - customer { - id - } - } - } -` diff --git a/packages/shopify/src/utils/mutations/customer-mutations.ts b/packages/shopify/src/utils/mutations/customer-mutations.ts new file mode 100644 index 000000000..9f502ed32 --- /dev/null +++ b/packages/shopify/src/utils/mutations/customer-mutations.ts @@ -0,0 +1,65 @@ +import { + customerUserErrorsFragment, + customerAccessTokenFragment, +} from '../fragments/customer-fragments' + +export const customerActivateMutation = /* GraphQL */ ` + mutation customerActivate($id: ID!, $input: CustomerActivateInput!) { + customerActivate(id: $id, input: $input) { + customer { + id + } + ...customerAccessToken + ...customerUserErrors + } + } + ${customerUserErrorsFragment} + ${customerAccessTokenFragment} +` +export const customerActivateByUrlMutation = /* GraphQL */ ` + mutation customerActivateByUrl($activationUrl: URL!, $password: String!) { + customerActivateByUrl(activationUrl: $activationUrl, password: $password) { + customer { + id + } + ...customerAccessToken + ...customerUserErrors + } + } + ${customerUserErrorsFragment} + ${customerAccessTokenFragment} +` + +export const customerAccessTokenCreateMutation = /* GraphQL */ ` + mutation customerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) { + customerAccessTokenCreate(input: $input) { + ...customerAccessToken + ...customerUserErrors + } + } + ${customerUserErrorsFragment} + ${customerAccessTokenFragment} +` + +export const customerAccessTokenDeleteMutation = /* GraphQL */ ` + mutation customerAccessTokenDelete($customerAccessToken: String!) { + customerAccessTokenDelete(customerAccessToken: $customerAccessToken) { + deletedAccessToken + deletedCustomerAccessTokenId + ...customerUserErrors + } + } + ${customerUserErrorsFragment} +` + +export const customerCreateMutation = /* GraphQL */ ` + mutation customerCreate($input: CustomerCreateInput!) { + customerCreate(input: $input) { + ...customerUserErrors + customer { + id + } + } + } + ${customerUserErrorsFragment} +` diff --git a/packages/shopify/src/utils/mutations/index.ts b/packages/shopify/src/utils/mutations/index.ts index 0c2430457..ccccac9c6 100644 --- a/packages/shopify/src/utils/mutations/index.ts +++ b/packages/shopify/src/utils/mutations/index.ts @@ -1,9 +1,2 @@ -export { customerCreateMutation } from './customer-create' -export { cartCreateMutation } from './cart-create' -export { cartLinesAddMutation } from './cart-lines-item-add' -export { cartLinesUpdateMutation } from './cart-lines-item-update' -export { cartLinesRemoveMutation } from './cart-lines-item-remove' -export { customerAccessTokenCreateMutation } from './customer-access-token-create' -export { customerAccessTokenDeleteMutation } from './customer-access-token-delete' -export { customerActivateMutation } from './customer-activate' -export { customerActivateByUrlMutation } from './customer-activate-by-url' +export * from './cart-mutations' +export * from './customer-mutations' diff --git a/packages/shopify/src/utils/normalize.ts b/packages/shopify/src/utils/normalize.ts index 7f91e0fbd..53ab122b8 100644 --- a/packages/shopify/src/utils/normalize.ts +++ b/packages/shopify/src/utils/normalize.ts @@ -1,5 +1,5 @@ import type { Page } from '../types/page' -import type { Product } from '../types/product' +import type { Product, ProductPrice } from '../types/product' import type { Cart, LineItem } from '../types/cart' import type { Category } from '../types/site' @@ -18,10 +18,14 @@ import { import { colorMap } from './colors' import { CommerceError } from '@vercel/commerce/utils/errors' -const money = ({ amount, currencyCode }: MoneyV2) => { +type MoneyProps = MoneyV2 & { retailPrice?: string } + +const money = (money: MoneyProps): ProductPrice => { + const { amount, currencyCode, retailPrice } = money || { currencyCode: 'USD' } return { value: +amount, currencyCode, + ...(retailPrice && { retailPrice: +retailPrice }), } } @@ -52,46 +56,47 @@ const normalizeProductOption = ({ } } -const normalizeProductImages = ({ edges }: ImageConnection) => - edges?.map(({ node: { altText: alt, url, ...rest } }) => ({ +const normalizeProductImages = (images: ImageConnection) => + images?.nodes?.map(({ altText: alt, url, ...rest }) => ({ ...rest, url, alt, - })) + })) ?? [] -const normalizeProductVariants = ({ edges }: ProductVariantConnection) => { - return edges?.map( - ({ - node: { +const normalizeProductVariants = (variants: ProductVariantConnection) => { + return ( + variants?.nodes?.map( + ({ id, selectedOptions, sku, title, priceV2, + image, compareAtPriceV2, requiresShipping, availableForSale, - }, - }) => { - return { - id, - name: title, - sku: sku ?? id, - price: +priceV2.amount, - listPrice: +compareAtPriceV2?.amount, - requiresShipping, - availableForSale, - options: selectedOptions.map(({ name, value }: SelectedOption) => { - const options = normalizeProductOption({ - id, - name, - values: [value], - }) + }) => { + return { + id, + name: title, + sku: sku ?? id, + image, + price: money({ ...priceV2, retailPrice: compareAtPriceV2?.amount }), + requiresShipping, + availableForSale, + options: selectedOptions.map(({ name, value }: SelectedOption) => { + const options = normalizeProductOption({ + id, + name, + values: [value], + }) - return options - }), + return options + }), + } } - } + ) ?? [] ) } @@ -104,20 +109,24 @@ export function normalizeProduct({ description, descriptionHtml, handle, - priceRange, options, metafields, ...rest }: ShopifyProduct): Product { + const variant = variants?.nodes?.[0] + return { id, name, vendor, path: `/${handle}`, slug: handle?.replace(/^\/+|\/+$/g, ''), - price: money(priceRange?.minVariantPrice), - images: normalizeProductImages(images), - variants: variants ? normalizeProductVariants(variants) : [], + price: money({ + ...variant?.priceV2, + retailPrice: variant?.compareAtPriceV2?.amount, + }), + images: images ? normalizeProductImages(images) : [variant?.image], + variants: normalizeProductVariants(variants), options: options ? options .filter((o) => o.name !== 'Title') // By default Shopify adds a 'Title' name when there's only one option. We don't need it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095 @@ -148,7 +157,7 @@ function normalizeLineItem({ }, requiresShipping: variant?.requiresShipping ?? false, price: +variant?.priceV2?.amount, - listPrice: variant?.compareAtPriceV2?.amount, + listPrice: +variant?.compareAtPriceV2?.amount, }, path: variant?.product?.handle, discounts: [], diff --git a/packages/shopify/src/utils/queries/get-all-products-query.ts b/packages/shopify/src/utils/queries/get-all-products-query.ts index 2286910fa..2990248c5 100644 --- a/packages/shopify/src/utils/queries/get-all-products-query.ts +++ b/packages/shopify/src/utils/queries/get-all-products-query.ts @@ -1,39 +1,4 @@ -export const productConnectionFragment = /* GraphQL */ ` - fragment productConnection on ProductConnection { - pageInfo { - hasNextPage - hasPreviousPage - } - edges { - node { - id - title - vendor - handle - priceRange { - minVariantPrice { - amount - currencyCode - } - } - images(first: 1) { - pageInfo { - hasNextPage - hasPreviousPage - } - edges { - node { - url - altText - width - height - } - } - } - } - } - } -` +import { productCardFragment } from '../fragments/product-card-fragment' export const getAllProductsQuery = /* GraphQL */ ` query getAllProducts( @@ -48,9 +13,16 @@ export const getAllProductsQuery = /* GraphQL */ ` reverse: $reverse query: $query ) { - ...productConnection + pageInfo { + hasNextPage + hasPreviousPage + } + edges { + node { + ...productCard + } + } } } - - ${productConnectionFragment} + ${productCardFragment} ` diff --git a/packages/shopify/src/utils/queries/get-cart-query.ts b/packages/shopify/src/utils/queries/get-cart-query.ts index 5a43cbcbb..eeb91ddbd 100644 --- a/packages/shopify/src/utils/queries/get-cart-query.ts +++ b/packages/shopify/src/utils/queries/get-cart-query.ts @@ -1,77 +1,4 @@ -export const cartDetailsFragment = /* GraphQL */ ` - fragment cartDetails on Cart { - id - checkoutUrl - createdAt - updatedAt - lines(first: 10) { - edges { - node { - id - quantity - merchandise { - ... on ProductVariant { - id - id - sku - title - selectedOptions { - name - value - } - image { - url - altText - width - height - } - priceV2 { - amount - currencyCode - } - compareAtPriceV2 { - amount - currencyCode - } - product { - title - handle - } - } - } - } - } - } - attributes { - key - value - } - buyerIdentity { - email - customer { - id - } - } - estimatedCost { - totalAmount { - amount - currencyCode - } - subtotalAmount { - amount - currencyCode - } - totalTaxAmount { - amount - currencyCode - } - totalDutyAmount { - amount - currencyCode - } - } - } -` +import { cartDetailsFragment } from '../fragments/cart-details-fragment' export const getCartQuery = /* GraphQL */ ` query getCart($cartId: ID!) { diff --git a/packages/shopify/src/utils/queries/get-checkout-query.ts b/packages/shopify/src/utils/queries/get-checkout-query.ts deleted file mode 100644 index 06b51d850..000000000 --- a/packages/shopify/src/utils/queries/get-checkout-query.ts +++ /dev/null @@ -1,69 +0,0 @@ -export const checkoutDetailsFragment = /* GraphQL */ ` - fragment checkoutDetails on Checkout { - id - webUrl - subtotalPriceV2 { - amount - currencyCode - } - totalTaxV2 { - amount - currencyCode - } - totalPriceV2 { - amount - currencyCode - } - completedAt - createdAt - taxesIncluded - lineItems(first: 250) { - pageInfo { - hasNextPage - hasPreviousPage - } - edges { - node { - id - title - variant { - id - sku - title - selectedOptions { - name - value - } - image { - url - altText - width - height - } - priceV2 { - amount - currencyCode - } - compareAtPriceV2 { - amount - currencyCode - } - product { - handle - } - } - quantity - } - } - } - } -` - -export const getCheckoutQuery = /* GraphQL */ ` - query getCheckout($checkoutId: ID!) { - node(id: $checkoutId) { - ...checkoutDetails - } - } - ${checkoutDetailsFragment} -` diff --git a/packages/shopify/src/utils/queries/get-collection-products-query.ts b/packages/shopify/src/utils/queries/get-collection-products-query.ts index 36e89603b..d8d43a674 100644 --- a/packages/shopify/src/utils/queries/get-collection-products-query.ts +++ b/packages/shopify/src/utils/queries/get-collection-products-query.ts @@ -1,4 +1,4 @@ -import { productConnectionFragment } from './get-all-products-query' +import { productCardFragment } from '../fragments/product-card-fragment' export const getCollectionProductsQuery = /* GraphQL */ ` query getProductsFromCollection( @@ -11,10 +11,20 @@ export const getCollectionProductsQuery = /* GraphQL */ ` id ... on Collection { products(first: $first, sortKey: $sortKey, reverse: $reverse) { - ...productConnection + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + edges { + node { + ...productCard + } + } } } } } - ${productConnectionFragment} + ${productCardFragment} ` diff --git a/packages/shopify/src/utils/queries/get-product-query.ts b/packages/shopify/src/utils/queries/get-product-query.ts index 635da9fe8..82295bae3 100644 --- a/packages/shopify/src/utils/queries/get-product-query.ts +++ b/packages/shopify/src/utils/queries/get-product-query.ts @@ -14,57 +14,56 @@ export const getProductQuery = /* GraphQL */ ` name values } - priceRange { - maxVariantPrice { - amount - currencyCode - } - minVariantPrice { - amount - currencyCode - } + seo { + description + title } - variants(first: 250) { - pageInfo { - hasNextPage - hasPreviousPage - } - edges { - node { + variants(first: 100) { + nodes { + id + title + sku + availableForSale + requiresShipping + image { id - title - sku - availableForSale - requiresShipping - selectedOptions { - name - value - } - priceV2 { - amount - currencyCode - } - compareAtPriceV2 { - amount - currencyCode - } - } - } - } - images(first: 250) { - pageInfo { - hasNextPage - hasPreviousPage - } - edges { - node { - url altText + url width height } + selectedOptions { + name + value + } + priceV2 { + amount + currencyCode + } + compareAtPriceV2 { + amount + currencyCode + } } } + images(first: 15) { + nodes { + url + altText + width + height + } + } + } + shop { + shippingPolicy { + body + handle + } + refundPolicy { + body + handle + } } } ` diff --git a/packages/shopify/src/utils/queries/get-site-info-query.ts b/packages/shopify/src/utils/queries/get-site-info-query.ts deleted file mode 100644 index 4a01ae4da..000000000 --- a/packages/shopify/src/utils/queries/get-site-info-query.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const getSiteInfoQuery = /* GraphQL */ ` - query getSiteInfo { - shop { - name - } - } -` diff --git a/packages/shopify/src/utils/queries/index.ts b/packages/shopify/src/utils/queries/index.ts index 9bb29f1ab..2ef7398c3 100644 --- a/packages/shopify/src/utils/queries/index.ts +++ b/packages/shopify/src/utils/queries/index.ts @@ -7,5 +7,4 @@ export { getCollectionProductsQuery } from './get-collection-products-query' export { getAllPagesQuery } from './get-all-pages-query' export { getPageQuery } from './get-page-query' export { getCustomerQuery } from './get-customer-query' -export { getSiteInfoQuery } from './get-site-info-query' export { getCartQuery } from './get-cart-query' diff --git a/packages/shopify/src/utils/set-cart-url-cookie.ts b/packages/shopify/src/utils/set-cart-url-cookie.ts deleted file mode 100644 index 68cc52865..000000000 --- a/packages/shopify/src/utils/set-cart-url-cookie.ts +++ /dev/null @@ -1,14 +0,0 @@ -import Cookies from 'js-cookie' - -import { SHOPIFY_CART_URL_COOKIE, SHOPIFY_COOKIE_EXPIRE } from '../const' - -export const setCartUrlCookie = (cartUrl: string) => { - if (cartUrl) { - const oldCookie = Cookies.get(SHOPIFY_CART_URL_COOKIE) - if (oldCookie !== cartUrl) { - Cookies.set(SHOPIFY_CART_URL_COOKIE, cartUrl, { - expires: SHOPIFY_COOKIE_EXPIRE, - }) - } - } -}