From f11ff29e3b1b1e335ea9b73d6a45eb1a6af0774a Mon Sep 17 00:00:00 2001 From: Alessandro Casazza Date: Tue, 10 May 2022 18:57:21 +0200 Subject: [PATCH] feat: Update hooks --- .../commercelayer/src/cart/use-add-item.tsx | 63 ++++++++------- packages/commercelayer/src/cart/use-cart.tsx | 33 ++++---- .../src/cart/use-remove-item.tsx | 17 +++- .../src/cart/use-update-item.tsx | 49 ++++++------ packages/commercelayer/src/index.tsx | 1 - .../commercelayer/src/product/use-search.tsx | 59 ++++++++------ packages/commercelayer/src/provider.ts | 10 ++- .../src/wishlist/use-add-item.tsx | 48 +++++++++--- .../src/wishlist/use-remove-item.tsx | 48 +++++++++--- .../src/wishlist/use-wishlist.tsx | 78 ++++++++----------- 10 files changed, 239 insertions(+), 167 deletions(-) diff --git a/packages/commercelayer/src/cart/use-add-item.tsx b/packages/commercelayer/src/cart/use-add-item.tsx index 89a4709d9..a1d7d7642 100644 --- a/packages/commercelayer/src/cart/use-add-item.tsx +++ b/packages/commercelayer/src/cart/use-add-item.tsx @@ -1,54 +1,52 @@ import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item' import { MutationHook } from '@vercel/commerce/utils/types' -import { LineItem, Order } from '@commercelayer/js-sdk' -import getCredentials from '../api/utils/getCredentials' +import CLSdk from '@commercelayer/sdk' +import getCredentials, { + getOrganizationSlug, +} from '../api/utils/getCredentials' import useCart from '../cart/use-cart' import { useCallback } from 'react' +import getContentData from '../api/utils/getContentData' export default useAddItem as UseAddItem export const handler: MutationHook = { fetchOptions: { query: '', }, - async fetcher({ input, options, fetch }) { + async fetcher({ input }) { const localOrderId = localStorage.getItem('CL_ORDER_ID') - const credentials = getCredentials() + const { accessToken, endpoint } = getCredentials() + const organization = getOrganizationSlug(endpoint).organization + const sdk = CLSdk({ + accessToken, + organization, + }) const orderId = - localOrderId || - (credentials.accessToken && - (await Order.withCredentials(credentials).create({})).id) + localOrderId || (accessToken && (await sdk.orders.create({})).id) if (orderId && input.variantId) { !localOrderId && localStorage.setItem('CL_ORDER_ID', orderId) - const lineItem = await LineItem.withCredentials(credentials).create( - { - skuCode: input.variantId, - order: Order.build({ id: orderId }), - quantity: 1, - reference: input.productId, - _update_quantity: 1, - }, - // @ts-ignore - { rawResponse: true } - ) - const attributes = lineItem.data.attributes + const [product] = await getContentData(input.productId) + const [image] = product.images + const lineItem = await sdk.line_items.create({ + sku_code: input.variantId, + order: sdk.orders.relationship(orderId), + quantity: 1, + reference: input.productId, + _update_quantity: true, + }) return { - id: lineItem.data.id, - name: attributes.name, + id: lineItem.id, + name: lineItem.name, productId: input.productId, variantId: input.variantId, - quantity: attributes.quantity, - price: attributes.unit_amount_float, + quantity: lineItem.quantity, + price: lineItem.unit_amount_float, variant: { - id: lineItem.data.id, - name: attributes.name, + id: lineItem.id, + name: lineItem.name, sku: input.variantId, - price: attributes.unit_amount_float, - image: { - url: `https://data.commercelayer.app/vercel-provider/${input.productId}_FLAT.png`, - altText: attributes.name, - width: 1000, - height: 1000, - }, + price: lineItem.unit_amount_float, + image, }, } } @@ -57,6 +55,7 @@ export const handler: MutationHook = { ({ fetch }) => () => { const { mutate } = useCart() + return useCallback( async function addItem(input) { const data = await fetch({ input }) diff --git a/packages/commercelayer/src/cart/use-cart.tsx b/packages/commercelayer/src/cart/use-cart.tsx index b18670883..98a1bcdda 100644 --- a/packages/commercelayer/src/cart/use-cart.tsx +++ b/packages/commercelayer/src/cart/use-cart.tsx @@ -1,8 +1,10 @@ import { useMemo } from 'react' import { SWRHook } from '@vercel/commerce/utils/types' import useCart, { UseCart } from '@vercel/commerce/cart/use-cart' -import { Order } from '@commercelayer/js-sdk' -import getCredentials from '../api/utils/getCredentials' +import CLSdk from '@commercelayer/sdk' +import getCredentials, { + getOrganizationSlug, +} from '../api/utils/getCredentials' import normalizeLineItems from '../api/utils/normalizeLineItems' export default useCart as UseCart @@ -14,25 +16,27 @@ export const handler: SWRHook = { async fetcher() { const id = localStorage.getItem('CL_ORDER_ID') || '' const credentials = getCredentials() + const organization = getOrganizationSlug(credentials.endpoint).organization + const sdk = CLSdk({ + accessToken: credentials.accessToken, + organization, + }) if (id && credentials.accessToken) { - const clOrder = await Order.withCredentials(credentials) - .includes('lineItems') - .find(id, { rawResponse: true }) - const attributes = clOrder.data.attributes - const orderStatus = attributes.status - if (['pending', 'draft'].includes(orderStatus)) { - const lineItems = clOrder?.included - ? normalizeLineItems(clOrder?.included) + const order = await sdk.orders.retrieve(id, { include: ['line_items'] }) + const orderStatus = order.status + if (orderStatus && ['pending', 'draft'].includes(orderStatus)) { + const lineItems = order.line_items + ? normalizeLineItems(order.line_items) : [] return { id, - createdAt: attributes.created_at, - currency: { code: attributes.currency_code }, + createdAt: order.created_at, + currency: { code: order.currency_code }, taxesIncluded: '', lineItems, lineItemsSubtotalPrice: '', - subtotalPrice: attributes.subtotal_amount_float, - totalPrice: attributes.total_amount_float, + subtotalPrice: order.subtotal_amount_float, + totalPrice: order.total_amount_float, } } else if (id) { localStorage.removeItem('CL_ORDER_ID') @@ -53,6 +57,7 @@ export const handler: SWRHook = { ({ useData }) => () => { const response = useData() + console.log('response', response) return useMemo( () => Object.create(response, { diff --git a/packages/commercelayer/src/cart/use-remove-item.tsx b/packages/commercelayer/src/cart/use-remove-item.tsx index 380b20c7e..6f5b91ed3 100644 --- a/packages/commercelayer/src/cart/use-remove-item.tsx +++ b/packages/commercelayer/src/cart/use-remove-item.tsx @@ -1,7 +1,11 @@ import { MutationHook } from '@vercel/commerce/utils/types' -import useRemoveItem, { UseRemoveItem } from '@vercel/commerce/cart/use-remove-item' -import getCredentials from '../api/utils/getCredentials' -import { LineItem } from '@commercelayer/js-sdk' +import useRemoveItem, { + UseRemoveItem, +} from '@vercel/commerce/cart/use-remove-item' +import getCredentials, { + getOrganizationSlug, +} from '../api/utils/getCredentials' +import CLSdk from '@commercelayer/sdk' import useCart from './use-cart' export default useRemoveItem as UseRemoveItem @@ -13,8 +17,13 @@ export const handler: MutationHook = { async fetcher({ input: { id } }) { const credentials = getCredentials() const orderId = localStorage.getItem('CL_ORDER_ID') + const organization = getOrganizationSlug(credentials.endpoint).organization + const sdk = CLSdk({ + accessToken: credentials.accessToken, + organization, + }) if (orderId && id) { - await LineItem.build({ id }).withCredentials(credentials).destroy() + await sdk.line_items.delete(id) return {} } }, diff --git a/packages/commercelayer/src/cart/use-update-item.tsx b/packages/commercelayer/src/cart/use-update-item.tsx index 874626f90..86980b936 100644 --- a/packages/commercelayer/src/cart/use-update-item.tsx +++ b/packages/commercelayer/src/cart/use-update-item.tsx @@ -1,8 +1,13 @@ import { MutationHook } from '@vercel/commerce/utils/types' -import useUpdateItem, { UseUpdateItem } from '@vercel/commerce/cart/use-update-item' +import useUpdateItem, { + UseUpdateItem, +} from '@vercel/commerce/cart/use-update-item' import useCart from '../cart/use-cart' -import getCredentials from '../api/utils/getCredentials' -import { LineItem } from '@commercelayer/js-sdk' +import getCredentials, { + getOrganizationSlug, +} from '../api/utils/getCredentials' +import CLSdk from '@commercelayer/sdk' +import getContentData from '../api/utils/getContentData' export default useUpdateItem as UseUpdateItem @@ -12,33 +17,29 @@ export const handler: MutationHook = { }, async fetcher({ input: { item, quantity } }) { const credentials = getCredentials() + const organization = getOrganizationSlug(credentials.endpoint).organization + const sdk = CLSdk({ + accessToken: credentials.accessToken, + organization, + }) const orderId = localStorage.getItem('CL_ORDER_ID') if (orderId && item.id) { - const lineItem = (await LineItem.build({ - id: item.id, - }) - .withCredentials(credentials) - // @ts-ignore - .update({ quantity }, null, { rawResponse: true })) as any - const attributes = lineItem.data.attributes + const lineItem = await sdk.line_items.update({ id: item.id, quantity }) + const [product] = await getContentData(item.productId) + const [image] = product.images return { - id: lineItem.data.id, - name: attributes.name, + id: lineItem.id, + name: lineItem.name, productId: item.productId, variantId: item.variantId, - quantity: attributes.quantity, - price: attributes.unit_amount_float, + quantity: lineItem.quantity, + price: lineItem.unit_amount_float, variant: { - id: lineItem.data.id, - name: attributes.name, - sku: lineItem.data.sku_code, - price: attributes.unit_amount_float, - image: { - url: `https://data.commercelayer.app/vercel-provider/${item.variantId}_FLAT.png`, - altText: 'Black Women Long Sleeve Shirt', - width: 1000, - height: 1000, - }, + id: lineItem.id, + name: lineItem.name, + sku: lineItem.sku_code, + price: lineItem.unit_amount_float, + image, }, } } diff --git a/packages/commercelayer/src/index.tsx b/packages/commercelayer/src/index.tsx index 124157dd3..d8cf959c5 100644 --- a/packages/commercelayer/src/index.tsx +++ b/packages/commercelayer/src/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ReactNode } from 'react' import { CommercelayerProvider } from './provider' import { diff --git a/packages/commercelayer/src/product/use-search.tsx b/packages/commercelayer/src/product/use-search.tsx index f1386ebd3..11951bad7 100644 --- a/packages/commercelayer/src/product/use-search.tsx +++ b/packages/commercelayer/src/product/use-search.tsx @@ -1,19 +1,20 @@ import { SWRHook } from '@vercel/commerce/utils/types' import useSearch, { UseSearch } from '@vercel/commerce/product/use-search' -import data from '../data.json' +import getContentData from '../api/utils/getContentData' +import type { Products } from '../api/utils/getContentData' export default useSearch as UseSearch -const productsFinder = (s: string, c?: string, b?: string) => { - const { products } = data +const productsFinder = ( + products: Products, + s: string, + c?: string, + b?: string +) => { let p = products - if (s) - p = p.filter((p) => p.name.toLowerCase().search(s.toLowerCase()) !== -1) + if (s) p = p.filter((p) => p.name.toLowerCase().includes(s.toLowerCase())) if (c) - p = p.filter( - (p) => p.categoryId.toLowerCase().search(c.toLowerCase()) !== -1 - ) - if (b) - p = p.filter((p) => p.brandId.toLowerCase().search(b.toLowerCase()) !== -1) + p = p.filter((p) => p.categoryId.toLowerCase().includes(c.toLowerCase())) + if (b) p = p.filter((p) => p.brandId.toLowerCase().includes(b.toLowerCase())) return p } @@ -21,19 +22,33 @@ export const handler: SWRHook = { fetchOptions: { query: '', }, - async fetcher({ input, options, fetch }) {}, + async fetcher({ input }) { + const { search, categoryId, brandId } = input + const contentData = await getContentData() + const products = productsFinder(contentData, search, categoryId, brandId) + return products.length > 0 + ? { + products, + found: true, + } + : { + products: contentData, + } + }, useHook: ({ useData }) => - ({ search, categoryId, brandId }) => { - const products = productsFinder(search, categoryId, brandId) - return { - data: - products.length > 0 - ? { - products, - found: true, - } - : data, - } + (input = {}) => { + return useData({ + input: [ + ['search', input.search], + ['categoryId', input.categoryId], + ['brandId', input.brandId], + ['sort', input.sort], + ], + swrOptions: { + revalidateOnFocus: false, + ...input.swrOptions, + }, + }) }, } diff --git a/packages/commercelayer/src/provider.ts b/packages/commercelayer/src/provider.ts index 436a10771..c2d2223ca 100644 --- a/packages/commercelayer/src/provider.ts +++ b/packages/commercelayer/src/provider.ts @@ -8,6 +8,9 @@ import { handler as useSearch } from './product/use-search' import { handler as useLogin } from './auth/use-login' import { handler as useLogout } from './auth/use-logout' import { handler as useSignup } from './auth/use-signup' +import { handler as useWishlist } from './wishlist/use-wishlist' +import { handler as useWishlistAddItem } from './wishlist/use-add-item' +import { handler as useWishlistRemoveItem } from './wishlist/use-remove-item' export const CommercelayerProvider = { locale: 'en-US', @@ -18,6 +21,11 @@ export const CommercelayerProvider = { customer: { useCustomer }, products: { useSearch }, auth: { useLogin, useLogout, useSignup }, + wishlist: { + useWishlist, + useAddItem: useWishlistAddItem, + useRemoveItem: useWishlistRemoveItem, + }, } -export type Provider = typeof CommercelayerProvider \ No newline at end of file +export type Provider = typeof CommercelayerProvider diff --git a/packages/commercelayer/src/wishlist/use-add-item.tsx b/packages/commercelayer/src/wishlist/use-add-item.tsx index 1d4ee532e..47283de86 100644 --- a/packages/commercelayer/src/wishlist/use-add-item.tsx +++ b/packages/commercelayer/src/wishlist/use-add-item.tsx @@ -1,22 +1,48 @@ -import { useCallback, useMemo } from 'react' +import useAddItem, { UseAddItem } from '@vercel/commerce/wishlist/use-add-item' +import useCustomer from '../customer/use-customer' +import { useCallback } from 'react' +import { MutationHook } from '@vercel/commerce/utils/types' +import useWishlist from './use-wishlist' +import { CommerceError } from '@vercel/commerce/utils/errors' +export default useAddItem as UseAddItem -export function emptyHook() { - const useEmptyHook = async (options: any = {}) => { +export const handler: MutationHook = { + fetchOptions: { + query: '', + }, + async fetcher({ input }) { + const { variantId } = input let wishlist = [] const localWishlist = localStorage.getItem('wishlist') if (localWishlist) { wishlist = JSON.parse(localWishlist) - if (!wishlist.includes(options.variantId)) { - wishlist.push(options.variantId) + if (!wishlist.includes(variantId)) { + wishlist.push(variantId) } } else { - wishlist.push(options.variantId) + wishlist.push(variantId) } localStorage.setItem('wishlist', JSON.stringify(wishlist)) return wishlist - } - - return useEmptyHook + }, + useHook: + ({ fetch }) => + () => { + const { mutate } = useWishlist() + const { data: customer } = useCustomer() + return useCallback( + async function addItem(input) { + if (!customer) { + // A signed customer is required in order to have a wishlist + throw new CommerceError({ + message: 'Signed customer not found', + }) + } + const data = await fetch({ input }) + await mutate() + return data + }, + [fetch] + ) + }, } - -export default emptyHook diff --git a/packages/commercelayer/src/wishlist/use-remove-item.tsx b/packages/commercelayer/src/wishlist/use-remove-item.tsx index 860e40883..639c74a74 100644 --- a/packages/commercelayer/src/wishlist/use-remove-item.tsx +++ b/packages/commercelayer/src/wishlist/use-remove-item.tsx @@ -1,20 +1,46 @@ -type Options = { - includeProducts?: boolean -} +import useRemoveItem, { + UseRemoveItem, +} from '@vercel/commerce/wishlist/use-remove-item' +import useCustomer from '../customer/use-customer' +import { useCallback } from 'react' +import { MutationHook } from '@vercel/commerce/utils/types' +import useWishlist from './use-wishlist' +import { CommerceError } from '@vercel/commerce/utils/errors' +export default useRemoveItem as UseRemoveItem -export function emptyHook(options?: Options) { - const useEmptyHook = async ({ id }: { id: string | number }) => { +export const handler: MutationHook = { + fetchOptions: { + query: '', + }, + async fetcher({ input }) { + const { id } = input let wishlist = [] const localWishlist = localStorage.getItem('wishlist') if (localWishlist) { wishlist = JSON.parse(localWishlist) - wishlist = wishlist.filter((p: string) => p !== id) + wishlist = wishlist.filter((_p: string, key: number) => key !== id) } localStorage.setItem('wishlist', JSON.stringify(wishlist)) return wishlist - } - - return useEmptyHook + }, + useHook: + ({ fetch }) => + () => { + const { mutate } = useWishlist() + const { data: customer } = useCustomer() + return useCallback( + async function addItem(input) { + if (!customer) { + // A signed customer is required in order to have a wishlist + throw new CommerceError({ + message: 'Signed customer not found', + }) + } + const data = await fetch({ input }) + await mutate() + return data + }, + [fetch] + ) + }, } - -export default emptyHook diff --git a/packages/commercelayer/src/wishlist/use-wishlist.tsx b/packages/commercelayer/src/wishlist/use-wishlist.tsx index e10ccc7fd..7cfbc8d97 100644 --- a/packages/commercelayer/src/wishlist/use-wishlist.tsx +++ b/packages/commercelayer/src/wishlist/use-wishlist.tsx @@ -1,46 +1,24 @@ -import { HookFetcher } from '@vercel/commerce/utils/types' -import type { Product } from '@vercel/commerce/types/product' -import data from '../data.json' -import { useCustomer } from '../customer' +import { SWRHook } from '@vercel/commerce/utils/types' +import useWishlist, { + UseWishlist, +} from '@vercel/commerce/wishlist/use-wishlist' +import useCustomer from '../customer/use-customer' +import getContentData from '../api/utils/getContentData' +export default useWishlist as UseWishlist -const defaultOpts = {} - -export type Wishlist = { - items: [ - { - variant_id: number - product_id: number - id: number - product: Product - } - ] -} - -export interface UseWishlistOptions { - includeProducts?: boolean -} - -export interface UseWishlistInput extends UseWishlistOptions { - customerId?: number -} - -export const fetcher: HookFetcher = () => { - return null -} - -export function extendHook( - customFetcher: typeof fetcher, - // swrOptions?: SwrOptions - swrOptions?: any -) { - const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => { - const { data: customer } = useCustomer() +export const handler: SWRHook = { + fetchOptions: { + query: '', + }, + async fetcher({ input }) { + const { customerEmail } = input const getWishlist = typeof localStorage !== 'undefined' && localStorage.getItem('wishlist') - if (getWishlist && customer?.email && data.products.length > 0) { + const products = await getContentData() + if (getWishlist && customerEmail && products.length > 0) { const wishlist = JSON.parse(getWishlist) const items = wishlist.map((wishlist: string, id: number) => { - const [product] = data.products.filter((p) => + const [product] = products.filter((p) => wishlist.startsWith(p.id) ) as any const [variant] = product?.variants @@ -51,14 +29,20 @@ export function extendHook( product, } }) - return { data: { items } } + return { items } } - return { data: null } - } - - useWishlist.extend = extendHook - - return useWishlist + return { items: [] } + }, + useHook: + ({ useData }) => + (input = {}) => { + const { data: customer } = useCustomer() + return useData({ + input: [['customerEmail', customer?.email]], + swrOptions: { + revalidateOnFocus: true, + ...input.swrOptions, + }, + }) + }, } - -export default extendHook(fetcher)