forked from crowetic/commerce
Add loading state for data hooks
This commit is contained in:
parent
4a268e738d
commit
10318b2fab
@ -1,10 +1,9 @@
|
|||||||
|
|
||||||
import { normalizeCart } from '../lib/normalize'
|
import { normalizeCart } from '../lib/normalize'
|
||||||
import type { HookFetcher } from '@commerce/utils/types'
|
import type { HookFetcher } from '@commerce/utils/types'
|
||||||
import type { SwrOptions } from '@commerce/utils/use-data'
|
import type { SwrOptions } from '@commerce/utils/use-data'
|
||||||
import useCommerceCart, { CartInput } from '@commerce/cart/use-cart'
|
import useCommerceCart, { CartInput } from '@commerce/cart/use-cart'
|
||||||
import type { Cart as BigCommerceCart } from '../api/cart'
|
import type { Cart as BigCommerceCart } from '../api/cart'
|
||||||
import update from "@framework/lib/immutability"
|
import update from '@framework/lib/immutability'
|
||||||
|
|
||||||
const defaultOpts = {
|
const defaultOpts = {
|
||||||
url: '/api/bigcommerce/cart',
|
url: '/api/bigcommerce/cart',
|
||||||
@ -42,11 +41,11 @@ export function extendHook(
|
|||||||
set: (x) => x,
|
set: (x) => x,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return response.data
|
||||||
|
? update(response, {
|
||||||
return response.data ? update(response, {
|
data: { $set: normalizeCart(response.data) },
|
||||||
data: { $set: normalizeCart(response.data ) }
|
})
|
||||||
}) : response
|
: response
|
||||||
}
|
}
|
||||||
|
|
||||||
useCart.extend = extendHook
|
useCart.extend = extendHook
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import update from "@framework/lib/immutability"
|
import update from '@framework/lib/immutability'
|
||||||
import { Cart, CartItem, Product } from '../../types'
|
|
||||||
|
|
||||||
function normalizeProductOption(productOption: any) {
|
function normalizeProductOption(productOption: any) {
|
||||||
const {
|
const {
|
||||||
@ -8,7 +7,7 @@ function normalizeProductOption(productOption:any) {
|
|||||||
values: { edges },
|
values: { edges },
|
||||||
...rest
|
...rest
|
||||||
},
|
},
|
||||||
} = productOption;
|
} = productOption
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: entityId,
|
id: entityId,
|
||||||
@ -30,41 +29,41 @@ export function normalizeProduct(productNode: any): Product {
|
|||||||
return update(productNode, {
|
return update(productNode, {
|
||||||
id: { $set: String(id) },
|
id: { $set: String(id) },
|
||||||
images: {
|
images: {
|
||||||
$apply: ({edges} : any) => edges?.map(
|
$apply: ({ edges }: any) =>
|
||||||
({ node: { urlOriginal, altText, ...rest } }: any) => ({
|
edges?.map(({ node: { urlOriginal, altText, ...rest } }: any) => ({
|
||||||
url: urlOriginal,
|
url: urlOriginal,
|
||||||
alt: altText,
|
alt: altText,
|
||||||
...rest,
|
...rest,
|
||||||
})
|
})),
|
||||||
)
|
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
$apply: ({edges} : any) => edges?.map(
|
$apply: ({ edges }: any) =>
|
||||||
({ node: { entityId, productOptions, ...rest } }: any) => ({
|
edges?.map(({ node: { entityId, productOptions, ...rest } }: any) => ({
|
||||||
id: entityId,
|
id: entityId,
|
||||||
options: productOptions?.edges
|
options: productOptions?.edges
|
||||||
? productOptions.edges.map(normalizeProductOption)
|
? productOptions.edges.map(normalizeProductOption)
|
||||||
: [],
|
: [],
|
||||||
...rest,
|
...rest,
|
||||||
})
|
})),
|
||||||
)
|
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
$set: productOptions.edges ? productOptions?.edges.map(normalizeProductOption) : []
|
$set: productOptions.edges
|
||||||
|
? productOptions?.edges.map(normalizeProductOption)
|
||||||
|
: [],
|
||||||
},
|
},
|
||||||
brand: {
|
brand: {
|
||||||
$apply:(brand : any) => brand?.entityId ? brand?.entityId : null,
|
$apply: (brand: any) => (brand?.entityId ? brand?.entityId : null),
|
||||||
},
|
},
|
||||||
slug: {
|
slug: {
|
||||||
$set: path?.replace(/^\/+|\/+$/g, '')
|
$set: path?.replace(/^\/+|\/+$/g, ''),
|
||||||
},
|
},
|
||||||
price: {
|
price: {
|
||||||
$set: {
|
$set: {
|
||||||
value: prices?.price.value,
|
value: prices?.price.value,
|
||||||
currencyCode: prices?.price.currencyCode,
|
currencyCode: prices?.price.currencyCode,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
$unset: ['entityId']
|
},
|
||||||
|
$unset: ['entityId'],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,9 +72,9 @@ export function normalizeCart(data: any): Cart {
|
|||||||
$auto: {
|
$auto: {
|
||||||
items: { $set: data?.line_items?.physical_items?.map(itemsToProducts) },
|
items: { $set: data?.line_items?.physical_items?.map(itemsToProducts) },
|
||||||
subTotal: { $set: data?.base_amount },
|
subTotal: { $set: data?.base_amount },
|
||||||
total: { $set: data?.cart_amount }
|
total: { $set: data?.cart_amount },
|
||||||
},
|
},
|
||||||
$unset: ['created_time', 'coupons', 'line_items', 'email']
|
$unset: ['created_time', 'coupons', 'line_items', 'email'],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ function itemsToProducts(item: any): CartItem {
|
|||||||
extended_list_price,
|
extended_list_price,
|
||||||
extended_sale_price,
|
extended_sale_price,
|
||||||
...rest
|
...rest
|
||||||
} = item;
|
} = item
|
||||||
|
|
||||||
return update(item, {
|
return update(item, {
|
||||||
$auto: {
|
$auto: {
|
||||||
@ -102,14 +101,18 @@ function itemsToProducts(item: any): CartItem {
|
|||||||
salePrice: { $set: sale_price },
|
salePrice: { $set: sale_price },
|
||||||
extendedListPrice: { $set: extended_list_price },
|
extendedListPrice: { $set: extended_list_price },
|
||||||
extendedSalePrice: { $set: extended_sale_price },
|
extendedSalePrice: { $set: extended_sale_price },
|
||||||
}
|
|
||||||
},
|
},
|
||||||
images: { $set: [{
|
},
|
||||||
|
images: {
|
||||||
|
$set: [
|
||||||
|
{
|
||||||
alt: name,
|
alt: name,
|
||||||
url: image_url
|
url: image_url,
|
||||||
}]},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
productId: { $set: product_id },
|
productId: { $set: product_id },
|
||||||
variantId: { $set: variant_id }
|
variantId: { $set: variant_id },
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import type { responseInterface } from 'swr'
|
import type { responseInterface } from 'swr'
|
||||||
import Cookies from 'js-cookie'
|
import Cookies from 'js-cookie'
|
||||||
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
|
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
|
||||||
import useData, { SwrOptions } from '../utils/use-data'
|
import useData, { ResponseState, SwrOptions } from '../utils/use-data'
|
||||||
import { useCommerce } from '..'
|
import { useCommerce } from '..'
|
||||||
|
|
||||||
export type CartResponse<Result> = responseInterface<Result, Error> & {
|
export type CartResponse<Result> = ResponseState<Result> & { isEmpty: boolean }
|
||||||
isEmpty: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CartInput = {
|
export type CartInput = {
|
||||||
cartId: Cart['id']
|
cartId: Cart['id']
|
||||||
@ -17,7 +15,7 @@ export default function useCart<Result>(
|
|||||||
input: HookInput,
|
input: HookInput,
|
||||||
fetcherFn: HookFetcher<Result, CartInput>,
|
fetcherFn: HookFetcher<Result, CartInput>,
|
||||||
swrOptions?: SwrOptions<Result, CartInput>
|
swrOptions?: SwrOptions<Result, CartInput>
|
||||||
) {
|
): CartResponse<Result> {
|
||||||
const { cartCookie } = useCommerce()
|
const { cartCookie } = useCommerce()
|
||||||
|
|
||||||
const fetcher: typeof fetcherFn = (options, input, fetch) => {
|
const fetcher: typeof fetcherFn = (options, input, fetch) => {
|
||||||
@ -27,5 +25,5 @@ export default function useCart<Result>(
|
|||||||
|
|
||||||
const response = useData(options, input, fetcher, swrOptions)
|
const response = useData(options, input, fetcher, swrOptions)
|
||||||
|
|
||||||
return Object.assign(response, { isEmpty: true }) as CartResponse<Result>
|
return Object.assign(response, { isEmpty: true })
|
||||||
}
|
}
|
||||||
|
37
framework/commerce/utils/define-property.ts
Normal file
37
framework/commerce/utils/define-property.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Taken from https://fettblog.eu/typescript-assertion-signatures/
|
||||||
|
|
||||||
|
type InferValue<Prop extends PropertyKey, Desc> = Desc extends {
|
||||||
|
get(): any
|
||||||
|
value: any
|
||||||
|
}
|
||||||
|
? never
|
||||||
|
: Desc extends { value: infer T }
|
||||||
|
? Record<Prop, T>
|
||||||
|
: Desc extends { get(): infer T }
|
||||||
|
? Record<Prop, T>
|
||||||
|
: never
|
||||||
|
|
||||||
|
type DefineProperty<
|
||||||
|
Prop extends PropertyKey,
|
||||||
|
Desc extends PropertyDescriptor
|
||||||
|
> = Desc extends { writable: any; set(val: any): any }
|
||||||
|
? never
|
||||||
|
: Desc extends { writable: any; get(): any }
|
||||||
|
? never
|
||||||
|
: Desc extends { writable: false }
|
||||||
|
? Readonly<InferValue<Prop, Desc>>
|
||||||
|
: Desc extends { writable: true }
|
||||||
|
? InferValue<Prop, Desc>
|
||||||
|
: Readonly<InferValue<Prop, Desc>>
|
||||||
|
|
||||||
|
export default function defineProperty<
|
||||||
|
Obj extends object,
|
||||||
|
Key extends PropertyKey,
|
||||||
|
PDesc extends PropertyDescriptor
|
||||||
|
>(
|
||||||
|
obj: Obj,
|
||||||
|
prop: Key,
|
||||||
|
val: PDesc
|
||||||
|
): asserts obj is Obj & DefineProperty<Key, PDesc> {
|
||||||
|
Object.defineProperty(obj, prop, val)
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import useSWR, { ConfigInterface, responseInterface } from 'swr'
|
import useSWR, { ConfigInterface, responseInterface } from 'swr'
|
||||||
import type { HookInput, HookFetcher, HookFetcherOptions } from './types'
|
import type { HookInput, HookFetcher, HookFetcherOptions } from './types'
|
||||||
|
import defineProperty from './define-property'
|
||||||
import { CommerceError } from './errors'
|
import { CommerceError } from './errors'
|
||||||
import { useCommerce } from '..'
|
import { useCommerce } from '..'
|
||||||
|
|
||||||
@ -9,12 +10,16 @@ export type SwrOptions<Result, Input = null> = ConfigInterface<
|
|||||||
HookFetcher<Result, Input>
|
HookFetcher<Result, Input>
|
||||||
>
|
>
|
||||||
|
|
||||||
|
export type ResponseState<Result> = responseInterface<Result, CommerceError> & {
|
||||||
|
isLoading: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type UseData = <Result = any, Input = null>(
|
export type UseData = <Result = any, Input = null>(
|
||||||
options: HookFetcherOptions | (() => HookFetcherOptions | null),
|
options: HookFetcherOptions | (() => HookFetcherOptions | null),
|
||||||
input: HookInput,
|
input: HookInput,
|
||||||
fetcherFn: HookFetcher<Result, Input>,
|
fetcherFn: HookFetcher<Result, Input>,
|
||||||
swrOptions?: SwrOptions<Result, Input>
|
swrOptions?: SwrOptions<Result, Input>
|
||||||
) => responseInterface<Result, CommerceError>
|
) => ResponseState<Result>
|
||||||
|
|
||||||
const useData: UseData = (options, input, fetcherFn, swrOptions) => {
|
const useData: UseData = (options, input, fetcherFn, swrOptions) => {
|
||||||
const { fetcherRef } = useCommerce()
|
const { fetcherRef } = useCommerce()
|
||||||
@ -54,6 +59,13 @@ const useData: UseData = (options, input, fetcherFn, swrOptions) => {
|
|||||||
swrOptions
|
swrOptions
|
||||||
)
|
)
|
||||||
|
|
||||||
|
defineProperty(response, 'isLoading', {
|
||||||
|
get() {
|
||||||
|
return response.data === undefined
|
||||||
|
},
|
||||||
|
set: (x) => x,
|
||||||
|
})
|
||||||
|
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import type { responseInterface } from 'swr'
|
import type { responseInterface } from 'swr'
|
||||||
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
|
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
|
||||||
import useData, { SwrOptions } from '../utils/use-data'
|
import useData, { ResponseState, SwrOptions } from '../utils/use-data'
|
||||||
|
|
||||||
export type WishlistResponse<Result> = responseInterface<Result, Error> & {
|
export type WishlistResponse<Result> = ResponseState<Result> & {
|
||||||
isEmpty: boolean
|
isEmpty: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export default function useWishlist<Result, Input = null>(
|
|||||||
input: HookInput,
|
input: HookInput,
|
||||||
fetcherFn: HookFetcher<Result, Input>,
|
fetcherFn: HookFetcher<Result, Input>,
|
||||||
swrOptions?: SwrOptions<Result, Input>
|
swrOptions?: SwrOptions<Result, Input>
|
||||||
) {
|
): WishlistResponse<Result> {
|
||||||
const response = useData(options, input, fetcherFn, swrOptions)
|
const response = useData(options, input, fetcherFn, swrOptions)
|
||||||
return Object.assign(response, { isEmpty: true }) as WishlistResponse<Result>
|
return Object.assign(response, { isEmpty: true })
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user