From b1210781514fde298ed5f10d9406aede5008fa14 Mon Sep 17 00:00:00 2001 From: B Date: Thu, 4 Feb 2021 17:03:44 -0300 Subject: [PATCH 01/12] Updates (#192) * userAvatar * Avatar background * Changes * Removing unused dependencies * Count Bag small and for bigger numbers * decresed bundle size --- assets/base.css | 8 +- assets/components.css | 2 +- components/common/Avatar/Avatar.tsx | 13 +- .../common/Searchbar/Searchbar.module.css | 2 +- components/common/UserNav/UserNav.module.css | 6 +- .../ProductSlider/ProductSlider.module.css | 4 +- components/ui/Button/Button.module.css | 2 +- components/ui/Input/Input.module.css | 2 +- components/ui/context.tsx | 13 + lib/hooks/useUserAvatar.ts | 26 + lib/logger.ts | 18 - package.json | 14 +- pages/search.tsx | 6 +- tailwind.config.js | 3 +- yarn.lock | 2331 ++++++----------- 15 files changed, 939 insertions(+), 1511 deletions(-) create mode 100644 lib/hooks/useUserAvatar.ts delete mode 100644 lib/logger.ts diff --git a/assets/base.css b/assets/base.css index f854065ba..dfdaf1475 100644 --- a/assets/base.css +++ b/assets/base.css @@ -3,7 +3,6 @@ --primary-2: #f1f3f5; --secondary: #000000; --secondary-2: #111; - --selection: var(--cyan); --text-base: #000000; @@ -13,18 +12,14 @@ --hover: rgba(0, 0, 0, 0.075); --hover-1: rgba(0, 0, 0, 0.15); --hover-2: rgba(0, 0, 0, 0.25); - --cyan: #22b8cf; --green: #37b679; --red: #da3c3c; --pink: #e64980; --purple: #f81ce5; - --blue: #0070f3; - - --violet-light: #7048e8; --violet: #5f3dc4; - + --violet-light: #7048e8; --accents-0: #f8f9fa; --accents-1: #f1f3f5; --accents-2: #e9ecef; @@ -132,3 +127,4 @@ a { opacity: 1; } } + diff --git a/assets/components.css b/assets/components.css index ebebcc238..8c4c5a357 100644 --- a/assets/components.css +++ b/assets/components.css @@ -1,3 +1,3 @@ .fit { min-height: calc(100vh - 88px); -} +} \ No newline at end of file diff --git a/components/common/Avatar/Avatar.tsx b/components/common/Avatar/Avatar.tsx index 351a117ec..f78aa1d01 100644 --- a/components/common/Avatar/Avatar.tsx +++ b/components/common/Avatar/Avatar.tsx @@ -1,5 +1,5 @@ -import { FC, useState, useMemo, useRef, useEffect } from 'react' -import { getRandomPairOfColors } from '@lib/colors' +import { FC, useRef, useEffect } from 'react' +import { useUserAvatar } from '@lib/hooks/useUserAvatar' interface Props { className?: string @@ -7,18 +7,13 @@ interface Props { } const Avatar: FC = ({}) => { - const [bg] = useState(useMemo(() => getRandomPairOfColors, [])) let ref = useRef() as React.MutableRefObject - - useEffect(() => { - if (ref && ref.current) { - ref.current.style.backgroundImage = `linear-gradient(140deg, ${bg[0]}, ${bg[1]} 100%)` - } - }, [bg]) + let { userAvatar } = useUserAvatar() return (
{/* Add an image - We're generating a gradient as placeholder */} diff --git a/components/common/Searchbar/Searchbar.module.css b/components/common/Searchbar/Searchbar.module.css index 500483195..071a14ef0 100644 --- a/components/common/Searchbar/Searchbar.module.css +++ b/components/common/Searchbar/Searchbar.module.css @@ -7,7 +7,7 @@ } .input:focus { - @apply outline-none shadow-outline-2; + @apply outline-none shadow-outline-normal; } .iconContainer { diff --git a/components/common/UserNav/UserNav.module.css b/components/common/UserNav/UserNav.module.css index a319e3dac..cd1a6ce1f 100644 --- a/components/common/UserNav/UserNav.module.css +++ b/components/common/UserNav/UserNav.module.css @@ -24,7 +24,11 @@ } .bagCount { - @apply border border-accents-1 bg-secondary text-secondary h-4 w-4 absolute rounded-full right-3 top-3 flex items-center justify-center font-bold text-xs; + @apply border border-accents-1 bg-secondary text-secondary absolute rounded-full right-3 top-3 flex items-center justify-center font-bold text-xs; + padding-left: 2.5px; + padding-right: 2.5px; + min-width: 1.25rem; + min-height: 1.25rem; } .avatarButton { diff --git a/components/product/ProductSlider/ProductSlider.module.css b/components/product/ProductSlider/ProductSlider.module.css index 5ad48cc3d..259d15801 100644 --- a/components/product/ProductSlider/ProductSlider.module.css +++ b/components/product/ProductSlider/ProductSlider.module.css @@ -15,7 +15,7 @@ .leftControl:hover, .rightControl:hover { - @apply outline-none shadow-outline-blue; + @apply outline-none shadow-outline-normal; } .leftControl { @@ -70,7 +70,7 @@ } .positionIndicator:focus .dot { - @apply shadow-outline-blue; + @apply shadow-outline-normal; } .positionIndicatorActive .dot { diff --git a/components/ui/Button/Button.module.css b/components/ui/Button/Button.module.css index df2be8802..5b563f496 100644 --- a/components/ui/Button/Button.module.css +++ b/components/ui/Button/Button.module.css @@ -7,7 +7,7 @@ } .root:focus { - @apply shadow-outline outline-none; + @apply shadow-outline-normal outline-none; } .root[data-active] { diff --git a/components/ui/Input/Input.module.css b/components/ui/Input/Input.module.css index 9ace85277..9daee1418 100644 --- a/components/ui/Input/Input.module.css +++ b/components/ui/Input/Input.module.css @@ -3,5 +3,5 @@ } .root:focus { - @apply outline-none shadow-outline-gray; + @apply outline-none shadow-outline-normal; } diff --git a/components/ui/context.tsx b/components/ui/context.tsx index 206573858..5746d1ca0 100644 --- a/components/ui/context.tsx +++ b/components/ui/context.tsx @@ -52,6 +52,10 @@ type Action = type: 'SET_MODAL_VIEW' view: MODAL_VIEWS } + | { + type: 'SET_USER_AVATAR' + value: string + } type MODAL_VIEWS = 'SIGNUP_VIEW' | 'LOGIN_VIEW' | 'FORGOT_VIEW' type ToastText = string @@ -123,6 +127,12 @@ function uiReducer(state: State, action: Action) { toastText: action.text, } } + case 'SET_USER_AVATAR': { + return { + ...state, + userAvatar: action.value, + } + } } } @@ -147,6 +157,8 @@ export const UIProvider: FC = (props) => { const openToast = () => dispatch({ type: 'OPEN_TOAST' }) const closeToast = () => dispatch({ type: 'CLOSE_TOAST' }) + const setUserAvatar = (value: string) => dispatch({ type: 'SET_USER_AVATAR', value }) + const setModalView = (view: MODAL_VIEWS) => dispatch({ type: 'SET_MODAL_VIEW', view }) @@ -164,6 +176,7 @@ export const UIProvider: FC = (props) => { setModalView, openToast, closeToast, + setUserAvatar }), [state] ) diff --git a/lib/hooks/useUserAvatar.ts b/lib/hooks/useUserAvatar.ts new file mode 100644 index 000000000..840daae6d --- /dev/null +++ b/lib/hooks/useUserAvatar.ts @@ -0,0 +1,26 @@ +import { useEffect } from 'react' +import { useUI } from '@components/ui/context' +import { getRandomPairOfColors } from '@lib/colors' + +export const useUserAvatar = (name = 'userAvatar') => { + const { userAvatar, setUserAvatar } = useUI() + + useEffect(() => { + if (!userAvatar && localStorage.getItem(name)) { + // Get bg from localStorage and push it to the context. + setUserAvatar(localStorage.getItem(name)) + } + if (!localStorage.getItem(name)) { + // bg not set locally, generating one, setting localStorage and context to persist. + const bg = getRandomPairOfColors() + const value = `linear-gradient(140deg, ${bg[0]}, ${bg[1]} 100%)` + localStorage.setItem(name, value) + setUserAvatar(value) + } + }, []) + + return { + userAvatar, + setUserAvatar, + } +} diff --git a/lib/logger.ts b/lib/logger.ts deleted file mode 100644 index eeda2c325..000000000 --- a/lib/logger.ts +++ /dev/null @@ -1,18 +0,0 @@ -import bunyan from 'bunyan' -import PrettyStream from 'bunyan-prettystream' - -const prettyStdOut = new PrettyStream() - -const log = bunyan.createLogger({ - name: 'Next.js - Commerce', - level: 'debug', - streams: [ - { - level: 'debug', - type: 'raw', - stream: prettyStdOut, - }, - ], -}) - -export default log diff --git a/package.json b/package.json index b90bf4cf8..8db481e69 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ }, "dependencies": { "@reach/portal": "^0.11.2", - "@tailwindcss/ui": "^0.6.2", "@vercel/fetch": "^6.1.0", "body-scroll-lock": "^3.1.5", "bowser": "^2.11.0", @@ -57,17 +56,18 @@ "lodash.debounce": "^4.0.8", "lodash.random": "^3.2.0", "lodash.throttle": "^4.1.1", - "next": "^10.0.5-canary.11", + "next": "^10.0.7-canary.3", "next-seo": "^4.11.0", "next-themes": "^0.0.4", + "postcss": "^8.2.4", "postcss-nesting": "^7.0.1", - "react": "^16.14.0", - "react-dom": "^16.14.0", + "react": "^17.0.1", + "react-dom": "^17.0.1", "react-merge-refs": "^1.1.0", "react-ticker": "^1.2.2", "swr": "^0.4.0", "tabbable": "^5.1.5", - "tailwindcss": "^1.9" + "tailwindcss": "^2.0.2" }, "devDependencies": { "@graphql-codegen/cli": "^1.20.0", @@ -77,8 +77,6 @@ "@manifoldco/swagger-to-ts": "^2.1.0", "@next/bundle-analyzer": "^10.0.1", "@types/body-scroll-lock": "^2.6.1", - "@types/bunyan": "^1.8.6", - "@types/bunyan-prettystream": "^0.1.31", "@types/classnames": "^2.2.10", "@types/cookie": "^0.4.0", "@types/js-cookie": "^2.2.6", @@ -87,8 +85,6 @@ "@types/lodash.throttle": "^4.1.6", "@types/node": "^14.14.16", "@types/react": "^17.0.0", - "bunyan": "^1.8.14", - "bunyan-prettystream": "^0.1.3", "graphql": "^15.4.0", "next-unused": "^0.0.3", "postcss-flexbugs-fixes": "^4.2.1", diff --git a/pages/search.tsx b/pages/search.tsx index 88a6354cd..06d29d1b1 100644 --- a/pages/search.tsx +++ b/pages/search.tsx @@ -91,7 +91,7 @@ export default function Search({
@@ -109,15 +111,15 @@ const CartItem = ({ {item.name} - {item.options && item.options.length > 0 ? ( + {options && options.length > 0 ? (
- {item.options.map((option: ItemOption, i: number) => ( + {options.map((option: ItemOption, i: number) => ( {option.value} - {i === item.options.length - 1 ? '' : ', '} + {i === options.length - 1 ? '' : ', '} ))}
From c636fcbc4be2a364bb09c1e78b1218114d52dc82 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 15 Feb 2021 00:09:57 -0500 Subject: [PATCH 04/12] Fixed more types --- components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx | 3 ++- components/product/ProductCard/ProductCard.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx b/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx index d55f51f7a..2e01fa0cb 100644 --- a/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx +++ b/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx @@ -8,7 +8,8 @@ import { getCategoryPath, getDesignerPath } from '@lib/search' interface Props { categories?: any brands?: any - products?: Product[] + // TODO: use the product type here + products?: any[] } const Head: FC = ({ categories, brands, products = [] }) => { diff --git a/components/product/ProductCard/ProductCard.tsx b/components/product/ProductCard/ProductCard.tsx index 464f097e8..461524855 100644 --- a/components/product/ProductCard/ProductCard.tsx +++ b/components/product/ProductCard/ProductCard.tsx @@ -7,7 +7,8 @@ import Image, { ImageProps } from 'next/image' interface Props { className?: string - product: Product + // TODO: use the product type here + product: any variant?: 'slim' | 'simple' imgProps?: Omit } From 62ed50a64641e1476b088ef69227cf62e3a4485b Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 15 Feb 2021 10:15:20 -0500 Subject: [PATCH 05/12] Fixed product types --- .../HomeAllProductsGrid.tsx | 4 +- .../product/ProductCard/ProductCard.tsx | 4 +- .../product/ProductView/ProductView.tsx | 3 +- components/product/helpers.ts | 2 + .../WishlistButton/WishlistButton.tsx | 1 + .../wishlist/WishlistCard/WishlistCard.tsx | 5 +- .../api/catalog/handlers/get-products.ts | 2 +- framework/bigcommerce/api/catalog/products.ts | 2 +- framework/bigcommerce/api/wishlist/index.ts | 3 +- framework/bigcommerce/lib/normalize.ts | 1 + .../bigcommerce/product/get-all-products.ts | 2 + framework/commerce/types.d.ts | 75 ------------------- framework/commerce/types.ts | 51 +++++++++++++ 13 files changed, 70 insertions(+), 85 deletions(-) delete mode 100644 framework/commerce/types.d.ts diff --git a/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx b/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx index 2e01fa0cb..49e115df6 100644 --- a/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx +++ b/components/common/HomeAllProductsGrid/HomeAllProductsGrid.tsx @@ -1,5 +1,6 @@ import { FC } from 'react' import Link from 'next/link' +import type { Product } from '@commerce/types' import { Grid } from '@components/ui' import { ProductCard } from '@components/product' import s from './HomeAllProductsGrid.module.css' @@ -8,8 +9,7 @@ import { getCategoryPath, getDesignerPath } from '@lib/search' interface Props { categories?: any brands?: any - // TODO: use the product type here - products?: any[] + products?: Product[] } const Head: FC = ({ categories, brands, products = [] }) => { diff --git a/components/product/ProductCard/ProductCard.tsx b/components/product/ProductCard/ProductCard.tsx index 461524855..a3bd73576 100644 --- a/components/product/ProductCard/ProductCard.tsx +++ b/components/product/ProductCard/ProductCard.tsx @@ -1,14 +1,14 @@ import { FC } from 'react' import cn from 'classnames' import Link from 'next/link' +import type { Product } from '@commerce/types' import s from './ProductCard.module.css' import Image, { ImageProps } from 'next/image' // import WishlistButton from '@components/wishlist/WishlistButton' interface Props { className?: string - // TODO: use the product type here - product: any + product: Product variant?: 'slim' | 'simple' imgProps?: Omit } diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx index 65fa4d93f..b5452bef1 100644 --- a/components/product/ProductView/ProductView.tsx +++ b/components/product/ProductView/ProductView.tsx @@ -8,6 +8,7 @@ import { useUI } from '@components/ui' import { Swatch, ProductSlider } from '@components/product' import { Button, Container, Text } from '@components/ui' +import type { Product } from '@commerce/types' import usePrice from '@framework/product/use-price' import { useAddItem } from '@framework/cart' @@ -41,7 +42,7 @@ const ProductView: FC = ({ product }) => { setLoading(true) try { await addItem({ - productId: product.id, + productId: String(product.id), variantId: variant ? variant.id : product.variants[0].id, }) openSidebar() diff --git a/components/product/helpers.ts b/components/product/helpers.ts index ae0c43530..029476c92 100644 --- a/components/product/helpers.ts +++ b/components/product/helpers.ts @@ -1,3 +1,5 @@ +import type { Product } from '@commerce/types' + export type SelectedOptions = { size: string | null color: string | null diff --git a/components/wishlist/WishlistButton/WishlistButton.tsx b/components/wishlist/WishlistButton/WishlistButton.tsx index dced18a89..0c4c20194 100644 --- a/components/wishlist/WishlistButton/WishlistButton.tsx +++ b/components/wishlist/WishlistButton/WishlistButton.tsx @@ -3,6 +3,7 @@ import cn from 'classnames' import { Heart } from '@components/icons' import { useUI } from '@components/ui' +import type { Product, ProductVariant } from '@commerce/types' import useCustomer from '@framework/customer/use-customer' import useAddItem from '@framework/wishlist/use-add-item' import useRemoveItem from '@framework/wishlist/use-remove-item' diff --git a/components/wishlist/WishlistCard/WishlistCard.tsx b/components/wishlist/WishlistCard/WishlistCard.tsx index d1a9403b3..38663ab68 100644 --- a/components/wishlist/WishlistCard/WishlistCard.tsx +++ b/components/wishlist/WishlistCard/WishlistCard.tsx @@ -7,6 +7,7 @@ import { Trash } from '@components/icons' import { Button, Text } from '@components/ui' import { useUI } from '@components/ui/context' +import type { Product } from '@commerce/types' import usePrice from '@framework/product/use-price' import useAddItem from '@framework/cart/use-add-item' import useRemoveItem from '@framework/wishlist/use-remove-item' @@ -42,8 +43,8 @@ const WishlistCard: FC = ({ product }) => { setLoading(true) try { await addItem({ - productId: product.id, - variantId: product.variants[0].id, + productId: String(product.id), + variantId: String(product.variants[0].id), }) openSidebar() setLoading(false) diff --git a/framework/bigcommerce/api/catalog/handlers/get-products.ts b/framework/bigcommerce/api/catalog/handlers/get-products.ts index 894dc5cf3..20b9c5254 100644 --- a/framework/bigcommerce/api/catalog/handlers/get-products.ts +++ b/framework/bigcommerce/api/catalog/handlers/get-products.ts @@ -1,4 +1,4 @@ -import { Product } from 'framework/types' +import { Product } from '@commerce/types' import getAllProducts, { ProductEdge } from '../../../product/get-all-products' import type { ProductsHandlers } from '../products' diff --git a/framework/bigcommerce/api/catalog/products.ts b/framework/bigcommerce/api/catalog/products.ts index d13dcd3c3..d52b2de7e 100644 --- a/framework/bigcommerce/api/catalog/products.ts +++ b/framework/bigcommerce/api/catalog/products.ts @@ -1,3 +1,4 @@ +import type { Product } from '@commerce/types' import isAllowedMethod from '../utils/is-allowed-method' import createApiHandler, { BigcommerceApiHandler, @@ -5,7 +6,6 @@ import createApiHandler, { } from '../utils/create-api-handler' import { BigcommerceApiError } from '../utils/errors' import getProducts from './handlers/get-products' -import { Product } from 'framework/types' export type SearchProductsData = { products: Product[] diff --git a/framework/bigcommerce/api/wishlist/index.ts b/framework/bigcommerce/api/wishlist/index.ts index e892d2e78..7c700689c 100644 --- a/framework/bigcommerce/api/wishlist/index.ts +++ b/framework/bigcommerce/api/wishlist/index.ts @@ -11,6 +11,7 @@ import type { import getWishlist from './handlers/get-wishlist' import addItem from './handlers/add-item' import removeItem from './handlers/remove-item' +import type { Product, ProductVariant, Customer } from '@commerce/types' export type { Wishlist, WishlistItem } @@ -24,7 +25,7 @@ export type AddItemBody = { item: ItemBody } export type RemoveItemBody = { itemId: Product['id'] } export type WishlistBody = { - customer_id: Customer['id'] + customer_id: Customer['entityId'] is_public: number name: string items: any[] diff --git a/framework/bigcommerce/lib/normalize.ts b/framework/bigcommerce/lib/normalize.ts index 89aed2c38..cc7606099 100644 --- a/framework/bigcommerce/lib/normalize.ts +++ b/framework/bigcommerce/lib/normalize.ts @@ -1,3 +1,4 @@ +import type { Product } from '@commerce/types' import type { Cart, BigcommerceCart, LineItem } from '../types' import update from './immutability' diff --git a/framework/bigcommerce/product/get-all-products.ts b/framework/bigcommerce/product/get-all-products.ts index b7d728c4a..4c563bc62 100644 --- a/framework/bigcommerce/product/get-all-products.ts +++ b/framework/bigcommerce/product/get-all-products.ts @@ -2,6 +2,7 @@ import type { GetAllProductsQuery, GetAllProductsQueryVariables, } from '../schema' +import type { Product } from '@commerce/types' import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' import filterEdges from '../api/utils/filter-edges' import setProductLocaleMeta from '../api/utils/set-product-locale-meta' @@ -94,6 +95,7 @@ async function getAllProducts({ variables?: ProductVariables config?: BigcommerceConfig preview?: boolean + // TODO: fix the product type here } = {}): Promise<{ products: Product[] | any[] }> { config = getConfig(config) diff --git a/framework/commerce/types.d.ts b/framework/commerce/types.d.ts deleted file mode 100644 index 9e69ec25d..000000000 --- a/framework/commerce/types.d.ts +++ /dev/null @@ -1,75 +0,0 @@ -interface Entity { - id: string | number - [prop: string]: any -} - -interface Product extends Entity { - name: string - description: string - slug?: string - path?: string - images: ProductImage[] - variants: ProductVariant[] - price: ProductPrice - options: ProductOption[] - sku?: string -} - -interface ProductOption extends Entity { - displayName: string - values: ProductOptionValues[] -} - -interface ProductOptionValues { - label: string - hexColors?: string[] -} - -interface ProductImage { - url: string - alt?: string -} - -interface ProductVariant { - id: string | number - options: ProductOption[] -} - -interface ProductPrice { - value: number - currencyCode: 'USD' | 'ARS' | string | undefined - retailPrice?: number - salePrice?: number - listPrice?: number - extendedSalePrice?: number - extendedListPrice?: number -} - -interface CartItem extends Entity { - quantity: number - productId: Product['id'] - variantId: ProductVariant['id'] - images: ProductImage[] -} - -interface Wishlist extends Entity { - products: Pick[] -} - -interface Order {} - -interface Customer extends Entity {} - -type UseCustomerResponse = { - customer: Customer -} | null - -interface Category extends Entity { - name: string -} - -interface Brand extends Entity { - name: string -} - -type Features = 'wishlist' | 'customer' diff --git a/framework/commerce/types.ts b/framework/commerce/types.ts index 41aedb228..1f8390535 100644 --- a/framework/commerce/types.ts +++ b/framework/commerce/types.ts @@ -148,3 +148,54 @@ export interface RemoveCartItemBody { export interface RemoveCartItemHandlerBody extends Partial { cartId?: string } + +/** + * Temporal types + */ + +interface Entity { + id: string | number + [prop: string]: any +} + +export interface Product extends Entity { + name: string + description: string + slug?: string + path?: string + images: ProductImage[] + variants: ProductVariant2[] + price: ProductPrice + options: ProductOption[] + sku?: string +} + +interface ProductOption extends Entity { + displayName: string + values: ProductOptionValues[] +} + +interface ProductOptionValues { + label: string + hexColors?: string[] +} + +interface ProductImage { + url: string + alt?: string +} + +interface ProductVariant2 { + id: string | number + options: ProductOption[] +} + +interface ProductPrice { + value: number + currencyCode: 'USD' | 'ARS' | string | undefined + retailPrice?: number + salePrice?: number + listPrice?: number + extendedSalePrice?: number + extendedListPrice?: number +} From c62187425053b6d347a76b40ce0a0ccba9019e11 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 15 Feb 2021 10:25:35 -0500 Subject: [PATCH 06/12] Fixed another product type --- components/product/ProductView/ProductView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/product/ProductView/ProductView.tsx b/components/product/ProductView/ProductView.tsx index b5452bef1..61beda7fe 100644 --- a/components/product/ProductView/ProductView.tsx +++ b/components/product/ProductView/ProductView.tsx @@ -43,7 +43,7 @@ const ProductView: FC = ({ product }) => { try { await addItem({ productId: String(product.id), - variantId: variant ? variant.id : product.variants[0].id, + variantId: String(variant ? variant.id : product.variants[0].id), }) openSidebar() setLoading(false) From 499516e967833749518a03d5ef6fe9448c30de31 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 15 Feb 2021 10:41:57 -0500 Subject: [PATCH 07/12] Updated type --- framework/bigcommerce/api/catalog/handlers/get-products.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/bigcommerce/api/catalog/handlers/get-products.ts b/framework/bigcommerce/api/catalog/handlers/get-products.ts index 20b9c5254..bedd773b0 100644 --- a/framework/bigcommerce/api/catalog/handlers/get-products.ts +++ b/framework/bigcommerce/api/catalog/handlers/get-products.ts @@ -60,7 +60,7 @@ const getProducts: ProductsHandlers['getProducts'] = async ({ const productsById = graphqlData.products.reduce<{ [k: number]: Product }>((prods, p) => { - prods[p.id] = p + prods[Number(p.id)] = p return prods }, {}) From bb0b8d2771afc8450f86351c6cd481c01a129073 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 15 Feb 2021 12:02:24 -0500 Subject: [PATCH 08/12] Fixed remaining issues with types --- .../customer/get-customer-wishlist.ts | 2 +- framework/bigcommerce/product/get-product.ts | 1 + framework/bigcommerce/provider.tsx | 2 +- framework/bigcommerce/wishlist/use-add-item.tsx | 16 ++++++++++------ .../bigcommerce/wishlist/use-remove-item.tsx | 8 ++++---- framework/commerce/wishlist/use-add-item.tsx | 7 +++++++ pages/product/[slug].tsx | 2 +- pages/wishlist.tsx | 2 +- 8 files changed, 26 insertions(+), 14 deletions(-) diff --git a/framework/bigcommerce/customer/get-customer-wishlist.ts b/framework/bigcommerce/customer/get-customer-wishlist.ts index a3c7413cc..e854ff933 100644 --- a/framework/bigcommerce/customer/get-customer-wishlist.ts +++ b/framework/bigcommerce/customer/get-customer-wishlist.ts @@ -68,7 +68,7 @@ async function getCustomerWishlist({ const productsById = graphqlData.products.reduce<{ [k: number]: ProductEdge }>((prods, p) => { - prods[p.node.entityId] = p + prods[Number(p.node.entityId)] = p as any return prods }, {}) // Populate the wishlist items with the graphql products diff --git a/framework/bigcommerce/product/get-product.ts b/framework/bigcommerce/product/get-product.ts index 3624a9cca..7d77eb194 100644 --- a/framework/bigcommerce/product/get-product.ts +++ b/framework/bigcommerce/product/get-product.ts @@ -3,6 +3,7 @@ import setProductLocaleMeta from '../api/utils/set-product-locale-meta' import { productInfoFragment } from '../api/fragments/product' import { BigcommerceConfig, getConfig } from '../api' import { normalizeProduct } from '@framework/lib/normalize' +import type { Product } from '@commerce/types' export const getProductQuery = /* GraphQL */ ` query getProduct( diff --git a/framework/bigcommerce/provider.tsx b/framework/bigcommerce/provider.tsx index 6fd02e304..e8ac29cf5 100644 --- a/framework/bigcommerce/provider.tsx +++ b/framework/bigcommerce/provider.tsx @@ -107,7 +107,7 @@ const useWishlist: HookHandler< const { data: customer } = useCustomer() const response = useData({ input: [ - ['customerId', customer?.id], + ['customerId', (customer as any)?.id], ['includeProducts', input.includeProducts], ], swrOptions: { diff --git a/framework/bigcommerce/wishlist/use-add-item.tsx b/framework/bigcommerce/wishlist/use-add-item.tsx index 6e7d9de41..eb961951a 100644 --- a/framework/bigcommerce/wishlist/use-add-item.tsx +++ b/framework/bigcommerce/wishlist/use-add-item.tsx @@ -1,19 +1,23 @@ import { useCallback } from 'react' import { HookFetcher } from '@commerce/utils/types' import { CommerceError } from '@commerce/utils/errors' -import useWishlistAddItem from '@commerce/wishlist/use-add-item' +import useWishlistAddItem, { + AddItemInput, +} from '@commerce/wishlist/use-add-item' +import { UseWishlistInput } from '@commerce/wishlist/use-wishlist' import type { ItemBody, AddItemBody } from '../api/wishlist' import useCustomer from '../customer/use-customer' -import useWishlist, { UseWishlistOptions, Wishlist } from './use-wishlist' +import useWishlist from './use-wishlist' +import type { BigcommerceProvider } from '..' const defaultOpts = { url: '/api/bigcommerce/wishlist', method: 'POST', } -export type AddItemInput = ItemBody +// export type AddItemInput = ItemBody -export const fetcher: HookFetcher = ( +export const fetcher: HookFetcher = ( options, { item }, fetch @@ -27,13 +31,13 @@ export const fetcher: HookFetcher = ( } export function extendHook(customFetcher: typeof fetcher) { - const useAddItem = (opts?: UseWishlistOptions) => { + const useAddItem = (opts?: UseWishlistInput) => { const { data: customer } = useCustomer() const { revalidate } = useWishlist(opts) const fn = useWishlistAddItem(defaultOpts, customFetcher) return useCallback( - async function addItem(input: AddItemInput) { + async function addItem(input: AddItemInput) { if (!customer) { // A signed customer is required in order to have a wishlist throw new CommerceError({ diff --git a/framework/bigcommerce/wishlist/use-remove-item.tsx b/framework/bigcommerce/wishlist/use-remove-item.tsx index 86614a21a..d00b3e78b 100644 --- a/framework/bigcommerce/wishlist/use-remove-item.tsx +++ b/framework/bigcommerce/wishlist/use-remove-item.tsx @@ -4,7 +4,7 @@ import { CommerceError } from '@commerce/utils/errors' import useWishlistRemoveItem from '@commerce/wishlist/use-remove-item' import type { RemoveItemBody } from '../api/wishlist' import useCustomer from '../customer/use-customer' -import useWishlist, { UseWishlistOptions, Wishlist } from './use-wishlist' +import useWishlist from './use-wishlist' const defaultOpts = { url: '/api/bigcommerce/wishlist', @@ -15,7 +15,7 @@ export type RemoveItemInput = { id: string | number } -export const fetcher: HookFetcher = ( +export const fetcher: HookFetcher = ( options, { itemId }, fetch @@ -28,10 +28,10 @@ export const fetcher: HookFetcher = ( } export function extendHook(customFetcher: typeof fetcher) { - const useRemoveItem = (opts?: UseWishlistOptions) => { + const useRemoveItem = (opts?: any) => { const { data: customer } = useCustomer() const { revalidate } = useWishlist(opts) - const fn = useWishlistRemoveItem( + const fn = useWishlistRemoveItem( defaultOpts, customFetcher ) diff --git a/framework/commerce/wishlist/use-add-item.tsx b/framework/commerce/wishlist/use-add-item.tsx index f6c069f2b..d9b513694 100644 --- a/framework/commerce/wishlist/use-add-item.tsx +++ b/framework/commerce/wishlist/use-add-item.tsx @@ -1,4 +1,11 @@ import useAction from '../utils/use-action' +import type { CartItemBody } from '../types' + +// Input expected by the action returned by the `useAddItem` hook +// export interface AddItemInput { +// includeProducts?: boolean +// } +export type AddItemInput = T const useAddItem = useAction diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index 3d2971eed..83aeaa54c 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -61,7 +61,7 @@ export default function Slug({ return router.isFallback ? (

Loading...

// TODO (BC) Add Skeleton Views ) : ( - + ) } diff --git a/pages/wishlist.tsx b/pages/wishlist.tsx index a3f25d0e7..6de798411 100644 --- a/pages/wishlist.tsx +++ b/pages/wishlist.tsx @@ -44,7 +44,7 @@ export default function Wishlist() { ) : ( data && data.items?.map((item) => ( - + )) )} From fe7d6df04f39f15c471a3a2f7343866efd94abf2 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 15 Feb 2021 12:02:31 -0500 Subject: [PATCH 09/12] Added a MutationHandler --- framework/commerce/utils/types.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/framework/commerce/utils/types.ts b/framework/commerce/utils/types.ts index 98e4ebbae..a06aa0477 100644 --- a/framework/commerce/utils/types.ts +++ b/framework/commerce/utils/types.ts @@ -76,6 +76,29 @@ export type HookHandler< fetcher?: HookFetcherFn } +export type MutationHandler< + // Data obj returned by the hook and fetch operation + Data, + // Input expected by the hook + Input extends { [k: string]: unknown } = {}, + // Input expected before doing a fetch operation + FetchInput extends HookFetchInput = {}, + // Custom state added to the response object of SWR + State = {} +> = { + useHook?(context: { + input: Input & { swrOptions?: SwrOptions } + useCallback( + fn: (context?: { + input?: HookFetchInput | HookSwrInput + swrOptions?: SwrOptions + }) => Data + ): ResponseState + }): ResponseState & State + fetchOptions: HookFetcherOptions + fetcher?: HookFetcherFn +} + export type SwrOptions = ConfigInterface< Data, CommerceError, From c4870a05e8fd183c62e93ec8c389823480c52263 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 15 Feb 2021 18:48:47 -0500 Subject: [PATCH 10/12] Moved the handlers to each hook --- framework/bigcommerce/cart/use-cart.tsx | 40 ++++- .../bigcommerce/customer/use-customer.tsx | 21 +++ framework/bigcommerce/product/use-search.tsx | 50 ++++++ framework/bigcommerce/provider.tsx | 167 +----------------- .../bigcommerce/wishlist/use-wishlist.tsx | 55 ++++++ 5 files changed, 171 insertions(+), 162 deletions(-) diff --git a/framework/bigcommerce/cart/use-cart.tsx b/framework/bigcommerce/cart/use-cart.tsx index 4f8a5cbcd..b5cc0cccf 100644 --- a/framework/bigcommerce/cart/use-cart.tsx +++ b/framework/bigcommerce/cart/use-cart.tsx @@ -1,4 +1,42 @@ -import useCart, { UseCart } from '@commerce/cart/use-cart' +import { useMemo } from 'react' +import { HookHandler } from '@commerce/utils/types' +import useCart, { UseCart, FetchCartInput } from '@commerce/cart/use-cart' +import { normalizeCart } from '../lib/normalize' +import type { Cart } from '../types' import type { BigcommerceProvider } from '..' export default useCart as UseCart + +export const handler: HookHandler< + Cart | null, + {}, + FetchCartInput, + { isEmpty?: boolean } +> = { + fetchOptions: { + url: '/api/bigcommerce/cart', + method: 'GET', + }, + async fetcher({ input: { cartId }, options, fetch }) { + const data = cartId ? await fetch(options) : null + return data && normalizeCart(data) + }, + useHook({ input, useData }) { + const response = useData({ + swrOptions: { revalidateOnFocus: false, ...input.swrOptions }, + }) + + return useMemo( + () => + Object.create(response, { + isEmpty: { + get() { + return (response.data?.lineItems.length ?? 0) <= 0 + }, + enumerable: true, + }, + }), + [response] + ) + }, +} diff --git a/framework/bigcommerce/customer/use-customer.tsx b/framework/bigcommerce/customer/use-customer.tsx index 95adb6fb3..3929002f7 100644 --- a/framework/bigcommerce/customer/use-customer.tsx +++ b/framework/bigcommerce/customer/use-customer.tsx @@ -1,4 +1,25 @@ +import { HookHandler } from '@commerce/utils/types' import useCustomer, { UseCustomer } from '@commerce/customer/use-customer' +import type { Customer, CustomerData } from '../api/customers' import type { BigcommerceProvider } from '..' export default useCustomer as UseCustomer + +export const handler: HookHandler = { + fetchOptions: { + url: '/api/bigcommerce/customers', + method: 'GET', + }, + async fetcher({ options, fetch }) { + const data = await fetch(options) + return data?.customer ?? null + }, + useHook({ input, useData }) { + return useData({ + swrOptions: { + revalidateOnFocus: false, + ...input.swrOptions, + }, + }) + }, +} diff --git a/framework/bigcommerce/product/use-search.tsx b/framework/bigcommerce/product/use-search.tsx index 52db6a72d..393a8c0b9 100644 --- a/framework/bigcommerce/product/use-search.tsx +++ b/framework/bigcommerce/product/use-search.tsx @@ -1,4 +1,54 @@ +import { HookHandler } from '@commerce/utils/types' import useSearch, { UseSearch } from '@commerce/products/use-search' +import type { SearchProductsData } from '../api/catalog/products' import type { BigcommerceProvider } from '..' export default useSearch as UseSearch + +export type SearchProductsInput = { + search?: string + categoryId?: number + brandId?: number + sort?: string +} + +export const handler: HookHandler< + SearchProductsData, + SearchProductsInput, + SearchProductsInput +> = { + fetchOptions: { + url: '/api/bigcommerce/catalog/products', + method: 'GET', + }, + fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) { + // Use a dummy base as we only care about the relative path + const url = new URL(options.url!, 'http://a') + + if (search) url.searchParams.set('search', search) + if (Number.isInteger(categoryId)) + url.searchParams.set('category', String(categoryId)) + if (Number.isInteger(brandId)) + url.searchParams.set('brand', String(brandId)) + if (sort) url.searchParams.set('sort', sort) + + return fetch({ + url: url.pathname + url.search, + method: options.method, + }) + }, + useHook({ input, useData }) { + return useData({ + input: [ + ['search', input.search], + ['categoryId', input.categoryId], + ['brandId', input.brandId], + ['sort', input.sort], + ], + swrOptions: { + revalidateOnFocus: false, + ...input.swrOptions, + }, + }) + }, +} diff --git a/framework/bigcommerce/provider.tsx b/framework/bigcommerce/provider.tsx index e8ac29cf5..a54fab0bb 100644 --- a/framework/bigcommerce/provider.tsx +++ b/framework/bigcommerce/provider.tsx @@ -1,13 +1,10 @@ -import { useMemo } from 'react' import { FetcherError } from '@commerce/utils/errors' -import type { Fetcher, HookHandler } from '@commerce/utils/types' -import type { FetchCartInput } from '@commerce/cart/use-cart' +import type { Fetcher } from '@commerce/utils/types' import { normalizeCart } from './lib/normalize' -import type { Wishlist } from './api/wishlist' -import type { Customer, CustomerData } from './api/customers' -import type { SearchProductsData } from './api/catalog/products' -import useCustomer from './customer/use-customer' -import type { Cart } from './types' +import { handler as useCart } from './cart/use-cart' +import { handler as useWishlist } from './wishlist/use-wishlist' +import { handler as useCustomer } from './customer/use-customer' +import { handler as useSearch } from './product/use-search' async function getText(res: Response) { try { @@ -46,158 +43,6 @@ const fetcher: Fetcher = async ({ throw await getError(res) } -const useCart: HookHandler< - Cart | null, - {}, - FetchCartInput, - { isEmpty?: boolean } -> = { - fetchOptions: { - url: '/api/bigcommerce/cart', - method: 'GET', - }, - async fetcher({ input: { cartId }, options, fetch }) { - const data = cartId ? await fetch(options) : null - return data && normalizeCart(data) - }, - useHook({ input, useData }) { - const response = useData({ - swrOptions: { revalidateOnFocus: false, ...input.swrOptions }, - }) - - return useMemo( - () => - Object.create(response, { - isEmpty: { - get() { - return (response.data?.lineItems.length ?? 0) <= 0 - }, - enumerable: true, - }, - }), - [response] - ) - }, -} - -const useWishlist: HookHandler< - Wishlist | null, - { includeProducts?: boolean }, - { customerId?: number; includeProducts: boolean }, - { isEmpty?: boolean } -> = { - fetchOptions: { - url: '/api/bigcommerce/wishlist', - method: 'GET', - }, - fetcher({ input: { customerId, includeProducts }, options, fetch }) { - if (!customerId) return null - - // Use a dummy base as we only care about the relative path - const url = new URL(options.url!, 'http://a') - - if (includeProducts) url.searchParams.set('products', '1') - - return fetch({ - url: url.pathname + url.search, - method: options.method, - }) - }, - useHook({ input, useData }) { - const { data: customer } = useCustomer() - const response = useData({ - input: [ - ['customerId', (customer as any)?.id], - ['includeProducts', input.includeProducts], - ], - swrOptions: { - revalidateOnFocus: false, - ...input.swrOptions, - }, - }) - - return useMemo( - () => - Object.create(response, { - isEmpty: { - get() { - return (response.data?.items?.length || 0) <= 0 - }, - enumerable: true, - }, - }), - [response] - ) - }, -} - -const useCustomerHandler: HookHandler = { - fetchOptions: { - url: '/api/bigcommerce/customers', - method: 'GET', - }, - async fetcher({ options, fetch }) { - const data = await fetch(options) - return data?.customer ?? null - }, - useHook({ input, useData }) { - return useData({ - swrOptions: { - revalidateOnFocus: false, - ...input.swrOptions, - }, - }) - }, -} - -export type SearchProductsInput = { - search?: string - categoryId?: number - brandId?: number - sort?: string -} - -const useSearch: HookHandler< - SearchProductsData, - SearchProductsInput, - SearchProductsInput -> = { - fetchOptions: { - url: '/api/bigcommerce/catalog/products', - method: 'GET', - }, - fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) { - // Use a dummy base as we only care about the relative path - const url = new URL(options.url!, 'http://a') - - if (search) url.searchParams.set('search', search) - if (Number.isInteger(categoryId)) - url.searchParams.set('category', String(categoryId)) - if (Number.isInteger(brandId)) - url.searchParams.set('brand', String(brandId)) - if (sort) url.searchParams.set('sort', sort) - - return fetch({ - url: url.pathname + url.search, - method: options.method, - }) - }, - useHook({ input, useData }) { - return useData({ - input: [ - ['search', input.search], - ['categoryId', input.categoryId], - ['brandId', input.brandId], - ['sort', input.sort], - ], - swrOptions: { - revalidateOnFocus: false, - ...input.swrOptions, - }, - }) - }, -} - export const bigcommerceProvider = { locale: 'en-us', cartCookie: 'bc_cartId', @@ -205,7 +50,7 @@ export const bigcommerceProvider = { cartNormalizer: normalizeCart, cart: { useCart }, wishlist: { useWishlist }, - customer: { useCustomer: useCustomerHandler }, + customer: { useCustomer }, products: { useSearch }, } diff --git a/framework/bigcommerce/wishlist/use-wishlist.tsx b/framework/bigcommerce/wishlist/use-wishlist.tsx index dfa3d9dbc..a93f0f6a4 100644 --- a/framework/bigcommerce/wishlist/use-wishlist.tsx +++ b/framework/bigcommerce/wishlist/use-wishlist.tsx @@ -1,4 +1,59 @@ +import { useMemo } from 'react' +import { HookHandler } from '@commerce/utils/types' import useWishlist, { UseWishlist } from '@commerce/wishlist/use-wishlist' +import type { Wishlist } from '../api/wishlist' +import useCustomer from '../customer/use-customer' import type { BigcommerceProvider } from '..' export default useWishlist as UseWishlist + +export const handler: HookHandler< + Wishlist | null, + { includeProducts?: boolean }, + { customerId?: number; includeProducts: boolean }, + { isEmpty?: boolean } +> = { + fetchOptions: { + url: '/api/bigcommerce/wishlist', + method: 'GET', + }, + fetcher({ input: { customerId, includeProducts }, options, fetch }) { + if (!customerId) return null + + // Use a dummy base as we only care about the relative path + const url = new URL(options.url!, 'http://a') + + if (includeProducts) url.searchParams.set('products', '1') + + return fetch({ + url: url.pathname + url.search, + method: options.method, + }) + }, + useHook({ input, useData }) { + const { data: customer } = useCustomer() + const response = useData({ + input: [ + ['customerId', (customer as any)?.id], + ['includeProducts', input.includeProducts], + ], + swrOptions: { + revalidateOnFocus: false, + ...input.swrOptions, + }, + }) + + return useMemo( + () => + Object.create(response, { + isEmpty: { + get() { + return (response.data?.items?.length || 0) <= 0 + }, + enumerable: true, + }, + }), + [response] + ) + }, +} From 75d485d35a14e5f81dc5ec6919b352a6d2c0b391 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 15 Feb 2021 18:50:52 -0500 Subject: [PATCH 11/12] Moved the fetcher to its own file --- .../bigcommerce/{provider.tsx => fetcher.ts} | 18 +----------------- framework/bigcommerce/provider.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 17 deletions(-) rename framework/bigcommerce/{provider.tsx => fetcher.ts} (64%) create mode 100644 framework/bigcommerce/provider.ts diff --git a/framework/bigcommerce/provider.tsx b/framework/bigcommerce/fetcher.ts similarity index 64% rename from framework/bigcommerce/provider.tsx rename to framework/bigcommerce/fetcher.ts index a54fab0bb..f8ca0c578 100644 --- a/framework/bigcommerce/provider.tsx +++ b/framework/bigcommerce/fetcher.ts @@ -1,10 +1,5 @@ import { FetcherError } from '@commerce/utils/errors' import type { Fetcher } from '@commerce/utils/types' -import { normalizeCart } from './lib/normalize' -import { handler as useCart } from './cart/use-cart' -import { handler as useWishlist } from './wishlist/use-wishlist' -import { handler as useCustomer } from './customer/use-customer' -import { handler as useSearch } from './product/use-search' async function getText(res: Response) { try { @@ -43,15 +38,4 @@ const fetcher: Fetcher = async ({ throw await getError(res) } -export const bigcommerceProvider = { - locale: 'en-us', - cartCookie: 'bc_cartId', - fetcher, - cartNormalizer: normalizeCart, - cart: { useCart }, - wishlist: { useWishlist }, - customer: { useCustomer }, - products: { useSearch }, -} - -export type BigcommerceProvider = typeof bigcommerceProvider +export default fetcher diff --git a/framework/bigcommerce/provider.ts b/framework/bigcommerce/provider.ts new file mode 100644 index 000000000..ee5630813 --- /dev/null +++ b/framework/bigcommerce/provider.ts @@ -0,0 +1,17 @@ +import { handler as useCart } from './cart/use-cart' +import { handler as useWishlist } from './wishlist/use-wishlist' +import { handler as useCustomer } from './customer/use-customer' +import { handler as useSearch } from './product/use-search' +import fetcher from './fetcher' + +export const bigcommerceProvider = { + locale: 'en-us', + cartCookie: 'bc_cartId', + fetcher, + cart: { useCart }, + wishlist: { useWishlist }, + customer: { useCustomer }, + products: { useSearch }, +} + +export type BigcommerceProvider = typeof bigcommerceProvider From c02d7fec62898917580bcf810d3205dcb82619ac Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Tue, 16 Feb 2021 21:14:11 -0500 Subject: [PATCH 12/12] Added initial version of useAddItem --- framework/bigcommerce/cart/use-add-item.tsx | 77 +++++++++------------ framework/bigcommerce/provider.ts | 3 +- framework/bigcommerce/types.ts | 27 ++++---- framework/commerce/cart/use-add-item.tsx | 70 +++++++++++++++++-- framework/commerce/index.tsx | 3 +- framework/commerce/types.ts | 34 ++++----- framework/commerce/utils/types.ts | 27 ++++---- 7 files changed, 145 insertions(+), 96 deletions(-) diff --git a/framework/bigcommerce/cart/use-add-item.tsx b/framework/bigcommerce/cart/use-add-item.tsx index c66ee462a..7aec2f9e0 100644 --- a/framework/bigcommerce/cart/use-add-item.tsx +++ b/framework/bigcommerce/cart/use-add-item.tsx @@ -1,9 +1,6 @@ -import { useCallback } from 'react' -import type { HookFetcher } from '@commerce/utils/types' +import type { MutationHandler } from '@commerce/utils/types' import { CommerceError } from '@commerce/utils/errors' -import useCartAddItem, { - AddItemInput as UseAddItemInput, -} from '@commerce/cart/use-add-item' +import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item' import { normalizeCart } from '../lib/normalize' import type { AddCartItemBody, @@ -12,55 +9,45 @@ import type { CartItemBody, } from '../types' import useCart from './use-cart' +import { BigcommerceProvider } from '..' const defaultOpts = { url: '/api/bigcommerce/cart', method: 'POST', } -export type AddItemInput = UseAddItemInput +export default useAddItem as UseAddItem -export const fetcher: HookFetcher = async ( - options, - { item }, - fetch -) => { - if ( - item.quantity && - (!Number.isInteger(item.quantity) || item.quantity! < 1) - ) { - throw new CommerceError({ - message: 'The item quantity has to be a valid integer greater than 0', +export const handler: MutationHandler = { + fetchOptions: { + url: '/api/bigcommerce/cart', + method: 'GET', + }, + async fetcher({ input: { item }, options, fetch }) { + if ( + item.quantity && + (!Number.isInteger(item.quantity) || item.quantity! < 1) + ) { + throw new CommerceError({ + message: 'The item quantity has to be a valid integer greater than 0', + }) + } + + const data = await fetch({ + ...defaultOpts, + ...options, + body: { item }, }) - } - const data = await fetch({ - ...defaultOpts, - ...options, - body: { item }, - }) - - return normalizeCart(data) -} - -export function extendHook(customFetcher: typeof fetcher) { - const useAddItem = () => { + return normalizeCart(data) + }, + useHook() { const { mutate } = useCart() - const fn = useCartAddItem(defaultOpts, customFetcher) - return useCallback( - async function addItem(input: AddItemInput) { - const data = await fn({ item: input }) - await mutate(data, false) - return data - }, - [fn, mutate] - ) - } - - useAddItem.extend = extendHook - - return useAddItem + return async function addItem({ input, fetch }) { + const data = await fetch({ input }) + await mutate(data, false) + return data + } + }, } - -export default extendHook(fetcher) diff --git a/framework/bigcommerce/provider.ts b/framework/bigcommerce/provider.ts index ee5630813..08192df37 100644 --- a/framework/bigcommerce/provider.ts +++ b/framework/bigcommerce/provider.ts @@ -1,4 +1,5 @@ import { handler as useCart } from './cart/use-cart' +import { handler as useAddItem } from './cart/use-add-item' import { handler as useWishlist } from './wishlist/use-wishlist' import { handler as useCustomer } from './customer/use-customer' import { handler as useSearch } from './product/use-search' @@ -8,7 +9,7 @@ export const bigcommerceProvider = { locale: 'en-us', cartCookie: 'bc_cartId', fetcher, - cart: { useCart }, + cart: { useCart, useAddItem }, wishlist: { useWishlist }, customer: { useCustomer }, products: { useSearch }, diff --git a/framework/bigcommerce/types.ts b/framework/bigcommerce/types.ts index 90afb425d..16d1ea07a 100644 --- a/framework/bigcommerce/types.ts +++ b/framework/bigcommerce/types.ts @@ -23,11 +23,11 @@ export type BigcommerceCart = { // TODO: add missing fields } -export interface Cart extends Core.Cart { +export type Cart = Core.Cart & { lineItems: LineItem[] } -export interface LineItem extends Core.LineItem {} +export type LineItem = Core.LineItem /** * Cart mutations @@ -38,25 +38,24 @@ export type OptionSelections = { option_value: number | string } -export interface CartItemBody extends Core.CartItemBody { +export type CartItemBody = Core.CartItemBody & { productId: string // The product id is always required for BC optionSelections?: OptionSelections } -export interface GetCartHandlerBody extends Core.GetCartHandlerBody {} +type X = Core.CartItemBody extends CartItemBody ? any : never +type Y = CartItemBody extends Core.CartItemBody ? any : never -export interface AddCartItemBody extends Core.AddCartItemBody {} +export type GetCartHandlerBody = Core.GetCartHandlerBody -export interface AddCartItemHandlerBody - extends Core.AddCartItemHandlerBody {} +export type AddCartItemBody = Core.AddCartItemBody -export interface UpdateCartItemBody - extends Core.UpdateCartItemBody {} +export type AddCartItemHandlerBody = Core.AddCartItemHandlerBody -export interface UpdateCartItemHandlerBody - extends Core.UpdateCartItemHandlerBody {} +export type UpdateCartItemBody = Core.UpdateCartItemBody -export interface RemoveCartItemBody extends Core.RemoveCartItemBody {} +export type UpdateCartItemHandlerBody = Core.UpdateCartItemHandlerBody -export interface RemoveCartItemHandlerBody - extends Core.RemoveCartItemHandlerBody {} +export type RemoveCartItemBody = Core.RemoveCartItemBody + +export type RemoveCartItemHandlerBody = Core.RemoveCartItemHandlerBody diff --git a/framework/commerce/cart/use-add-item.tsx b/framework/commerce/cart/use-add-item.tsx index 2f6422ab3..0a70ff30d 100644 --- a/framework/commerce/cart/use-add-item.tsx +++ b/framework/commerce/cart/use-add-item.tsx @@ -1,9 +1,69 @@ -import useAction from '../utils/use-action' -import type { CartItemBody } from '../types' +import { useCallback } from 'react' +import type { + Prop, + HookFetcherFn, + UseHookInput, + UseHookResponse, +} from '../utils/types' +import type { Cart, CartItemBody, AddCartItemBody } from '../types' +import { Provider, useCommerce } from '..' +import { BigcommerceProvider } from '@framework' + +export type UseAddItemHandler

= Prop< + Prop, + 'useAddItem' +> // Input expected by the action returned by the `useAddItem` hook -export type AddItemInput = T +export type UseAddItemInput

= UseHookInput< + UseAddItemHandler

+> -const useAddItem = useAction +export type UseAddItemResult

= ReturnType< + UseHookResponse> +> -export default useAddItem +export type UseAddItem

= Partial< + UseAddItemInput

+> extends UseAddItemInput

+ ? (input?: UseAddItemInput

) => (input: Input) => UseAddItemResult

+ : (input: UseAddItemInput

) => (input: Input) => UseAddItemResult

+ +export const fetcher: HookFetcherFn< + Cart, + AddCartItemBody +> = async ({ options, input, fetch }) => { + return fetch({ ...options, body: input }) +} + +type X = UseAddItemResult + +export default function useAddItem

( + input: UseAddItemInput

+) { + const { providerRef, fetcherRef } = useCommerce

() + + const provider = providerRef.current + const opts = provider.cart?.useAddItem + + const fetcherFn = opts?.fetcher ?? fetcher + const useHook = opts?.useHook ?? (() => () => {}) + const fetchFn = provider.fetcher ?? fetcherRef.current + const action = useHook({ input }) + + return useCallback( + function addItem(input: Input) { + return action({ + input, + fetch({ input }) { + return fetcherFn({ + input, + options: opts!.fetchOptions, + fetch: fetchFn, + }) + }, + }) + }, + [input, fetchFn, opts?.fetchOptions] + ) +} diff --git a/framework/commerce/index.tsx b/framework/commerce/index.tsx index d8d882f93..243fba2db 100644 --- a/framework/commerce/index.tsx +++ b/framework/commerce/index.tsx @@ -6,7 +6,7 @@ import { useMemo, useRef, } from 'react' -import { Fetcher, HookHandler } from './utils/types' +import { Fetcher, HookHandler, MutationHandler } from './utils/types' import type { FetchCartInput } from './cart/use-cart' import type { Cart, Wishlist, Customer, SearchProductsData } from './types' @@ -16,6 +16,7 @@ export type Provider = CommerceConfig & { fetcher: Fetcher cart?: { useCart?: HookHandler + useAddItem?: MutationHandler } wishlist?: { useWishlist?: HookHandler diff --git a/framework/commerce/types.ts b/framework/commerce/types.ts index 1f8390535..bf635c9dc 100644 --- a/framework/commerce/types.ts +++ b/framework/commerce/types.ts @@ -2,12 +2,12 @@ import type { Wishlist as BCWishlist } from '@framework/api/wishlist' import type { Customer as BCCustomer } from '@framework/api/customers' import type { SearchProductsData as BCSearchProductsData } from '@framework/api/catalog/products' -export interface Discount { +export type Discount = { // The value of the discount, can be an amount or percentage value: number } -export interface LineItem { +export type LineItem = { id: string variantId: string productId: string @@ -19,19 +19,19 @@ export interface LineItem { variant: ProductVariant } -export interface Measurement { +export type Measurement = { value: number unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES' } -export interface Image { +export type Image = { url: string altText?: string width?: number height?: number } -export interface ProductVariant { +export type ProductVariant = { id: string // The SKU (stock keeping unit) associated with the product variant. sku: string @@ -66,7 +66,7 @@ export interface ProductVariant { } // Shopping cart, a.k.a Checkout -export interface Cart { +export type Cart = { id: string // ID of the customer to which the cart belongs. customerId?: string @@ -105,47 +105,49 @@ export interface SearchProductsData extends BCSearchProductsData {} */ // Base cart item body used for cart mutations -export interface CartItemBody { +export type CartItemBody = { variantId: string productId?: string quantity?: number } // Body used by the `getCart` operation handler -export interface GetCartHandlerBody { +export type GetCartHandlerBody = { cartId?: string } // Body used by the add item to cart operation -export interface AddCartItemBody { +export type AddCartItemBody = { item: T } // Body expected by the add item to cart operation handler -export interface AddCartItemHandlerBody - extends Partial> { +export type AddCartItemHandlerBody = Partial< + AddCartItemBody +> & { cartId?: string } // Body used by the update cart item operation -export interface UpdateCartItemBody { +export type UpdateCartItemBody = { itemId: string item: T } // Body expected by the update cart item operation handler -export interface UpdateCartItemHandlerBody - extends Partial> { +export type UpdateCartItemHandlerBody = Partial< + UpdateCartItemBody +> & { cartId?: string } // Body used by the remove cart item operation -export interface RemoveCartItemBody { +export type RemoveCartItemBody = { itemId: string } // Body expected by the remove cart item operation handler -export interface RemoveCartItemHandlerBody extends Partial { +export type RemoveCartItemHandlerBody = Partial & { cartId?: string } diff --git a/framework/commerce/utils/types.ts b/framework/commerce/utils/types.ts index a06aa0477..1d3adef81 100644 --- a/framework/commerce/utils/types.ts +++ b/framework/commerce/utils/types.ts @@ -82,19 +82,14 @@ export type MutationHandler< // Input expected by the hook Input extends { [k: string]: unknown } = {}, // Input expected before doing a fetch operation - FetchInput extends HookFetchInput = {}, - // Custom state added to the response object of SWR - State = {} + FetchInput extends { [k: string]: unknown } = {} > = { useHook?(context: { - input: Input & { swrOptions?: SwrOptions } - useCallback( - fn: (context?: { - input?: HookFetchInput | HookSwrInput - swrOptions?: SwrOptions - }) => Data - ): ResponseState - }): ResponseState & State + input: Input + }): (context: { + input: FetchInput + fetch: (context: { input: FetchInput }) => Data | Promise + }) => Data | Promise fetchOptions: HookFetcherOptions fetcher?: HookFetcherFn } @@ -110,14 +105,18 @@ export type SwrOptions = ConfigInterface< */ export type Prop = NonNullable -export type UseHookParameters> = Parameters< +export type HookHandlerType = + | HookHandler + | MutationHandler + +export type UseHookParameters = Parameters< Prop > -export type UseHookResponse> = ReturnType< +export type UseHookResponse = ReturnType< Prop > export type UseHookInput< - H extends HookHandler + H extends HookHandlerType > = UseHookParameters[0]['input']