Skip to content

Commit

Permalink
feat: new cart hook (#147)
Browse files Browse the repository at this point in the history
* docs: added docs for use product

* feat: added cart hooks

* feat: added tanstack powered product hooks

* docs: added cart stories

* feat: account creds type

* build: export new hook

* feat: cart state simplified

* feat: account hooks

* feat: updated account, cart, currency and payments hooks

* feat: remove unused services

* feat: add order confirm hooks

* feat: replace use cart hook with new use cart

* chore: changeset

* feat: fix the hook library version for schematics

* chore: changeset

* feat: removed payment gateway register as it's no longer needed
  • Loading branch information
field123 committed Jan 12, 2024
1 parent 3355c74 commit 5f0df93
Show file tree
Hide file tree
Showing 55 changed files with 1,016 additions and 1,216 deletions.
5 changes: 5 additions & 0 deletions .changeset/calm-parents-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@elasticpath/react-shopper-hooks": minor
---

New interface for useCart
5 changes: 5 additions & 0 deletions .changeset/metal-queens-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@elasticpath/d2c-schematics": minor
---

fix shopper hooks version
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,59 @@ This endpoint doesn't require any parameters, although it support additional opt

### Filtering Products

You can filter products by using the `filter` query parameter. For example, to filter products by category, you can use the `category` filter:
You can filter products by using the `filter` query parameter. For example, to filter products by category, you can use the `category` filter:

## Get Product by ID

You can get a single product in your storefront using the [PXM catalog view (shopper) endpoints, get a product](https://elasticpath.dev/docs/pxm/catalogs/shopper-catalog/get-a-product).


<Tabs>
<TabItem value="js-sdk" label="JS SDK">

```typescript
client.ShopperCatalog.Products.Get({productId: productId}).then((product) => {
console.log(product.id)
})
```

</TabItem>

<TabItem value="elastic-path-react" label="Elastic Path React">

```tsx
import { useProduct } from "@elasticpath/react-shopper-hooks"
export default function Products() {
const { data: product, isLoading } = useProduct()
return (
<div>
{isLoading && <span>Loading...</span>}
{product && <span>{product.attributes.name}</span>}
</div>
)
}
```

</TabItem>
<TabItem value="fetch" label="Fetch API">

```js
fetch("https://useast.api.elasticpath.com/catalog/products/${productId}", {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer XXXX"
}
}).then(response => response.json())
.then(data => console.log(data));
```

</TabItem>
</Tabs>

Returns the specified product from the catalog. The product must be in the live status.

If you have multiple catalog rules defined, the rule that best matches the shopperʼs context is used to determine which catalog is retrieved. For information about how rules are matched, see [Resolving Catalog Rules](https://elasticpath.dev/docs/pxm/catalogs/shopper-catalog/catalog-shopper-overview#resolving-catalog-rules).

You can see the parent nodes a product is associated with in the bread_crumbs and bread_crumb_nodes metadata for each product. This is useful if you want to improve how your shoppers search your store, for example. For more information, see [Catalog Releases Overview](https://elasticpath.dev/docs/pxm/catalogs/catalog-latest-release/overview).
2 changes: 1 addition & 1 deletion apps/composable-cli-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"start": "docusaurus start --port 3005",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
Expand Down
4 changes: 2 additions & 2 deletions packages/d2c-schematics/utility/latest-versions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"private": true,
"dependencies": {
"@moltin/sdk": "^27.0.0",
"@elasticpath/react-shopper-hooks": "^0.6.2",
"@elasticpath/shopper-common": "^0.2.1",
"@elasticpath/react-shopper-hooks": "0.6.2",
"@elasticpath/shopper-common": "0.2.1",
"clsx": "^1.2.1",
"cookies-next": "^4.0.0",
"focus-visible": "^5.2.0",
Expand Down
44 changes: 41 additions & 3 deletions packages/react-shopper-hooks/src/account/account-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import React, { createContext, ReactNode } from "react"
import { AccountMember } from "@moltin/sdk"
import Cookies from "js-cookie"
import { AccountCredentials } from "./types"
import { AccountTokenBase, AccountMember } from "@moltin/sdk"

interface AccountState {
export interface AccountState {
accountCookieName: string
profile: AccountMember | null
getSelectedAccountToken: () => AccountTokenBase | undefined
getAccountMemberTokens: () => Record<string, AccountTokenBase> | undefined
}

export const AccountProviderContext = createContext<AccountState | null>(null)
Expand All @@ -19,9 +23,43 @@ export const AccountProvider = ({
children,
accountCookieName = ACCOUNT_MEMBER_TOKEN_STR,
}: AccountProviderProps) => {
function getParsedCookie(): AccountCredentials | undefined {
const cookie = Cookies.get(accountCookieName)
return cookie && JSON.parse(cookie)
}

function getAccountMemberTokens():
| Record<string, AccountTokenBase>
| undefined {
const parsedCookie = getParsedCookie()

if (!parsedCookie) {
return undefined
}

return parsedCookie.accounts
}

function getSelectedAccountToken(): AccountTokenBase | undefined {
const parsedCookie = getParsedCookie()

if (!parsedCookie) {
return undefined
}

const token = parsedCookie.accounts[parsedCookie.selected]

return token
}

return (
<AccountProviderContext.Provider
value={{ accountCookieName, profile: null }}
value={{
accountCookieName,
getAccountMemberTokens,
getSelectedAccountToken,
profile: null
}}
>
{children}
</AccountProviderContext.Provider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useElasticPath } from "../../elasticpath"
import { UseQueryOptionsWrapper } from "../../types"
import { AccountAddress, ResourcePage } from "@moltin/sdk"
import { useQuery, UseQueryResult } from "@tanstack/react-query"
import { queryKeysFactory } from "../../shared/util/query-keys-factory"

const ACCOUNT_ADDRESSES_QUERY_KEY = "account-addresses" as const

export const accountAddressesQueryKeys = queryKeysFactory(
ACCOUNT_ADDRESSES_QUERY_KEY,
)
type AccountAddressesQueryKey = typeof accountAddressesQueryKeys

export function useAccountAddresses(
accountId: string,
options?: UseQueryOptionsWrapper<
ResourcePage<AccountAddress>,
Error,
ReturnType<AccountAddressesQueryKey["list"] & string>
> & { ep?: { accountMemberToken?: string } },
): Partial<ResourcePage<AccountAddress>> &
Omit<UseQueryResult<ResourcePage<AccountAddress>, Error>, "data"> {
const { client } = useElasticPath()
const { data, ...rest } = useQuery({
queryKey: [...accountAddressesQueryKeys.list({ accountId })],
queryFn: () =>
client.AccountAddresses.All({
account: accountId,
...(options?.ep?.accountMemberToken && {
token: options.ep.accountMemberToken,
}),
}),
...options,
})

return { ...data, ...rest } as const
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { useElasticPath } from "../../elasticpath/elasticpath"
import { UseQueryOptionsWrapper } from "../../types"
import { AccountMember, Resource } from "@moltin/sdk"
import { useQuery } from "@tanstack/react-query"
import { useQuery, UseQueryResult } from "@tanstack/react-query"
import { queryKeysFactory } from "../../shared/util/query-keys-factory"
import { UseQueryResult } from "@tanstack/react-query/src/types"

const ACCOUNT_MEMBER_QUERY_KEY = "account-member" as const

export const accountMemberQueryKeys = queryKeysFactory(ACCOUNT_MEMBER_QUERY_KEY)
type AccountMemberQueryKey = typeof accountMemberQueryKeys
type Temp = UseQueryResult<Resource<AccountMember> | undefined, Error>

export function useAccountMember(
id: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,50 @@
import { useAccountMember } from "./use-account-member"
import { useContext } from "react"
import { useContext, useEffect, useState } from "react"
import { AccountProviderContext } from "../account-provider"
import Cookies from "js-cookie"
import {
createCookieTokenStore,
resolveAccountMemberIdFromToken,
} from "../login-account"
import { useElasticPath } from "../../elasticpath/elasticpath"
import { AccountMember, Resource } from "@moltin/sdk"
import { AccountMember, AccountTokenBase, Resource } from "@moltin/sdk"
import { UseQueryResult } from "@tanstack/react-query/src/types"
import { AccountCredentials } from "../types"

export function useAuthedAccountMember(): Partial<Resource<AccountMember>> &
Omit<UseQueryResult<Resource<AccountMember>, Error>, "data"> {
Omit<UseQueryResult<Resource<AccountMember>, Error>, "data"> & {
accountMemberTokens?: Record<string, AccountTokenBase>
selectedAccountToken?: AccountTokenBase
} {
const ctx = useContext(AccountProviderContext)
const [accountMemberTokens, setAccountMemberTokens] = useState<
Record<string, AccountTokenBase> | undefined
>()
const [selectedAccountToken, setSelectedAccountToken] = useState<
AccountTokenBase | undefined
>()

if (!ctx) {
throw new Error(
"useAuthedAccountMember must be used within an AccountProvider",
)
}

const { client } = useElasticPath()

const tokenStore = createCookieTokenStore(ctx.accountCookieName)
const authedAccountMemberId = resolveAccountMemberIdFromToken(
client,
tokenStore,
)
const accountCookie = Cookies.get(ctx.accountCookieName)
const parsedAccountCookie: AccountCredentials | undefined =
accountCookie && JSON.parse(accountCookie)

const selectedAccount =
parsedAccountCookie?.accounts[parsedAccountCookie?.selected]

const result = useAccountMember(authedAccountMemberId ?? "", {
enabled: !!accountCookie && !!authedAccountMemberId,
ep: { accountMemberToken: accountCookie },
const result = useAccountMember(parsedAccountCookie?.accountMemberId ?? "", {
enabled: !!accountCookie && !!parsedAccountCookie?.accountMemberId,
ep: { accountMemberToken: selectedAccount?.token },
})

return { ...result } as const
useEffect(() => {
setAccountMemberTokens(ctx.getAccountMemberTokens())
setSelectedAccountToken(ctx.getSelectedAccountToken())
}, [result.data])

return {
...result,
accountMemberTokens,
selectedAccountToken,
} as const
}
1 change: 1 addition & 0 deletions packages/react-shopper-hooks/src/account/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./hooks/use-account-member"
export * from "./hooks/use-authed-account-member"
export * from "./account-provider"
export * from "./hooks/use-account-addresses"
30 changes: 7 additions & 23 deletions packages/react-shopper-hooks/src/account/login-account.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AccountMember, Moltin, Resource } from "@moltin/sdk"
import { AccountMember, AccountTokenBase, Moltin, Resource } from "@moltin/sdk"
import Cookies from "js-cookie"
import jwtDecode from "jwt-decode"

Expand Down Expand Up @@ -57,7 +57,12 @@ export async function resolveAccountMember(
return undefined
}

const decodedToken = token ? jwtDecode<{ sub?: string }>(token) : undefined
const parsedToken: AccountTokenBase & { account_member_id: string } =
JSON.parse(token)

const decodedToken = parsedToken?.token
? jwtDecode<{ sub?: string }>(parsedToken.token)
: undefined

if (!decodedToken) {
return undefined
Expand Down Expand Up @@ -89,24 +94,3 @@ export async function resolveAccountMember(
return undefined
}
}

export function resolveAccountMemberIdFromToken(
client: Moltin,
tokenStore: TokenStore,
) {
const token = tokenStore.getToken()

if (!token) {
return undefined
}

const decodedToken = token ? jwtDecode<{ sub?: string }>(token) : undefined

if (!decodedToken) {
return undefined
}

const { sub: accountMemberId } = decodedToken

return accountMemberId
}
7 changes: 7 additions & 0 deletions packages/react-shopper-hooks/src/account/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AccountTokenBase } from "@moltin/sdk"

export type AccountCredentials = {
accountMemberId: string
accounts: Record<string, AccountTokenBase>
selected: string
}
Loading

0 comments on commit 5f0df93

Please sign in to comment.