Skip to content

Commit

Permalink
Merge branch 'main' into DEV-2106-docs-redirect-startup
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-calabrese authored Feb 10, 2025
2 parents b39169d + 53d7e31 commit 93acdc0
Show file tree
Hide file tree
Showing 19 changed files with 370 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/twenty-cougars-begin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nextjs-website": major
---

Add release note page to product section
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import ProductLayout, {
ProductLayoutProps,
} from '@/components/organisms/ProductLayout/ProductLayout';
import { Product } from '@/lib/types/product';
import { SEO } from '@/lib/types/seo';
import { generateStructuredDataScripts } from '@/helpers/generateStructuredDataScripts.helpers';
import {
convertSeoToStructuredDataArticle,
productToBreadcrumb,
} from '@/helpers/structuredData.helpers';
import { getGitBookSubPaths, getReleaseNote } from '@/lib/api';
import {
getCachedUrlReplaceMapProps,
getReleaseNotesProps,
} from '@/lib/cmsApi';
import {
BreadcrumbItem,
gitBookPageToBreadcrumbs,
productPageToBreadcrumbs,
} from '@/helpers/breadcrumbs.helpers';
import GitBookTemplate from '@/components/templates/GitBookTemplate/GitBookTemplate';
import React from 'react';
import { BannerLinkProps } from '@/components/atoms/BannerLink/BannerLink';
import { Metadata } from 'next';
import {
makeMetadata,
makeMetadataFromStrapi,
} from '@/helpers/metadata.helpers';
import { BreadcrumbSegment } from '@/lib/types/path';
import { baseUrl } from '@/config';

type ReleaseNotePageStaticParams = {
productSlug: string;
releaseNoteSubPathSlugs: string[];
};

export async function generateStaticParams() {
return (await getReleaseNotesProps()).map((releaseNoteProps) => {
return {
productSlug: releaseNoteProps.product.slug,
releaseNoteSubPathSlugs: [
'release-note',
...getGitBookSubPaths(releaseNoteProps.page.path),
],
};
});
}

export async function generateMetadata({
params,
}: {
params: ReleaseNotePageStaticParams;
}): Promise<Metadata> {
const {
page: { path, title },
seo,
} = await getReleaseNote(
params?.productSlug,
params?.releaseNoteSubPathSlugs
);

if (seo) {
return makeMetadataFromStrapi(seo);
}

return makeMetadata({
title,
url: path,
});
}

export type ReleaseNotePageProps = {
readonly bannerLinks?: BannerLinkProps[];
readonly dirName: string;
readonly landingFile: string;
readonly path: string;
readonly product: Product;
readonly seo?: SEO;
readonly title: string;
} & ProductLayoutProps;

const ReleaseNotePage = async ({
params,
}: {
params: ReleaseNotePageStaticParams;
}) => {
const { bannerLinks, page, path, product, seo, source, title, bodyConfig } =
await getReleaseNote(params.productSlug, params.releaseNoteSubPathSlugs);

const urlReplaceMap = await getCachedUrlReplaceMapProps();

const props = {
...page,
pathPrefix: source.pathPrefix,
bodyConfig: {
...bodyConfig,
isPageIndex: page.isIndex,
pagePath: page.path,
assetsPrefix: source.assetsPrefix,
urlReplaces: urlReplaceMap,
},
};

const breadcrumbs: readonly BreadcrumbSegment[] = gitBookPageToBreadcrumbs(
bodyConfig.pagePath,
bodyConfig.gitBookPagesWithTitle
);

const breadcrumbsItems: BreadcrumbItem[] = breadcrumbs.map((breadcrumb) => ({
name: breadcrumb.name,
item: [baseUrl, breadcrumb.path].join(''),
}));

const structuredData = generateStructuredDataScripts({
breadcrumbsItems: [
productToBreadcrumb(product),
{
name: title,
item: `${baseUrl}/${product.slug}/release-note`,
},
...breadcrumbsItems,
],
seo: seo,
things: [convertSeoToStructuredDataArticle(seo)],
});

return (
<ProductLayout
product={product}
path={path}
bannerLinks={bannerLinks}
structuredData={structuredData}
>
<GitBookTemplate
menuName={title}
breadcrumbs={[
...productPageToBreadcrumbs(product, [
{
name: title,
path: `/${product.slug}/release-note`,
},
...breadcrumbs,
]),
]}
hasInPageMenu={false}
{...props}
/>
</ProductLayout>
);
};

