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

[DEV-1955] Dynamic menu based on strapi products #1179

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
81e2d29
Add bannerLinks attribute to Product codec
marcobottaro Sep 20, 2024
492999c
Fix CaseHistoriesCodec product attribute type
marcobottaro Sep 20, 2024
41f243c
Add bannerLinks attribute to Product codec and add BaseProductWithBan…
marcobottaro Sep 23, 2024
2630da2
Move fetchProducts to a dedicated file
marcobottaro Sep 23, 2024
4adc511
Merge branch 'main' into DEV-1928-refactor-product-codec
marcobottaro Sep 23, 2024
f2c4763
Add relationships to Product codec
marcobottaro Sep 23, 2024
e862bda
Add changeset
marcobottaro Sep 23, 2024
6aae6f2
Fix comment
marcobottaro Sep 23, 2024
a415d0f
Merge remote-tracking branch 'origin/main' into DEV-1928-refactor-pro…
marcobottaro Sep 25, 2024
40cc214
Fix linting
marcobottaro Sep 25, 2024
8305590
Add seo to makeStrapiGuidesPopulate
marcobottaro Sep 25, 2024
8cd2b48
Remove unused BaseOverviewAttributesCodec and BaseOverviewCodec
marcobottaro Sep 30, 2024
3be2980
Merge remote-tracking branch 'origin/main' into DEV-1928-refactor-pro…
marcobottaro Oct 1, 2024
f87bff5
data preparation
petros-double-test1 Oct 1, 2024
9eb163e
populate
petros-double-test1 Oct 2, 2024
21a70ee
linter
petros-double-test1 Oct 2, 2024
ce3abff
filter out products not having overview
petros-double-test1 Oct 2, 2024
175e1e9
add populate product to overview
petros-double-test1 Oct 2, 2024
36ab412
dyanmic menu
petros-double-test1 Oct 2, 2024
1a1b0bd
changeset
petros-double-test1 Oct 3, 2024
22b0072
Merge branch 'main' into DEV-1955
tommaso1 Oct 4, 2024
d0a798f
Merge remote-tracking branch 'origin/main' into DEV-1955
petros-double-test1 Oct 8, 2024
a263dba
add tutorial populate to make the build build again
petros-double-test1 Oct 9, 2024
8544d94
fix test
petros-double-test1 Oct 9, 2024
6256049
Merge branch 'main' into DEV-1955
marcobottaro Oct 10, 2024
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
5 changes: 5 additions & 0 deletions .changeset/quiet-foxes-reflect.md
Copy link
Collaborator

Choose a reason for hiding this comment

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

You should remove this file

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nextjs-website": patch
---

Add the bannerLinks attribute to the product codec and refactor its relationships
5 changes: 5 additions & 0 deletions .changeset/sharp-pears-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nextjs-website": minor
---

Filter product based on the presence of the overview data, Product menu depends on strapi configured relations with pages
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,7 @@ it('should convert product to menu items', () => {
'/path/overview_path',
themeLight
);
expect(menuItems.length).toEqual(3);
const overviewItem = menuItems.find(
({ href }) => href === product.subpaths.overview.path
);
expect(overviewItem).not.toBeUndefined();
expect(overviewItem?.label).toBe(product.subpaths.overview.name);
expect(overviewItem?.href).toBe(product.subpaths.overview.path);
expect(overviewItem?.active).toBeTruthy();
expect(overviewItem?.theme).toBe(themeLight);
expect(menuItems.length).toEqual(1);
});

