Skip to content

Commit

Permalink
Showing 19 changed files with 306 additions and 113 deletions.
5 changes: 5 additions & 0 deletions .changeset/wild-days-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@bigcommerce/catalyst-core": minor
---

Adds support for nested web page children / trees. Restructure web page routing to support a layout file.
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import { getSessionCustomerAccessToken } from '~/auth';
import { client } from '~/client';
import { graphql, VariablesOf } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { BreadcrumbsFragment } from '~/components/breadcrumbs/fragment';
import { BreadcrumbsCategoryFragment } from '~/components/breadcrumbs/fragment';

const CategoryPageQuery = graphql(
`
@@ -38,7 +38,7 @@ const CategoryPageQuery = graphql(
}
}
`,
[BreadcrumbsFragment],
[BreadcrumbsCategoryFragment],
);

type Variables = VariablesOf<typeof CategoryPageQuery>;
24 changes: 15 additions & 9 deletions core/app/[locale]/(default)/account/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getTranslations, setRequestLocale } from 'next-intl/server';
import { PropsWithChildren } from 'react';

import { AccountLayout } from '@/vibes/soul/sections/account-layout';
import { SidebarMenu } from '@/vibes/soul/sections/sidebar-menu';
import { StickySidebarLayout } from '@/vibes/soul/sections/sticky-sidebar-layout';
import { auth } from '~/auth';
import { redirect } from '~/i18n/routing';

@@ -22,15 +23,20 @@ export default async function Layout({ children, params }: Props) {
}

return (
<AccountLayout
links={[
{ href: '/account/orders', label: t('orders') },
{ href: '/account/addresses', label: t('addresses') },
{ href: '/account/settings', label: t('settings') },
{ href: '/logout', label: t('logout') },
]}
<StickySidebarLayout
sidebar={
<SidebarMenu
links={[
{ href: '/account/orders', label: t('orders') },
{ href: '/account/addresses', label: t('addresses') },
{ href: '/account/settings', label: t('settings') },
{ href: '/logout', label: t('logout') },
]}
/>
}
sidebarSize="small"
>
{children}
</AccountLayout>
</StickySidebarLayout>
);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { clsx } from 'clsx';

import { Stream, Streamable } from '@/vibes/soul/lib/streamable';
import { Breadcrumb, Breadcrumbs, BreadcrumbsSkeleton } from '@/vibes/soul/primitives/breadcrumbs';
import { SectionLayout } from '@/vibes/soul/sections/section-layout';

