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

Acccount detail page hook integration #995

Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import {
BasketProvider,
CommerceAPIProvider as _CommerceAPIProvider,
CustomerProductListsProvider,
CustomerProvider
//TODO: Remove when integration is finished
CustomerProvider as _CustomerProvider
} from '../../commerce-api/contexts'
import {MultiSiteProvider} from '../../contexts'
import {CustomerProvider, MultiSiteProvider} from '../../contexts'
import {resolveSiteFromUrl} from '../../utils/site-utils'
import {resolveLocaleFromUrl} from '../../utils/utils'
import {getConfig} from 'pwa-kit-runtime/utils/ssr-config'
Expand Down Expand Up @@ -64,17 +65,23 @@ const AppConfig = ({children, locals = {}}) => {
proxy={`${appOrigin}${commerceApiConfig.proxyPath}`}
headers={headers}
>
<MultiSiteProvider site={locals.site} locale={locals.locale} buildUrl={locals.buildUrl}>
<_CommerceAPIProvider value={locals.api}>
<CustomerProvider value={{customer, setCustomer}}>
<BasketProvider value={{basket, setBasket}}>
<CustomerProductListsProvider>
<ChakraProvider theme={theme}>{children}</ChakraProvider>
</CustomerProductListsProvider>
</BasketProvider>
</CustomerProvider>
</_CommerceAPIProvider>
</MultiSiteProvider>
<CustomerProvider>
<MultiSiteProvider
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a particular reason why CustomerProvider is the parent of the MultiSiteProvider? I'd imagine customer data is associate with a site on the backend, so i feel like it should be the other way around, MultiSiteProvider being the parent.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, it should be inside MultiSiteProvider

site={locals.site}
locale={locals.locale}
buildUrl={locals.buildUrl}
>
<_CommerceAPIProvider value={locals.api}>
<_CustomerProvider value={{customer, setCustomer}}>
<BasketProvider value={{basket, setBasket}}>
<CustomerProductListsProvider>
<ChakraProvider theme={theme}>{children}</ChakraProvider>
</CustomerProductListsProvider>
</BasketProvider>
</_CustomerProvider>
</_CommerceAPIProvider>
</MultiSiteProvider>
</CustomerProvider>
</CommerceApiProvider>
)
}
Expand Down
27 changes: 27 additions & 0 deletions packages/template-retail-react-app/app/contexts/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, {useEffect, useState} from 'react'
import PropTypes from 'prop-types'
import {useCommerceAPI} from '../commerce-api/contexts'
import {CAT_MENU_STALE_TIME} from '../constants'
import {useCustomer, useCustomerId, useCustomerType} from 'commerce-sdk-react-preview'

