From d363306235f2a48f16e488f20f73e2233ddcf281 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Wed, 29 Nov 2023 18:07:20 +0100 Subject: refactor(pages): improve Homepage * move custom homepage components that does not require props to the MDX file (links should not need to be translated here but where they are defined) * move SEO title and meta desc to MDX file * make Page component the wrapper instead of using a React fragment * fix MDX module types --- mdx.d.ts | 6 +- .../buttons/button-link/button-link.module.scss | 2 + src/components/mdx.tsx | 17 +- src/components/molecules/grid/grid.module.scss | 12 + src/components/molecules/grid/grid.test.tsx | 40 +++ src/components/molecules/grid/grid.tsx | 8 + src/content | 2 +- src/i18n/en.json | 52 +-- src/i18n/fr.json | 52 +-- src/pages/index.tsx | 347 +++++---------------- src/pages/thematique/[slug].tsx | 5 +- .../helpers/convert-taxonomy-to-page-link.test.ts | 2 +- .../helpers/convert-taxonomy-to-page-link.ts | 2 +- .../convert-wp-thematic-to-thematic.test.ts | 2 +- .../helpers/convert-wp-thematic-to-thematic.ts | 2 +- src/styles/pages/home.module.scss | 34 -- src/utils/constants.ts | 8 +- src/utils/helpers/schema-org.ts | 41 +-- src/utils/helpers/strings.ts | 13 + src/utils/hooks/use-breadcrumb.ts | 3 +- tests/cypress/e2e/pages/homepage.cy.ts | 27 +- 21 files changed, 242 insertions(+), 435 deletions(-) delete mode 100644 src/styles/pages/home.module.scss diff --git a/mdx.d.ts b/mdx.d.ts index 4fd2076..91cf7ea 100644 --- a/mdx.d.ts +++ b/mdx.d.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/consistent-type-imports */ declare module '*.mdx' { type MDXProps = import('mdx/types').MDXProps; - type MDXData = import('./src/types/mdx').MDXData; - type MDXPageMeta = import('./src/types/mdx').MDXPageMeta; - type MDXProjectMeta = import('./src/types/mdx').MDXProjectMeta; + type MDXData = import('./src/types/data').MDXData; + type MDXPageMeta = import('./src/types/data').MDXPageMeta; + type MDXProjectMeta = import('./src/types/data').MDXProjectMeta; const MDXComponent: (props: MDXProps) => JSX.Element; export default MDXComponent; diff --git a/src/components/atoms/buttons/button-link/button-link.module.scss b/src/components/atoms/buttons/button-link/button-link.module.scss index 0f35a24..3ddeffe 100644 --- a/src/components/atoms/buttons/button-link/button-link.module.scss +++ b/src/components/atoms/buttons/button-link/button-link.module.scss @@ -3,6 +3,8 @@ .btn { @extend %button; + width: fit-content; + &--circle { @extend %circle-button; } diff --git a/src/components/mdx.tsx b/src/components/mdx.tsx index 9f0a4a5..eea80a9 100644 --- a/src/components/mdx.tsx +++ b/src/components/mdx.tsx @@ -1,8 +1,17 @@ import type { MDXComponents } from 'mdx/types'; import NextImage from 'next/image'; import type { AnchorHTMLAttributes, ImgHTMLAttributes, ReactNode } from 'react'; -import { Figure, Heading, Link, List, ListItem } from './atoms'; +import { + ButtonLink, + Figure, + Heading, + Icon, + Link, + List, + ListItem, +} from './atoms'; import { Code, Grid, GridItem } from './molecules'; +import { PageSection } from './templates'; const Anchor = ({ children = '', @@ -58,6 +67,7 @@ const Gallery = ({ children }: { children: ReactNode }) => ( export const mdxComponents: MDXComponents = { a: Anchor, + ButtonLink, Code, figure: ({ ref, ...props }) =>
, Figure, @@ -70,9 +80,14 @@ export const mdxComponents: MDXComponents = { h4: ({ ref, ...props }) => , h5: ({ ref, ...props }) => , h6: ({ ref, ...props }) => , + Icon, img: Img, + Img, li: ({ ref, ...props }) => , Link, + List, + ListItem, + PageSection, ol: ({ ref, ...props }) => ( { expect(rtlScreen.getByRole('list')).toHaveClass('wrapper--is-centered'); }); + + it('can render a list of centered items', () => { + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); + + expect(rtlScreen.getByRole('list')).toHaveClass( + 'wrapper--align-items-center' + ); + }); + + it('can render a list of items with end alignment', () => { + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); + + expect(rtlScreen.getByRole('list')).toHaveClass('wrapper--align-items-end'); + }); + + it('can render a list of items with start alignment', () => { + render( + + {items.map((item) => ( + {item.contents} + ))} + + ); + + expect(rtlScreen.getByRole('list')).toHaveClass( + 'wrapper--align-items-start' + ); + }); }); diff --git a/src/components/molecules/grid/grid.tsx b/src/components/molecules/grid/grid.tsx index 3d0ecf1..38f6e55 100644 --- a/src/components/molecules/grid/grid.tsx +++ b/src/components/molecules/grid/grid.tsx @@ -12,6 +12,12 @@ export type GridProps = Omit< ListProps, 'children' | 'hideMarker' | 'isHierarchical' | 'isInline' | 'spacing' > & { + /** + * How the items should be aligned? + * + * @default undefined // The default behavior is `stretch`. + */ + alignItems?: 'center' | 'end' | 'start'; /** * The grid items. */ @@ -62,6 +68,7 @@ export type GridProps = Omit< const GridWithRef = ( { + alignItems, children, className = '', col = 'auto-fit', @@ -77,6 +84,7 @@ const GridWithRef = ( ) => { const gridClass = [ styles.wrapper, + styles[alignItems ? `wrapper--align-items-${alignItems}` : ''], styles[isCentered ? 'wrapper--is-centered' : ''], styles[size ? 'wrapper--has-fixed-size' : ''], styles[sizeMin ? 'wrapper--has-min-size' : ''], diff --git a/src/content b/src/content index 6c0f225..a9aa65d 160000 --- a/src/content +++ b/src/content @@ -1 +1 @@ -Subproject commit 6c0f2250ea956f9511fed8d2620466cb43d18d31 +Subproject commit a9aa65d7f81b6ad72ef707f40f14e955cf4fcd67 diff --git a/src/i18n/en.json b/src/i18n/en.json index aac327d..be67b38 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -99,10 +99,6 @@ "defaultMessage": "Repositories:", "description": "ProjectOverview: repositories label" }, - "3f3PzH": { - "defaultMessage": "Github", - "description": "HomePage: Github link" - }, "48Ww//": { "defaultMessage": "Page not found.", "description": "404Page: SEO - Meta description" @@ -135,10 +131,6 @@ "defaultMessage": "Github profile", "description": "ContactPage: Github profile link" }, - "7AnwZ7": { - "defaultMessage": "Gitlab", - "description": "HomePage: Gitlab link" - }, "7TbbIk": { "defaultMessage": "Blog", "description": "BlogPage: page title" @@ -315,10 +307,6 @@ "defaultMessage": "CV", "description": "SiteNavbar: main nav - cv link" }, - "N44SOc": { - "defaultMessage": "Projects", - "description": "HomePage: link to projects" - }, "N804XO": { "defaultMessage": "Topics", "description": "SearchPage: topics list widget title" @@ -363,10 +351,6 @@ "defaultMessage": "{starsCount, plural, =0 {No stars} one {# star} other {# stars}}", "description": "ProjectOverview: stars count" }, - "PXp2hv": { - "defaultMessage": "{websiteName} | Front-end developer: WordPress/React", - "description": "HomePage: SEO - Page title" - }, "PnrHgZ": { "defaultMessage": "Home", "description": "SiteNavbar: main nav - home link" @@ -407,10 +391,6 @@ "defaultMessage": "LinkedIn profile", "description": "CVPage: LinkedIn profile link" }, - "T4YA64": { - "defaultMessage": "Subscribe", - "description": "HomePage: RSS feed subscription text" - }, "TpyFZ6": { "defaultMessage": "An error occurred:", "description": "Contact: error message" @@ -551,10 +531,6 @@ "defaultMessage": "Light Theme 🌞", "description": "usePrism: toggle light theme button text" }, - "i5L19t": { - "defaultMessage": "Shaarli", - "description": "HomePage: link to Shaarli" - }, "iG5SHf": { "defaultMessage": "{postTitle} cover", "description": "PostPreview: an accessible name for the figure wrapping the cover" @@ -567,14 +543,14 @@ "defaultMessage": "Home", "description": "Breadcrumb: home label" }, - "jASD7k": { - "defaultMessage": "Linux", - "description": "HomePage: link to Linux thematic" - }, "jJm8wd": { "defaultMessage": "Reading time:", "description": "PageHeader: reading time label" }, + "kq+fzI": { + "defaultMessage": "Cover of {pageTitle}", + "description": "RecentPosts: card cover accessible name" + }, "l50cYa": { "defaultMessage": "Open settings", "description": "SiteNavbar: settings button label in navbar" @@ -587,6 +563,10 @@ "defaultMessage": "Legal notice", "description": "SiteFooter: Legal notice link label" }, + "mWZU4R": { + "defaultMessage": "View {pageTitle}", + "description": "RecentPosts: card accessible name" + }, "nGss/j": { "defaultMessage": "Ackee tracking (analytics)", "description": "AckeeToggle: tooltip title" @@ -647,10 +627,6 @@ "defaultMessage": "Gitlab profile", "description": "ProjectsPage: Gitlab profile link" }, - "sO/Iwj": { - "defaultMessage": "Contact me", - "description": "HomePage: contact button text" - }, "sR5hah": { "defaultMessage": "Updated on:", "description": "PageHeader: update date label" @@ -663,10 +639,6 @@ "defaultMessage": "Partial", "description": "AckeeToggle: partial option name" }, - "tMuNTy": { - "defaultMessage": "{websiteName} is a front-end developer located in France. He codes and he writes mostly about web development and open-source.", - "description": "HomePage: SEO - Meta description" - }, "tsWh8x": { "defaultMessage": "Light theme", "description": "PrismThemeToggle: light theme label" @@ -687,10 +659,6 @@ "defaultMessage": "On", "description": "MotionToggle: activate reduce motion label" }, - "vkF/RP": { - "defaultMessage": "Web development", - "description": "HomePage: link to web development thematic" - }, "vtDLzG": { "defaultMessage": "Would you like to try a new search?", "description": "SearchPage: try a new search message" @@ -703,10 +671,6 @@ "defaultMessage": "Email:", "description": "ContactForm: email label" }, - "w8GrOf": { - "defaultMessage": "Free", - "description": "HomePage: link to free thematic" - }, "xaqaYQ": { "defaultMessage": "Sending mail...", "description": "ContactForm: spinner message on submit" diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 17514a3..0226f1e 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -99,10 +99,6 @@ "defaultMessage": "Dépôts :", "description": "ProjectOverview: repositories label" }, - "3f3PzH": { - "defaultMessage": "Github", - "description": "HomePage: Github link" - }, "48Ww//": { "defaultMessage": "Page non trouvée.", "description": "404Page: SEO - Meta description" @@ -135,10 +131,6 @@ "defaultMessage": "Profil Github", "description": "ContactPage: Github profile link" }, - "7AnwZ7": { - "defaultMessage": "Gitlab", - "description": "HomePage: Gitlab link" - }, "7TbbIk": { "defaultMessage": "Blog", "description": "BlogPage: page title" @@ -315,10 +307,6 @@ "defaultMessage": "CV", "description": "SiteNavbar: main nav - cv link" }, - "N44SOc": { - "defaultMessage": "Projets", - "description": "HomePage: link to projects" - }, "N804XO": { "defaultMessage": "Sujets", "description": "SearchPage: topics list widget title" @@ -363,10 +351,6 @@ "defaultMessage": "{starsCount, plural, =0 {0 étoile} one {# étoile} other {# étoiles}}", "description": "ProjectOverview: stars count" }, - "PXp2hv": { - "defaultMessage": "{websiteName} | Intégrateur web - Développeur WordPress / React", - "description": "HomePage: SEO - Page title" - }, "PnrHgZ": { "defaultMessage": "Accueil", "description": "SiteNavbar: main nav - home link" @@ -407,10 +391,6 @@ "defaultMessage": "Profil LinkedIn", "description": "CVPage: LinkedIn profile link" }, - "T4YA64": { - "defaultMessage": "Vous abonner", - "description": "HomePage: RSS feed subscription text" - }, "TpyFZ6": { "defaultMessage": "Une erreur est survenue :", "description": "Contact: error message" @@ -551,10 +531,6 @@ "defaultMessage": "Thème clair 🌞", "description": "usePrism: toggle light theme button text" }, - "i5L19t": { - "defaultMessage": "Shaarli", - "description": "HomePage: link to Shaarli" - }, "iG5SHf": { "defaultMessage": "Illustration de {postTitle}", "description": "PostPreview: an accessible name for the figure wrapping the cover" @@ -567,14 +543,14 @@ "defaultMessage": "Accueil", "description": "Breadcrumb: home label" }, - "jASD7k": { - "defaultMessage": "Linux", - "description": "HomePage: link to Linux thematic" - }, "jJm8wd": { "defaultMessage": "Temps de lecture :", "description": "PageHeader: reading time label" }, + "kq+fzI": { + "defaultMessage": "Illustration de {pageTitle}", + "description": "RecentPosts: card cover accessible name" + }, "l50cYa": { "defaultMessage": "Ouvrir les réglages", "description": "SiteNavbar: settings button label in navbar" @@ -587,6 +563,10 @@ "defaultMessage": "Mentions légales", "description": "SiteFooter: Legal notice link label" }, + "mWZU4R": { + "defaultMessage": "Consulter {pageTitle}", + "description": "RecentPosts: card accessible name" + }, "nGss/j": { "defaultMessage": "Suivi Ackee (analytique)", "description": "AckeeToggle: tooltip title" @@ -647,10 +627,6 @@ "defaultMessage": "Profil Gitlab", "description": "ProjectsPage: Gitlab profile link" }, - "sO/Iwj": { - "defaultMessage": "Me contacter", - "description": "HomePage: contact button text" - }, "sR5hah": { "defaultMessage": "Mis à jour le :", "description": "PageHeader: update date label" @@ -663,10 +639,6 @@ "defaultMessage": "Partiel", "description": "AckeeToggle: partial option name" }, - "tMuNTy": { - "defaultMessage": "{websiteName} est intégrateur web / développeur front-end en France. Il code et il écrit essentiellement à propos de développement web et du libre.", - "description": "HomePage: SEO - Meta description" - }, "tsWh8x": { "defaultMessage": "Thème clair", "description": "PrismThemeToggle: light theme label" @@ -687,10 +659,6 @@ "defaultMessage": "Marche", "description": "MotionToggle: activate reduce motion label" }, - "vkF/RP": { - "defaultMessage": "Développement web", - "description": "HomePage: link to web development thematic" - }, "vtDLzG": { "defaultMessage": "Souhaitez-vous essayer une nouvelle recherche ?", "description": "SearchPage: try a new search message" @@ -703,10 +671,6 @@ "defaultMessage": "E-mail :", "description": "ContactForm: email label" }, - "w8GrOf": { - "defaultMessage": "Libre", - "description": "HomePage: link to free thematic" - }, "xaqaYQ": { "defaultMessage": "Mail en cours d’envoi…", "description": "ContactForm: spinner message on submit" diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 7bd8aec..f4d36c1 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -3,10 +3,9 @@ import type { GetStaticProps } from 'next'; import Head from 'next/head'; import NextImage from 'next/image'; import Script from 'next/script'; -import type { FC, HTMLAttributes, ReactNode } from 'react'; +import type { FC } from 'react'; import { useIntl } from 'react-intl'; import { - ButtonLink, Card, CardCover, CardFooter, @@ -15,261 +14,77 @@ import { CardTitle, getLayout, Grid, - Icon, - List, - ListItem, Time, MetaItem, - type PageSectionProps, - PageSection, Page, } from '../components'; import { mdxComponents } from '../components/mdx'; -import HomePageContent from '../content/pages/homepage.mdx'; +import HomePageContent, { meta } from '../content/pages/homepage.mdx'; import { convertRecentPostToRecentArticle, fetchRecentPosts, } from '../services/graphql'; -import styles from '../styles/pages/home.module.scss'; import type { NextPageWithLayout, RecentArticle } from '../types'; import { CONFIG } from '../utils/config'; -import { PERSONAL_LINKS, ROUTES } from '../utils/constants'; +import { ROUTES } from '../utils/constants'; import { getSchemaJson, getWebPageSchema } from '../utils/helpers'; import { loadTranslation, type Messages } from '../utils/helpers/server'; import { useBreadcrumb } from '../utils/hooks'; -/** - * Column component. - * - * Render the body as a column. - */ -const Column = ({ children, ...props }: HTMLAttributes) => ( -
{children}
-); - -/** - * Retrieve a list of coding links. - * - * @returns {JSX.Element} - A list of links. - */ -const CodingLinks: FC = () => { - const intl = useIntl(); - - return ( - - - - {intl.formatMessage({ - defaultMessage: 'Web development', - description: 'HomePage: link to web development thematic', - id: 'vkF/RP', - })} - - - - - {intl.formatMessage({ - defaultMessage: 'Projects', - description: 'HomePage: link to projects', - id: 'N44SOc', - })} - - - - ); -}; - -/** - * Retrieve a list of Coldark repositories. - * - * @returns {JSX.Element} - A list of links. - */ -const ColdarkRepos: FC = () => { - const intl = useIntl(); - const repo = { - github: 'https://github.com/ArmandPhilippot/coldark', - gitlab: 'https://gitlab.com/ArmandPhilippot/coldark', - }; - - return ( - - - - {intl.formatMessage({ - defaultMessage: 'Github', - description: 'HomePage: Github link', - id: '3f3PzH', - })} - - - - - {intl.formatMessage({ - defaultMessage: 'Gitlab', - description: 'HomePage: Gitlab link', - id: '7AnwZ7', - })} - - - - ); -}; - -/** - * Retrieve a list of links related to Free thematic. - * - * @returns {JSX.Element} - A list of links. - */ -const LibreLinks: FC = () => { - const intl = useIntl(); - - return ( - - - - {intl.formatMessage({ - defaultMessage: 'Free', - description: 'HomePage: link to free thematic', - id: 'w8GrOf', - })} - - - - - {intl.formatMessage({ - defaultMessage: 'Linux', - description: 'HomePage: link to Linux thematic', - id: 'jASD7k', - })} - - - - ); -}; - -/** - * Retrieve the Shaarli link. - * - * @returns {JSX.Element} - A list of links - */ -const ShaarliLink: FC = () => { - const intl = useIntl(); - - return ( - - - - {intl.formatMessage({ - defaultMessage: 'Shaarli', - description: 'HomePage: link to Shaarli', - id: 'i5L19t', - })} - - - - ); -}; - -/** - * Retrieve the additional links. - * - * @returns {JSX.Element} - A list of links. - */ -const MoreLinks: FC = () => { - const intl = useIntl(); - - return ( - - - - - {intl.formatMessage({ - defaultMessage: 'Contact me', - description: 'HomePage: contact button text', - id: 'sO/Iwj', - })} - - - - - - {intl.formatMessage({ - defaultMessage: 'Subscribe', - description: 'HomePage: RSS feed subscription text', - id: 'T4YA64', - })} - - - - ); +type RecentPostsProps = { + posts: RecentArticle[]; }; -const StyledGrid = ({ children }: { children: ReactNode }) => ( - - {children} - -); - /** - * Create the page sections. + * Get a cards list of recent posts. * - * @param {object} obj - An object containing the section body. - * @param {ReactNode[]} obj.children - The section body. - * @returns {JSX.Element} A section element. - */ -const HomePageSection: FC = ({ - children, - hasBorder = true, - variant, -}) => ( - - {children} - -); - -type HomeProps = { - recentPosts: RecentArticle[]; - translation?: Messages; -}; - -/** - * Home page. + * @returns {JSX.Element} - The cards list. */ -const HomePage: NextPageWithLayout = ({ recentPosts }) => { +const RecentPosts: FC = ({ posts }): JSX.Element => { const intl = useIntl(); const publicationDate = intl.formatMessage({ defaultMessage: 'Published on:', description: 'HomePage: publication date label', id: 'pT5nHk', }); - const { schema: breadcrumbSchema } = useBreadcrumb({ - title: '', - url: `/`, - }); - - /** - * Get a cards list of recent posts. - * - * @returns {JSX.Element} - The cards list. - */ - const getRecentPosts = (): JSX.Element => { - const listClass = `${styles.list} ${styles['list--cards']}`; - return ( - - {recentPosts.map((post) => ( + return ( + + {posts.map((post) => { + const postUrl = `${ROUTES.ARTICLE}/${post.slug}`; + const cardLabel = intl.formatMessage( + { + defaultMessage: 'View {pageTitle}', + description: 'RecentPosts: card accessible name', + id: 'mWZU4R', + }, + { + pageTitle: post.title, + } + ); + const coverLabel = intl.formatMessage( + { + defaultMessage: 'Cover of {pageTitle}', + description: 'RecentPosts: card cover accessible name', + id: 'kq+fzI', + }, + { + pageTitle: post.title, + } + ); + + return ( - + + ) : undefined } @@ -285,65 +100,57 @@ const HomePage: NextPageWithLayout = ({ recentPosts }) => { } isCentered - linkTo={`${ROUTES.ARTICLE}/${post.slug}`} + linkTo={postUrl} > {post.title} - ))} - - ); - }; + ); + })} + + ); +}; - const components: MDXComponents = { +const getComponents = (recentPosts: RecentArticle[]): MDXComponents => { + return { ...mdxComponents, - CodingLinks, - ColdarkRepos, - Column, - Grid: StyledGrid, - LibreLinks, - MoreLinks, - RecentPosts: getRecentPosts, - Section: HomePageSection, - ShaarliLink, + RecentPosts: () => , }; +}; + +type HomeProps = { + recentPosts: RecentArticle[]; + translation?: Messages; +}; + +/** + * Home page. + */ +const HomePage: NextPageWithLayout = ({ recentPosts }) => { + const { schema: breadcrumbSchema } = useBreadcrumb({ + title: '', + url: ROUTES.HOME, + }); - const pageTitle = intl.formatMessage( - { - defaultMessage: '{websiteName} | Front-end developer: WordPress/React', - description: 'HomePage: SEO - Page title', - id: 'PXp2hv', - }, - { websiteName: CONFIG.name } - ); - const pageDescription = intl.formatMessage( - { - defaultMessage: - '{websiteName} is a front-end developer located in France. He codes and he writes mostly about web development and open-source.', - description: 'HomePage: SEO - Meta description', - id: 'tMuNTy', - }, - { websiteName: CONFIG.name } - ); const webpageSchema = getWebPageSchema({ - description: pageDescription, + description: meta.seo.description, locale: CONFIG.locales.defaultLocale, - slug: '', - title: pageTitle, + slug: ROUTES.HOME, + title: meta.seo.title, }); const schemaJsonLd = getSchemaJson([webpageSchema]); return ( - <> + - {pageTitle} + {meta.seo.title} {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} - + - - + +