export interface WebPage {
title: string;
content: string;
breadcrumbs: Breadcrumb[];
seo: {
pageTitle: string;
metaDescription: string;
@@ -17,18 +15,12 @@ export interface WebPage {
interface Props {
webPage: Streamable<WebPage>;
breadcrumbs?: Streamable<Breadcrumb[]>;
className?: string;
children?: React.ReactNode;
}

export function WebPageContent({
webPage: streamableWebPage,
className = '',
breadcrumbs,
children,
}: Props) {
export function WebPageContent({ webPage: streamableWebPage, breadcrumbs, children }: Props) {
return (
<SectionLayout className={clsx('mx-auto w-full max-w-4xl', className)}>
<section className="w-full max-w-4xl">
<Stream fallback={<WebPageContentSkeleton />} value={streamableWebPage}>
{(webPage) => {
const { title, content } = webPage;
@@ -52,7 +44,7 @@ export function WebPageContent({
);
}}
</Stream>
</SectionLayout>
</section>
);
}

@@ -78,7 +70,7 @@ function WebPageBodySkeleton() {
);
}

export function WebPageContentSkeleton() {
function WebPageContentSkeleton() {
return (
<div>
<div className="mx-auto w-full max-w-4xl pb-8 @2xl:pb-12 @4xl:pb-16">
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ import { cache } from 'react';
import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { BreadcrumbsWebPageFragment } from '~/components/breadcrumbs/fragment';

const ContactPageQuery = graphql(
`
@@ -12,6 +13,7 @@ const ContactPageQuery = graphql(
... on ContactPage {
entityId
name
...BreadcrumbsFragment
path
contactFields
htmlBody
@@ -32,7 +34,7 @@ const ContactPageQuery = graphql(
}
}
`,
[],
[BreadcrumbsWebPageFragment],
);

export const getWebpageData = cache(async (variables: { id: string }) => {
Original file line number Diff line number Diff line change
@@ -6,8 +6,12 @@ import { Breadcrumb } from '@/vibes/soul/primitives/breadcrumbs';
import { ButtonLink } from '@/vibes/soul/primitives/button-link';
import { DynamicForm } from '@/vibes/soul/primitives/dynamic-form';
import type { Field, FieldGroup } from '@/vibes/soul/primitives/dynamic-form/schema';
import {
breadcrumbsTransformer,
truncateBreadcrumbs,
} from '~/data-transformers/breadcrumbs-transformer';

import { WebPage, WebPageContent } from '../../_components/web-page';
import { WebPage, WebPageContent } from '../_components/web-page';

import { submitContactForm } from './_actions/submit-contact-form';
import { getWebpageData } from './page-data';
@@ -46,10 +50,13 @@ async function getWebPage(id: string): Promise<ContactPage> {
return notFound();
}

const breadcrumbs = breadcrumbsTransformer(webpage.breadcrumbs);

return {
entityId: webpage.entityId,
title: webpage.name,
path: webpage.path,
breadcrumbs,
content: webpage.htmlBody,
contactFields: webpage.contactFields,
seo: webpage.seo,
@@ -59,17 +66,20 @@ async function getWebPage(id: string): Promise<ContactPage> {

async function getWebPageBreadcrumbs(id: string): Promise<Breadcrumb[]> {
const webpage = await getWebPage(id);

return [
const [, ...rest] = webpage.breadcrumbs.reverse();
const breadcrumbs = [
{
label: 'Home',
href: '/',
},
...rest.reverse(),
{
label: webpage.title,
href: '#',
},
];

return truncateBreadcrumbs(breadcrumbs, 5);
}

async function getWebPageWithSuccessContent(id: string, message: string) {
88 changes: 88 additions & 0 deletions core/app/[locale]/(default)/webpages/[id]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client';
import { cache } from 'react';

import { SidebarMenu } from '@/vibes/soul/sections/sidebar-menu';
import { StickySidebarLayout } from '@/vibes/soul/sections/sticky-sidebar-layout';
import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';

interface Props extends React.PropsWithChildren {
params: Promise<{ id: string }>;
}

const WebPageChildrenQuery = graphql(`
query WebPageChildren($id: ID!) {
node(id: $id) {
... on WebPage {
children(first: 20) {
edges {
node {
name
... on NormalPage {
path
}
... on ContactPage {
path
}
... on RawHtmlPage {
path
}
... on ExternalLinkPage {
link
}
}
}
}
}
}
}
`);

interface PageLink {
label: string;
href: string;
}

const getWebPageChildren = cache(async (id: string): Promise<PageLink[]> => {
const { data } = await client.fetch({
document: WebPageChildrenQuery,
variables: { id: decodeURIComponent(id) },
fetchOptions: { next: { revalidate } },
});

if (!data.node) {
return [];
}

if (!('children' in data.node)) {
return [];
}

const { children } = data.node;

return removeEdgesAndNodes(children).reduce((acc: PageLink[], child) => {
if ('path' in child) {
return [...acc, { label: child.name, href: child.path }];
}

if ('link' in child) {
return [...acc, { label: child.name, href: child.link }];
}

return acc;
}, []);
});

export default async function WebPageLayout({ params, children }: Props) {
const { id } = await params;

return (
<StickySidebarLayout
sidebar={<SidebarMenu links={getWebPageChildren(id)} />}
sidebarSize="small"
>
{children}
</StickySidebarLayout>
);
}
38 changes: 38 additions & 0 deletions core/app/[locale]/(default)/webpages/[id]/normal/page-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { cache } from 'react';

import { client } from '~/client';
import { graphql } from '~/client/graphql';
import { revalidate } from '~/client/revalidate-target';
import { BreadcrumbsWebPageFragment } from '~/components/breadcrumbs/fragment';

const NormalPageQuery = graphql(
`
query NormalPageQuery($id: ID!) {
node(id: $id) {
... on NormalPage {
__typename
name
...BreadcrumbsFragment
htmlBody
entityId
seo {
pageTitle
metaDescription
metaKeywords
}
}
}
}
`,
[BreadcrumbsWebPageFragment],
);

export const getWebpageData = cache(async (variables: { id: string }) => {
const { data } = await client.fetch({
document: NormalPageQuery,
variables,
fetchOptions: { next: { revalidate } },
});

return data;
});
Original file line number Diff line number Diff line change
@@ -2,8 +2,12 @@ import type { Metadata } from 'next';
import { notFound } from 'next/navigation';

import { Breadcrumb } from '@/vibes/soul/primitives/breadcrumbs';
import {
breadcrumbsTransformer,
truncateBreadcrumbs,
} from '~/data-transformers/breadcrumbs-transformer';

import { WebPageContent, WebPage as WebPageData } from '../../_components/web-page';
import { WebPageContent, WebPage as WebPageData } from '../_components/web-page';

import { getWebpageData } from './page-data';

@@ -19,26 +23,32 @@ async function getWebPage(id: string): Promise<WebPageData> {
return notFound();
}

const breadcrumbs = breadcrumbsTransformer(webpage.breadcrumbs);

return {
title: webpage.name,
breadcrumbs,
content: webpage.htmlBody,
seo: webpage.seo,
};
}

async function getWebPageBreadcrumbs(id: string): Promise<Breadcrumb[]> {
const webpage = await getWebPage(id);

return [
const [, ...rest] = webpage.breadcrumbs.reverse();
const breadcrumbs = [
{
label: 'Home',
href: '/',
},
...rest.reverse(),
{
label: webpage.title,
href: '#',
},
];

return truncateBreadcrumbs(breadcrumbs, 5);
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
Loading

0 comments on commit 62b891c

Please sign in to comment.