Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
79 changes: 36 additions & 43 deletions components/cms-modern/Navigation/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,49 +64,42 @@ const Navigation = ({ pages, style }: NavigationProps) => {
return (
<nav className="navigation" style={style}>
<ul className="navigation__list">
{pages
.sort((p1, p2) => {
const menu1Priority = p1.content?.menu?.priority || 0;
const menu2Priority = p2.content?.menu?.priority || 0;
return menu1Priority > menu2Priority ? 1 : menu1Priority < menu2Priority ? -1 : 0;
})
.map(({ title, href = '', children = [], content, category }, index) => {
return (
<li
key={index}
className={clsx('navigation__list__item', {
['navigation__list__item--active']: isRouteActive(href || '', category),
['navigation__list__item--open']: isMenuOpen(index),
})}
>
{title && href && (
<Link
href={href || ''}
onClick={(event) =>
children.length === 0 ? handleRouteChange() : handleClick(event, index)
}
className="navigation__list__item__link"
title={`priority: ${content?.menu?.priority}`}
>
{title}
</Link>
)}
{children.length > 0 ? (
<MegaMenu
content={content?.menu?.content}
handleRouteChange={handleRouteChange}
closeMenu={closeMenu}
title={title}
href={href || ''}
>
{children}
</MegaMenu>
) : (
<div></div>
)}
</li>
);
})}
{pages.map(({ title, href = '', children = [], content, category }, index) => {
return (
<li
key={index}
className={clsx('navigation__list__item', {
['navigation__list__item--active']: isRouteActive(href || '', category),
['navigation__list__item--open']: isMenuOpen(index),
})}
>
{title && href && (
<Link
href={href || ''}
onClick={(event) =>
children.length === 0 ? handleRouteChange() : handleClick(event, index)
}
className="navigation__list__item__link"
>
{title}
</Link>
)}
{children.length > 0 ? (
<MegaMenu
content={content?.menu?.content}
handleRouteChange={handleRouteChange}
closeMenu={closeMenu}
title={title}
href={href || ''}
>
{children}
</MegaMenu>
) : (
<div></div>
)}
</li>
);
})}
</ul>
<ul className="navigation__list--search">
<li className="navigation__list__item navigation__list__item--search" key={pages.length}>
Expand Down
44 changes: 18 additions & 26 deletions components/core/Masthead/NavigationContext.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { createContext, useMemo, useContext, PropsWithChildren } from 'react';
import { CmsHierarchyNode } from '@lib/cms/fetchHierarchy';
import { CmsContent } from '@lib/cms/CmsContent';
import walkNavigation, { enrichCmsEntries, getTypeFromSchema } from './walkNavigation';
import walkNavigation, { enrichHierarchyNodes, getTypeFromSchema } from './walkNavigation';
import { useUserContext } from '@lib/user/UserContext';
import { Category } from '@amplience/dc-integration-middleware';

export type NavigationItem = {
type: 'page' | 'external-page' | 'category' | 'group';
type: 'page' | 'external-page' | 'category' | 'group' | 'ecommerce-container-generated';
title: string;
href?: string;
children: NavigationItem[];
parents: NavigationItem[];

content?: CmsContent;
category?: any;
category?: Category;

nodeContentItem?: CmsContent;
};
Expand All @@ -22,26 +23,28 @@ export type NavigationState = {
findByHref: (href: string) => NavigationItem | undefined;
};

export type CategoryById = Record<string, Category>;

const NavigationContext = createContext<NavigationState | null>(null);

interface WithNavigationContextProps extends PropsWithChildren {
pages: CmsHierarchyNode;
categories: any;
categories: Category[];
}

export const WithNavigationContext = ({ pages, categories, children }: WithNavigationContextProps) => {
const { language } = useUserContext();
const flattenCategories = (categories: any[]) => {
const allCategories: any[] = [];
const bulldozeCategories = (cat: any) => {
const flattenCategories = (categories: Category[]) => {
const allCategories: Category[] = [];
const bulldozeCategories = (cat: Category) => {
allCategories.push(cat);
cat.children && cat.children.forEach(bulldozeCategories);
};
categories.forEach(bulldozeCategories);
return allCategories;
};
const categoriesById = useMemo(() => {
const result: any = {};
const result: CategoryById = {};
for (let item of flattenCategories(categories)) {
result[item.id] = item;
}
Expand All @@ -51,14 +54,14 @@ export const WithNavigationContext = ({ pages, categories, children }: WithNavig
const rootItems = useMemo(() => {
const buildCategoryItem = (
cmsCategory: CmsHierarchyNode | undefined,
ecommerceCategory: any | undefined,
ecommerceCategory: Category | undefined,
): NavigationItem | null => {
if (!cmsCategory && !ecommerceCategory) {
return null;
}
const children: NavigationItem[] = [];
const result = {
type: 'category',
type: getTypeFromSchema(cmsCategory?.content?._meta?.schema) || 'category',
title: ecommerceCategory?.name,
href: cmsCategory?.content?._meta?.deliveryKey
? `/category/${cmsCategory?.content?._meta?.deliveryKey.split('/')[1]}`
Expand Down Expand Up @@ -154,7 +157,8 @@ export const WithNavigationContext = ({ pages, categories, children }: WithNavig
}
switch (type) {
case 'category':
let category = categoriesById[node.content.name];
case 'ecommerce-container-generated':
const category = categoriesById[node.content.name];
return buildCategoryItem(node, category);
case 'group':
return buildGroupItem(node);
Expand All @@ -163,35 +167,23 @@ export const WithNavigationContext = ({ pages, categories, children }: WithNavig
case 'external-page':
return buildExternalPageItem(node);
}

return null;
};

const buildCmsEntries = (children: CmsHierarchyNode[] = []): NavigationItem[] => {
children.sort(function (a: any, b: any) {
const priorityA = a.menu?.priority || a.priority || a.title;
const priorityB = b.menu?.priority || b.priority || b.title;

if (priorityA < priorityB) {
return 1;
} else if (priorityA > priorityB) {
return -1;
}
return 0;
});
return children.map(buildCmsItem).filter((x) => x != null) as NavigationItem[];
};

enrichCmsEntries(pages, categoriesById, categories);
const enrichedPages = enrichHierarchyNodes(pages, categoriesById);

const rootEntries = buildCmsEntries(pages.children);
const rootEntries = buildCmsEntries(enrichedPages.children);
rootEntries.forEach((rootEntry) => {
walkNavigation(rootEntry, (node: NavigationItem, parents: NavigationItem[]) => {
node.parents = parents;
});
});
return rootEntries as NavigationItem[];
}, [pages, categories, categoriesById, language]);
}, [pages, categoriesById, language]);

const findByHref = (href: string) => {
let result: NavigationItem | undefined;
Expand Down
136 changes: 57 additions & 79 deletions components/core/Masthead/walkNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { CmsHierarchyNode } from '@lib/cms/fetchHierarchy';
import { NavigationItem } from './NavigationContext';
import { CategoryById, NavigationItem } from './NavigationContext';
import { Category } from '@amplience/dc-integration-middleware';

export default function walkNavigation(
current: NavigationItem,
visitor: (item: NavigationItem, parents: NavigationItem[]) => void,
parents: NavigationItem[] = []
parents: NavigationItem[] = [],
) {
visitor(current, parents);
for (let child of current.children) {
Expand All @@ -14,7 +15,7 @@ export default function walkNavigation(

export function walkNavigationItems(
items: NavigationItem[],
visitor: (item: NavigationItem, parents: NavigationItem[]) => void
visitor: (item: NavigationItem, parents: NavigationItem[]) => void,
) {
for (let item of items) {
walkNavigation(item, visitor);
Expand All @@ -31,88 +32,65 @@ export function getTypeFromSchema(schema: string) {
return 'group';
case 'https://demostore.amplience.com/site/pages/category':
return 'category';
case 'https://demostore.amplience.com/site/pages/ecommerce-container':
return 'ecommerce-container';
case 'https://demostore.amplience.com/site/pages/ecommerce-container-generated':
return 'ecommerce-container-generated';
}
return null;
}

export function enrichCmsEntries(cmsEntry: CmsHierarchyNode, categoriesById: any, categories: any) {
// Generate fake cms entries for categories that exist on the backend,
// are flagged as being visible in menu, and don't have an existing cms entry.

const myType = getTypeFromSchema(cmsEntry.content?._meta?.schema);

if (!cmsEntry.content.ecommCategories) {
categories = null;
} else if (categories == null && myType === 'category') {
// Locate the category by ID.

categories = categoriesById[cmsEntry.content.name]?.children;
}

if (categories == null) {
for (const child of cmsEntry.children) {
enrichCmsEntries(child, categoriesById, null);
}
} else {
const children = cmsEntry.children;
const remainingChildren = [...children];

let generated = 0;
for (const category of categories) {
// Does a CMS category exist for this entry?

let cmsChild = children.find((child) => {
const type = getTypeFromSchema(child.content?._meta?.schema);
if (!type) {
return false;
}
export function generateCmsCategory(
{ id, name, slug, children }: Category,
options?: { active: boolean },
): CmsHierarchyNode {
return {
content: {
_meta: {
name,
schema: 'https://demostore.amplience.com/site/pages/ecommerce-container-generated',
deliveryKey: `category/${slug}`,
hierarchy: {
parentId: 'generated',
root: false,
},
deliveryId: id,
},
hideProductList: false,
components: [],
slots: [],
active: options?.active ?? true,
menu: {
hidden: false,
},
name: id,
},
children: children.map((child) => generateCmsCategory(child)),
};
}

return type === 'category' && child.content.name === category.id;
export function enrichHierarchyNodes(rootCmsNode: CmsHierarchyNode, categoriesById: CategoryById): CmsHierarchyNode {
const enrichedRootNodeChildren = rootCmsNode.children.reduce((cmsNodes: CmsHierarchyNode[], childNode) => {
const childNodeType = getTypeFromSchema(childNode.content?._meta?.schema);
if (childNodeType === 'ecommerce-container') {
const enrichedChildNodes = childNode.content.name.map((n: string) => {
return enrichHierarchyNodes(
generateCmsCategory(categoriesById[n], {
active: childNode.content?.active,
}),
categoriesById,
);
});

let pushCount = 0;

if (!cmsChild && category.showInMenu) {
// Create a dummy one, if it's meant to be visible
generated++;
cmsChild = {
content: {
_meta: {
name: category.name,
schema: 'https://demostore.amplience.com/site/pages/category',
deliveryKey: 'category/' + category.slug,
hierarchy: {
parentId: 'generated',
root: false,
},
deliveryId: category.id,
},
ecommCategories: true,
hideProductList: false,
components: [],
slots: [],
active: true,
menu: {
hidden: false,
priority: generated * 10,
},
name: category.id,
},
children: [],
};

children.splice(pushCount++, 0, cmsChild);
}

if (cmsChild) {
enrichCmsEntries(cmsChild, categoriesById, category.children);

remainingChildren.splice(remainingChildren.indexOf(cmsChild), 1);
}
return [...cmsNodes, ...enrichedChildNodes];
}

for (const cmsChild of remainingChildren) {
enrichCmsEntries(cmsChild, categoriesById, null);
if (childNodeType === 'category') {
childNode.children = categoriesById[childNode.content.name]?.children.map((child) =>
generateCmsCategory(child),
);
return [...cmsNodes, childNode];
}
}
return [...cmsNodes, enrichHierarchyNodes(childNode, categoriesById)];
}, []);

return { content: rootCmsNode.content, children: enrichedRootNodeChildren };
}
Loading