export default ReleaseNotePage;
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, getGitBookSubPaths } from '@/lib/api';
import { Product } from '@/lib/types/product';
import React from 'react';
import { ParseContentConfig } from 'gitbook-docs/parseContent';
Expand All @@ -28,7 +28,7 @@ type Params = {
export async function generateStaticParams() {
return (await getGuidesProps()).map((guidePage) => ({
productSlug: guidePage.product.slug,
productGuidePage: getProductGuidePath(guidePage.page.path),
productGuidePage: getGitBookSubPaths(guidePage.page.path),
}));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type GitBookTemplateProps = {
versions?: GuideMenuItemsProps['versions'];
versionName?: GuideMenuItemsProps['versionName'];
hasHeader?: boolean;
hasInPageMenu?: boolean;
} & Pick<
ProductGuidePageProps,
'menu' | 'body' | 'bodyConfig' | 'path' | 'pathPrefix'
Expand All @@ -36,6 +37,7 @@ const GitBookTemplate = ({
menuDistanceFromTop,
contentMarginTop = 75,
hasHeader = true,
hasInPageMenu = true,
}: GitBookTemplateProps) => {
const t = useTranslations();
const paddingTop = hasHeader ? '60px' : '-80px';
Expand Down Expand Up @@ -80,29 +82,31 @@ const GitBookTemplate = ({
<GitBookContent content={body} config={bodyConfig} />
</Box>
</Stack>
<Box
sx={{
display: { xs: 'none', lg: 'initial' },
position: 'relative',
padding: { lg: hasHeader ? '80px 64px' : '48px 64px' },
width: { lg: '378px' },
}}
>
{hasInPageMenu && (
<Box
sx={{
position: 'sticky',
maxWidth: '378px',
top: hasHeader ? 144 : 64,
display: { xs: 'none', lg: 'initial' },
position: 'relative',
padding: { lg: hasHeader ? '80px 64px' : '48px 64px' },
width: { lg: '378px' },
}}
>
<GuideInPageMenu
assetsPrefix={bodyConfig.assetsPrefix}
pagePath={path}
inPageMenu={body}
title={t('productGuidePage.onThisPage')}
/>
<Box
sx={{
position: 'sticky',
maxWidth: '378px',
top: hasHeader ? 144 : 64,
}}
>
<GuideInPageMenu
assetsPrefix={bodyConfig.assetsPrefix}
pagePath={path}
inPageMenu={body}
title={t('productGuidePage.onThisPage')}
/>
</Box>
</Box>
</Box>
)}
</Box>
</FragmentProvider>
);
Expand Down
27 changes: 27 additions & 0 deletions apps/nextjs-website/src/helpers/breadcrumbs.helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BreadcrumbSegment } from '@/lib/types/path';
import { Product } from '@/lib/types/product';

export type BreadcrumbItem = { readonly name?: string; readonly item?: string };

export function productPageToBreadcrumbs(
product: Product,
breadcrumbSegments?: readonly BreadcrumbSegment[]
Expand Down Expand Up @@ -41,3 +43,28 @@ export function pageToBreadcrumbs(
...(paths || []),
];
}

export function gitBookPageToBreadcrumbs(
pagePath: string,
gitBookPagesWithTitle: readonly {
readonly title: string;
readonly path: string;
}[]
): readonly BreadcrumbSegment[] {
// Generate a list of hierarchical breadcrumb paths from the page path
// (e.g., '/send/release-note/2023/16-ottobre-2023' -> [ '/send/release-note/2023', '/send/release-note/2023/16-ottobre-2023'])
const currentPageBreadcrumbPaths = pagePath
.split('/') // Split the path into parts based on '/'
.filter(Boolean) // Remove empty segments (e.g., leading or trailing slashes)
.map((_, index, arr) => '/' + arr.slice(0, index + 1).join('/')) // Reconstruct breadcrumb paths
.slice(2); // Ignore the first two levels

// Match breadcrumb paths with available pages and sort them in hierarchical order
return gitBookPagesWithTitle
.filter(({ path }) => currentPageBreadcrumbPaths.includes(path)) // Keep only the paths present in breadcrumbs
.map(({ title, path }) => ({
name: title,
path,
}))
.sort((a, b) => a.path.length - b.path.length); // Sort breadcrumbs by path length (ensuring hierarchy)
}
17 changes: 17 additions & 0 deletions apps/nextjs-website/src/helpers/makeDocs.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { parseDoc } from 'gitbook-docs/parseDoc';
import { BannerLinkProps } from '@/components/atoms/BannerLink/BannerLink';
import { SolutionTemplateProps } from '@/components/templates/SolutionTemplate/SolutionTemplate';
import { SEO } from '@/lib/types/seo';
import { ReleaseNotePageProps } from '@/app/[productSlug]/[...releaseNoteSubPathSlugs]/page';

export type TutorialsDefinition = {
readonly product: Product;
Expand Down Expand Up @@ -133,3 +134,19 @@ export const makeSolution = (solution: SolutionTemplateProps) =>
],
parseDocOrThrow
);

export const makeReleaseNote = (releaseNote: ReleaseNotePageProps) =>
pipe(
[
{
...releaseNote,
source: {
pathPrefix: `/${releaseNote.product.slug}/release-note`,
assetsPrefix: `${docsAssetsPath}/${releaseNote.dirName}`,
dirPath: `${docsPath}/${releaseNote.dirName}`,
spaceId: releaseNote.dirName,
},
},
],
parseDocOrThrow
);
9 changes: 9 additions & 0 deletions apps/nextjs-website/src/helpers/productHeader.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,14 @@ export function productToMenuItems(
theme,
}
: null,
// if there's release notes data, add it to the menu
product.hasReleaseNotePage
? {
label: 'devPortal.productHeader.releaseNote',
href: `/${product.slug}/release-note`,
active: path.startsWith(`/${product.slug}/release-note`),
theme,
}
: null,
].filter((item) => item !== null) as readonly MenuDropdownProp[];
}
43 changes: 41 additions & 2 deletions apps/nextjs-website/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import {
getApiDataListPagesProps,
getApiDataProps,
getCaseHistoriesProps,
getSolutionsProps,
getGuideListPagesProps,
getGuidesProps,
getOverviewsProps,
getProductsProps,
getQuickStartGuidesProps,
getReleaseNotesProps,
getSolutionListPageProps,
getSolutionsProps,
getTutorialListPagesProps,
getTutorialsProps,
getWebinarsProps,
Expand Down Expand Up @@ -66,7 +67,7 @@ export async function getGuide(
};
}

