diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx index c0fdd32d2..05e7a1cee 100644 --- a/components/product/ProductView/ProductView.tsx +++ b/components/product/ProductView/ProductView.tsx @@ -1,23 +1,20 @@ import cn from 'classnames' import Image from 'next/image' import { NextSeo } from 'next-seo' -import { FC, useState } from 'react' +import { FC, useEffect, useState } from 'react' import s from './ProductView.module.css' - import { Swatch, ProductSlider } from '@components/product' import { Button, Container, Text, useUI } from '@components/ui' - import type { Product } from '@commerce/types' import usePrice from '@framework/product/use-price' import { useAddItem } from '@framework/cart' - import { getVariant, SelectedOptions } from '../helpers' import WishlistButton from '@components/wishlist/WishlistButton' interface Props { - className?: string children?: any product: Product + className?: string } const ProductView: FC = ({ product }) => { @@ -29,12 +26,18 @@ const ProductView: FC = ({ product }) => { }) const { openSidebar } = useUI() const [loading, setLoading] = useState(false) - const [choices, setChoices] = useState({ - size: null, - color: null, - }) + const [choices, setChoices] = useState({}) + + useEffect(() => { + // Selects the default option + product.variants[0].options?.forEach((v) => { + setChoices((choices) => ({ + ...choices, + [v.displayName.toLowerCase()]: v.values[0].label.toLowerCase(), + })) + }) + }, []) - // Select the correct variant based on choices const variant = getVariant(product, choices) const addToCart = async () => { @@ -143,7 +146,6 @@ const ProductView: FC = ({ product }) => { className={s.button} onClick={addToCart} loading={loading} - disabled={!variant && product.options.length > 0} > Add to Cart diff --git a/components/product/helpers.ts b/components/product/helpers.ts index 029476c92..a0ceb7aa5 100644 --- a/components/product/helpers.ts +++ b/components/product/helpers.ts @@ -1,9 +1,5 @@ import type { Product } from '@commerce/types' - -export type SelectedOptions = { - size: string | null - color: string | null -} +export type SelectedOptions = Record export function getVariant(product: Product, opts: SelectedOptions) { const variant = product.variants.find((variant) => { diff --git a/framework/shopify/api/index.ts b/framework/shopify/api/index.ts index 4f15cae15..387ed02fc 100644 --- a/framework/shopify/api/index.ts +++ b/framework/shopify/api/index.ts @@ -5,7 +5,6 @@ import { API_TOKEN, SHOPIFY_CHECKOUT_ID_COOKIE, SHOPIFY_CUSTOMER_TOKEN_COOKIE, - SHOPIFY_COOKIE_EXPIRE, } from '../const' if (!API_URL) { @@ -48,7 +47,7 @@ const config = new Config({ commerceUrl: API_URL, apiToken: API_TOKEN!, cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE, - cartCookieMaxAge: SHOPIFY_COOKIE_EXPIRE, + cartCookieMaxAge: 60 * 60 * 24 * 30, fetch: fetchGraphqlApi, customerCookie: SHOPIFY_CUSTOMER_TOKEN_COOKIE, }) diff --git a/framework/shopify/api/operations/get-all-collections.ts b/framework/shopify/api/operations/get-all-collections.ts deleted file mode 100644 index 9cf216a91..000000000 --- a/framework/shopify/api/operations/get-all-collections.ts +++ /dev/null @@ -1,21 +0,0 @@ -import Client from 'shopify-buy' -import { ShopifyConfig } from '../index' - -type Options = { - config: ShopifyConfig -} - -const getAllCollections = async (options: Options) => { - const { config } = options - - const client = Client.buildClient({ - storefrontAccessToken: config.apiToken, - domain: config.commerceUrl, - }) - - const res = await client.collection.fetchAllWithProducts() - - return JSON.parse(JSON.stringify(res)) -} - -export default getAllCollections diff --git a/framework/shopify/api/operations/get-page.ts b/framework/shopify/api/operations/get-page.ts deleted file mode 100644 index 32acb7c8f..000000000 --- a/framework/shopify/api/operations/get-page.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Page } from '../../schema' -import { ShopifyConfig, getConfig } from '..' - -export type GetPageResult = T - -export type PageVariables = { - id: string -} - -async function getPage({ - url, - variables, - config, - preview, -}: { - url?: string - variables: PageVariables - config?: ShopifyConfig - preview?: boolean -}): Promise { - config = getConfig(config) - return {} -} - -export default getPage diff --git a/framework/shopify/auth/use-login.tsx b/framework/shopify/auth/use-login.tsx index 188dd54a2..7993822cd 100644 --- a/framework/shopify/auth/use-login.tsx +++ b/framework/shopify/auth/use-login.tsx @@ -10,7 +10,7 @@ import { MutationCheckoutCreateArgs, } from '../schema' import useLogin, { UseLogin } from '@commerce/auth/use-login' -import { setCustomerToken } from '../utils' +import { setCustomerToken, throwUserErrors } from '../utils' export default useLogin as UseLogin @@ -45,13 +45,8 @@ export const handler: MutationHook = { }, }) - const errors = customerAccessTokenCreate?.customerUserErrors + throwUserErrors(customerAccessTokenCreate?.customerUserErrors) - if (errors && errors.length) { - throw new ValidationError({ - message: getErrorMessage(errors[0]), - }) - } const customerAccessToken = customerAccessTokenCreate?.customerAccessToken const accessToken = customerAccessToken?.accessToken diff --git a/framework/shopify/auth/use-signup.tsx b/framework/shopify/auth/use-signup.tsx index 7f66448d3..9ca5c682f 100644 --- a/framework/shopify/auth/use-signup.tsx +++ b/framework/shopify/auth/use-signup.tsx @@ -1,15 +1,16 @@ import { useCallback } from 'react' import type { MutationHook } from '@commerce/utils/types' -import { CommerceError } from '@commerce/utils/errors' +import { CommerceError, ValidationError } from '@commerce/utils/errors' import useSignup, { UseSignup } from '@commerce/auth/use-signup' import useCustomer from '../customer/use-customer' -import { CustomerCreateInput } from '../schema' - import { - customerCreateMutation, - customerAccessTokenCreateMutation, -} from '../utils/mutations' -import handleLogin from '../utils/handle-login' + CustomerCreateInput, + Mutation, + MutationCustomerCreateArgs, +} from '../schema' + +import { customerCreateMutation } from '../utils/mutations' +import { handleAutomaticLogin, throwUserErrors } from '../utils' export default useSignup as UseSignup @@ -33,7 +34,11 @@ export const handler: MutationHook< 'A first name, last name, email and password are required to signup', }) } - const data = await fetch({ + + const { customerCreate } = await fetch< + Mutation, + MutationCustomerCreateArgs + >({ ...options, variables: { input: { @@ -45,19 +50,10 @@ export const handler: MutationHook< }, }) - try { - const loginData = await fetch({ - query: customerAccessTokenCreateMutation, - variables: { - input: { - email, - password, - }, - }, - }) - handleLogin(loginData) - } catch (error) {} - return data + throwUserErrors(customerCreate?.customerUserErrors) + await handleAutomaticLogin(fetch, { email, password }) + + return null }, useHook: ({ fetch }) => () => { const { revalidate } = useCustomer() diff --git a/framework/shopify/cart/index.ts b/framework/shopify/cart/index.ts index 3d288b1df..f6d36b443 100644 --- a/framework/shopify/cart/index.ts +++ b/framework/shopify/cart/index.ts @@ -1,3 +1,4 @@ export { default as useCart } from './use-cart' export { default as useAddItem } from './use-add-item' +export { default as useUpdateItem } from './use-update-item' export { default as useRemoveItem } from './use-remove-item' diff --git a/framework/shopify/cart/use-add-item.tsx b/framework/shopify/cart/use-add-item.tsx index d0f891148..cce0950e9 100644 --- a/framework/shopify/cart/use-add-item.tsx +++ b/framework/shopify/cart/use-add-item.tsx @@ -1,12 +1,15 @@ +import { useCallback } from 'react' import type { MutationHook } from '@commerce/utils/types' import { CommerceError } from '@commerce/utils/errors' import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item' import useCart from './use-cart' +import { + checkoutLineItemAddMutation, + getCheckoutId, + checkoutToCart, +} from '../utils' import { Cart, CartItemBody } from '../types' -import { checkoutLineItemAddMutation, getCheckoutId } from '../utils' -import { checkoutToCart } from './utils' import { Mutation, MutationCheckoutLineItemsAddArgs } from '../schema' -import { useCallback } from 'react' export default useAddItem as UseAddItem @@ -40,8 +43,7 @@ export const handler: MutationHook = { }, }) - // TODO: Fix this Cart type here - return checkoutToCart(checkoutLineItemsAdd) as any + return checkoutToCart(checkoutLineItemsAdd) }, useHook: ({ fetch }) => () => { const { mutate } = useCart() diff --git a/framework/shopify/cart/use-cart.tsx b/framework/shopify/cart/use-cart.tsx index d154bb837..5f77360bb 100644 --- a/framework/shopify/cart/use-cart.tsx +++ b/framework/shopify/cart/use-cart.tsx @@ -6,7 +6,7 @@ import useCommerceCart, { import { Cart } from '../types' import { SWRHook } from '@commerce/utils/types' -import { checkoutCreate, checkoutToCart } from './utils' +import { checkoutCreate, checkoutToCart } from '../utils' import getCheckoutQuery from '../utils/queries/get-checkout-query' export default useCommerceCart as UseCart @@ -22,11 +22,12 @@ export const handler: SWRHook< }, async fetcher({ input: { cartId: checkoutId }, options, fetch }) { let checkout + if (checkoutId) { const data = await fetch({ ...options, variables: { - checkoutId, + checkoutId: checkoutId, }, }) checkout = data.node @@ -36,8 +37,7 @@ export const handler: SWRHook< checkout = await checkoutCreate(fetch) } - // TODO: Fix this type - return checkoutToCart({ checkout } as any) + return checkoutToCart({ checkout }) }, useHook: ({ useData }) => (input) => { const response = useData({ diff --git a/framework/shopify/cart/use-remove-item.tsx b/framework/shopify/cart/use-remove-item.tsx index e2aef13d8..8db38eac2 100644 --- a/framework/shopify/cart/use-remove-item.tsx +++ b/framework/shopify/cart/use-remove-item.tsx @@ -1,23 +1,22 @@ import { useCallback } from 'react' - import type { MutationHookContext, HookFetcherContext, } from '@commerce/utils/types' - +import { RemoveCartItemBody } from '@commerce/types' import { ValidationError } from '@commerce/utils/errors' - import useRemoveItem, { RemoveItemInput as RemoveItemInputBase, UseRemoveItem, } from '@commerce/cart/use-remove-item' - import useCart from './use-cart' -import { checkoutLineItemRemoveMutation, getCheckoutId } from '../utils' -import { checkoutToCart } from './utils' +import { + checkoutLineItemRemoveMutation, + getCheckoutId, + checkoutToCart, +} from '../utils' import { Cart, LineItem } from '../types' import { Mutation, MutationCheckoutLineItemsRemoveArgs } from '../schema' -import { RemoveCartItemBody } from '@commerce/types' export type RemoveItemFn = T extends LineItem ? (input?: RemoveItemInput) => Promise diff --git a/framework/shopify/cart/use-update-item.tsx b/framework/shopify/cart/use-update-item.tsx index 666ce3d08..49dd6be14 100644 --- a/framework/shopify/cart/use-update-item.tsx +++ b/framework/shopify/cart/use-update-item.tsx @@ -13,7 +13,7 @@ import useUpdateItem, { import useCart from './use-cart' import { handler as removeItemHandler } from './use-remove-item' import type { Cart, LineItem, UpdateCartItemBody } from '../types' -import { checkoutToCart } from './utils' +import { checkoutToCart } from '../utils' import { getCheckoutId, checkoutLineItemUpdateMutation } from '../utils' import { Mutation, MutationCheckoutLineItemsUpdateArgs } from '../schema' diff --git a/framework/shopify/cart/utils/checkout-to-cart.ts b/framework/shopify/cart/utils/checkout-to-cart.ts deleted file mode 100644 index 03005f342..000000000 --- a/framework/shopify/cart/utils/checkout-to-cart.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Cart } from '../../types' -import { CommerceError, ValidationError } from '@commerce/utils/errors' - -import { - CheckoutLineItemsAddPayload, - CheckoutLineItemsRemovePayload, - CheckoutLineItemsUpdatePayload, - Maybe, -} from '../../schema' -import { normalizeCart } from '../../utils' - -export type CheckoutPayload = - | CheckoutLineItemsAddPayload - | CheckoutLineItemsUpdatePayload - | CheckoutLineItemsRemovePayload - -const checkoutToCart = (checkoutPayload?: Maybe): Cart => { - if (!checkoutPayload) { - throw new CommerceError({ - message: 'Invalid response from Shopify', - }) - } - - const checkout = checkoutPayload?.checkout - const userErrors = checkoutPayload?.userErrors - - if (userErrors && userErrors.length) { - throw new ValidationError({ - message: userErrors[0].message, - }) - } - - if (!checkout) { - throw new CommerceError({ - message: 'Invalid response from Shopify', - }) - } - - return normalizeCart(checkout) -} - -export default checkoutToCart diff --git a/framework/shopify/cart/utils/fetcher.ts b/framework/shopify/cart/utils/fetcher.ts deleted file mode 100644 index 6afb55f18..000000000 --- a/framework/shopify/cart/utils/fetcher.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { HookFetcherFn } from '@commerce/utils/types' -import { Cart } from '@commerce/types' -import { checkoutCreate, checkoutToCart } from '.' -import { FetchCartInput } from '@commerce/cart/use-cart' - -const fetcher: HookFetcherFn = async ({ - options, - input: { cartId: checkoutId }, - fetch, -}) => { - let checkout - - if (checkoutId) { - const data = await fetch({ - ...options, - variables: { - checkoutId, - }, - }) - checkout = data.node - } - - if (checkout?.completedAt || !checkoutId) { - checkout = await checkoutCreate(fetch) - } - - // TODO: Fix this type - return checkoutToCart({ checkout } as any) -} - -export default fetcher diff --git a/framework/shopify/cart/utils/index.ts b/framework/shopify/cart/utils/index.ts deleted file mode 100644 index 20d04955d..000000000 --- a/framework/shopify/cart/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as checkoutToCart } from './checkout-to-cart' -export { default as checkoutCreate } from './checkout-create' diff --git a/framework/shopify/fetcher.ts b/framework/shopify/fetcher.ts index 9c4fe9a9e..a69150503 100644 --- a/framework/shopify/fetcher.ts +++ b/framework/shopify/fetcher.ts @@ -2,9 +2,14 @@ import { Fetcher } from '@commerce/utils/types' import { API_TOKEN, API_URL } from './const' import { handleFetchResponse } from './utils' -const fetcher: Fetcher = async ({ method = 'POST', variables, query }) => { +const fetcher: Fetcher = async ({ + url = API_URL, + method = 'POST', + variables, + query, +}) => { return handleFetchResponse( - await fetch(API_URL, { + await fetch(url, { method, body: JSON.stringify({ query, variables }), headers: { diff --git a/framework/shopify/index.tsx b/framework/shopify/index.tsx index c26704771..5b25d6b21 100644 --- a/framework/shopify/index.tsx +++ b/framework/shopify/index.tsx @@ -28,8 +28,7 @@ export type ShopifyProps = { export function CommerceProvider({ children, ...config }: ShopifyProps) { return ( {children} diff --git a/framework/shopify/product/use-search.tsx b/framework/shopify/product/use-search.tsx index 425df9e83..bf812af3d 100644 --- a/framework/shopify/product/use-search.tsx +++ b/framework/shopify/product/use-search.tsx @@ -48,7 +48,8 @@ export const handler: SWRHook< edges = data.node?.products?.edges ?? [] if (brandId) { edges = edges.filter( - ({ node: { vendor } }: ProductEdge) => vendor === brandId + ({ node: { vendor } }: ProductEdge) => + vendor.replace(/\s+/g, '-').toLowerCase() === brandId ) } } else { diff --git a/framework/shopify/provider.ts b/framework/shopify/provider.ts index 383822baa..00db5c1d3 100644 --- a/framework/shopify/provider.ts +++ b/framework/shopify/provider.ts @@ -1,4 +1,4 @@ -import { SHOPIFY_CHECKOUT_ID_COOKIE, STORE_DOMAIN } from './const' +import { SHOPIFY_CHECKOUT_ID_COOKIE } from './const' import { handler as useCart } from './cart/use-cart' import { handler as useAddItem } from './cart/use-add-item' @@ -17,15 +17,11 @@ import fetcher from './fetcher' export const shopifyProvider = { locale: 'en-us', cartCookie: SHOPIFY_CHECKOUT_ID_COOKIE, - storeDomain: STORE_DOMAIN, fetcher, cart: { useCart, useAddItem, useUpdateItem, useRemoveItem }, customer: { useCustomer }, products: { useSearch }, auth: { useLogin, useLogout, useSignup }, - features: { - wishlist: false, - }, } export type ShopifyProvider = typeof shopifyProvider diff --git a/framework/shopify/types.ts b/framework/shopify/types.ts index c4e42b67d..e7bcb2476 100644 --- a/framework/shopify/types.ts +++ b/framework/shopify/types.ts @@ -7,13 +7,11 @@ export type ShopifyCheckout = { lineItems: CheckoutLineItem[] } -export interface Cart extends Core.Cart { - id: string +export type Cart = Core.Cart & { lineItems: LineItem[] } - export interface LineItem extends Core.LineItem { - options: any[] + options?: any[] } /** diff --git a/framework/shopify/cart/utils/checkout-create.ts b/framework/shopify/utils/checkout-create.ts similarity index 62% rename from framework/shopify/cart/utils/checkout-create.ts rename to framework/shopify/utils/checkout-create.ts index e950cc7e4..359d16315 100644 --- a/framework/shopify/cart/utils/checkout-create.ts +++ b/framework/shopify/utils/checkout-create.ts @@ -1,13 +1,17 @@ +import Cookies from 'js-cookie' + import { SHOPIFY_CHECKOUT_ID_COOKIE, SHOPIFY_CHECKOUT_URL_COOKIE, SHOPIFY_COOKIE_EXPIRE, -} from '../../const' +} from '../const' -import checkoutCreateMutation from '../../utils/mutations/checkout-create' -import Cookies from 'js-cookie' +import checkoutCreateMutation from './mutations/checkout-create' +import { CheckoutCreatePayload } from '../schema' -export const checkoutCreate = async (fetch: any) => { +export const checkoutCreate = async ( + fetch: any +): Promise => { const data = await fetch({ query: checkoutCreateMutation, }) @@ -20,7 +24,7 @@ export const checkoutCreate = async (fetch: any) => { expires: SHOPIFY_COOKIE_EXPIRE, } Cookies.set(SHOPIFY_CHECKOUT_ID_COOKIE, checkoutId, options) - Cookies.set(SHOPIFY_CHECKOUT_URL_COOKIE, checkout?.webUrl, options) + Cookies.set(SHOPIFY_CHECKOUT_URL_COOKIE, checkout.webUrl, options) } return checkout diff --git a/framework/shopify/utils/checkout-to-cart.ts b/framework/shopify/utils/checkout-to-cart.ts new file mode 100644 index 000000000..034ff11d7 --- /dev/null +++ b/framework/shopify/utils/checkout-to-cart.ts @@ -0,0 +1,48 @@ +import { Cart } from '../types' +import { CommerceError } from '@commerce/utils/errors' + +import { + CheckoutLineItemsAddPayload, + CheckoutLineItemsRemovePayload, + CheckoutLineItemsUpdatePayload, + CheckoutCreatePayload, + CheckoutUserError, + Checkout, + Maybe, +} from '../schema' + +import { normalizeCart } from './normalize' +import throwUserErrors from './throw-user-errors' + +export type CheckoutQuery = { + checkout: Checkout + checkoutUserErrors?: Array +} + +export type CheckoutPayload = + | CheckoutLineItemsAddPayload + | CheckoutLineItemsUpdatePayload + | CheckoutLineItemsRemovePayload + | CheckoutCreatePayload + | CheckoutQuery + +const checkoutToCart = (checkoutPayload?: Maybe): Cart => { + if (!checkoutPayload) { + throw new CommerceError({ + message: 'Missing checkout payload from response', + }) + } + + const checkout = checkoutPayload?.checkout + throwUserErrors(checkoutPayload?.checkoutUserErrors) + + if (!checkout) { + throw new CommerceError({ + message: 'Missing checkout object from response', + }) + } + + return normalizeCart(checkout) +} + +export default checkoutToCart diff --git a/framework/shopify/utils/get-sort-variables.ts b/framework/shopify/utils/get-sort-variables.ts index b8cdeec51..141d9a180 100644 --- a/framework/shopify/utils/get-sort-variables.ts +++ b/framework/shopify/utils/get-sort-variables.ts @@ -1,4 +1,4 @@ -const getSortVariables = (sort?: string, isCategory = false) => { +const getSortVariables = (sort?: string, isCategory: boolean = false) => { let output = {} switch (sort) { case 'price-asc': diff --git a/framework/shopify/utils/get-vendors.ts b/framework/shopify/utils/get-vendors.ts index f04483bb1..24843f177 100644 --- a/framework/shopify/utils/get-vendors.ts +++ b/framework/shopify/utils/get-vendors.ts @@ -2,13 +2,14 @@ import { ShopifyConfig } from '../api' import fetchAllProducts from '../api/utils/fetch-all-products' import getAllProductVendors from './queries/get-all-product-vendors-query' -export type BrandNode = { +export type Brand = { + entityId: string name: string path: string } export type BrandEdge = { - node: BrandNode + node: Brand } export type Brands = BrandEdge[] @@ -24,13 +25,16 @@ const getVendors = async (config: ShopifyConfig): Promise => { let vendorsStrings = vendors.map(({ node: { vendor } }) => vendor) - return [...new Set(vendorsStrings)].map((v) => ({ - node: { - entityId: v, - name: v, - path: `brands/${v}`, - }, - })) + return [...new Set(vendorsStrings)].map((v) => { + const id = v.replace(/\s+/g, '-').toLowerCase() + return { + node: { + entityId: id, + name: v, + path: `brands/${id}`, + }, + } + }) } export default getVendors diff --git a/framework/shopify/utils/handle-account-activation.ts b/framework/shopify/utils/handle-account-activation.ts new file mode 100644 index 000000000..d11f80ba1 --- /dev/null +++ b/framework/shopify/utils/handle-account-activation.ts @@ -0,0 +1,30 @@ +import { FetcherOptions } from '@commerce/utils/types' +import throwUserErrors from './throw-user-errors' + +import { + MutationCustomerActivateArgs, + MutationCustomerActivateByUrlArgs, +} from '../schema' +import { Mutation } from '../schema' +import { customerActivateByUrlMutation } from './mutations' + +const handleAccountActivation = async ( + fetch: (options: FetcherOptions) => Promise, + input: MutationCustomerActivateByUrlArgs +) => { + try { + const { customerActivateByUrl } = await fetch< + Mutation, + MutationCustomerActivateArgs + >({ + query: customerActivateByUrlMutation, + variables: { + input, + }, + }) + + throwUserErrors(customerActivateByUrl?.customerUserErrors) + } catch (error) {} +} + +export default handleAccountActivation diff --git a/framework/shopify/utils/handle-login.ts b/framework/shopify/utils/handle-login.ts index 77b6873e3..de86fa1d2 100644 --- a/framework/shopify/utils/handle-login.ts +++ b/framework/shopify/utils/handle-login.ts @@ -1,30 +1,12 @@ -import { ValidationError } from '@commerce/utils/errors' +import { FetcherOptions } from '@commerce/utils/types' +import { CustomerAccessTokenCreateInput } from '../schema' import { setCustomerToken } from './customer-token' - -const getErrorMessage = ({ - code, - message, -}: { - code: string - message: string -}) => { - switch (code) { - case 'UNIDENTIFIED_CUSTOMER': - message = 'Cannot find an account that matches the provided credentials' - break - } - return message -} +import { customerAccessTokenCreateMutation } from './mutations' +import throwUserErrors from './throw-user-errors' const handleLogin = (data: any) => { const response = data.customerAccessTokenCreate - const errors = response?.customerUserErrors - - if (errors && errors.length) { - throw new ValidationError({ - message: getErrorMessage(errors[0]), - }) - } + throwUserErrors(response?.customerUserErrors) const customerAccessToken = response?.customerAccessToken const accessToken = customerAccessToken?.accessToken @@ -36,4 +18,19 @@ const handleLogin = (data: any) => { return customerAccessToken } +export const handleAutomaticLogin = async ( + fetch: (options: FetcherOptions) => Promise, + input: CustomerAccessTokenCreateInput +) => { + try { + const loginData = await fetch({ + query: customerAccessTokenCreateMutation, + variables: { + input, + }, + }) + handleLogin(loginData) + } catch (error) {} +} + export default handleLogin diff --git a/framework/shopify/utils/index.ts b/framework/shopify/utils/index.ts index 2d59aa506..61e5975d7 100644 --- a/framework/shopify/utils/index.ts +++ b/framework/shopify/utils/index.ts @@ -4,6 +4,11 @@ export { default as getSortVariables } from './get-sort-variables' export { default as getVendors } from './get-vendors' export { default as getCategories } from './get-categories' export { default as getCheckoutId } from './get-checkout-id' +export { default as checkoutCreate } from './checkout-create' +export { default as checkoutToCart } from './checkout-to-cart' +export { default as handleLogin, handleAutomaticLogin } from './handle-login' +export { default as handleAccountActivation } from './handle-account-activation' +export { default as throwUserErrors } from './throw-user-errors' export * from './queries' export * from './mutations' export * from './normalize' diff --git a/framework/shopify/utils/mutations/checkout-create.ts b/framework/shopify/utils/mutations/checkout-create.ts index 912e1cbd2..ffbd555c7 100644 --- a/framework/shopify/utils/mutations/checkout-create.ts +++ b/framework/shopify/utils/mutations/checkout-create.ts @@ -3,9 +3,10 @@ import { checkoutDetailsFragment } from '../queries/get-checkout-query' const checkoutCreateMutation = /* GraphQL */ ` mutation { checkoutCreate(input: {}) { - userErrors { - message + checkoutUserErrors { + code field + message } checkout { ${checkoutDetailsFragment} diff --git a/framework/shopify/utils/mutations/checkout-line-item-add.ts b/framework/shopify/utils/mutations/checkout-line-item-add.ts index 67b9cf250..2282c4e26 100644 --- a/framework/shopify/utils/mutations/checkout-line-item-add.ts +++ b/framework/shopify/utils/mutations/checkout-line-item-add.ts @@ -3,9 +3,10 @@ import { checkoutDetailsFragment } from '../queries/get-checkout-query' const checkoutLineItemAddMutation = /* GraphQL */ ` mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemInput!]!) { checkoutLineItemsAdd(checkoutId: $checkoutId, lineItems: $lineItems) { - userErrors { - message + checkoutUserErrors { + code field + message } checkout { ${checkoutDetailsFragment} diff --git a/framework/shopify/utils/mutations/checkout-line-item-remove.ts b/framework/shopify/utils/mutations/checkout-line-item-remove.ts index d967a5168..8dea4ce08 100644 --- a/framework/shopify/utils/mutations/checkout-line-item-remove.ts +++ b/framework/shopify/utils/mutations/checkout-line-item-remove.ts @@ -6,9 +6,10 @@ const checkoutLineItemRemoveMutation = /* GraphQL */ ` checkoutId: $checkoutId lineItemIds: $lineItemIds ) { - userErrors { - message + checkoutUserErrors { + code field + message } checkout { ${checkoutDetailsFragment} diff --git a/framework/shopify/utils/mutations/checkout-line-item-update.ts b/framework/shopify/utils/mutations/checkout-line-item-update.ts index 8edf17587..76254341e 100644 --- a/framework/shopify/utils/mutations/checkout-line-item-update.ts +++ b/framework/shopify/utils/mutations/checkout-line-item-update.ts @@ -3,9 +3,10 @@ import { checkoutDetailsFragment } from '../queries/get-checkout-query' const checkoutLineItemUpdateMutation = /* GraphQL */ ` mutation($checkoutId: ID!, $lineItems: [CheckoutLineItemUpdateInput!]!) { checkoutLineItemsUpdate(checkoutId: $checkoutId, lineItems: $lineItems) { - userErrors { - message + checkoutUserErrors { + code field + message } checkout { ${checkoutDetailsFragment} diff --git a/framework/shopify/utils/mutations/customer-activate-by-url.ts b/framework/shopify/utils/mutations/customer-activate-by-url.ts new file mode 100644 index 000000000..345d502bd --- /dev/null +++ b/framework/shopify/utils/mutations/customer-activate-by-url.ts @@ -0,0 +1,19 @@ +const customerActivateByUrlMutation = /* GraphQL */ ` + mutation customerActivateByUrl($activationUrl: URL!, $password: String!) { + customerActivateByUrl(activationUrl: $activationUrl, password: $password) { + customer { + id + } + customerAccessToken { + accessToken + expiresAt + } + customerUserErrors { + code + field + message + } + } + } +` +export default customerActivateByUrlMutation diff --git a/framework/shopify/utils/mutations/customer-activate.ts b/framework/shopify/utils/mutations/customer-activate.ts new file mode 100644 index 000000000..b1d057c69 --- /dev/null +++ b/framework/shopify/utils/mutations/customer-activate.ts @@ -0,0 +1,19 @@ +const customerActivateMutation = /* GraphQL */ ` + mutation customerActivate($id: ID!, $input: CustomerActivateInput!) { + customerActivate(id: $id, input: $input) { + customer { + id + } + customerAccessToken { + accessToken + expiresAt + } + customerUserErrors { + code + field + message + } + } + } +` +export default customerActivateMutation diff --git a/framework/shopify/utils/mutations/index.ts b/framework/shopify/utils/mutations/index.ts index 3a16d7cec..165fb192d 100644 --- a/framework/shopify/utils/mutations/index.ts +++ b/framework/shopify/utils/mutations/index.ts @@ -5,3 +5,5 @@ export { default as checkoutLineItemUpdateMutation } from './checkout-line-item- export { default as checkoutLineItemRemoveMutation } from './checkout-line-item-remove' export { default as customerAccessTokenCreateMutation } from './customer-access-token-create' export { default as customerAccessTokenDeleteMutation } from './customer-access-token-delete' +export { default as customerActivateMutation } from './customer-activate' +export { default as customerActivateByUrlMutation } from './customer-activate-by-url' diff --git a/framework/shopify/utils/normalize.ts b/framework/shopify/utils/normalize.ts index c9b428b37..25bdca053 100644 --- a/framework/shopify/utils/normalize.ts +++ b/framework/shopify/utils/normalize.ts @@ -33,7 +33,7 @@ const normalizeProductOption = ({ let output: any = { label: value, } - if (displayName === 'Color') { + if (displayName.match(/colou?r/gi)) { output = { ...output, hexColors: [value], @@ -54,21 +54,24 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => { return edges?.map( ({ node: { id, selectedOptions, sku, title, priceV2, compareAtPriceV2 }, - }) => ({ - id, - name: title, - sku: sku ?? id, - price: +priceV2.amount, - listPrice: +compareAtPriceV2?.amount, - requiresShipping: true, - options: selectedOptions.map(({ name, value }: SelectedOption) => - normalizeProductOption({ - id, - name, - values: [value], - }) - ), - }) + }) => { + return { + id, + name: title, + sku: sku ?? id, + price: +priceV2.amount, + listPrice: +compareAtPriceV2?.amount, + requiresShipping: true, + options: selectedOptions.map(({ name, value }: SelectedOption) => { + const options = normalizeProductOption({ + id, + name, + values: [value], + }) + return options + }), + } + } ) } @@ -96,7 +99,11 @@ export function normalizeProduct(productNode: ShopifyProduct): Product { price: money(priceRange?.minVariantPrice), images: normalizeProductImages(images), variants: variants ? normalizeProductVariants(variants) : [], - options: options ? options.map((o) => normalizeProductOption(o)) : [], + 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 + .map((o) => normalizeProductOption(o)) + : [], ...rest, } @@ -122,7 +129,7 @@ export function normalizeCart(checkout: Checkout): Cart { } function normalizeLineItem({ - node: { id, title, variant, quantity }, + node: { id, title, variant, quantity, ...rest }, }: CheckoutLineItemEdge): LineItem { return { id, @@ -135,18 +142,22 @@ function normalizeLineItem({ sku: variant?.sku ?? '', name: variant?.title!, image: { - url: variant?.image?.originalSrc, + url: variant?.image?.originalSrc ?? '/product-img-placeholder.svg', }, requiresShipping: variant?.requiresShipping ?? false, price: variant?.priceV2?.amount, listPrice: variant?.compareAtPriceV2?.amount, }, - path: '', + path: String(variant?.product?.handle), discounts: [], - options: [ - { - value: variant?.title, - }, - ], + options: + // By default Shopify adds a default variant with default names, we're removing it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095 + variant?.title == 'Default Title' + ? [] + : [ + { + value: variant?.title, + }, + ], } } diff --git a/framework/shopify/utils/queries/get-checkout-query.ts b/framework/shopify/utils/queries/get-checkout-query.ts index 194e1619a..d8758e321 100644 --- a/framework/shopify/utils/queries/get-checkout-query.ts +++ b/framework/shopify/utils/queries/get-checkout-query.ts @@ -43,6 +43,9 @@ export const checkoutDetailsFragment = ` amount currencyCode } + product { + handle + } } quantity } diff --git a/framework/shopify/utils/storage.ts b/framework/shopify/utils/storage.ts deleted file mode 100644 index d46dadb21..000000000 --- a/framework/shopify/utils/storage.ts +++ /dev/null @@ -1,13 +0,0 @@ -export const getCheckoutIdFromStorage = (token: string) => { - if (window && window.sessionStorage) { - return window.sessionStorage.getItem(token) - } - - return null -} - -export const setCheckoutIdInStorage = (token: string, id: string | number) => { - if (window && window.sessionStorage) { - return window.sessionStorage.setItem(token, id + '') - } -} diff --git a/framework/shopify/utils/throw-user-errors.ts b/framework/shopify/utils/throw-user-errors.ts new file mode 100644 index 000000000..5488ba282 --- /dev/null +++ b/framework/shopify/utils/throw-user-errors.ts @@ -0,0 +1,38 @@ +import { ValidationError } from '@commerce/utils/errors' + +import { + CheckoutErrorCode, + CheckoutUserError, + CustomerErrorCode, + CustomerUserError, +} from '../schema' + +export type UserErrors = Array + +export type UserErrorCode = + | CustomerErrorCode + | CheckoutErrorCode + | null + | undefined + +const getCustomMessage = (code: UserErrorCode, message: string) => { + switch (code) { + case 'UNIDENTIFIED_CUSTOMER': + message = 'Cannot find an account that matches the provided credentials' + break + } + return message +} + +export const throwUserErrors = (errors?: UserErrors) => { + if (errors && errors.length) { + throw new ValidationError({ + errors: errors.map(({ code, message }) => ({ + code: code ?? 'validation_error', + message: getCustomMessage(code, message), + })), + }) + } +} + +export default throwUserErrors diff --git a/pages/[...pages].tsx b/pages/[...pages].tsx index 67adb6287..3f39845b5 100644 --- a/pages/[...pages].tsx +++ b/pages/[...pages].tsx @@ -25,8 +25,7 @@ export async function getStaticProps({ const pageItem = pages.find((p) => (p.url ? getSlug(p.url) === slug : false)) const data = pageItem && - // TODO: Shopify - Fix this type - (await getPage({ variables: { id: pageItem.id! } as any, config, preview })) + (await getPage({ variables: { id: pageItem.id! }, config, preview })) const page = data?.page if (!page) { diff --git a/pages/search.tsx b/pages/search.tsx index da2edccd8..4100108bc 100644 --- a/pages/search.tsx +++ b/pages/search.tsx @@ -75,10 +75,8 @@ export default function Search({ const { data } = useSearch({ search: typeof q === 'string' ? q : '', - // TODO: Shopify - Fix this type - categoryId: activeCategory?.entityId as any, - // TODO: Shopify - Fix this type - brandId: (activeBrand as any)?.entityId, + categoryId: activeCategory?.entityId, + brandId: activeBrand?.entityId, sort: typeof sort === 'string' ? sort : '', }) diff --git a/tsconfig.json b/tsconfig.json index 9e712fb18..e20f37099 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,8 +22,8 @@ "@components/*": ["components/*"], "@commerce": ["framework/commerce"], "@commerce/*": ["framework/commerce/*"], - "@framework": ["framework/bigcommerce"], - "@framework/*": ["framework/bigcommerce/*"] + "@framework": ["framework/shopify"], + "@framework/*": ["framework/shopify/*"] } }, "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],