Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement shopper products #681

Merged
merged 33 commits into from
Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
391ff28
implement useProducts
alexvuong Jul 25, 2022
253bc4e
implement useProducts, useProduct, useCategories
alexvuong Jul 27, 2022
675a6ec
Merge branch 'develop' into feat/implement-shopper-products
alexvuong Jul 27, 2022
81ef8ca
clean up
alexvuong Jul 27, 2022
554cd5f
fix links in test project, and clean up
alexvuong Jul 28, 2022
2d5de32
fix links in test project, and clean up
alexvuong Jul 28, 2022
dcb4f2d
broke the options into parameters and opts
alexvuong Jul 28, 2022
186195d
address PR feedback
alexvuong Aug 3, 2022
7407145
make deps optional in overload declaration
alexvuong Aug 3, 2022
85e7741
Minor adjustment in the test project
alexvuong Aug 4, 2022
4e053ac
Merge branch 'develop' into feat/implement-shopper-products
alexvuong Aug 5, 2022
1b2f467
use react query useQuery
alexvuong Aug 8, 2022
d0f1295
Merge branch 'develop' into feat/implement-shopper-products
alexvuong Aug 8, 2022
211b943
use useAsync for the hooks
alexvuong Aug 8, 2022
29ebf01
fix copyright
alexvuong Aug 8, 2022
c3b6c84
remove unused code
alexvuong Aug 8, 2022
eabd7fd
Merge branch 'develop' into feat/implement-shopper-products
alexvuong Aug 9, 2022
76f4bf0
remove third generic overload signature
alexvuong Aug 9, 2022
fce562c
PR feedback
alexvuong Aug 9, 2022
fd5c528
lint
alexvuong Aug 9, 2022
fd0a851
add tests for shopper products hook
alexvuong Aug 10, 2022
d691fe1
Merge branch 'develop' into feat/implement-shopper-products
alexvuong Aug 17, 2022
5a35f51
fix the failing tests
alexvuong Aug 23, 2022
1dabb8d
refactor test cases
alexvuong Aug 23, 2022
9cf7240
PR feedback
alexvuong Aug 24, 2022
8dd2b0d
Move the ts comment back above over load for editor to pick up
alexvuong Aug 24, 2022
432a968
Merge branch 'develop' into feat/implement-shopper-products
alexvuong Aug 24, 2022
c002ed7
Merge branch 'develop' into feat/implement-shopper-products
alexvuong Aug 29, 2022
1876a7e
update lock file
alexvuong Aug 29, 2022
a501d97
PR feedback, use correct version of @type/react-router-dom
alexvuong Aug 29, 2022
dbf39bd
Merge branch 'develop' into feat/implement-shopper-products
alexvuong Aug 29, 2022
6f8111a
fix tsconfig ourDir in tsconfig, regenerate lock file
alexvuong Aug 30, 2022
4d2e233
fix outDir
alexvuong Aug 30, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 43 additions & 6 deletions packages/commerce-sdk-react/src/hooks/ShopperProducts/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import {ApiClients, Argument, DataType, QueryResponse} from '../types'
import {ShopperProductsTypes} from 'commerce-sdk-isomorphic'
import {useAsync} from '../useAsync'
import useCommerceApi from '../useCommerceApi'

