Skip to content

Commit

Permalink
feat: Add new SKUs components
Browse files Browse the repository at this point in the history
  • Loading branch information
acasazza committed Mar 31, 2022
1 parent 3c34a60 commit ae7b03c
Show file tree
Hide file tree
Showing 12 changed files with 259 additions and 31 deletions.
22 changes: 9 additions & 13 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import React, { useEffect, useState } from 'react'
import Price from '../src/components/Price'
import PricesContainer from '../src/components/PricesContainer'
import CommerceLayer from '../src/components/CommerceLayer'
import SkuContainer from '#components/SkuContainer'
import Skus from '#components/Skus'
import SkuField from '#components/SkuField'

const clientId = process.env.NEXT_PUBLIC_CLIENT_ID_INTEGRATION as string
const clientSecret = process.env.NEXT_PUBLIC_CLIENT_SECRET as string
Expand Down Expand Up @@ -359,15 +362,16 @@ const Home = () => {
<div className="container mx-auto">
<CommerceLayer accessToken={token} endpoint={endpoint}>
<div className="flex flex-row flex-wrap justify-around">
<SkuContainer skus={skus}>
<Skus>
<SkuField attribute="image_url" tagElement="img" />
<SkuField attribute="code" tagElement="p" />
</Skus>
</SkuContainer>
<PricesContainer perPage={20} loader={<Loading />}>
{skus.map((s, k) => {
const lImg = s.substring(0, s.length - 4)
return (
<div key={k} className="text-center p-3">
<img
src={`https://img.commercelayer.io/skus/${lImg}.png?fm=png&q=70`}
className="rounded-lg md:w-56 m-auto"
/>
<div className="flex flex-row flex-wrap justify-center">
<Price
data-test={`price-${k}`}
Expand All @@ -376,14 +380,6 @@ const Home = () => {
compareClassName="text-gray-500 text-2xl m-1 line-through"
/>
</div>
<div className="p-3">
<a
className="mt-2 primary font-bold py-2 px-4 rounded"
href="/order"
>
Order
</a>
</div>
</div>
)
})}
Expand Down
9 changes: 2 additions & 7 deletions src/components/LineItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import React, {
FunctionComponent,
Fragment,
useContext,
ReactNode,
} from 'react'
import React, { Fragment, useContext, ReactNode } from 'react'
import LineItemContext from '#context/LineItemContext'
import LineItemChildrenContext from '#context/LineItemChildrenContext'
import components from '#config/components'
Expand All @@ -19,7 +14,7 @@ type LineItemProps = {
type?: LineItemType
}

const LineItem: FunctionComponent<LineItemProps> = (props) => {
const LineItem: React.FunctionComponent<LineItemProps> = (props) => {
const { type = 'skus', children } = props
const { lineItems } = useContext(LineItemContext)
const { lineItems: shipmentLineItems } = useContext(ShipmentChildrenContext)
Expand Down
41 changes: 41 additions & 0 deletions src/components/SkuContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import components from '#config/components'
import CommerceLayerContext from '#context/CommerceLayerContext'
import SkuContext from '#context/SkuContext'
import skuReducer, { getSku, skuInitialState } from '#reducers/SkuReducer'
import { QueryParamsList } from '@commercelayer/sdk'
import { ReactNode, useContext, useEffect, useMemo, useReducer } from 'react'

type Props = {
skus: string[]
children: ReactNode
queryParams?: QueryParamsList
}

export default function SkuContainer<P extends Props>(props: P): JSX.Element {
const { skus, children, queryParams } = props
const [state, dispatch] = useReducer(skuReducer, skuInitialState)
const config = useContext(CommerceLayerContext)
const loadSkus = async () =>
await getSku({ config, dispatch, skus, queryParams })
useEffect(() => {
if (config.accessToken && state?.skus) {
if (state?.skus.length === 0) {
loadSkus()
}
}
return () => {
dispatch({
type: 'setLoading',
payload: {
loading: true,
},
})
}
}, [config, skus])
const contextValue = useMemo(() => state, [state])
return (
<SkuContext.Provider value={contextValue}>{children}</SkuContext.Provider>
)
}

SkuContainer.propTypes = components.SkuContainer.propTypes
56 changes: 56 additions & 0 deletions src/components/SkuField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Sku } from '@commercelayer/sdk'
import SkuChildrenContext from '#context/SkuChildrenContext'
import { useContext } from 'react'
import Parent from './utils/Parent'
import components from '#config/components'

type ExcludeTag<T extends keyof JSX.IntrinsicElements> = Exclude<
keyof JSX.IntrinsicElements,
T
>

type Conditional =
| ({
attribute: 'image_url'
tagElement: 'img'
} & JSX.IntrinsicElements['img'])
| ({
attribute: Exclude<keyof Sku, 'image_url'>
tagElement: ExcludeTag<'img'>
} & JSX.IntrinsicElements[ExcludeTag<'img'>])

type ChildrenProps = Omit<Props, 'children' | 'attribute'> & {
element: Sku[keyof Sku]
}

type Props = {
children?: (props: ChildrenProps) => JSX.Element
} & Conditional

export default function SkuField<P extends Props>({
attribute,
tagElement,
children,
...p
}: P): JSX.Element {
const { sku } = useContext(SkuChildrenContext)
const element = (sku && sku[attribute]) || ''
const Tag = tagElement
if (Tag === 'img') {
const src = element as string
const name = sku?.name
return <img alt={name} src={src} {...(p as JSX.IntrinsicElements['img'])} />
}
const parentProps = {
element,
tagElement,
...p,
}
return children ? (
<Parent {...parentProps}>{children}</Parent>
) : (
<Tag>{element}</Tag>
)
}

SkuField.propTypes = components.SkuField.propTypes
25 changes: 25 additions & 0 deletions src/components/Skus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import components from '#config/components'
import SkuChildrenContext from '#context/SkuChildrenContext'
import SkuContext from '#context/SkuContext'
import { ReactNode, useContext } from 'react'

type Props = {
children: ReactNode
}

export default function Skus<P extends Props>({ children }: P): JSX.Element {
const { skus } = useContext(SkuContext)
const components =
skus &&
skus.map((sku, key) => {
const value = { sku }
return (
<SkuChildrenContext.Provider key={key} value={value}>
{children}
</SkuChildrenContext.Provider>
)
})
return <>{components}</>
}

Skus.propTypes = components.Skus.propTypes
11 changes: 2 additions & 9 deletions src/components/VariantsContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import React, {
useEffect,
FunctionComponent,
useReducer,
useContext,
ReactNode,
useMemo,
} from 'react'
import React, { useEffect, useReducer, useContext, ReactNode } from 'react'
import variantReducer, {
variantInitialState,
unsetVariantState,
Expand Down Expand Up @@ -33,7 +26,7 @@ type VariantsContainerProps = {
skuCode?: string
}

const VariantsContainer: FunctionComponent<VariantsContainerProps> = (
const VariantsContainer: React.FunctionComponent<VariantsContainerProps> = (
props
) => {
const { children, skuCode = '', filters = {} } = props
Expand Down
22 changes: 22 additions & 0 deletions src/config/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,28 @@ const components = {
type: 'amount',
},
},
SkuContainer: {
displayName: 'SkuContainer',
permittedChildren: ['Skus', 'ReactNode'],
propTypes: {
children: childrenTypes.isRequired,
},
},
Skus: {
displayName: 'Skus',
permittedChildren: ['SkuField', 'ReactNode'],
propTypes: {
children: childrenTypes.isRequired,
},
},
SkuField: {
displayName: 'SkuField',
propTypes: {
children: PropTypes.func,
attribute: PropTypes.string,
tagElement: PropTypes.string,
},
},
SkuList: {
permittedChildren: ['AddToCartButton', 'QuantitySelector', 'ReactNode'],
displayName: 'SkuList',
Expand Down
12 changes: 12 additions & 0 deletions src/context/SkuChildrenContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createContext } from 'react'
import { Sku } from '@commercelayer/sdk'

export type InitialSkuContext = Partial<{
sku: Sku
}>

const initial: InitialSkuContext = {}

const SkuChildrenContext = createContext<InitialSkuContext>(initial)

export default SkuChildrenContext
8 changes: 8 additions & 0 deletions src/context/SkuContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SkuState } from '#reducers/SkuReducer'
import { createContext } from 'react'

export type SkuContextValue = SkuState

const SkuContext = createContext<SkuContextValue>({})

export default SkuContext
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ export { default as ShippingMethod } from '#components/ShippingMethod'
export { default as ShippingMethodName } from '#components/ShippingMethodName'
export { default as ShippingMethodPrice } from '#components/ShippingMethodPrice'
export { default as ShippingMethodRadioButton } from '#components/ShippingMethodRadioButton'
export { default as Skus } from '#components/Skus'
export { default as SkuContainer } from '#components/SkuContainer'
export { default as SkuField } from '#components/SkuField'
export { default as SkuList } from '#components/SkuList'
export { default as SkuListsContainer } from '#components/SkuListsContainer'
export { default as SkuOption } from '#components/SkuOption'
Expand Down
77 changes: 77 additions & 0 deletions src/reducers/SkuReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import baseReducer from '#utils/baseReducer'
import { CommerceLayerConfig } from '#context/CommerceLayerContext'
import getSdk from '#utils/getSdk'
import { BaseAction } from '#typings'
import { QueryParamsList, Sku } from '@commercelayer/sdk'
import { Dispatch } from 'react'

type SkuActionType = 'getSkus' | 'setLoading'
type SkuAction = BaseAction<SkuActionType, SkuState>
export type SkuState = Partial<{
skus: Sku[]
loading: boolean
}>

const actionType: SkuActionType[] = ['getSkus']

export const skuInitialState: SkuState = {
skus: [],
loading: true,
}

type GetSku = {
config: CommerceLayerConfig
skus: string[]
dispatch: Dispatch<SkuAction>
queryParams?: QueryParamsList
}

export async function getSku<T extends GetSku>({
config,
skus,
dispatch,
queryParams,
}: T): Promise<void> {
if (!config.accessToken) return
if (skus.length === 0) return
const sdk = getSdk(config)
let allSkus: Sku[] = []
const get = await sdk.skus.list({
...queryParams,
filters: { ...queryParams?.filters, code_in: skus.join(',') },
})
allSkus = [...get]
const meta = get.meta
if (meta.pageCount > 1) {
for (
let pageNumber = meta.currentPage + 1;
pageNumber <= meta.pageCount;
pageNumber++
) {
const getPage = await sdk.skus.list({
...queryParams,
filters: { ...queryParams?.filters, code_in: skus.join(',') },
pageNumber,
})
allSkus = [...allSkus, ...getPage]
}
}
dispatch({
type: 'getSkus',
payload: {
skus: allSkus,
loading: false,
},
})
}

export default function skuReducer(
state: SkuState,
reducer: SkuAction
): SkuState {
return baseReducer<SkuState, SkuAction, SkuActionType[]>(
state,
reducer,
actionType
)
}
4 changes: 2 additions & 2 deletions src/typings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@ export type TimeFormat = 'days' | 'hours'

export type BaseComponent = InferProps<typeof BC>

export interface BaseAction<A = string> {
export interface BaseAction<A = string, P = Record<string, any>> {
type: A
payload: Record<string, any>
payload: P
}

export interface BaseState {
Expand Down

0 comments on commit ae7b03c

Please sign in to comment.