export function getProductGuidePath(path: string) {
export function getGitBookSubPaths(path: string) {
// the filter is to remove the first 3 elements of the path which are
// an empty string (the path begins with a / symbol), the product slug and 'guides' hard-coded string
return path.split('/').filter((p, index) => index > 2);
Expand Down Expand Up @@ -194,6 +195,44 @@ export async function getApiData(apiDataSlug: string) {
return props;
}

export async function getReleaseNote(
productSlug?: string,
releaseNoteSubPathSlugs?: readonly string[]
) {
const products = await getProducts();
const releaseNotesProps = await getReleaseNotesProps();
const releaseNotesPath = releaseNoteSubPathSlugs?.join('/');
const path = `/${productSlug}/${releaseNotesPath}`;

const releaseNoteProps = manageUndefined(
(await getReleaseNotesProps()).find(
(releaseNoteData) => releaseNoteData.page.path === path
)
);

const gitBookPagesWithTitle = releaseNotesProps.map((content) => ({
title: content.page.title,
path: content.page.path,
}));

const spaceToPrefix = releaseNotesProps.map((content) => ({
spaceId: content.source.spaceId,
pathPrefix: content.source.pathPrefix,
}));

return {
...releaseNoteProps,
products,
bodyConfig: {
isPageIndex: releaseNoteProps.page.isIndex,
pagePath: releaseNoteProps.page.path,
assetsPrefix: releaseNoteProps.source.assetsPrefix,
gitBookPagesWithTitle,
spaceToPrefix,
},
};
}

export async function getSolution(solutionSlug?: string) {
const props = manageUndefined(
(await getSolutionsProps()).find(({ slug }) => slug === solutionSlug)
Expand Down
Loading

0 comments on commit 93acdc0

Please sign in to comment.