Expand All @@ -18,10 +19,22 @@ type Client = ApiClients['shopperProducts']
* @returns An object describing the state of the request.
*/
export const useProducts = (
arg: Argument<Client['getProducts']>
arg: Argument<Client['getProducts']>,
deps: unknown[] = []
): QueryResponse<DataType<Client['getProducts']>> => {
if (!arg) {
throw new Error('Missing ids in parameters to make request')
}
const {shopperProducts: client} = useCommerceApi()
return useAsync(() => client.getProducts(arg), [arg])
const {
parameters: {ids}
} = arg
// by default the source is the ids string
let source: unknown[] = [ids]
if (deps.length) {
source = deps
}
return useAsync(() => client.getProducts(arg), source)
}
/**
* A hook for `ShopperProducts#getProduct`.
Expand All @@ -31,10 +44,23 @@ export const useProducts = (
* @returns An object describing the state of the request.
*/
export const useProduct = (
arg: Argument<Client['getProduct']>
arg: Argument<Client['getProduct']>,
deps: unknown[] = []
): QueryResponse<DataType<Client['getProduct']>> => {
if (!arg) {
throw new Error('useProducts requires product id in parameters ')
}
const {
parameters: {id}
} = arg
// by default the source is the ids string
let source: unknown[] = [id]
if (deps.length) {
source = deps
}
console.log('source', source)
const {shopperProducts: client} = useCommerceApi()
return useAsync(() => client.getProduct(arg), [arg])
return useAsync(() => client.getProduct(arg), source)
}
/**
* A hook for `ShopperProducts#getCategories`.
Expand All @@ -44,10 +70,21 @@ export const useProduct = (
* @returns An object describing the state of the request.
*/
export const useCategories = (
arg: Argument<Client['getCategories']>
arg: Argument<Client['getCategories']>,
deps: unknown[] = []
): QueryResponse<DataType<Client['getCategories']>> => {
if (!arg) {
throw new Error('useCategories requires categories ids string in parameters ')
}
const {
parameters: {ids, levels = 1}
} = arg
let source: unknown[] = [ids, levels]
if (deps.length) {
source = deps
}
const {shopperProducts: client} = useCommerceApi()
return useAsync(() => client.getCategories(arg), [arg])
return useAsync(() => client.getCategories(arg), source)
}
/**
* A hook for `ShopperProducts#getCategory`.
Expand Down
53 changes: 36 additions & 17 deletions packages/commerce-sdk-react/src/hooks/useAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,44 @@
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import {useState} from 'react'
import {useState, useEffect} from 'react'
import {ActionResponse, QueryResponse} from './types'

export const useAsync = <T>(fn: () => Promise<T>, deps?: unknown[]): QueryResponse<T> => {
// This is a stub implementation to validate the types.
// The real implementation will be more React-y.
const result: QueryResponse<T> = {
isLoading: true
}
fn()
.then((data) => {
result.isLoading = false
result.data = data
})
.catch((error) => {
result.isLoading = false
result.error = error
})
return result
export const useAsync = <T>(fn: () => Promise<T>, deps: unknown[] = []): QueryResponse<T> => {
alexvuong marked this conversation as resolved.
Show resolved Hide resolved
const [data, setData] = useState<T | undefined>()
const [error, setError] = useState<Error | undefined>()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
// use this variable to avoid race condition
let subscribe = true
setIsLoading(true)

const runAsync = async () => {
try {
if (subscribe) {
const res = await fn()
setData(res)
setIsLoading(false)
}
} catch (error) {
if (subscribe) {
setIsLoading(false)
if (error instanceof Error) {
setError(error)
}
}
}
}

runAsync()

// clean up
return () => {
subscribe = false
}
}, deps)

return {data, isLoading, error}
}

export const useAsyncCallback = <Args extends unknown[], Ret>(
Expand Down
96 changes: 86 additions & 10 deletions packages/commerce-sdk-react/src/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React, {ReactElement} from 'react'
import React, {useState, ReactElement, useEffect} from 'react'
import {
ShopperBaskets,
ShopperContexts,
Expand All @@ -15,7 +15,8 @@ import {
ShopperPromotions,
ShopperDiscoverySearch,
ShopperGiftCertificates,
ShopperSearch
ShopperSearch,
helpers
} from 'commerce-sdk-isomorphic'

import {ApiClientConfigParams, ApiClients} from './hooks/types'
Expand All @@ -32,18 +33,37 @@ export interface CommerceApiProviderProps extends ApiClientConfigParams {
*/
export const CommerceApiContext = React.createContext({} as ApiClients)

