From 8966fde6ff520cfbe74c031c8b2e3a66d298b172 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Thu, 20 Jan 2022 16:23:48 +0100 Subject: chore: add single project view --- src/components/ProjectPreview/ProjectPreview.tsx | 4 +- src/components/ProjectsList/ProjectsList.tsx | 2 +- src/pages/projet/[slug].tsx | 154 +++++++++++++++++++++++ src/ts/types/app.ts | 13 +- src/utils/helpers/projects.ts | 86 +++++++------ 5 files changed, 218 insertions(+), 41 deletions(-) create mode 100644 src/pages/projet/[slug].tsx (limited to 'src') diff --git a/src/components/ProjectPreview/ProjectPreview.tsx b/src/components/ProjectPreview/ProjectPreview.tsx index 91969b0..f5d2e8f 100644 --- a/src/components/ProjectPreview/ProjectPreview.tsx +++ b/src/components/ProjectPreview/ProjectPreview.tsx @@ -18,11 +18,11 @@ const ProjectPreview = ({ project }: { project: Project }) => { layout="fill" objectFit="contain" objectPosition="center" - alt={`${project.meta.title} picture`} + alt={t`${project.title} picture`} /> )} -

{project.meta.title}

+

{project.title}

{ const getProjectItems = () => { return projects.map((project) => { - return project.meta.title ? ( + return project.title ? (
  • diff --git a/src/pages/projet/[slug].tsx b/src/pages/projet/[slug].tsx new file mode 100644 index 0000000..03aa6ea --- /dev/null +++ b/src/pages/projet/[slug].tsx @@ -0,0 +1,154 @@ +import { getLayout } from '@components/Layouts/Layout'; +import PostHeader from '@components/PostHeader/PostHeader'; +import Sidebar from '@components/Sidebar/Sidebar'; +import { ToC } from '@components/Widgets'; +import { config } from '@config/website'; +import styles from '@styles/pages/Page.module.scss'; +import { + NextPageWithLayout, + Project as ProjectData, + ProjectProps, +} from '@ts/types/app'; +import { loadTranslation } from '@utils/helpers/i18n'; +import { + getAllProjectsFilename, + getProjectData, +} from '@utils/helpers/projects'; +import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next'; +import dynamic from 'next/dynamic'; +import Head from 'next/head'; +import { useRouter } from 'next/router'; +import { ParsedUrlQuery } from 'querystring'; +import { Article, Graph, WebPage } from 'schema-dts'; + +const Project: NextPageWithLayout = ({ + project, +}: { + project: ProjectData; +}) => { + const router = useRouter(); + const projectUrl = `${config.url}${router.asPath}`; + const { cover, id, intro, meta, title, seo } = project; + const dates = { + publication: meta.publishedOn, + update: meta.updatedOn, + }; + + const ProjectContent = dynamic( + () => import(`../../content/projects/${id}.mdx`) + ); + + const webpageSchema: WebPage = { + '@id': `${projectUrl}`, + '@type': 'WebPage', + breadcrumb: { '@id': `${config.url}/#breadcrumb` }, + name: seo.title, + description: seo.description, + inLanguage: config.locales.defaultLocale, + reviewedBy: { '@id': `${config.url}/#branding` }, + url: `${config.url}`, + isPartOf: { + '@id': `${config.url}`, + }, + }; + + const publicationDate = new Date(dates.publication); + const updateDate = new Date(dates.update); + + const articleSchema: Article = { + '@id': `${config.url}/subject`, + '@type': 'Article', + name: title, + description: intro, + author: { '@id': `${config.url}/#branding` }, + copyrightYear: publicationDate.getFullYear(), + creator: { '@id': `${config.url}/#branding` }, + dateCreated: publicationDate.toISOString(), + dateModified: updateDate.toISOString(), + datePublished: publicationDate.toISOString(), + editor: { '@id': `${config.url}/#branding` }, + thumbnailUrl: cover, + image: cover, + inLanguage: config.locales.defaultLocale, + license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr', + mainEntityOfPage: { '@id': `${projectUrl}` }, + }; + + const schemaJsonLd: Graph = { + '@context': 'https://schema.org', + '@graph': [webpageSchema, articleSchema], + }; + + return ( + <> + + {seo.title} + + + + + + + +
    + + + + +
    + +
    +
    + + ); +}; + +Project.getLayout = getLayout; + +interface ProjectParams extends ParsedUrlQuery { + slug: string; +} + +export const getStaticProps: GetStaticProps = async ( + context: GetStaticPropsContext +) => { + const translation = await loadTranslation( + context.locale!, + process.env.NODE_ENV === 'production' + ); + const breadcrumbTitle = ''; + const { slug } = context.params as ProjectParams; + const project = await getProjectData(slug); + + return { + props: { + breadcrumbTitle, + project, + translation, + }, + }; +}; + +export const getStaticPaths: GetStaticPaths = async () => { + const filenames = getAllProjectsFilename(); + const paths = filenames.map((filename) => { + return { + params: { + slug: filename, + }, + }; + }); + + return { + paths, + fallback: false, + }; +}; + +export default Project; diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts index 6f9bbce..b2a5cd6 100644 --- a/src/ts/types/app.ts +++ b/src/ts/types/app.ts @@ -1,6 +1,6 @@ import { NextPage } from 'next'; import { AppProps } from 'next/app'; -import { ReactElement, ReactNode } from 'react'; +import { ComponentType, ReactElement, ReactNode } from 'react'; import { PostBy } from './articles'; import { AllPostsSlug, RawPostsList } from './blog'; import { CommentData, CreateComment } from './comments'; @@ -89,7 +89,7 @@ export type Meta = { updatedOn: string; }; -export type ProjectMeta = Meta & { +export type ProjectMeta = Omit & { license: string; repos?: { github?: string; @@ -109,6 +109,15 @@ export type Project = { intro: string; meta: ProjectMeta; slug: string; + title: string; + seo: { + title: string; + description: string; + }; +}; + +export type ProjectProps = { + project: Project; }; export type Slug = { diff --git a/src/utils/helpers/projects.ts b/src/utils/helpers/projects.ts index 3736242..12e5912 100644 --- a/src/utils/helpers/projects.ts +++ b/src/utils/helpers/projects.ts @@ -2,47 +2,51 @@ import { Project, ProjectMeta } from '@ts/types/app'; import { readdirSync } from 'fs'; import path from 'path'; +/** + * Retrieve project's data by id. + * @param {string} id - The filename without extension. + * @returns {Promise} - The project data. + */ +export const getProjectData = async (id: string): Promise => { + try { + const { + image, + intro, + meta, + seo, + }: { + image: string; + intro: string; + meta: ProjectMeta & { title: string }; + seo: { title: string; description: string }; + } = await import(`../../content/projects/${id}.mdx`); + + const { title, ...onlyMeta } = meta; + + return { + id, + cover: image || '', + intro: intro || '', + meta: onlyMeta || {}, + slug: id, + title, + seo: seo || {}, + }; + } catch (err) { + console.error(err); + throw err; + } +}; + /** * Retrieve the projects data from filenames. * @param {string[]} filenames - An array of filenames. - * @returns {Promise} An array of projects. + * @returns {Promise} An array of projects with meta. */ const getProjectsWithMeta = async (filenames: string[]): Promise => { return Promise.all( filenames.map(async (filename) => { - const id = filename.replace(/\.mdx$/, ''); - - try { - const { - image, - intro, - meta, - }: { image: string; intro: string; meta: ProjectMeta } = await import( - `../../content/projects/${filename}` - ); - - const projectMeta: ProjectMeta = meta - ? meta - : { - title: '', - publishedOn: '', - updatedOn: '', - license: '', - }; - const projectIntro = intro ? intro : ''; - const projectCover = image ? image : ''; - - return { - id, - cover: projectCover, - intro: projectIntro, - meta: projectMeta, - slug: id, - }; - } catch (err) { - console.error(err); - throw err; - } + return getProjectData(filename); }) ); }; @@ -59,13 +63,23 @@ const sortProjectByPublicationDate = (a: Project, b: Project) => { return 0; }; +/** + * Retrieve all the projects filename. + * @returns {string[]} An array of filenames. + */ +export const getAllProjectsFilename = (): string[] => { + const projectsDirectory = path.join(process.cwd(), 'src/content/projects'); + const filenames = readdirSync(projectsDirectory); + + return filenames.map((filename) => filename.replace(/\.mdx$/, '')); +}; + /** * Retrieve all projects in content folder sorted by publication date. * @returns {Promise} An array of projects. */ export const getSortedProjects = async (): Promise => { - const projectsDirectory = path.join(process.cwd(), 'src/content/projects'); - const filenames = readdirSync(projectsDirectory); + const filenames = getAllProjectsFilename(); const projects = await getProjectsWithMeta(filenames); return [...projects].sort(sortProjectByPublicationDate); -- cgit v1.2.3