diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-29 18:07:20 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-29 18:07:20 +0100 |
| commit | d363306235f2a48f16e488f20f73e2233ddcf281 (patch) | |
| tree | 5e86a7b5f38416d7ee56a9aff5ef972aa73d82b1 /src/pages | |
| parent | dfa894b76ee3584bf169710c78c57330c5d6ee67 (diff) | |
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
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/index.tsx | 347 | ||||
| -rw-r--r-- | src/pages/thematique/[slug].tsx | 5 |
2 files changed, 78 insertions, 274 deletions
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<HTMLDivElement>) => ( - <div {...props}>{children}</div> -); - -/** - * Retrieve a list of coding links. - * - * @returns {JSX.Element} - A list of links. - */ -const CodingLinks: FC = () => { - const intl = useIntl(); - - return ( - <List className={styles.list} hideMarker isInline spacing="sm"> - <ListItem> - <ButtonLink to={ROUTES.THEMATICS.WEB_DEV}> - {intl.formatMessage({ - defaultMessage: 'Web development', - description: 'HomePage: link to web development thematic', - id: 'vkF/RP', - })} - </ButtonLink> - </ListItem> - <ListItem> - <ButtonLink to={ROUTES.PROJECTS}> - {intl.formatMessage({ - defaultMessage: 'Projects', - description: 'HomePage: link to projects', - id: 'N44SOc', - })} - </ButtonLink> - </ListItem> - </List> - ); -}; - -/** - * 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 ( - <List className={styles.list} hideMarker isInline spacing="sm"> - <ListItem> - <ButtonLink isExternal to={repo.github}> - {intl.formatMessage({ - defaultMessage: 'Github', - description: 'HomePage: Github link', - id: '3f3PzH', - })} - </ButtonLink> - </ListItem> - <ListItem> - <ButtonLink isExternal to={repo.gitlab}> - {intl.formatMessage({ - defaultMessage: 'Gitlab', - description: 'HomePage: Gitlab link', - id: '7AnwZ7', - })} - </ButtonLink> - </ListItem> - </List> - ); -}; - -/** - * Retrieve a list of links related to Free thematic. - * - * @returns {JSX.Element} - A list of links. - */ -const LibreLinks: FC = () => { - const intl = useIntl(); - - return ( - <List className={styles.list} hideMarker isInline spacing="sm"> - <ListItem> - <ButtonLink to={ROUTES.THEMATICS.FREE}> - {intl.formatMessage({ - defaultMessage: 'Free', - description: 'HomePage: link to free thematic', - id: 'w8GrOf', - })} - </ButtonLink> - </ListItem> - <ListItem> - <ButtonLink to={ROUTES.THEMATICS.LINUX}> - {intl.formatMessage({ - defaultMessage: 'Linux', - description: 'HomePage: link to Linux thematic', - id: 'jASD7k', - })} - </ButtonLink> - </ListItem> - </List> - ); -}; - -/** - * Retrieve the Shaarli link. - * - * @returns {JSX.Element} - A list of links - */ -const ShaarliLink: FC = () => { - const intl = useIntl(); - - return ( - <List className={styles.list} hideMarker isInline spacing="sm"> - <ListItem> - <ButtonLink isExternal to={PERSONAL_LINKS.SHAARLI}> - {intl.formatMessage({ - defaultMessage: 'Shaarli', - description: 'HomePage: link to Shaarli', - id: 'i5L19t', - })} - </ButtonLink> - </ListItem> - </List> - ); -}; - -/** - * Retrieve the additional links. - * - * @returns {JSX.Element} - A list of links. - */ -const MoreLinks: FC = () => { - const intl = useIntl(); - - return ( - <List className={styles.list} hideMarker isInline spacing="sm"> - <ListItem> - <ButtonLink to={ROUTES.CONTACT}> - <Icon aria-hidden={true} shape="envelop" /> - {intl.formatMessage({ - defaultMessage: 'Contact me', - description: 'HomePage: contact button text', - id: 'sO/Iwj', - })} - </ButtonLink> - </ListItem> - <ListItem> - <ButtonLink to={ROUTES.RSS}> - <Icon aria-hidden={true} shape="feed" /> - {intl.formatMessage({ - defaultMessage: 'Subscribe', - description: 'HomePage: RSS feed subscription text', - id: 'T4YA64', - })} - </ButtonLink> - </ListItem> - </List> - ); +type RecentPostsProps = { + posts: RecentArticle[]; }; -const StyledGrid = ({ children }: { children: ReactNode }) => ( - <Grid className={styles.columns} gap="sm" sizeMin="250px"> - {children} - </Grid> -); - /** - * 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<PageSectionProps> = ({ - children, - hasBorder = true, - variant, -}) => ( - <PageSection - className={styles.section} - hasBorder={hasBorder} - variant={variant} - > - {children} - </PageSection> -); - -type HomeProps = { - recentPosts: RecentArticle[]; - translation?: Messages; -}; - -/** - * Home page. + * @returns {JSX.Element} - The cards list. */ -const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => { +const RecentPosts: FC<RecentPostsProps> = ({ 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 ( - <Grid className={listClass} gap="sm" isCentered sizeMax="25ch"> - {recentPosts.map((post) => ( + return ( + <Grid + // eslint-disable-next-line react/jsx-no-literals + gap="sm" + // eslint-disable-next-line react/jsx-no-literals + sizeMax="25ch" + > + {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 ( <Card + aria-label={cardLabel} cover={ post.cover ? ( - <CardCover hasBorders> - <NextImage - {...post.cover} - style={{ objectFit: 'scale-down' }} - /> + <CardCover aria-label={coverLabel} hasBorders> + <NextImage {...post.cover} /> </CardCover> ) : undefined } @@ -285,65 +100,57 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => { </CardMeta> } isCentered - linkTo={`${ROUTES.ARTICLE}/${post.slug}`} + linkTo={postUrl} > <CardHeader> <CardTitle level={3}>{post.title}</CardTitle> </CardHeader> <CardFooter /> </Card> - ))} - </Grid> - ); - }; + ); + })} + </Grid> + ); +}; - const components: MDXComponents = { +const getComponents = (recentPosts: RecentArticle[]): MDXComponents => { + return { ...mdxComponents, - CodingLinks, - ColdarkRepos, - Column, - Grid: StyledGrid, - LibreLinks, - MoreLinks, - RecentPosts: getRecentPosts, - Section: HomePageSection, - ShaarliLink, + RecentPosts: () => <RecentPosts posts={recentPosts} />, }; +}; + +type HomeProps = { + recentPosts: RecentArticle[]; + translation?: Messages; +}; + +/** + * Home page. + */ +const HomePage: NextPageWithLayout<HomeProps> = ({ 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 ( - <> + <Page hasSections> <Head> - <title>{pageTitle}</title> + <title>{meta.seo.title}</title> {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} - <meta name="description" content={pageDescription} /> + <meta name="description" content={meta.seo.description} /> <meta property="og:url" content={CONFIG.url} /> - <meta property="og:title" content={pageTitle} /> - <meta property="og:description" content={pageDescription} /> + <meta property="og:title" content={meta.seo.title} /> + <meta property="og:description" content={meta.seo.description} /> </Head> <Script // eslint-disable-next-line react/jsx-no-literals -- Id allowed @@ -357,10 +164,8 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => { type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} /> - <Page hasSections> - <HomePageContent components={components} /> - </Page> - </> + <HomePageContent components={getComponents(recentPosts)} /> + </Page> ); }; diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx index 9ea52e1..3d1e966 100644 --- a/src/pages/thematique/[slug].tsx +++ b/src/pages/thematique/[slug].tsx @@ -52,7 +52,7 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ const intl = useIntl(); const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ title, - url: `${ROUTES.THEMATICS.INDEX}/${slug}`, + url: `${ROUTES.THEMATICS}/${slug}`, }); const { asPath } = useRouter(); @@ -189,8 +189,7 @@ export const getStaticProps: GetStaticProps<ThematicPageProps> = async ({ ); const allThematicsLinks = allThematics.filter( (thematic) => - thematic.url !== - `${ROUTES.THEMATICS.INDEX}/${(params as ThematicParams).slug}` + thematic.url !== `${ROUTES.THEMATICS}/${(params as ThematicParams).slug}` ); const translation = await loadTranslation(locale); |