/**
* This is the global state for categories, we use this for navigation and for
Expand Down Expand Up @@ -191,3 +192,29 @@ CurrencyProvider.propTypes = {
children: PropTypes.node.isRequired,
currency: PropTypes.string
}
/**
* Provider that manages the customer data
* @type {React.Context<unknown>}
*/
export const CustomerContext = React.createContext()
export const CustomerProvider = ({children}) => {
const customerId = useCustomerId()
const {isRegistered, customerType} = useCustomerType()
const {data, isLoading, isError, ...restOfCustomer} = useCustomer(
{customerId},
{enabled: !!customerId && isRegistered}
)
const value = {
...data,
customerId,
isRegistered,
customerType,
isLoading,
isError,
...restOfCustomer
}
return <CustomerContext.Provider value={value}>{children}</CustomerContext.Provider>
}
CustomerProvider.propTypes = {
children: PropTypes.node.isRequired
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2022, Salesforce, 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
*/

/**
* A hook that returns the current state of the app.
* It is a combination of many commerce-sdk-react hooks that needs to be used together in many places.
*/
import {useContext} from 'react'
import {CustomerContext} from '../contexts'
export const useCurrentCustomer = () => {
const context = useContext(CustomerContext)
if (context === undefined) {
throw new Error('useCurrentCustomer must be used within CustomerProvider')
}
return context
}
91 changes: 48 additions & 43 deletions packages/template-retail-react-app/app/pages/account/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,7 @@
import React, {useEffect, useState} from 'react'
import PropTypes from 'prop-types'
import {FormattedMessage, useIntl} from 'react-intl'
import {
Route,
Switch,
useRouteMatch
// Redirect
} from 'react-router'
import {Route, Switch, useRouteMatch, Redirect} from 'react-router'
import {
Accordion,
AccordionButton,
Expand All @@ -28,7 +23,6 @@ import {
Text,
Divider
} from '@chakra-ui/react'
import useCustomer from '../../commerce-api/hooks/useCustomer'
import Seo from '../../components/seo'
import Link from '../../components/link'
import {ChevronDownIcon, ChevronUpIcon, SignoutIcon} from '../../components/icons'
Expand All @@ -42,40 +36,20 @@ import {useLocation} from 'react-router-dom'
import {messages, navLinks} from './constant'
import useNavigation from '../../hooks/use-navigation'
import LoadingSpinner from '../../components/loading-spinner'
// import useMultiSite from '../../hooks/use-multi-site'
import useMultiSite from '../../hooks/use-multi-site'
import useEinstein from '../../commerce-api/hooks/useEinstein'
import {useShopperLoginHelper, ShopperLoginHelpers} from 'commerce-sdk-react-preview'
import {useCurrentCustomer} from '../../hooks/use-current-customer'

const Account = () => {
const {path} = useRouteMatch()
const onClient = typeof window !== 'undefined'
const LogoutButton = ({onClick}) => {
const {formatMessage} = useIntl()
const customer = useCustomer()
const location = useLocation()
const navigate = useNavigation()

const [mobileNavIndex, setMobileNavIndex] = useState(-1)
const [showLoading, setShowLoading] = useState(false)

const einstein = useEinstein()

// const {buildUrl} = useMultiSite()

/**************** Einstein ****************/
useEffect(() => {
einstein.sendViewPage(location.pathname)
}, [location])

const onSignoutClick = async () => {
setShowLoading(true)
await customer.logout()
navigate('/login')
}

const LogoutButton = () => (
return (
<>
<Divider colorScheme={'gray'} marginTop={3} />
<Button
fontWeight="500"
onClick={onSignoutClick}
onClick={onClick}
padding={4}
py={0}
variant="unstyled"
Expand All @@ -97,19 +71,50 @@ const Account = () => {
</Button>
</>
)
}

LogoutButton.propTypes = {
onClick: PropTypes.func.isRequired
}
const Account = () => {
const {path} = useRouteMatch()
const {formatMessage} = useIntl()
const customer = useCurrentCustomer()
const {isRegistered, customerType} = customer

const logout = useShopperLoginHelper(ShopperLoginHelpers.Logout)
const location = useLocation()
const navigate = useNavigation()

const [mobileNavIndex, setMobileNavIndex] = useState(-1)
const [showLoading, setShowLoading] = useState(false)

const einstein = useEinstein()

const {buildUrl} = useMultiSite()

/**************** Einstein ****************/
useEffect(() => {
einstein.sendViewPage(location.pathname)
}, [location])

const onSignoutClick = async () => {
setShowLoading(true)
await logout.mutateAsync()
navigate('/login')
}

// TODO: hook integration WIP
// If we have customer data and they are not registered, push to login page
// Using Redirect allows us to store the directed page to location
// so we can direct users back after they are successfully log in
// if (customer.authType != null && !customer.isRegistered) {
// const path = buildUrl('/login')
// return <Redirect to={{pathname: path, state: {directedFrom: location.pathname}}} />
// }

// we don't want redirect on server side
if (customerType !== null && !isRegistered && onClient) {
const path = buildUrl('/login')
return <Redirect to={{pathname: path, state: {directedFrom: '/account'}}} />
}
return (
<Box
data-testid={customer.isRegistered ? 'account-page' : 'account-page-skeleton'}
data-testid={isRegistered ? 'account-page' : 'account-page-skeleton'}
layerStyle="page"
paddingTop={[4, 4, 12, 12, 16]}
>
Expand Down Expand Up @@ -162,7 +167,7 @@ const Account = () => {
</Button>
))}

<LogoutButton justify="center" />
<LogoutButton justify="center" onClick={onSignoutClick} />
</Flex>
</AccordionPanel>
</>
Expand Down Expand Up @@ -197,7 +202,7 @@ const Account = () => {
</Button>
)
})}
<LogoutButton />
<LogoutButton onClick={onSignoutClick} />
</Flex>
</Stack>

Expand Down
Loading