interface configType {
alexvuong marked this conversation as resolved.
Show resolved Hide resolved
proxy: string
headers: {
authorization?: string
}
parameters: {
clientId: string
organizationId: string
shortCode: string
siteId: string
}
throwOnBadResponse: boolean
}
/**
* Initialize a set of Commerce API clients and make it available to all of descendant components
*
* @param props
* @returns Provider to wrap your app with
*/
const localhost = 'http://localhost:3000'
// Input your token here to for the apiClients to work
// this is a temporary solution to implement hooks while waiting for slas hook
const token = ''
const CommerceApiProvider = (props: CommerceApiProviderProps): ReactElement => {
const {children, clientId, organizationId, shortCode, siteId, proxy} = props

const config = {
proxy,
headers: {},
const {children, clientId, organizationId, shortCode, siteId} = props
alexvuong marked this conversation as resolved.
Show resolved Hide resolved
alexvuong marked this conversation as resolved.
Show resolved Hide resolved
const defaultConfig: configType = {
proxy: `${localhost}${props.proxy}`,
headers: {
// mock a token to use
authorization: `Bearer ${token}`
},
parameters: {
clientId,
organizationId,
Expand All @@ -52,10 +72,66 @@ const CommerceApiProvider = (props: CommerceApiProviderProps): ReactElement => {
},
throwOnBadResponse: true
}
const [config, setConfig] = useState(defaultConfig)

//NOTE: this logic is for temporary use to grab the access token
// token handling session should be provided by SLAS hook which is still in implementation
useEffect(() => {
const origin = 'http://localhost:3000'
// only for temporary use until slas hook is ready
const setupToken = async (config: configType) => {
if (typeof window !== 'undefined') {
const shopperLogin = new ShopperLogin(config)

const access_token = window.localStorage.getItem('token')
const refresh_token = window.localStorage.getItem('refresh_token')
console.log('access_token', access_token)
console.log('refresh_token', refresh_token)
if (access_token) {
// const data = new URLSearchParams()
// data.append('grant_type', 'refresh_token')
// data.append('refresh_token', refresh_token)
// data.append('client_id', clientId)
//
// const options = {
// headers: {
// 'Content-Type': `application/x-www-form-urlencoded`
// },
// body: data
// }
// this code here is not important and will be deleted, do not need to care about typescript error
// @ts-ignore
// const res = await shopperLogin.getAccessToken(options)
// console.log('res', res)
// window.localStorage.setItem('token', res.access_token)
// window.localStorage.setItem('refresh_token', res.refresh_token)
return access_token
} else {
console.log('shopperLogin>>>>>>>>>>>>>>>>>>>>>>>')
const {access_token, refresh_token, ...rest} = await helpers.loginGuestUser(
shopperLogin,
{redirectURI: `${origin}/callback`} // Callback URL must be configured in SLAS Admin
)
console.log('access_token', access_token)
setConfig({
...config,
headers: {
authorization: `Bearer ${access_token}`
}
})
console.log('refresh_token', refresh_token)
console.log('...rest', rest)
window.localStorage.setItem('token', access_token)
window.localStorage.setItem('refresh_token', refresh_token)
}
}
}
setupToken(config)
}, [])

// TODO: Initialize the api clients immediately, without waiting for an access token.
// See template-retail-react-app/app/commerce-api/index.js for inspiration
// especially how Proxy class can be used to wait for the access token and inject it to each request header.
// // TODO: Initialize the api clients immediately, without waiting for an access token.
// // See template-retail-react-app/app/commerce-api/index.js for inspiration
// // especially how Proxy class can be used to wait for the access token and inject it to each request header.
const apiClients: ApiClients = {
shopperBaskets: new ShopperBaskets(config),
shopperContexts: new ShopperContexts(config),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import React, {ReactElement} from 'react'
// @ts-ignore
import {CommerceApiProvider} from 'commerce-sdk-react'


interface AppConfigProps {
children: React.ReactNode
}
Expand Down
1 change: 1 addition & 0 deletions packages/test-commerce-sdk-react/app/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
// @ts-ignore
import {start} from 'pwa-kit-react-sdk/ssr/browser/main'

start()
4 changes: 3 additions & 1 deletion packages/test-commerce-sdk-react/app/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ const Home = () => {
</p>
<p>Happy coding.</p>
<h2>Hooks</h2>
<Link to="/useShopperProducts">useShopperProducts</Link>
<Link to="/use-shopper-products">useShopperProducts</Link>
<br />
<Link to="/use-shopper-categories">useCategories</Link>
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

// @ts-ignore
import {useCategories} from 'commerce-sdk-react'

import React, {useState} from 'react'
import Json from '../components/Json'
import {Link} from 'react-router-dom'
import {flatten} from '../utils/utils'

function UseShopperCategories() {
// how to get the categories type
const {isLoading, error, data: result} = useCategories({
parameters: {
ids: 'root',
levels: 2
}
})
if (isLoading) {
return (
<div>
<h1>useShopperCategories page</h1>
<div style={{background: 'yellow'}}>Loading...</div>
</div>
)
}
if (error) {
return <div>Something is wrong</div>
}
let flattened: Record<string, any> = {}
if (result) {
flattened = flatten(result.data[0], 'categories')
}
return (
<div>
<h1>useShopperCategories page</h1>
{result &&
Object.keys(flattened).map((key) => (
<div key={key}>
<Link to={`/use-shopper-categories/${key}`}>Category {key}</Link>
</div>
))}

<hr />
<div>
<div>Returning data</div>
<Json data={result} />
</div>
</div>
)
}

export default UseShopperCategories
Loading