summaryrefslogtreecommitdiffstats
path: root/src/pages
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-05-10 17:38:07 +0200
committerArmand Philippot <git@armandphilippot.com>2022-05-13 15:46:01 +0200
commit9c8921db92d16b07ffc2a63ff3c80c4dcdd9ff9d (patch)
tree52e87fa8e758ec51cfbf7aa200982e0a6f5ab1ca /src/pages
parent0d59a6d2995b4119865271ed1908ede0bb96497c (diff)
chore: add Project single pages
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/projets/[slug].tsx249
-rw-r--r--src/pages/projets/index.tsx (renamed from src/pages/projets.tsx)37
2 files changed, 264 insertions, 22 deletions
diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx
new file mode 100644
index 0000000..711a5cd
--- /dev/null
+++ b/src/pages/projets/[slug].tsx
@@ -0,0 +1,249 @@
+import Link from '@components/atoms/links/link';
+import SocialLink, { SocialWebsite } from '@components/atoms/links/social-link';
+import Spinner from '@components/atoms/loaders/spinner';
+import ResponsiveImage from '@components/molecules/images/responsive-image';
+import Code from '@components/molecules/layout/code';
+import { BreadcrumbItem } from '@components/molecules/nav/breadcrumb';
+import Gallery from '@components/organisms/images/gallery';
+import Overview, { OverviewMeta } from '@components/organisms/layout/overview';
+import Sharing from '@components/organisms/widgets/sharing';
+import PageLayout, {
+ PageLayoutProps,
+} from '@components/templates/page/page-layout';
+import { ProjectPreview, Repos } from '@ts/types/app';
+import { loadTranslation, Messages } from '@utils/helpers/i18n';
+import { getProjectData, getProjectFilenames } from '@utils/helpers/projects';
+import { capitalize } from '@utils/helpers/strings';
+import useGithubApi, { RepoData } from '@utils/hooks/use-github-api';
+import useSettings from '@utils/hooks/use-settings';
+import { MDXComponents, NestedMDXComponents } from 'mdx/types';
+import { GetStaticPaths, GetStaticProps, NextPage } from 'next';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import Script from 'next/script';
+import { ComponentType } from 'react';
+import { useIntl } from 'react-intl';
+import { Article, Graph, WebPage } from 'schema-dts';
+
+type ProjectPageProps = {
+ project: ProjectPreview;
+ translation: Messages;
+};
+
+/**
+ * Project page.
+ */
+const ProjectPage: NextPage<ProjectPageProps> = ({ project }) => {
+ const { id, intro, meta, title } = project;
+ const { cover, dates, license, repos, seo, technologies } = meta;
+ const intl = useIntl();
+ const homeLabel = intl.formatMessage({
+ defaultMessage: 'Home',
+ description: 'Breadcrumb: home label',
+ id: 'j5k9Fe',
+ });
+ const projectsLabel = intl.formatMessage({
+ defaultMessage: 'Projects',
+ description: 'Breadcrumb: projects label',
+ id: '28GZdv',
+ });
+ const breadcrumb: BreadcrumbItem[] = [
+ { id: 'home', name: homeLabel, url: '/' },
+ { id: 'projects', name: projectsLabel, url: '/projets' },
+ { id: 'project', name: title, url: `/projets/${id}` },
+ ];
+
+ const ProjectContent: ComponentType<MDXComponents> =
+ require(`../../content/projects/${id}.mdx`).default;
+
+ const components: NestedMDXComponents = {
+ Code: (props) => <Code {...props} />,
+ Gallery: (props) => <Gallery {...props} />,
+ Image: (props) => <ResponsiveImage {...props} />,
+ Link: (props) => <Link {...props} />,
+ pre: ({ children }) => <Code {...children.props} />,
+ };
+
+ const { website } = useSettings();
+ const { asPath } = useRouter();
+ const pageUrl = `${website.url}${asPath}`;
+ const pagePublicationDate = new Date(dates.publication);
+ const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
+
+ const headerMeta: PageLayoutProps['headerMeta'] = {
+ publication: { date: dates.publication },
+ update: dates.update ? { date: dates.update } : undefined,
+ };
+
+ /**
+ * Retrieve the repositories links.
+ *
+ * @param {Repos} repos - A repositories object.
+ * @returns {JSX.Element[]} - An array of SocialLink.
+ */
+ const getReposLinks = (repositories: Repos): JSX.Element[] => {
+ const links = [];
+
+ for (const [name, url] of Object.entries(repositories)) {
+ const socialWebsite = capitalize(name) as SocialWebsite;
+ const socialUrl = `https://${name}.com/${url}`;
+
+ links.push(<SocialLink name={socialWebsite} url={socialUrl} />);
+ }
+
+ return links;
+ };
+
+ const { isError, isLoading, data } = useGithubApi(meta.repos!.github!);
+
+ const getGithubData = (key: keyof RepoData) => {
+ if (isError) return 'Error';
+ if (isLoading || !data) return <Spinner />;
+
+ switch (key) {
+ case 'created_at':
+ return data.created_at;
+ case 'updated_at':
+ return data.updated_at;
+ case 'stargazers_count':
+ const stars = intl.formatMessage(
+ {
+ defaultMessage:
+ '{starsCount, plural, =0 {No stars on Github} one {# star on Github} other {# stars on Github}}',
+ id: 'Gnf1Si',
+ description: 'Projets: Github stars count',
+ },
+ { starsCount: data.stargazers_count }
+ );
+ return (
+ <>
+ ⭐&nbsp;
+ <Link href={`https://github.com/${repos!.github}/stargazers`}>
+ {stars}
+ </Link>
+ </>
+ );
+ }
+ };
+
+ const overviewData: OverviewMeta = {
+ creation: data && { date: getGithubData('created_at') as string },
+ update: data && { date: getGithubData('updated_at') as string },
+ license,
+ popularity: data && getGithubData('stargazers_count'),
+ repositories: repos ? getReposLinks(repos) : undefined,
+ technologies,
+ };
+
+ const webpageSchema: WebPage = {
+ '@id': `${pageUrl}`,
+ '@type': 'WebPage',
+ breadcrumb: { '@id': `${website.url}/#breadcrumb` },
+ name: seo.title,
+ description: seo.description,
+ inLanguage: website.locales.default,
+ reviewedBy: { '@id': `${website.url}/#branding` },
+ url: `${website.url}`,
+ isPartOf: {
+ '@id': `${website.url}`,
+ },
+ };
+
+ const articleSchema: Article = {
+ '@id': `${website.url}/project`,
+ '@type': 'Article',
+ name: title,
+ description: intro,
+ author: { '@id': `${website.url}/#branding` },
+ copyrightYear: pagePublicationDate.getFullYear(),
+ creator: { '@id': `${website.url}/#branding` },
+ dateCreated: pagePublicationDate.toISOString(),
+ dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
+ datePublished: pagePublicationDate.toISOString(),
+ editor: { '@id': `${website.url}/#branding` },
+ headline: title,
+ thumbnailUrl: `/projects/${id}.jpg`,
+ image: `/projects/${id}.jpg`,
+ 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 (
+ <>
+ <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={title} />
+ <meta property="og:description" content={intro} />
+ </Head>
+ <Script
+ id="schema-project"
+ type="application/ld+json"
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
+ />
+ <PageLayout
+ title={title}
+ intro={intro}
+ breadcrumb={breadcrumb}
+ headerMeta={headerMeta}
+ withToC={true}
+ widgets={[
+ <Sharing
+ key="sharing-widget"
+ data={{ excerpt: intro, title, url: pageUrl }}
+ media={[
+ 'diaspora',
+ 'email',
+ 'facebook',
+ 'journal-du-hacker',
+ 'linkedin',
+ 'twitter',
+ ]}
+ />,
+ ]}
+ >
+ <Overview cover={cover} meta={overviewData} />
+ <ProjectContent components={components} />
+ </PageLayout>
+ </>
+ );
+};
+
+export const getStaticProps: GetStaticProps = async ({ locale, params }) => {
+ const translation = await loadTranslation(locale);
+ const { slug } = params!;
+ const project = await getProjectData(slug as string);
+
+ return {
+ props: {
+ project,
+ translation,
+ },
+ };
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const filenames = getProjectFilenames();
+ const paths = filenames.map((filename) => {
+ return {
+ params: {
+ slug: filename,
+ },
+ };
+ });
+
+ return {
+ paths,
+ fallback: false,
+ };
+};
+
+export default ProjectPage;
diff --git a/src/pages/projets.tsx b/src/pages/projets/index.tsx
index 996af74..a6858c8 100644
--- a/src/pages/projets.tsx
+++ b/src/pages/projets/index.tsx
@@ -41,21 +41,12 @@ const ProjectsPage: NextPage<ProjectsPageProps> = ({ projects }) => {
const items: CardsListItem[] = projects.map(
({ id, meta: projectMeta, slug, title: projectTitle }) => {
- const { cover, tagline, ...remainingMeta } = projectMeta;
- const formattedMeta: CardsListItem['meta'] = remainingMeta.technologies
- ? [
- {
- id: 'technologies',
- term: 'Technologies',
- value: remainingMeta.technologies,
- },
- ]
- : undefined;
+ const { cover, tagline, technologies } = projectMeta;
return {
cover,
id: id as string,
- meta: formattedMeta,
+ meta: { technologies: technologies },
tagline,
title: projectTitle,
url: `/projets/${slug}`,
@@ -71,7 +62,7 @@ const ProjectsPage: NextPage<ProjectsPageProps> = ({ projects }) => {
const { asPath } = useRouter();
const pageUrl = `${website.url}${asPath}`;
const pagePublicationDate = new Date(dates.publication);
- const pageUpdateDate = new Date(dates.update);
+ const pageUpdateDate = dates.update ? new Date(dates.update) : undefined;
const webpageSchema: WebPage = {
'@id': `${pageUrl}`,
@@ -97,7 +88,7 @@ const ProjectsPage: NextPage<ProjectsPageProps> = ({ projects }) => {
copyrightYear: pagePublicationDate.getFullYear(),
creator: { '@id': `${website.url}/#branding` },
dateCreated: pagePublicationDate.toISOString(),
- dateModified: pageUpdateDate.toISOString(),
+ dateModified: pageUpdateDate && pageUpdateDate.toISOString(),
datePublished: pagePublicationDate.toISOString(),
editor: { '@id': `${website.url}/#branding` },
headline: meta.title,
@@ -112,13 +103,9 @@ const ProjectsPage: NextPage<ProjectsPageProps> = ({ projects }) => {
};
return (
- <PageLayout
- title={title}
- intro={<PageContent components={components} />}
- breadcrumb={breadcrumb}
- >
+ <>
<Head>
- <title>{seo.title}</title>
+ <title>{`${seo.title} - ${website.name}`}</title>
<meta name="description" content={seo.description} />
<meta property="og:url" content={`${pageUrl}`} />
<meta property="og:type" content="article" />
@@ -130,8 +117,14 @@ const ProjectsPage: NextPage<ProjectsPageProps> = ({ projects }) => {
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
/>
- <CardsList items={items} titleLevel={2} className={styles.list} />
- </PageLayout>
+ <PageLayout
+ title={title}
+ intro={<PageContent components={components} />}
+ breadcrumb={breadcrumb}
+ >
+ <CardsList items={items} titleLevel={2} className={styles.list} />
+ </PageLayout>
+ </>
);
};
@@ -141,7 +134,7 @@ export const getStaticProps: GetStaticProps = async ({ locale }) => {
return {
props: {
- projects,
+ projects: JSON.parse(JSON.stringify(projects)),
translation,
},
};