aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-05-03 16:51:22 +0200
committerArmand Philippot <git@armandphilippot.com>2022-05-03 16:51:22 +0200
commit83a029084f1bbfd78b7099d9bea3371d4533c6d9 (patch)
treef99854e4cb430ccbdb725cb2e287423f80cb9791
parent732d0943f8041d76262222a092b014f2557085ef (diff)
chore: add a LegalNotice page
-rw-r--r--mdx.d.ts10
-rw-r--r--src/components/molecules/buttons/heading-button.module.scss4
-rw-r--r--src/components/molecules/layout/meta.module.scss8
-rw-r--r--src/components/molecules/layout/page-header.module.scss7
-rw-r--r--src/components/organisms/widgets/links-list-widget.module.scss10
-rw-r--r--src/components/organisms/widgets/links-list-widget.tsx5
-rw-r--r--src/components/organisms/widgets/table-of-contents.module.scss4
-rw-r--r--src/components/organisms/widgets/table-of-contents.tsx2
-rw-r--r--src/pages/mentions-legales.tsx140
-rw-r--r--src/ts/types/app.ts16
-rw-r--r--src/ts/types/mdx.ts20
-rw-r--r--src/ts/types/raw-data.ts6
-rw-r--r--src/utils/helpers/author.ts10
13 files changed, 209 insertions, 33 deletions
diff --git a/mdx.d.ts b/mdx.d.ts
index f3a9a90..b4e333d 100644
--- a/mdx.d.ts
+++ b/mdx.d.ts
@@ -1,13 +1,9 @@
declare module '*.mdx' {
+ import { MDXData, MDXPageMeta, MDXProjectMeta } from '@ts/types/mdx';
import { MDXProps } from 'mdx/types';
- import { Meta } from '@ts/types/app';
let MDXComponent: (props: MDXProps) => JSX.Element;
export default MDXComponent;
- export const cover: string;
- export const image: string;
- export const intro: string;
- export const meta: Meta;
- export const pdf: string;
- export const seo: { title: string; description: string };
+ export const data: MDXData;
+ export const meta: MDXPageMeta | MDXProjectMeta;
}
diff --git a/src/components/molecules/buttons/heading-button.module.scss b/src/components/molecules/buttons/heading-button.module.scss
index 1d16410..9c278e4 100644
--- a/src/components/molecules/buttons/heading-button.module.scss
+++ b/src/components/molecules/buttons/heading-button.module.scss
@@ -36,6 +36,8 @@
}
.heading {
- background: none;
padding: var(--spacing-2xs) 0;
+ background: none;
+ font-size: var(--font-size-xl);
+ text-align: left;
}
diff --git a/src/components/molecules/layout/meta.module.scss b/src/components/molecules/layout/meta.module.scss
index f7cc55b..0485545 100644
--- a/src/components/molecules/layout/meta.module.scss
+++ b/src/components/molecules/layout/meta.module.scss
@@ -2,12 +2,18 @@
.list {
display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
+ grid-template-columns: repeat(1, minmax(0, 1fr));
+ gap: var(--spacing-sm);
@include mix.media("screen") {
+ @include mix.dimensions("2xs") {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
@include mix.dimensions("sm") {
display: flex;
flex-flow: column nowrap;
+ gap: var(--spacing-2xs);
}
}
}
diff --git a/src/components/molecules/layout/page-header.module.scss b/src/components/molecules/layout/page-header.module.scss
index 93f7595..4c7df5f 100644
--- a/src/components/molecules/layout/page-header.module.scss
+++ b/src/components/molecules/layout/page-header.module.scss
@@ -1,5 +1,4 @@
@use "@styles/abstracts/functions" as fun;
-@use "@styles/abstracts/mixins" as mix;
@use "@styles/abstracts/placeholders";
.wrapper {
@@ -55,9 +54,5 @@
}
.meta {
- @include mix.media("screen") {
- @include mix.dimensions("xs") {
- font-size: var(--font-size-sm);
- }
- }
+ font-size: var(--font-size-sm);
}
diff --git a/src/components/organisms/widgets/links-list-widget.module.scss b/src/components/organisms/widgets/links-list-widget.module.scss
index cbad83e..4444df4 100644
--- a/src/components/organisms/widgets/links-list-widget.module.scss
+++ b/src/components/organisms/widgets/links-list-widget.module.scss
@@ -3,6 +3,12 @@
.widget {
.list {
+ .list {
+ > *:first-child {
+ border-top: fun.convert-px(1) solid var(--color-primary);
+ }
+ }
+
&__link {
display: block;
padding: var(--spacing-2xs) var(--spacing-xs);
@@ -50,9 +56,7 @@
&__item {
&:not(:last-child) {
- .list__link {
- border-bottom: fun.convert-px(1) solid var(--color-primary);
- }
+ border-bottom: fun.convert-px(1) solid var(--color-primary);
}
> .list {
diff --git a/src/components/organisms/widgets/links-list-widget.tsx b/src/components/organisms/widgets/links-list-widget.tsx
index 559d0b6..37a20fc 100644
--- a/src/components/organisms/widgets/links-list-widget.tsx
+++ b/src/components/organisms/widgets/links-list-widget.tsx
@@ -24,7 +24,7 @@ export type LinksListItems = {
};
export type LinksListWidgetProps = Pick<WidgetProps, 'level' | 'title'> &
- Pick<ListProps, 'kind'> & {
+ Pick<ListProps, 'className' | 'kind'> & {
/**
* An array of name/url couple.
*/
@@ -37,6 +37,7 @@ export type LinksListWidgetProps = Pick<WidgetProps, 'level' | 'title'> &
* Render a list of links inside a widget.
*/
const LinksListWidget: FC<LinksListWidgetProps> = ({
+ className = '',
items,
kind = 'unordered',
...props
@@ -74,7 +75,7 @@ const LinksListWidget: FC<LinksListWidgetProps> = ({
items={getListItems(items)}
kind={kind}
withMargin={false}
- className={`${styles.list} ${styles[listKindClass]}`}
+ className={`${styles.list} ${styles[listKindClass]} ${className}`}
itemsClassName={styles.list__item}
/>
</Widget>
diff --git a/src/components/organisms/widgets/table-of-contents.module.scss b/src/components/organisms/widgets/table-of-contents.module.scss
new file mode 100644
index 0000000..36217ed
--- /dev/null
+++ b/src/components/organisms/widgets/table-of-contents.module.scss
@@ -0,0 +1,4 @@
+.list {
+ font-size: var(--font-size-sm);
+ font-weight: 500;
+}
diff --git a/src/components/organisms/widgets/table-of-contents.tsx b/src/components/organisms/widgets/table-of-contents.tsx
index 3778e02..800ff58 100644
--- a/src/components/organisms/widgets/table-of-contents.tsx
+++ b/src/components/organisms/widgets/table-of-contents.tsx
@@ -2,6 +2,7 @@ import useHeadingsTree, { type Heading } from '@utils/hooks/use-headings-tree';
import { FC } from 'react';
import { useIntl } from 'react-intl';
import LinksListWidget, { type LinksListItems } from './links-list-widget';
+import styles from './table-of-contents.module.scss';
type TableOfContentsProps = {
/**
@@ -46,6 +47,7 @@ const TableOfContents: FC<TableOfContentsProps> = ({ wrapper }) => {
title={title}
level={2}
items={getItems(headingsTree)}
+ className={styles.list}
/>
);
};
diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx
new file mode 100644
index 0000000..8dd0a1d
--- /dev/null
+++ b/src/pages/mentions-legales.tsx
@@ -0,0 +1,140 @@
+import Link from '@components/atoms/links/link';
+import ResponsiveImage from '@components/molecules/images/responsive-image';
+import { type BreadcrumbItem } from '@components/molecules/nav/breadcrumb';
+import PageLayout, {
+ type PageLayoutProps,
+} from '@components/templates/page/page-layout';
+import LegalNoticeContent, { meta } from '@content/pages/legal-notice.mdx';
+import { getFormattedDate } from '@utils/helpers/dates';
+import { loadTranslation } from '@utils/helpers/i18n';
+import useSettings from '@utils/hooks/use-settings';
+import { NestedMDXComponents } from 'mdx/types';
+import { GetStaticProps, NextPage } from 'next';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import Script from 'next/script';
+import { useIntl } from 'react-intl';
+import { Article, Graph, WebPage } from 'schema-dts';
+
+/**
+ * Legal Notice page.
+ */
+const LegalNoticePage: NextPage = () => {
+ const intl = useIntl();
+ const { dates, intro, seo, title } = meta;
+ const homeLabel = intl.formatMessage({
+ defaultMessage: 'Home',
+ description: 'Breadcrumb: home label',
+ id: 'j5k9Fe',
+ });
+ const breadcrumb: BreadcrumbItem[] = [
+ { id: 'home', name: homeLabel, url: '/' },
+ { id: 'legal-notice', name: title, url: '/mentions-legales' },
+ ];
+
+ const publicationLabel = intl.formatMessage({
+ defaultMessage: 'Published on:',
+ description: 'Meta: publication date label',
+ id: 'QGi5uD',
+ });
+
+ const updateLabel = intl.formatMessage({
+ defaultMessage: 'Updated on:',
+ description: 'Meta: update date label',
+ id: 'tLC7bh',
+ });
+
+ const headerMeta: PageLayoutProps['headerMeta'] = {
+ publication: {
+ name: publicationLabel,
+ value: getFormattedDate(dates.publication),
+ },
+ update: { name: updateLabel, value: getFormattedDate(dates.update) },
+ };
+
+ const components: NestedMDXComponents = {
+ Image: (props) => <ResponsiveImage {...props} />,
+ Link: (props) => <Link {...props} />,
+ };
+
+ const { website } = useSettings();
+ const { asPath } = useRouter();
+ const pageUrl = `${website.url}${asPath}`;
+ const pagePublicationDate = new Date(dates.publication);
+ const pageUpdateDate = new Date(dates.update);
+
+ const webpageSchema: WebPage = {
+ '@id': `${pageUrl}`,
+ '@type': 'WebPage',
+ breadcrumb: { '@id': `${website.url}/#breadcrumb` },
+ name: seo.title,
+ description: seo.description,
+ inLanguage: website.locales.default,
+ license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
+ reviewedBy: { '@id': `${website.url}/#branding` },
+ url: `${pageUrl}`,
+ isPartOf: {
+ '@id': `${website.url}`,
+ },
+ };
+
+ const articleSchema: Article = {
+ '@id': `${website.url}/#legal-notice`,
+ '@type': 'Article',
+ name: title,
+ description: intro,
+ author: { '@id': `${website.url}/#branding` },
+ copyrightYear: pagePublicationDate.getFullYear(),
+ creator: { '@id': `${website.url}/#branding` },
+ dateCreated: pagePublicationDate.toISOString(),
+ dateModified: pageUpdateDate.toISOString(),
+ datePublished: pagePublicationDate.toISOString(),
+ editor: { '@id': `${website.url}/#branding` },
+ headline: title,
+ inLanguage: website.locales.default,
+ license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
+ mainEntityOfPage: { '@id': `${pageUrl}` },
+ };
+
+ const schemaJsonLd: Graph = {
+ '@context': 'https://schema.org',
+ '@graph': [webpageSchema, articleSchema],
+ };
+
+ return (
+ <PageLayout
+ title={title}
+ intro={intro}
+ headerMeta={headerMeta}
+ breadcrumb={breadcrumb}
+ withToC={true}
+ >
+ <Head>
+ <title>{`${seo.title} - ${website.name}`}</title>
+ <meta name="description" content={seo.description} />
+ <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:type" content="article" />
+ <meta property="og:title" content={`${seo.title} - ${website.name}`} />
+ <meta property="og:description" content={intro} />
+ </Head>
+ <Script
+ id="schema-legal-notice"
+ type="application/ld+json"
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
+ />
+ <LegalNoticeContent components={components} />
+ </PageLayout>
+ );
+};
+
+export const getStaticProps: GetStaticProps = async ({ locale }) => {
+ const translation = await loadTranslation(locale);
+
+ return {
+ props: {
+ translation,
+ },
+ };
+};
+
+export default LegalNoticePage;
diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts
index b09f3d5..4d9c71c 100644
--- a/src/ts/types/app.ts
+++ b/src/ts/types/app.ts
@@ -1,8 +1,14 @@
-export type AuthorKind = 'page' | 'comment';
+export type ContentKind =
+ | 'article'
+ | 'comment'
+ | 'page'
+ | 'project'
+ | 'thematic'
+ | 'topic';
-export type Author<T extends AuthorKind> = {
+export type Author<T extends ContentKind> = {
avatar?: Image;
- description?: T extends 'page' ? string | undefined : never;
+ description?: T extends 'comment' ? never : string;
name: string;
website?: string;
};
@@ -44,11 +50,11 @@ export type SEO = {
title: string;
};
-export type PageKind = 'article' | 'project' | 'thematic' | 'topic';
+export type PageKind = Exclude<ContentKind, 'comment'>;
export type Meta<T extends PageKind> = {
articles?: T extends 'thematic' | 'topic' ? Article[] : never;
- author: Author<'page'>;
+ author?: T extends 'article' | 'page' ? Author<T> : never;
commentsCount?: T extends 'article' ? number : never;
cover?: Image;
dates: Dates;
diff --git a/src/ts/types/mdx.ts b/src/ts/types/mdx.ts
new file mode 100644
index 0000000..6b72f21
--- /dev/null
+++ b/src/ts/types/mdx.ts
@@ -0,0 +1,20 @@
+import { StaticImageData } from 'next/image';
+import { Meta } from './app';
+
+export type MDXData = {
+ file: string;
+ image: StaticImageData;
+};
+
+export type MDXPageMeta = Pick<Meta<'page'>, 'cover' | 'dates' | 'seo'> & {
+ intro: string;
+ title: string;
+};
+
+export type MDXProjectMeta = Omit<
+ Meta<'project'>,
+ 'readingTime' | 'wordsCount'
+> & {
+ intro: string;
+ title: string;
+};
diff --git a/src/ts/types/raw-data.ts b/src/ts/types/raw-data.ts
index 43a2453..7e12e7f 100644
--- a/src/ts/types/raw-data.ts
+++ b/src/ts/types/raw-data.ts
@@ -3,7 +3,7 @@
*/
import { NodeResponse, PageInfo } from '@services/graphql/api';
-import { AuthorKind } from './app';
+import { ContentKind } from './app';
export type ACFPosts = {
postsInThematic?: RawThematicPreview[];
@@ -29,8 +29,8 @@ export type Info = {
wordsCount: number;
};
-export type RawAuthor<T extends AuthorKind> = {
- description?: T extends 'page' ? string | undefined : never;
+export type RawAuthor<T extends ContentKind> = {
+ description?: T extends 'comment' ? never : string;
gravatarUrl?: string;
name: string;
url?: string;
diff --git a/src/utils/helpers/author.ts b/src/utils/helpers/author.ts
index cf125fc..40743ca 100644
--- a/src/utils/helpers/author.ts
+++ b/src/utils/helpers/author.ts
@@ -1,17 +1,17 @@
-import { type Author, type AuthorKind } from '@ts/types/app';
+import { type Author, type ContentKind } from '@ts/types/app';
import { type RawAuthor } from '@ts/types/raw-data';
/**
* Convert author raw data to regular data.
*
- * @param {RawAuthor<AuthorKind>} data - The author raw data.
- * @param {AuthorKind} kind - The author kind. Either `page` or `comment`.
+ * @param {RawAuthor<ContentKind>} data - The author raw data.
+ * @param {ContentKind} kind - The author kind. Either `page` or `comment`.
* @param {number} [avatarSize] - The author avatar size.
- * @returns {Author<AuthorKind>} The author data.
+ * @returns {Author<ContentKind>} The author data.
*/
export const getAuthorFromRawData = (
data: RawAuthor<typeof kind>,
- kind: AuthorKind,
+ kind: ContentKind,
avatarSize: number = 80
): Author<typeof kind> => {
const { name, description, gravatarUrl, url } = data;