aboutsummaryrefslogtreecommitdiffstats
path: root/src/pages
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-11-20 12:27:46 +0100
committerArmand Philippot <git@armandphilippot.com>2023-11-20 19:32:09 +0100
commit70b4f633a6fbedb58c8b9134ac64ede854d489de (patch)
treec757bb12ad9a588e23b25cdb8b46710ac14dbcb1 /src/pages
parent9a481f066e1427d53a06cf7aeec525a745abf03f (diff)
refactor(components): replace PageLayout template with Page
* split pages in smaller components (it is both easier to maintain and more readable, we avoid the use of fragments in pages directory) * extract breadcrumbs from article tag (the navigation is not related to the page contents) * remove useReadingTime hook * remove layout options except `isHome`
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/404.tsx80
-rw-r--r--src/pages/article/[slug].tsx216
-rw-r--r--src/pages/blog/index.tsx102
-rw-r--r--src/pages/blog/page/[number].tsx105
-rw-r--r--src/pages/contact.tsx91
-rw-r--r--src/pages/cv.tsx160
-rw-r--r--src/pages/index.tsx3
-rw-r--r--src/pages/mentions-legales.tsx79
-rw-r--r--src/pages/projets/[slug].tsx112
-rw-r--r--src/pages/projets/index.tsx28
-rw-r--r--src/pages/recherche/index.tsx102
-rw-r--r--src/pages/sujet/[slug].tsx153
-rw-r--r--src/pages/thematique/[slug].tsx141
13 files changed, 591 insertions, 781 deletions
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index 75e2205..d6785b6 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -2,6 +2,7 @@
import type { GetStaticProps } from 'next';
import Head from 'next/head';
import { useRouter } from 'next/router';
+import Script from 'next/script';
import { useCallback, type ReactNode } from 'react';
import { useIntl } from 'react-intl';
import {
@@ -9,7 +10,10 @@ import {
Heading,
Link,
LinksWidget,
- PageLayout,
+ Page,
+ PageBody,
+ PageHeader,
+ PageSidebar,
SearchForm,
type SearchFormSubmit,
} from '../components';
@@ -111,45 +115,20 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({
);
return (
- <>
+ <Page breadcrumbs={breadcrumbItems}>
<Head>
<title>{pageTitle}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
<meta name="description" content={pageDescription} />
</Head>
- <PageLayout
- title={title}
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- widgets={[
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {thematicsListTitle}
- </Heading>
- }
- items={getLinksItemData(
- thematicsList.map((thematic) =>
- getPageLinkFromRawData(thematic, 'thematic')
- )
- )}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="thematics-list"
- />,
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {topicsListTitle}
- </Heading>
- }
- items={getLinksItemData(
- topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic'))
- )}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="topics-list"
- />,
- ]}
- >
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader heading={title} />
+ <PageBody>
{body}
<p>
{intl.formatMessage({
@@ -159,13 +138,36 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({
})}
</p>
<SearchForm isLabelHidden onSubmit={searchSubmitHandler} />
- </PageLayout>
- </>
+ </PageBody>
+ <PageSidebar>
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {thematicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(
+ thematicsList.map((thematic) =>
+ getPageLinkFromRawData(thematic, 'thematic')
+ )
+ )}
+ />
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {topicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(
+ topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic'))
+ )}
+ />
+ </PageSidebar>
+ </Page>
);
};
-Error404Page.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+Error404Page.getLayout = (page) => getLayout(page);
export const getStaticProps: GetStaticProps<Error404PageProps> = async ({
locale,
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index 0cba7a6..224b1c5 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -2,24 +2,23 @@
import type { ParsedUrlQuery } from 'querystring';
import type { GetStaticPaths, GetStaticProps } from 'next';
import Head from 'next/head';
-import NextImage from 'next/image';
import { useRouter } from 'next/router';
import Script from 'next/script';
-import type { HTMLAttributes } from 'react';
import { useIntl } from 'react-intl';
import type { Comment as CommentSchema, WithContext } from 'schema-dts';
import {
- ButtonLink,
getLayout,
- Link,
- PageLayout,
SharingWidget,
Spinner,
- Time,
type CommentData,
Heading,
- MetaList,
- MetaItem,
+ Page,
+ PageHeader,
+ PageBody,
+ PageFooter,
+ PageComments,
+ PageSidebar,
+ TocWidget,
} from '../../components';
import {
getAllArticlesSlugs,
@@ -41,8 +40,8 @@ import {
useArticle,
useBreadcrumb,
useComments,
+ useHeadingsTree,
usePrism,
- useReadingTime,
} from '../../utils/hooks';
type ArticlePageProps = {
@@ -84,7 +83,6 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({
title: article?.title ?? '',
url: `${ROUTES.ARTICLE}/${slug}`,
});
- const readingTime = useReadingTime(article?.meta.wordsCount ?? 0, true);
const { attributes, className } = usePrism({
attributes: {
'data-toolbar-order': 'show-language,copy-to-clipboard,color-scheme',
@@ -107,11 +105,21 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({
description: 'ArticlePage: loading article message',
id: '4iYISO',
});
+ const { ref, tree } = useHeadingsTree({ fromLevel: 2 });
if (isFallback || !article) return <Spinner>{loadingArticle}</Spinner>;
const { content, id, intro, meta, title } = article;
- const { author, commentsCount, cover, dates, seo, thematics, topics } = meta;
+ const {
+ author,
+ commentsCount,
+ cover,
+ dates,
+ seo,
+ thematics,
+ topics,
+ wordsCount,
+ } = meta;
const webpageSchema = getWebPageSchema({
description: intro,
@@ -211,9 +219,15 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({
id: 'HKKkQk',
description: 'SharingWidget: widget title',
});
+ const tocTitle = intl.formatMessage({
+ defaultMessage: 'Table of Contents',
+ description: 'PageLayout: table of contents title',
+ id: 'eys2uX',
+ });
+ const articleComments = getComments(commentsData);
return (
- <>
+ <Page breadcrumbs={breadcrumbItems}>
<Head>
<title>{seo.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -231,135 +245,63 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({
// eslint-disable-next-line react/no-danger -- Necessary for schema
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <PageLayout
- allowComments={true}
- bodyAttributes={attributes as HTMLAttributes<HTMLDivElement>}
- bodyClassName={styles.body}
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- comments={getComments(commentsData)}
- footerMeta={
- topics ? (
- <MetaList>
- <MetaItem
- hasInlinedValues
- label={intl.formatMessage({
- defaultMessage: 'Read more articles about:',
- description: 'ArticlePage: footer topics list label',
- id: '50xc4o',
- })}
- value={topics.map((topic) => {
- return {
- id: `topic--${topic.id}`,
- value: (
- <ButtonLink
- className={styles.btn}
- key={topic.id}
- to={topic.url}
- >
- {topic.logo ? <NextImage {...topic.logo} /> : null}{' '}
- {topic.name}
- </ButtonLink>
- ),
- };
- })}
- />
- </MetaList>
- ) : undefined
- }
- headerMeta={
- <MetaList>
- {author ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Written by:',
- description: 'ArticlePage: author label',
- id: 'MJbZfX',
- })}
- value={author.name}
- />
- ) : null}
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Published on:',
- description: 'Page: publication date label',
- id: '4QbTDq',
- })}
- value={<Time date={dates.publication} />}
- />
- {dates.update ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Updated on:',
- description: 'Page: update date label',
- id: 'Ez8Qim',
- })}
- value={<Time date={dates.update} />}
- />
- ) : null}
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Reading time:',
- description: 'ArticlePage: reading time label',
- id: 'Gw7X3x',
- })}
- value={readingTime}
- />
- {thematics ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Thematics:',
- description: 'ArticlePage: thematics meta label',
- id: 'CvOqoh',
- })}
- value={thematics.map((thematic) => {
- return {
- id: `thematic-${thematic.id}`,
- value: (
- <Link key={thematic.id} href={thematic.url}>
- {thematic.name}
- </Link>
- ),
- };
- })}
- />
- ) : null}
- </MetaList>
- }
- id={id as number}
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader
+ heading={title}
intro={intro}
- title={title}
- withToC={true}
- widgets={[
- <SharingWidget
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="sharing-widget"
- className={styles.widget}
- data={{ excerpt: intro, title, url: pageUrl }}
- heading={<Heading level={3}>{sharingWidgetTitle}</Heading>}
- media={[
- 'diaspora',
- 'email',
- 'facebook',
- 'journal-du-hacker',
- 'linkedin',
- 'twitter',
- ]}
- />,
- ]}
- >
- {contentWithPrismClasses}
- </PageLayout>
- </>
+ meta={{
+ author: author?.name,
+ publicationDate: dates.publication,
+ thematics,
+ updateDate: dates.update,
+ wordsCount,
+ }}
+ />
+ <PageSidebar>
+ <TocWidget
+ heading={<Heading level={3}>{tocTitle}</Heading>}
+ tree={tree}
+ />
+ </PageSidebar>
+ <PageBody
+ {...attributes}
+ className={styles.body}
+ dangerouslySetInnerHTML={{ __html: contentWithPrismClasses }}
+ ref={ref}
+ />
+ {topics ? <PageFooter readMoreAbout={topics} /> : null}
+ <PageSidebar>
+ <SharingWidget
+ // eslint-disable-next-line react/jsx-no-literals -- Key allowed
+ key="sharing-widget"
+ className={styles.widget}
+ data={{ excerpt: intro, title, url: pageUrl }}
+ heading={<Heading level={3}>{sharingWidgetTitle}</Heading>}
+ media={[
+ 'diaspora',
+ 'email',
+ 'facebook',
+ 'journal-du-hacker',
+ 'linkedin',
+ 'twitter',
+ ]}
+ />
+ </PageSidebar>
+ <PageComments
+ comments={articleComments ?? []}
+ depth={2}
+ pageId={id as number}
+ />
+ </Page>
);
};
-ArticlePage.getLayout = (page) => getLayout(page, { useGrid: true });
+ArticlePage.getLayout = (page) => getLayout(page);
type PostParams = {
slug: string;
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index 6ed6eda..0de5523 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -10,13 +10,14 @@ import {
Heading,
LinksWidget,
Notice,
- PageLayout,
PostsList,
Pagination,
type RenderPaginationLink,
type RenderPaginationItemAriaLabel,
- MetaList,
- MetaItem,
+ Page,
+ PageHeader,
+ PageBody,
+ PageSidebar,
} from '../../components';
import {
getArticles,
@@ -191,7 +192,7 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
});
return (
- <>
+ <Page breadcrumbs={breadcrumbItems} isBodyLastChild>
<Head>
<title>{page.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -209,60 +210,14 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
// eslint-disable-next-line react/no-danger -- Necessary for schema
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <PageLayout
- title={title}
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- headerMeta={
- <MetaList>
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Total:',
- description: 'Page: total label',
- id: 'kNBXyK',
- })}
- value={intl.formatMessage(
- {
- defaultMessage:
- '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}',
- description: 'Page: posts count meta',
- id: 'RvGb2c',
- },
- { postsCount: totalArticles }
- )}
- />
- </MetaList>
- }
- widgets={[
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {thematicsListTitle}
- </Heading>
- }
- items={getLinksItemData(
- thematicsList.map((thematic) =>
- getPageLinkFromRawData(thematic, 'thematic')
- )
- )}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="thematics-list"
- />,
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {topicsListTitle}
- </Heading>
- }
- items={getLinksItemData(
- topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic'))
- )}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="topics-list"
- />,
- ]}
- >
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader heading={title} meta={{ total: totalArticles }} />
+ <PageBody className={styles.body}>
{posts ? (
<PostsList
className={styles.list}
@@ -297,13 +252,36 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
})}
</Notice>
) : null}
- </PageLayout>
- </>
+ </PageBody>
+ <PageSidebar>
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {thematicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(
+ thematicsList.map((thematic) =>
+ getPageLinkFromRawData(thematic, 'thematic')
+ )
+ )}
+ />
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {topicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(
+ topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic'))
+ )}
+ />
+ </PageSidebar>
+ </Page>
);
};
-BlogPage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+BlogPage.getLayout = (page) => getLayout(page);
export const getStaticProps: GetStaticProps<BlogPageProps> = async ({
locale,
diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx
index 27d1816..b254603 100644
--- a/src/pages/blog/page/[number].tsx
+++ b/src/pages/blog/page/[number].tsx
@@ -10,13 +10,14 @@ import {
getLayout,
Heading,
LinksWidget,
- PageLayout,
PostsList,
Pagination,
type RenderPaginationLink,
type RenderPaginationItemAriaLabel,
- MetaList,
- MetaItem,
+ Page,
+ PageHeader,
+ PageBody,
+ PageSidebar,
} from '../../../components';
import {
getArticles,
@@ -195,7 +196,7 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
});
return (
- <>
+ <Page breadcrumbs={breadcrumbItems} isBodyLastChild>
<Head>
<title>{page.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -213,60 +214,17 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
// eslint-disable-next-line react/no-danger -- Necessary for schema
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <PageLayout
- title={pageTitleWithPageNumber}
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- headerMeta={
- <MetaList>
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Total:',
- description: 'Page: total label',
- id: 'kNBXyK',
- })}
- value={intl.formatMessage(
- {
- defaultMessage:
- '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}',
- description: 'Page: posts count meta',
- id: 'RvGb2c',
- },
- { postsCount: totalArticles }
- )}
- />
- </MetaList>
- }
- widgets={[
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {thematicsListTitle}
- </Heading>
- }
- items={getLinksItemData(
- thematicsList.map((thematic) =>
- getPageLinkFromRawData(thematic, 'thematic')
- )
- )}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="thematics-list"
- />,
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {topicsListTitle}
- </Heading>
- }
- items={getLinksItemData(
- topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic'))
- )}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="topics-list"
- />,
- ]}
- >
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader
+ heading={pageTitleWithPageNumber}
+ meta={{ total: totalArticles }}
+ />
+ <PageBody>
<PostsList posts={posts ?? []} sortByYear />
<Pagination
aria-label={paginationAriaLabel}
@@ -276,13 +234,36 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({
renderLink={renderPaginationLink}
total={totalArticles}
/>
- </PageLayout>
- </>
+ </PageBody>
+ <PageSidebar>
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {thematicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(
+ thematicsList.map((thematic) =>
+ getPageLinkFromRawData(thematic, 'thematic')
+ )
+ )}
+ />
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {topicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(
+ topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic'))
+ )}
+ />
+ </PageSidebar>
+ </Page>
);
};
-BlogPage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+BlogPage.getLayout = (page) => getLayout(page);
type BlogPageParams = {
number: string;
diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx
index f316143..b10d161 100644
--- a/src/pages/contact.tsx
+++ b/src/pages/contact.tsx
@@ -8,10 +8,13 @@ import { useIntl } from 'react-intl';
import {
ContactForm,
getLayout,
- PageLayout,
SocialMediaWidget,
Heading,
type ContactFormSubmit,
+ Page,
+ PageHeader,
+ PageBody,
+ PageSidebar,
} from '../components';
import { meta } from '../content/pages/contact.mdx';
import { sendMail } from '../services/graphql';
@@ -78,39 +81,6 @@ const ContactPage: NextPageWithLayout = () => {
description: 'ContactPage: LinkedIn profile link',
id: 'Q3oEQn',
});
-
- const widgets = [
- <SocialMediaWidget
- heading={
- <Heading isFake level={3}>
- {socialMediaTitle}
- </Heading>
- }
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="social-media"
- media={[
- {
- icon: 'Github',
- id: 'github',
- label: githubLabel,
- url: 'https://github.com/ArmandPhilippot',
- },
- {
- icon: 'Gitlab',
- id: 'gitlab',
- label: gitlabLabel,
- url: 'https://gitlab.com/ArmandPhilippot',
- },
- {
- icon: 'LinkedIn',
- id: 'linkedin',
- label: linkedinLabel,
- url: 'https://www.linkedin.com/in/armandphilippot',
- },
- ]}
- />,
- ];
-
const formName = intl.formatMessage({
defaultMessage: 'Contact form',
description: 'Contact: form accessible name',
@@ -163,7 +133,7 @@ const ContactPage: NextPageWithLayout = () => {
};
return (
- <>
+ <Page breadcrumbs={breadcrumbItems} isBodyLastChild>
<Head>
<title>{page.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -180,21 +150,50 @@ const ContactPage: NextPageWithLayout = () => {
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <PageLayout
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- intro={intro}
- title={pageTitle}
- widgets={widgets}
- >
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader heading={pageTitle} intro={intro} />
+ <PageBody>
<ContactForm aria-label={formName} onSubmit={submitMail} />
- </PageLayout>
- </>
+ </PageBody>
+ <PageSidebar>
+ <SocialMediaWidget
+ heading={
+ <Heading isFake level={3}>
+ {socialMediaTitle}
+ </Heading>
+ }
+ media={[
+ {
+ icon: 'Github',
+ id: 'github',
+ label: githubLabel,
+ url: 'https://github.com/ArmandPhilippot',
+ },
+ {
+ icon: 'Gitlab',
+ id: 'gitlab',
+ label: gitlabLabel,
+ url: 'https://gitlab.com/ArmandPhilippot',
+ },
+ {
+ icon: 'LinkedIn',
+ id: 'linkedin',
+ label: linkedinLabel,
+ url: 'https://www.linkedin.com/in/armandphilippot',
+ },
+ ]}
+ />
+ </PageSidebar>
+ </Page>
);
};
-ContactPage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+ContactPage.getLayout = (page) => getLayout(page);
export const getStaticProps: GetStaticProps = async ({ locale }) => {
const translation = await loadTranslation(locale);
diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx
index 0cda194..fd19a83 100644
--- a/src/pages/cv.tsx
+++ b/src/pages/cv.tsx
@@ -17,12 +17,13 @@ import {
ImageWidget,
Link,
List,
- PageLayout,
SocialMediaWidget,
ListItem,
- Time,
- MetaList,
- MetaItem,
+ Page,
+ PageHeader,
+ PageSidebar,
+ TocWidget,
+ PageBody,
} from '../components';
import CVContent, { data, meta } from '../content/pages/cv.mdx';
import type { NextPageWithLayout } from '../types';
@@ -34,7 +35,7 @@ import {
getWebPageSchema,
} from '../utils/helpers';
import { loadTranslation } from '../utils/helpers/server';
-import { useBreadcrumb } from '../utils/hooks';
+import { useBreadcrumb, useHeadingsTree } from '../utils/hooks';
const ExternalLink = ({
children = '',
@@ -137,6 +138,7 @@ const components: MDXComponents = {
*/
const CVPage: NextPageWithLayout = () => {
const intl = useIntl();
+ const { ref, tree } = useHeadingsTree({ fromLevel: 2 });
const { file, image } = data;
const { dates, intro, seo, title } = meta;
const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({
@@ -154,6 +156,11 @@ const CVPage: NextPageWithLayout = () => {
description: 'CVPage: social media widget title',
id: '+Dre5J',
});
+ const tocTitle = intl.formatMessage({
+ defaultMessage: 'Table of Contents',
+ description: 'PageLayout: table of contents title',
+ id: 'eys2uX',
+ });
const cvCaption = intl.formatMessage(
{
@@ -186,49 +193,6 @@ const CVPage: NextPageWithLayout = () => {
id: 'Sm2wCk',
});
- const widgets = [
- <ImageWidget
- description={cvCaption}
- heading={
- <Heading isFake level={3}>
- {imageWidgetTitle}
- </Heading>
- }
- img={<NextImage {...image} />}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="image-widget"
- />,
- <SocialMediaWidget
- heading={
- <Heading isFake level={3}>
- {socialMediaTitle}
- </Heading>
- }
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="social-media"
- media={[
- {
- icon: 'Github',
- id: 'github',
- label: githubLabel,
- url: PERSONAL_LINKS.GITHUB,
- },
- {
- icon: 'Gitlab',
- id: 'gitlab',
- label: gitlabLabel,
- url: PERSONAL_LINKS.GITLAB,
- },
- {
- icon: 'LinkedIn',
- id: 'linkedin',
- label: linkedinLabel,
- url: PERSONAL_LINKS.LINKEDIN,
- },
- ]}
- />,
- ];
-
const { asPath } = useRouter();
const webpageSchema = getWebPageSchema({
description: seo.description,
@@ -254,38 +218,7 @@ const CVPage: NextPageWithLayout = () => {
};
return (
- <PageLayout
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- headerMeta={
- <MetaList>
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Published on:',
- description: 'Page: publication date label',
- id: '4QbTDq',
- })}
- value={<Time date={dates.publication} />}
- />
- {dates.update ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Updated on:',
- description: 'Page: update date label',
- id: 'Ez8Qim',
- })}
- value={<Time date={dates.update} />}
- />
- ) : null}
- </MetaList>
- }
- intro={intro}
- title={title}
- widgets={widgets}
- withToC={true}
- >
+ <Page breadcrumbs={breadcrumbItems} isBodyLastChild>
<Head>
<title>{page.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -304,13 +237,72 @@ const CVPage: NextPageWithLayout = () => {
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <CVContent components={components} />
- </PageLayout>
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader
+ heading={title}
+ intro={intro}
+ meta={{
+ publicationDate: dates.publication,
+ updateDate: dates.update,
+ }}
+ />
+ <PageSidebar>
+ <TocWidget
+ heading={<Heading level={3}>{tocTitle}</Heading>}
+ tree={tree}
+ />
+ </PageSidebar>
+ <PageBody ref={ref}>
+ <CVContent components={components} />
+ </PageBody>
+ <PageSidebar>
+ <ImageWidget
+ description={cvCaption}
+ heading={
+ <Heading isFake level={3}>
+ {imageWidgetTitle}
+ </Heading>
+ }
+ img={<NextImage {...image} />}
+ />
+ <SocialMediaWidget
+ heading={
+ <Heading isFake level={3}>
+ {socialMediaTitle}
+ </Heading>
+ }
+ media={[
+ {
+ icon: 'Github',
+ id: 'github',
+ label: githubLabel,
+ url: PERSONAL_LINKS.GITHUB,
+ },
+ {
+ icon: 'Gitlab',
+ id: 'gitlab',
+ label: gitlabLabel,
+ url: PERSONAL_LINKS.GITLAB,
+ },
+ {
+ icon: 'LinkedIn',
+ id: 'linkedin',
+ label: linkedinLabel,
+ url: PERSONAL_LINKS.LINKEDIN,
+ },
+ ]}
+ />
+ </PageSidebar>
+ </Page>
);
};
-CVPage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+CVPage.getLayout = (page) => getLayout(page);
export const getStaticProps: GetStaticProps = async ({ locale }) => {
const translation = await loadTranslation(locale);
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index d708ac5..81883fc 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -438,8 +438,7 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => {
);
};
-HomePage.getLayout = (page) =>
- getLayout(page, { isHome: true, withExtraPadding: false });
+HomePage.getLayout = (page) => getLayout(page, { isHome: true });
export const getStaticProps: GetStaticProps<HomeProps> = async ({ locale }) => {
const translation = await loadTranslation(locale);
diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx
index e07263f..e3aabc5 100644
--- a/src/pages/mentions-legales.tsx
+++ b/src/pages/mentions-legales.tsx
@@ -9,11 +9,13 @@ import { useIntl } from 'react-intl';
import {
getLayout,
Link,
- PageLayout,
Figure,
- Time,
- MetaList,
- MetaItem,
+ Page,
+ PageHeader,
+ PageSidebar,
+ TocWidget,
+ Heading,
+ PageBody,
} from '../components';
import LegalNoticeContent, { meta } from '../content/pages/legal-notice.mdx';
import type { NextPageWithLayout } from '../types';
@@ -25,7 +27,7 @@ import {
getWebPageSchema,
} from '../utils/helpers';
import { loadTranslation } from '../utils/helpers/server';
-import { useBreadcrumb } from '../utils/hooks';
+import { useBreadcrumb, useHeadingsTree } from '../utils/hooks';
const ResponsiveImage = (props: NextImageProps) => (
<Figure>
@@ -49,6 +51,7 @@ const LegalNoticePage: NextPageWithLayout = () => {
url: ROUTES.LEGAL_NOTICE,
});
+ const { ref, tree } = useHeadingsTree({ fromLevel: 2 });
const { asPath } = useRouter();
const webpageSchema = getWebPageSchema({
description: seo.description,
@@ -71,39 +74,14 @@ const LegalNoticePage: NextPageWithLayout = () => {
title: `${seo.title} - ${CONFIG.name}`,
url: `${CONFIG.url}${asPath}`,
};
+ const tocTitle = intl.formatMessage({
+ defaultMessage: 'Table of Contents',
+ description: 'PageLayout: table of contents title',
+ id: 'eys2uX',
+ });
return (
- <PageLayout
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- headerMeta={
- <MetaList>
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Published on:',
- description: 'Page: publication date label',
- id: '4QbTDq',
- })}
- value={<Time date={dates.publication} />}
- />
- {dates.update ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Updated on:',
- description: 'Page: update date label',
- id: 'Ez8Qim',
- })}
- value={<Time date={dates.update} />}
- />
- ) : null}
- </MetaList>
- }
- intro={intro}
- title={title}
- withToC={true}
- >
+ <Page breadcrumbs={breadcrumbItems} isBodyLastChild>
<Head>
<title>{page.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -120,13 +98,34 @@ const LegalNoticePage: NextPageWithLayout = () => {
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <LegalNoticeContent components={components} />
- </PageLayout>
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader
+ heading={title}
+ intro={intro}
+ meta={{
+ publicationDate: dates.publication,
+ updateDate: dates.update,
+ }}
+ />
+ <PageSidebar>
+ <TocWidget
+ heading={<Heading level={3}>{tocTitle}</Heading>}
+ tree={tree}
+ />
+ </PageSidebar>
+ <PageBody ref={ref}>
+ <LegalNoticeContent components={components} />
+ </PageBody>
+ </Page>
);
};
-LegalNoticePage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+LegalNoticePage.getLayout = (page) => getLayout(page);
export const getStaticProps: GetStaticProps = async ({ locale }) => {
const translation = await loadTranslation(locale);
diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx
index a8a4fea..82d9149 100644
--- a/src/pages/projets/[slug].tsx
+++ b/src/pages/projets/[slug].tsx
@@ -12,20 +12,21 @@ import {
Code,
getLayout,
Link,
- PageLayout,
SharingWidget,
Spinner,
Heading,
List,
ListItem,
Figure,
- Time,
Grid,
ProjectOverview,
type ProjectMeta,
type Repository,
- MetaList,
- MetaItem,
+ Page,
+ PageHeader,
+ PageSidebar,
+ TocWidget,
+ PageBody,
} from '../../components';
import styles from '../../styles/pages/project.module.scss';
import type { NextPageWithLayout, ProjectPreview, Repos } from '../../types';
@@ -42,7 +43,11 @@ import {
loadTranslation,
type Messages,
} from '../../utils/helpers/server';
-import { useBreadcrumb, useGithubApi } from '../../utils/hooks';
+import {
+ useBreadcrumb,
+ useGithubApi,
+ useHeadingsTree,
+} from '../../utils/hooks';
const BorderedImage = (props: NextImageProps) => (
<Figure hasBorders>
@@ -164,6 +169,7 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => {
title,
url: `${ROUTES.PROJECTS}/${id}`,
});
+ const { ref, tree } = useHeadingsTree({ fromLevel: 2 });
const ProjectContent: ComponentType<MDXComponents> = dynamic(
async () => import(`../../content/projects/${id}.mdx`),
@@ -269,9 +275,14 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => {
id: 'HKKkQk',
description: 'SharingWidget: widget title',
});
+ const tocTitle = intl.formatMessage({
+ defaultMessage: 'Table of Contents',
+ description: 'PageLayout: table of contents title',
+ id: 'eys2uX',
+ });
return (
- <>
+ <Page breadcrumbs={breadcrumbItems} isBodyLastChild>
<Head>
<title>{page.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -289,67 +300,54 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => {
// eslint-disable-next-line react/no-danger -- Necessary for schema
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <PageLayout
- title={title}
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader
+ heading={title}
intro={intro}
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- headerMeta={
- <MetaList>
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Published on:',
- description: 'Page: publication date label',
- id: '4QbTDq',
- })}
- value={<Time date={dates.publication} />}
- />
- {dates.update ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Updated on:',
- description: 'Page: update date label',
- id: 'Ez8Qim',
- })}
- value={<Time date={dates.update} />}
- />
- ) : null}
- </MetaList>
- }
- withToC={true}
- widgets={[
- <SharingWidget
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="sharing-widget"
- data={{ excerpt: intro, title, url: page.url }}
- heading={<Heading level={3}>{sharingWidgetTitle}</Heading>}
- media={[
- 'diaspora',
- 'email',
- 'facebook',
- 'journal-du-hacker',
- 'linkedin',
- 'twitter',
- ]}
- className={styles.widget}
- />,
- ]}
- >
+ meta={{
+ publicationDate: dates.publication,
+ updateDate: dates.update,
+ }}
+ />
+ <PageSidebar>
+ <TocWidget
+ heading={<Heading level={3}>{tocTitle}</Heading>}
+ tree={tree}
+ />
+ </PageSidebar>
+ <PageBody ref={ref}>
<ProjectOverview
cover={cover ? <NextImage {...cover} /> : undefined}
meta={overviewMeta}
name={project.title}
/>
<ProjectContent components={components} />
- </PageLayout>
- </>
+ </PageBody>
+ <PageSidebar>
+ <SharingWidget
+ data={{ excerpt: intro, title, url: page.url }}
+ heading={<Heading level={3}>{sharingWidgetTitle}</Heading>}
+ media={[
+ 'diaspora',
+ 'email',
+ 'facebook',
+ 'journal-du-hacker',
+ 'linkedin',
+ 'twitter',
+ ]}
+ className={styles.widget}
+ />
+ </PageSidebar>
+ </Page>
);
};
-ProjectPage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+ProjectPage.getLayout = (page) => getLayout(page);
export const getStaticProps: GetStaticProps<ProjectPageProps> = async ({
locale,
diff --git a/src/pages/projets/index.tsx b/src/pages/projets/index.tsx
index 8feb701..0b9a91c 100644
--- a/src/pages/projets/index.tsx
+++ b/src/pages/projets/index.tsx
@@ -18,8 +18,10 @@ import {
type GridItem,
Link,
MetaList,
- PageLayout,
MetaItem,
+ Page,
+ PageHeader,
+ PageBody,
} from '../../components';
import PageContent, { meta } from '../../content/pages/projects.mdx';
import styles from '../../styles/pages/projects.module.scss';
@@ -139,7 +141,7 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => {
};
return (
- <>
+ <Page breadcrumbs={breadcrumbItems} isBodyLastChild>
<Head>
<title>{page.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -157,12 +159,17 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => {
// eslint-disable-next-line react/no-danger -- Necessary for schema
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <PageLayout
- title={title}
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader
+ heading={title}
intro={<PageContent components={components} />}
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- >
+ />
+ <PageBody className={styles.body}>
<Grid
className={styles.list}
gap="sm"
@@ -170,13 +177,12 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => {
items={items}
sizeMax="30ch"
/>
- </PageLayout>
- </>
+ </PageBody>
+ </Page>
);
};
-ProjectsPage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+ProjectsPage.getLayout = (page) => getLayout(page);
export const getStaticProps: GetStaticProps<ProjectsPageProps> = async ({
locale,
diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx
index 0fb279b..2a18aa3 100644
--- a/src/pages/recherche/index.tsx
+++ b/src/pages/recherche/index.tsx
@@ -10,13 +10,14 @@ import {
Heading,
LinksWidget,
Notice,
- PageLayout,
PostsList,
Spinner,
SearchForm,
type SearchFormSubmit,
- MetaList,
- MetaItem,
+ PageHeader,
+ Page,
+ PageSidebar,
+ PageBody,
} from '../../components';
import {
getArticles,
@@ -172,7 +173,7 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({
);
return (
- <>
+ <Page breadcrumbs={breadcrumbItems} isBodyLastChild>
<Head>
<title>{page.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -190,60 +191,14 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({
// eslint-disable-next-line react/no-danger -- Necessary for schema
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <PageLayout
- title={title}
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- headerMeta={
- <MetaList>
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Total:',
- description: 'Page: total label',
- id: 'kNBXyK',
- })}
- value={intl.formatMessage(
- {
- defaultMessage:
- '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}',
- description: 'Page: posts count meta',
- id: 'RvGb2c',
- },
- { postsCount: totalArticles }
- )}
- />
- </MetaList>
- }
- widgets={[
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {thematicsListTitle}
- </Heading>
- }
- items={getLinksItemData(
- thematicsList.map((thematic) =>
- getPageLinkFromRawData(thematic, 'thematic')
- )
- )}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="thematics-list"
- />,
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {topicsListTitle}
- </Heading>
- }
- items={getLinksItemData(
- topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic'))
- )}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="topics-list"
- />,
- ]}
- >
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader heading={title} meta={{ total: totalArticles }} />
+ <PageBody className={styles.body}>
{posts ? null : <Spinner>{loadingResults}</Spinner>}
{posts?.length ? (
<PostsList
@@ -285,13 +240,36 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({
})}
</Notice>
) : null}
- </PageLayout>
- </>
+ </PageBody>
+ <PageSidebar>
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {thematicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(
+ thematicsList.map((thematic) =>
+ getPageLinkFromRawData(thematic, 'thematic')
+ )
+ )}
+ />
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {topicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(
+ topicsList.map((topic) => getPageLinkFromRawData(topic, 'topic'))
+ )}
+ />
+ </PageSidebar>
+ </Page>
);
};
-SearchPage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+SearchPage.getLayout = (page) => getLayout(page);
export const getStaticProps: GetStaticProps<SearchPageProps> = async ({
locale,
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx
index d9734a3..30adec3 100644
--- a/src/pages/sujet/[slug].tsx
+++ b/src/pages/sujet/[slug].tsx
@@ -10,11 +10,12 @@ import {
getLayout,
Heading,
LinksWidget,
- PageLayout,
PostsList,
- Time,
- MetaList,
- MetaItem,
+ Page,
+ PageHeader,
+ PageSidebar,
+ TocWidget,
+ PageBody,
} from '../../components';
import {
getAllTopicsSlugs,
@@ -35,7 +36,7 @@ import {
getWebPageSchema,
} from '../../utils/helpers';
import { loadTranslation, type Messages } from '../../utils/helpers/server';
-import { useBreadcrumb } from '../../utils/hooks';
+import { useBreadcrumb, useHeadingsTree } from '../../utils/hooks';
export type TopicPageProps = {
currentTopic: Topic;
@@ -61,6 +62,7 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({
title,
url: `${ROUTES.TOPICS}/${slug}`,
});
+ const { ref, tree } = useHeadingsTree({ fromLevel: 2 });
const { asPath } = useRouter();
const webpageSchema = getWebPageSchema({
@@ -101,9 +103,14 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({
</>
);
const pageUrl = `${CONFIG.url}${asPath}`;
+ const tocTitle = intl.formatMessage({
+ defaultMessage: 'Table of Contents',
+ description: 'PageLayout: table of contents title',
+ id: 'eys2uX',
+ });
return (
- <>
+ <Page breadcrumbs={breadcrumbItems}>
<Head>
<title>{seo.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -121,92 +128,29 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({
// eslint-disable-next-line react/no-danger -- Necessary for schema
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <PageLayout
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- title={getPageHeading()}
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader
+ heading={getPageHeading()}
intro={intro}
- headerMeta={
- <MetaList>
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Published on:',
- description: 'Page: publication date label',
- id: '4QbTDq',
- })}
- value={<Time date={dates.publication} />}
- />
- {dates.update ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Updated on:',
- description: 'Page: update date label',
- id: 'Ez8Qim',
- })}
- value={<Time date={dates.update} />}
- />
- ) : null}
- {officialWebsite ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Official website:',
- description: 'TopicPage: official website label',
- id: 'zoifQd',
- })}
- value={officialWebsite}
- />
- ) : null}
- {articles ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Total:',
- description: 'ThematicPage: total label',
- id: 'lHkta9',
- })}
- value={intl.formatMessage(
- {
- defaultMessage:
- '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}',
- description: 'ThematicPage: posts count meta',
- id: 'iv3Ex1',
- },
- { postsCount: articles.length }
- )}
- />
- ) : null}
- </MetaList>
- }
- widgets={
- thematics
- ? [
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {thematicsListTitle}
- </Heading>
- }
- items={getLinksItemData(thematics)}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="related-thematics"
- />,
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {topicsListTitle}
- </Heading>
- }
- items={getLinksItemData(topics)}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="topics"
- />,
- ]
- : []
- }
- >
+ meta={{
+ publicationDate: dates.publication,
+ total: articles?.length,
+ updateDate: dates.update,
+ website: officialWebsite,
+ }}
+ />
+ <PageSidebar>
+ <TocWidget
+ heading={<Heading level={3}>{tocTitle}</Heading>}
+ tree={tree}
+ />
+ </PageSidebar>
+ <PageBody className={styles.body} ref={ref}>
{/*eslint-disable-next-line react/no-danger -- Necessary for content*/}
{content ? <div dangerouslySetInnerHTML={{ __html: content }} /> : null}
{articles ? (
@@ -229,13 +173,32 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({
/>
</>
) : null}
- </PageLayout>
- </>
+ </PageBody>
+ <PageSidebar>
+ {thematics ? (
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {thematicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(thematics)}
+ />
+ ) : null}
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {topicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(topics)}
+ />
+ </PageSidebar>
+ </Page>
);
};
-TopicPage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+TopicPage.getLayout = (page) => getLayout(page);
type TopicParams = {
slug: string;
diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx
index 9220ccd..b8518c5 100644
--- a/src/pages/thematique/[slug].tsx
+++ b/src/pages/thematique/[slug].tsx
@@ -9,11 +9,12 @@ import {
getLayout,
Heading,
LinksWidget,
- PageLayout,
PostsList,
- Time,
- MetaList,
- MetaItem,
+ Page,
+ PageHeader,
+ PageSidebar,
+ TocWidget,
+ PageBody,
} from '../../components';
import {
getAllThematicsSlugs,
@@ -34,7 +35,7 @@ import {
getWebPageSchema,
} from '../../utils/helpers';
import { loadTranslation, type Messages } from '../../utils/helpers/server';
-import { useBreadcrumb } from '../../utils/hooks';
+import { useBreadcrumb, useHeadingsTree } from '../../utils/hooks';
export type ThematicPageProps = {
currentThematic: Thematic;
@@ -53,6 +54,7 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({
title,
url: `${ROUTES.THEMATICS.INDEX}/${slug}`,
});
+ const { ref, tree } = useHeadingsTree({ fromLevel: 2 });
const { asPath } = useRouter();
const webpageSchema = getWebPageSchema({
@@ -85,9 +87,14 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({
id: '/42Z0z',
});
const pageUrl = `${CONFIG.url}${asPath}`;
+ const tocTitle = intl.formatMessage({
+ defaultMessage: 'Table of Contents',
+ description: 'PageLayout: table of contents title',
+ id: 'eys2uX',
+ });
return (
- <>
+ <Page breadcrumbs={breadcrumbItems}>
<Head>
<title>{seo.title}</title>
{/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
@@ -105,81 +112,28 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({
// eslint-disable-next-line react/no-danger -- Necessary for schema
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <PageLayout
- breadcrumb={breadcrumbItems}
- breadcrumbSchema={breadcrumbSchema}
- title={title}
+ <Script
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ // eslint-disable-next-line react/jsx-no-literals -- Id allowed
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ />
+ <PageHeader
+ heading={title}
intro={intro}
- headerMeta={
- <MetaList>
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Published on:',
- description: 'Page: publication date label',
- id: '4QbTDq',
- })}
- value={<Time date={dates.publication} />}
- />
- {dates.update ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Updated on:',
- description: 'Page: update date label',
- id: 'Ez8Qim',
- })}
- value={<Time date={dates.update} />}
- />
- ) : null}
- {articles ? (
- <MetaItem
- isInline
- label={intl.formatMessage({
- defaultMessage: 'Total:',
- description: 'ThematicPage: total label',
- id: 'lHkta9',
- })}
- value={intl.formatMessage(
- {
- defaultMessage:
- '{postsCount, plural, =0 {No articles} one {# article} other {# articles}}',
- description: 'ThematicPage: posts count meta',
- id: 'iv3Ex1',
- },
- { postsCount: articles.length }
- )}
- />
- ) : null}
- </MetaList>
- }
- widgets={
- topics
- ? [
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {thematicsListTitle}
- </Heading>
- }
- items={getLinksItemData(thematics)}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="thematics"
- />,
- <LinksWidget
- heading={
- <Heading isFake level={3}>
- {topicsListTitle}
- </Heading>
- }
- items={getLinksItemData(topics)}
- // eslint-disable-next-line react/jsx-no-literals -- Key allowed
- key="related-topics"
- />,
- ]
- : []
- }
- >
+ meta={{
+ publicationDate: dates.publication,
+ total: articles?.length,
+ updateDate: dates.update,
+ }}
+ />
+ <PageSidebar>
+ <TocWidget
+ heading={<Heading level={3}>{tocTitle}</Heading>}
+ tree={tree}
+ />
+ </PageSidebar>
+ <PageBody className={styles.body} ref={ref}>
{/*eslint-disable-next-line react/no-danger -- Necessary for content*/}
<div dangerouslySetInnerHTML={{ __html: content }} />
{articles ? (
@@ -202,13 +156,32 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({
/>
</>
) : null}
- </PageLayout>
- </>
+ </PageBody>
+ <PageSidebar>
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {thematicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(thematics)}
+ />
+ {topics ? (
+ <LinksWidget
+ heading={
+ <Heading isFake level={3}>
+ {topicsListTitle}
+ </Heading>
+ }
+ items={getLinksItemData(topics)}
+ />
+ ) : null}
+ </PageSidebar>
+ </Page>
);
};
-ThematicPage.getLayout = (page) =>
- getLayout(page, { useGrid: true, withExtraPadding: true });
+ThematicPage.getLayout = (page) => getLayout(page);
type ThematicParams = {
slug: string;