From 328517ce50e0d3a0334d3a789c8e72d0eee68394 Mon Sep 17 00:00:00 2001 From: Robert Field Date: Tue, 24 Oct 2023 17:16:29 +0200 Subject: [PATCH] feat: add navigation to shopper store context --- .../lib/store/store-provider.tsx | 6 +- .../lib/store/types/store-context-types.ts | 11 +-- .../lib/store/use-store.tsx | 4 +- packages/shopper-common/src/index.ts | 3 +- .../src/navigation/build-navigation.ts | 89 +++++++++++++++++++ .../shopper-common/src/navigation/index.ts | 2 + .../src/navigation/navigation-types.ts | 15 ++++ .../src/navigation/services/hierarchy.ts | 28 ++++++ 8 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 packages/shopper-common/src/navigation/build-navigation.ts create mode 100644 packages/shopper-common/src/navigation/index.ts create mode 100644 packages/shopper-common/src/navigation/navigation-types.ts create mode 100644 packages/shopper-common/src/navigation/services/hierarchy.ts diff --git a/packages/react-shopper-hooks/lib/store/store-provider.tsx b/packages/react-shopper-hooks/lib/store/store-provider.tsx index 37056270..ea8c83c9 100644 --- a/packages/react-shopper-hooks/lib/store/store-provider.tsx +++ b/packages/react-shopper-hooks/lib/store/store-provider.tsx @@ -4,10 +4,12 @@ import { emitter } from "@lib/event/event-context" import { CartProvider } from "@lib/cart" import { createContext, Dispatch, SetStateAction, useState } from "react" import type { Moltin as EPCCClient } from "@moltin/sdk" +import { NavigationNode } from "@elasticpath/shopper-common" interface StoreState { client: EPCCClient setClient: Dispatch> + nav?: NavigationNode[] } export const StoreProviderContext = createContext(null) @@ -22,7 +24,9 @@ export const StoreProvider = ({ const [client, setClient] = useState(initialClient) return ( - + string } -export interface NavigationNode { - name: string - slug: string - href: string - id: string - children: NavigationNode[] -} - interface StoreContextBase { nav: NavigationNode[] } diff --git a/packages/react-shopper-hooks/lib/store/use-store.tsx b/packages/react-shopper-hooks/lib/store/use-store.tsx index ce90976f..53dc9180 100644 --- a/packages/react-shopper-hooks/lib/store/use-store.tsx +++ b/packages/react-shopper-hooks/lib/store/use-store.tsx @@ -1,8 +1,9 @@ import { useContext } from "react" import { StoreProviderContext } from "@lib/store/store-provider" import { Moltin } from "@moltin/sdk" +import { NavigationNode } from "@elasticpath/shopper-common" -export function useStore(): { client: Moltin } { +export function useStore(): { client: Moltin; nav?: NavigationNode[] } { const ctx = useContext(StoreProviderContext) if (!ctx) { @@ -13,5 +14,6 @@ export function useStore(): { client: Moltin } { return { client: ctx.client, + nav: ctx.nav, } } diff --git a/packages/shopper-common/src/index.ts b/packages/shopper-common/src/index.ts index e7c7e32f..7e9d9ac8 100644 --- a/packages/shopper-common/src/index.ts +++ b/packages/shopper-common/src/index.ts @@ -1,2 +1,3 @@ export * from "./products" -export * from "./shared" \ No newline at end of file +export * from "./shared" +export * from "./navigation" diff --git a/packages/shopper-common/src/navigation/build-navigation.ts b/packages/shopper-common/src/navigation/build-navigation.ts new file mode 100644 index 00000000..e5850922 --- /dev/null +++ b/packages/shopper-common/src/navigation/build-navigation.ts @@ -0,0 +1,89 @@ +import type { Hierarchy, Moltin as EPCCClient } from "@moltin/sdk" +import { + getHierarchies, + getHierarchyChildren, + getHierarchyNodes, +} from "./services/hierarchy" +import { ISchema, NavigationNode } from "./navigation-types" + +export async function buildSiteNavigation( + client: EPCCClient, +): Promise { + // Fetch hierarchies to be used as top level nav + const hierarchies = await getHierarchies(client) + return constructTree(hierarchies, client) +} + +/** + * Construct hierarchy tree, limited to 5 hierarchies at the top level + */ +function constructTree( + hierarchies: Hierarchy[], + client: EPCCClient, +): Promise { + const tree = hierarchies + .slice(0, 4) + .map((hierarchy) => + createNode({ + name: hierarchy.attributes.name, + id: hierarchy.id, + slug: hierarchy.attributes.slug, + }), + ) + .map(async (hierarchy) => { + // Fetch first-level nav ('parent nodes') - the direct children of each hierarchy + const directChildren = await getHierarchyChildren(hierarchy.id, client) + // Fetch all nodes in each hierarchy (i.e. all 'child nodes' belonging to a hierarchy) + const allNodes = await getHierarchyNodes(hierarchy.id, client) + + // Build 2nd level by finding all 'child nodes' belonging to each first level featured-nodes + const directs = directChildren.slice(0, 4).map((child) => { + const children: ISchema[] = allNodes + .filter((node) => node?.relationships?.parent.data.id === child.id) + .map((node) => + createNode({ + name: node.attributes.name, + id: node.id, + slug: node.attributes.slug, + hrefBase: `${hierarchy.href}/${child.attributes.slug}`, + }), + ) + + return createNode({ + name: child.attributes.name, + id: child.id, + slug: child.attributes.slug, + hrefBase: hierarchy.href, + children, + }) + }) + + return { ...hierarchy, children: directs } + }) + + return Promise.all(tree) +} + +interface CreateNodeDefinition { + name: string + id: string + slug?: string + hrefBase?: string + children?: ISchema[] +} + +function createNode({ + name, + id, + slug = "missing-slug", + hrefBase = "", + children = [], +}: CreateNodeDefinition): ISchema { + return { + name, + id, + slug, + href: `${hrefBase}/${slug}`, + children, + } +} diff --git a/packages/shopper-common/src/navigation/index.ts b/packages/shopper-common/src/navigation/index.ts new file mode 100644 index 00000000..63f5a445 --- /dev/null +++ b/packages/shopper-common/src/navigation/index.ts @@ -0,0 +1,2 @@ +export * from "./build-navigation" +export * from "./navigation-types" diff --git a/packages/shopper-common/src/navigation/navigation-types.ts b/packages/shopper-common/src/navigation/navigation-types.ts new file mode 100644 index 00000000..502030f8 --- /dev/null +++ b/packages/shopper-common/src/navigation/navigation-types.ts @@ -0,0 +1,15 @@ +export interface ISchema { + name: string + slug: string + href: string + id: string + children: ISchema[] +} + +export interface NavigationNode { + name: string + slug: string + href: string + id: string + children: NavigationNode[] +} diff --git a/packages/shopper-common/src/navigation/services/hierarchy.ts b/packages/shopper-common/src/navigation/services/hierarchy.ts new file mode 100644 index 00000000..b3d7b6a9 --- /dev/null +++ b/packages/shopper-common/src/navigation/services/hierarchy.ts @@ -0,0 +1,28 @@ +import type { Node, Hierarchy } from "@moltin/sdk" +import { Moltin as EPCCClient } from "@moltin/sdk" + +export async function getHierarchies(client: EPCCClient): Promise { + const result = await client.ShopperCatalog.Hierarchies.All() + return result.data +} + +export async function getHierarchyChildren( + hierarchyId: string, + client: EPCCClient, +): Promise { + const result = await client.ShopperCatalog.Hierarchies.GetHierarchyChildren({ + hierarchyId, + }) + return result.data +} + +export async function getHierarchyNodes( + hierarchyId: string, + client: EPCCClient, +): Promise { + const result = await client.ShopperCatalog.Hierarchies.GetHierarchyNodes({ + hierarchyId, + }) + + return result.data +}