it('should return the correct active value', () => {
Expand All @@ -60,15 +52,7 @@ it('should return the correct active value', () => {
'/guides/some-guide/some-guide-version/some-guide-page',
themeLight
);
expect(menuItems.length).toEqual(3);
const overviewItem = menuItems.find(
({ href }) => href === product.subpaths.guides?.path
);
expect(overviewItem).not.toBeUndefined();
expect(overviewItem?.label).toBe(product.subpaths.guides?.name);
expect(overviewItem?.href).toBe(product.subpaths.guides?.path);
expect(overviewItem?.active).toBeTruthy();
expect(overviewItem?.theme).toBe(themeLight);
expect(menuItems.length).toEqual(1);
});

it('should return the correct active value if the subpath.path contains the path', () => {
Expand All @@ -78,11 +62,5 @@ it('should return the correct active value if the subpath.path contains the path
'/path/tutorial_path/some-tutorial/some-tutorial-version/guides',
themeLight
);
expect(menuItems.length).toEqual(3);
const overviewItem = menuItems.find(
({ href }) => href === product.subpaths.guides?.path
);
expect(overviewItem).not.toBeUndefined();
expect(overviewItem?.active).toBeFalsy();
expect(overviewItem?.theme).toBe(themeLight);
expect(menuItems.length).toEqual(1);
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ProductLayout, {
ProductLayoutProps,
} from '@/components/organisms/ProductLayout/ProductLayout';
import { getGuide, getProductGuidePath } from '@/lib/api';
import { getGuide, getProduct, getProductGuidePath } from '@/lib/api';
import { Product } from '@/lib/types/product';
import React from 'react';
import {
Expand Down Expand Up @@ -82,11 +82,13 @@ const Page = async ({ params }: { params: Params }) => {
params?.productGuidePage ?? ['']
);

const fetchedProduct = await getProduct(params.productSlug);

const { product, page, guide, version, versions, source, bannerLinks, seo } =
guideProps;
const props: ProductGuidePageProps = {
...page,
product,
product: fetchedProduct ?? product,
guide,
version,
versions: Array.from(versions),
Expand All @@ -104,10 +106,10 @@ const Page = async ({ params }: { params: Params }) => {

const structuredData = generateStructuredDataScripts({
breadcrumbsItems: [
productToBreadcrumb(product),
productToBreadcrumb(props.product),
{
name: seo?.metaTitle,
item: breadcrumbItemByProduct(product, [
item: breadcrumbItemByProduct(props.product, [
'guides',
...(params?.productGuidePage || []),
]),
Expand Down
15 changes: 10 additions & 5 deletions apps/nextjs-website/src/app/[productSlug]/overview/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getOverview } from '@/lib/api';
import { getOverview, getProduct } from '@/lib/api';
import Hero from '@/editorialComponents/Hero/Hero';
import { Metadata, ResolvingMetadata } from 'next';
import ProductLayout, {
Expand Down Expand Up @@ -26,6 +26,7 @@ import {
convertSeoToStructuredDataArticle,
productToBreadcrumb,
} from '@/helpers/structuredData.helpers';
import { Path } from '@/lib/types/path';

const MAX_NUM_TUTORIALS_IN_OVERVIEW = 3;

Expand Down Expand Up @@ -124,7 +125,6 @@ const OverviewPage = async ({ params }: ProductParams) => {
hero,
startInfo,
feature,
product,
path,
tutorials,
postIntegration,
Expand All @@ -134,6 +134,8 @@ const OverviewPage = async ({ params }: ProductParams) => {
} = await getOverview(params.productSlug);
const { overview } = translations;

const product = await getProduct(params.productSlug);

const tutorialsListToShow = tutorials?.list
?.filter((tutorial) => tutorial.showInOverview)
.slice(0, MAX_NUM_TUTORIALS_IN_OVERVIEW);
Expand Down Expand Up @@ -175,16 +177,19 @@ const OverviewPage = async ({ params }: ProductParams) => {
cards={startInfo.cards}
/>
)}
{product.subpaths.tutorials && tutorials && (
{product?.tutorial_list_page?.data && tutorials && (
<TutorialsOverview
title={tutorials.title || overview.tutorial.title}
subtitle={tutorials.subtitle}
ctaLabel={overview.tutorial.ctaLabel}
tutorialPath={product.subpaths.tutorials}
tutorialPath={
product.subpaths.tutorials ??
({ name: 'tutorials', path: '/tutorials' } as Path)
}
tutorials={[...(tutorialsListToShow || [])]}
/>
)}
{product.subpaths.guides && postIntegration && (
{product?.guide_list_page?.data && postIntegration && (
<PostIntegration
title={postIntegration.title || overview.postIntegration.title}
subtitle={postIntegration.subtitle}
Expand Down
15 changes: 5 additions & 10 deletions apps/nextjs-website/src/app/[productSlug]/quick-start/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Abstract } from '@/editorialComponents/Abstract/Abstract';
import ProductLayout, {
ProductLayoutProps,
} from '@/components/organisms/ProductLayout/ProductLayout';
import { getProductsSlugs, getQuickStartGuide } from '@/lib/api';
import { getProduct, getProductsSlugs, getQuickStartGuide } from '@/lib/api';
import React from 'react';
import QuickStartGuideStepper from '@/components/molecules/QuickStartGuideStepper/QuickStartGuideStepper';
import { Step } from '@/lib/types/step';
Expand Down Expand Up @@ -59,15 +59,10 @@ export async function generateMetadata(
}

const QuickStartGuidesPage = async ({ params }: ProductParams) => {
const {
abstract,
bannerLinks,
defaultStepAnchor,
path,
product,
steps,
seo,
} = await getQuickStartGuide(params?.productSlug);
const { abstract, bannerLinks, defaultStepAnchor, path, steps, seo } =
await getQuickStartGuide(params?.productSlug);

const product = await getProduct(params.productSlug);

const structuredData = generateStructuredDataScripts({
breadcrumbsItems: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import ProductLayout, {
ProductLayoutProps,
} from '@/components/organisms/ProductLayout/ProductLayout';
import {
getProduct,
getStaticTutorial,
getStrapiTutorial,
getTutorialPaths,
Expand Down Expand Up @@ -126,9 +127,12 @@ const Page = async ({ params }: { params: Params }) => {

const tutorialProps = await getStaticTutorial(productSlug, [tutorialPath]);
const { product, page, bannerLinks, source, relatedLinks } = tutorialProps;

const fetchedProduct = await getProduct(params.productSlug);

const props: ProductTutorialPageProps = {
...page,
product,
product: fetchedProduct ?? product,
bannerLinks,
relatedLinks,
bodyConfig: {
Expand All @@ -145,7 +149,7 @@ const Page = async ({ params }: { params: Params }) => {

const structuredData = generateStructuredDataScripts({
breadcrumbsItems: [
productToBreadcrumb(product),
productToBreadcrumb(props.product),
{
name: tutorialProps.page.title,
item: breadcrumbItemByProduct(product, [
Expand Down
12 changes: 9 additions & 3 deletions apps/nextjs-website/src/app/[productSlug]/tutorials/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Product } from '@/lib/types/product';
import { Metadata, ResolvingMetadata } from 'next';
import { getProductsSlugs, getTutorialListPageProps } from '@/lib/api';
import {
getProduct,
getProductsSlugs,
getTutorialListPageProps,
} from '@/lib/api';
import { Abstract } from '@/editorialComponents/Abstract/Abstract';
import { Box } from '@mui/material';
import ProductLayout, {
Expand Down Expand Up @@ -62,11 +66,13 @@ export async function generateMetadata(

const TutorialsPage = async ({ params }: ProductParams) => {
const { productSlug } = params;
const { abstract, bannerLinks, path, product, tutorials, seo } =
const { abstract, bannerLinks, path, tutorials, seo } =
await getTutorialListPageProps(productSlug);

const { shared } = translations;

const product = await getProduct(params.productSlug);

const structuredData = generateStructuredDataScripts({
breadcrumbsItems: [
productToBreadcrumb(product),
Expand All @@ -93,7 +99,7 @@ const TutorialsPage = async ({ params }: ProductParams) => {
title={abstract?.title}
/>
)}
{product.subpaths.tutorials && tutorials && (
{product?.tutorial_list_page?.data && tutorials && (
<Box>
<Newsroom
items={tutorials.map((tutorial) => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ import Dropdown from '@/components/atoms/Dropdown/Dropdown';
import { Box, Link as LinkMui } from '@mui/material';
import Link from 'next/link';
import DesktopUserInfo from '@/components/atoms/DesktopUserInfo/DesktopUserInfo';
import React from 'react';
import React, { useMemo } from 'react';
import { SiteHeaderProps } from '@/components/molecules/SiteHeader/SiteHeader';
import { useTranslations } from 'next-intl';
import { Product } from '@/lib/types/product';

const DesktopSiteHeader = ({ products }: SiteHeaderProps) => {
const t = useTranslations('devPortal');

// Filter out products that don't have an overview page
const filteredProducts = useMemo(
() => products.filter((product: Product) => product.overview?.data),
[products]
);

return (
<Box
sx={{
Expand All @@ -22,7 +29,7 @@ const DesktopSiteHeader = ({ products }: SiteHeaderProps) => {
>
<Dropdown
label={t('siteHeader.products')}
items={products.map((product) => ({
items={filteredProducts.map((product) => ({
href: product.subpaths.overview.path,
label: product.name,
}))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
SiteHeaderProps,
} from '@/components/molecules/SiteHeader/SiteHeader';
import Button from '@mui/material/Button';
import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslations } from 'next-intl';
import { Box, Divider, useTheme } from '@mui/material';
import ArrowDropUp from '@mui/icons-material/ArrowDropUp';
Expand All @@ -14,6 +14,7 @@ import { styled } from '@mui/material/styles';
import Typography from '@mui/material/Typography';
import NextLink from 'next/link';
import MobileUserInfo from '@/components/atoms/MobileUserInfo/MobileUserInfo';
import { Product } from '@/lib/types/product';

export const MobileSiteHeaderStyledTreeItem = styled(TreeItem)(({ theme }) => ({
[`&`]: {
Expand Down Expand Up @@ -140,6 +141,12 @@ const MobileSiteHeader = ({ products }: SiteHeaderProps) => {
setIsOpen(!isOpen);
};

// Filter out products that don't have an overview page
const filteredProducts = useMemo(
() => products.filter((product: Product) => product.overview?.data),
[products]
);

return (
<Box
sx={{
Expand Down Expand Up @@ -184,7 +191,7 @@ const MobileSiteHeader = ({ products }: SiteHeaderProps) => {
label={t('siteHeader.products')}
disabled={false}
>
{products.map((product, index) => {
{filteredProducts.map((product, index) => {
return (
<Typography
key={index}
Expand Down
77 changes: 67 additions & 10 deletions apps/nextjs-website/src/helpers/productHeader.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,71 @@ export function productToMenuItems(
path: string,
theme: Theme
): readonly MenuDropdownProp[] {
return Object.entries(product.subpaths)
.filter(([name, subpath]) => !!name && !!subpath)
.map(([, subpath]) => {
return {
label: subpath.name,
href: subpath.path,
active: path.startsWith(subpath.path),
theme,
};
});
// total possible items and corresponding links are:
// Overview: app-io/overview
// Quickstart Guide: app-io/quick-start
// API: app-io/api or app-io/api/main if there's only one api in the list
// Tutorials: app-io/tutorials
// Guides: app-io/guides

return [
{
label: 'Overview',
href: `/${product.slug}/overview`,
active: path.startsWith(`/${product.slug}/overview`),
theme,
},
// if there's quiskstart guide data, add it to the menu
product.quickstart_guide?.data
? {
label: 'Quickstart Guide',
href: `/${product.slug}/quick-start`,
active: path.startsWith(`/${product.slug}/quick-start`),
theme,
}
: null,
// if there's api data, and consists of only one item in the list, add it to the menu
product.api_data_list_page?.data &&
product.api_data_list_page.data.attributes.apiData.data.length === 1
? {
label: 'API',
href: `/${product.slug}/api/${
product.api_data_list_page.data.attributes.apiData.data[0]
.attributes.apiRestDetail?.slug ??
product.api_data_list_page.data.attributes.apiData.data[0]
.attributes.apiSoapUrl
}`,
active: path.startsWith(`/${product.slug}/api/main`),
theme,
}
: null,
// if there's api data, and consists of more than one item in the list, add it to the menu
product.api_data_list_page?.data &&
product.api_data_list_page.data.attributes.apiData.data.length > 1
? {
label: 'API',
href: `/${product.slug}/api`,
active: path.startsWith(`/${product.slug}/api`),
theme,
}
: null,
// if there's tutorials data, add it to the menu
product.tutorial_list_page?.data
? {
label: 'Tutorials',
href: `/${product.slug}/tutorials`,
active: path.startsWith(`/${product.slug}/tutorials`),
theme,
}
: null,
// if there's guides data, add it to the menu
product.guide_list_page?.data
? {
label: 'Guides',
href: `/${product.slug}/guides`,
active: path.startsWith(`/${product.slug}/guides`),
theme,
}
: null,
].filter((item) => item !== null) as readonly MenuDropdownProp[];
}
Loading
Loading