From 70efcfeaa0603415dd992cb662d8efb960e6e49a Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Tue, 26 Sep 2023 15:54:28 +0200 Subject: refactor(routes): replace hardcoded routes with constants It makes it easier to change a route if needed and it avoid typo mistakes. I also refactored a bit the concerned files to be complient with the new ESlint config. However, I should rewrite the pages to reduce the number of statements. --- src/utils/constants.ts | 32 +++++++++++ src/utils/helpers/pages.ts | 39 +++++++------- src/utils/helpers/rss.ts | 19 +++---- src/utils/helpers/schema-org.ts | 13 ++--- src/utils/hooks/use-breadcrumb.ts | 107 +++++++++++++++++++++++++++++++++++++ src/utils/hooks/use-breadcrumb.tsx | 105 ------------------------------------ 6 files changed, 176 insertions(+), 139 deletions(-) create mode 100644 src/utils/constants.ts create mode 100644 src/utils/hooks/use-breadcrumb.ts delete mode 100644 src/utils/hooks/use-breadcrumb.tsx (limited to 'src/utils') diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..e642af9 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,32 @@ +export const PERSONAL_LINKS = { + GITHUB: 'https://github.com/ArmandPhilippot', + GITLAB: 'https://gitlab.com/ArmandPhilippot', + LINKEDIN: 'https://www.linkedin.com/in/armandphilippot', + SHAARLI: 'https://shaarli.armandphilippot.com/', +} as const; + +/** + * App routes. + * + * All static routes should be configured here to avoid 404 if a route changes. + */ +export const ROUTES = { + ARTICLE: '/article', + BLOG: '/blog', + CONTACT: '/contact', + CV: '/cv', + LEGAL_NOTICE: '/mentions-legales', + NOT_FOUND: '/404', + PROJECTS: '/projets', + RSS: '/feed', + SEARCH: '/recherche', + THEMATICS: { + INDEX: '/thematique', + FREE: '/thematique/libre', + LINUX: '/thematique/linux', + WEB_DEV: '/thematique/developpement-web', + }, + TOPICS: '/sujet', +} as const; + +// cSpell:ignore legales thematique developpement diff --git a/src/utils/helpers/pages.ts b/src/utils/helpers/pages.ts index 6b27b6d..84854cd 100644 --- a/src/utils/helpers/pages.ts +++ b/src/utils/helpers/pages.ts @@ -1,13 +1,14 @@ -import { type LinksListItems, type Post } from '../../components'; +import type { LinksListItems, Post } from '../../components'; import { getArticleFromRawData } from '../../services/graphql'; -import { - type Article, - type EdgesResponse, - type PageLink, - type RawArticle, - type RawThematicPreview, - type RawTopicPreview, +import type { + Article, + EdgesResponse, + PageLink, + RawArticle, + RawThematicPreview, + RawTopicPreview, } from '../../types'; +import { ROUTES } from '../constants'; import { getImageFromRawData } from './images'; /** @@ -25,11 +26,13 @@ export const getPageLinkFromRawData = ( kind: 'thematic' | 'topic' ): PageLink => { const { databaseId, featuredImage, slug, title } = data; - const baseUrl = kind === 'thematic' ? '/thematique/' : '/sujet/'; + const baseUrl = `${ + kind === 'thematic' ? ROUTES.THEMATICS.INDEX : ROUTES.TOPICS + }/`; return { id: databaseId, - logo: featuredImage ? getImageFromRawData(featuredImage?.node) : undefined, + logo: featuredImage ? getImageFromRawData(featuredImage.node) : undefined, name: title, url: `${baseUrl}${slug}`, }; @@ -57,14 +60,13 @@ export const sortPageLinksByName = (a: PageLink, b: PageLink) => { * @param {PageLink[]} links - An array of page links. * @returns {LinksListItem[]} An array of links items. */ -export const getLinksListItems = (links: PageLink[]): LinksListItems[] => { - return links.map((link) => { +export const getLinksListItems = (links: PageLink[]): LinksListItems[] => + links.map((link) => { return { name: link.name, url: link.url, }; }); -}; /** * Retrieve the posts list with the article URL. @@ -72,14 +74,13 @@ export const getLinksListItems = (links: PageLink[]): LinksListItems[] => { * @param {Article[]} posts - An array of articles. * @returns {Post[]} An array of posts with full article URL. */ -export const getPostsWithUrl = (posts: Article[]): Post[] => { - return posts.map((post) => { +export const getPostsWithUrl = (posts: Article[]): Post[] => + posts.map((post) => { return { ...post, url: `/article/${post.slug}`, }; }); -}; /** * Retrieve the posts list from raw data. @@ -89,11 +90,11 @@ export const getPostsWithUrl = (posts: Article[]): Post[] => { */ export const getPostsList = (rawData: EdgesResponse[]): Post[] => { const articlesList: RawArticle[] = []; - rawData.forEach((articleData) => + rawData.forEach((articleData) => { articleData.edges.forEach((edge) => { articlesList.push(edge.node); - }) - ); + }); + }); return getPostsWithUrl( articlesList.map((article) => getArticleFromRawData(article)) diff --git a/src/utils/helpers/rss.ts b/src/utils/helpers/rss.ts index 28f3c7b..0381c68 100644 --- a/src/utils/helpers/rss.ts +++ b/src/utils/helpers/rss.ts @@ -4,8 +4,9 @@ import { getArticles, getTotalArticles, } from '../../services/graphql'; -import { type Article } from '../../types'; -import { settings } from '../../utils/config'; +import type { Article } from '../../types'; +import { settings } from '../config'; +import { ROUTES } from '../constants'; /** * Retrieve the data for all the articles. @@ -17,9 +18,9 @@ const getAllArticles = async (): Promise => { const rawArticles = await getArticles({ first: totalArticles }); const articles: Article[] = []; - rawArticles.edges.forEach((edge) => - articles.push(getArticleFromRawData(edge.node)) - ); + rawArticles.edges.forEach((edge) => { + articles.push(getArticleFromRawData(edge.node)); + }); return articles; }; @@ -43,8 +44,8 @@ export const generateFeed = async (): Promise => { copyright, description: process.env.APP_FEED_DESCRIPTION, feedLinks: { - json: `${settings.url}/feed/json`, - atom: `${settings.url}/feed/atom`, + json: `${settings.url}${ROUTES.RSS}/json`, + atom: `${settings.url}${ROUTES.RSS}/atom`, }, generator: 'Feed & NextJS', id: settings.url, @@ -58,10 +59,10 @@ export const generateFeed = async (): Promise => { articles.forEach((article) => { feed.addItem({ content: article.intro, - date: new Date(article.meta!.dates.publication), + date: new Date(article.meta.dates.publication), description: article.intro, id: `${article.id}`, - link: `${settings.url}/article/${article.slug}`, + link: `${settings.url}${ROUTES.ARTICLE}/${article.slug}`, title: article.title, }); }); diff --git a/src/utils/helpers/schema-org.ts b/src/utils/helpers/schema-org.ts index 82f99c2..12bad28 100644 --- a/src/utils/helpers/schema-org.ts +++ b/src/utils/helpers/schema-org.ts @@ -1,4 +1,4 @@ -import { +import type { AboutPage, Article, Blog, @@ -7,8 +7,9 @@ import { Graph, WebPage, } from 'schema-dts'; -import { type Dates } from '../../types'; -import { settings } from '../../utils/config'; +import type { Dates } from '../../types'; +import { settings } from '../config'; +import { ROUTES } from '../constants'; export type GetBlogSchemaProps = { /** @@ -146,7 +147,7 @@ export const getSinglePageSchema = ({ copyrightYear: publicationDate.getFullYear(), creator: { '@id': `${settings.url}/#branding` }, dateCreated: publicationDate.toISOString(), - dateModified: updateDate && updateDate.toISOString(), + dateModified: updateDate?.toISOString(), datePublished: publicationDate.toISOString(), editor: { '@id': `${settings.url}/#branding` }, headline: title, @@ -157,7 +158,7 @@ export const getSinglePageSchema = ({ isPartOf: kind === 'post' ? { - '@id': `${settings.url}/blog`, + '@id': `${settings.url}${ROUTES.BLOG}`, } : undefined, mainEntityOfPage: { '@id': `${settings.url}${slug}` }, @@ -206,7 +207,7 @@ export const getWebPageSchema = ({ breadcrumb: { '@id': `${settings.url}/#breadcrumb` }, lastReviewed: updateDate, name: title, - description: description, + description, inLanguage: locale, reviewedBy: { '@id': `${settings.url}/#branding` }, url: `${settings.url}${slug}`, diff --git a/src/utils/hooks/use-breadcrumb.ts b/src/utils/hooks/use-breadcrumb.ts new file mode 100644 index 0000000..5839299 --- /dev/null +++ b/src/utils/hooks/use-breadcrumb.ts @@ -0,0 +1,107 @@ +/* eslint-disable max-statements */ +import { useIntl } from 'react-intl'; +import type { BreadcrumbList } from 'schema-dts'; +import type { BreadcrumbItem } from '../../components'; +import { ROUTES } from '../constants'; +import { slugify } from '../helpers'; +import { useSettings } from './use-settings'; + +export type useBreadcrumbProps = { + /** + * The current page title. + */ + title: string; + /** + * The current page url. + */ + url: string; +}; + +export type useBreadcrumbReturn = { + /** + * The breadcrumb items. + */ + items: BreadcrumbItem[]; + /** + * The breadcrumb JSON schema. + */ + schema: BreadcrumbList['itemListElement'][]; +}; + +/** + * Retrieve the breadcrumb items. + * + * @param {useBreadcrumbProps} props - An object (the current page title & url). + * @returns {useBreadcrumbReturn} The breadcrumb items and its JSON schema. + */ +export const useBreadcrumb = ({ + title, + url, +}: useBreadcrumbProps): useBreadcrumbReturn => { + const intl = useIntl(); + const { website } = useSettings(); + const isArticle = url.startsWith(`${ROUTES.ARTICLE}/`); + const isHome = url === '/'; + const isPageNumber = url.includes('/page/'); + const isProject = url.startsWith(`${ROUTES.PROJECTS}/`); + const isSearch = url.startsWith(ROUTES.SEARCH); + const isThematic = url.startsWith(`${ROUTES.THEMATICS.INDEX}/`); + const isTopic = url.startsWith(`${ROUTES.TOPICS}/`); + + const homeLabel = intl.formatMessage({ + defaultMessage: 'Home', + description: 'Breadcrumb: home label', + id: 'j5k9Fe', + }); + const items: BreadcrumbItem[] = [{ id: 'home', name: homeLabel, url: '/' }]; + const schema: BreadcrumbList['itemListElement'][] = [ + { + '@type': 'ListItem', + position: 1, + name: homeLabel, + item: website.url, + }, + ]; + + if (isHome) return { items, schema }; + + if (isArticle || isPageNumber || isSearch || isThematic || isTopic) { + const blogLabel = intl.formatMessage({ + defaultMessage: 'Blog', + description: 'Breadcrumb: blog label', + id: 'Es52wh', + }); + items.push({ id: 'blog', name: blogLabel, url: ROUTES.BLOG }); + schema.push({ + '@type': 'ListItem', + position: 2, + name: blogLabel, + item: `${website.url}${ROUTES.BLOG}`, + }); + } + + if (isProject) { + const projectsLabel = intl.formatMessage({ + defaultMessage: 'Projects', + description: 'Breadcrumb: projects label', + id: '28GZdv', + }); + items.push({ id: 'projects', name: projectsLabel, url: ROUTES.PROJECTS }); + schema.push({ + '@type': 'ListItem', + position: 2, + name: projectsLabel, + item: `${website.url}${ROUTES.PROJECTS}`, + }); + } + + items.push({ id: slugify(title), name: title, url }); + schema.push({ + '@type': 'ListItem', + position: schema.length + 1, + name: title, + item: `${website.url}${url}`, + }); + + return { items, schema }; +}; diff --git a/src/utils/hooks/use-breadcrumb.tsx b/src/utils/hooks/use-breadcrumb.tsx deleted file mode 100644 index f4506d7..0000000 --- a/src/utils/hooks/use-breadcrumb.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { useIntl } from 'react-intl'; -import { BreadcrumbList } from 'schema-dts'; -import { BreadcrumbItem } from '../../components'; -import { slugify } from '../helpers'; -import { useSettings } from './use-settings'; - -export type useBreadcrumbProps = { - /** - * The current page title. - */ - title: string; - /** - * The current page url. - */ - url: string; -}; - -export type useBreadcrumbReturn = { - /** - * The breadcrumb items. - */ - items: BreadcrumbItem[]; - /** - * The breadcrumb JSON schema. - */ - schema: BreadcrumbList['itemListElement'][]; -}; - -/** - * Retrieve the breadcrumb items. - * - * @param {useBreadcrumbProps} props - An object (the current page title & url). - * @returns {useBreadcrumbReturn} The breadcrumb items and its JSON schema. - */ -export const useBreadcrumb = ({ - title, - url, -}: useBreadcrumbProps): useBreadcrumbReturn => { - const intl = useIntl(); - const { website } = useSettings(); - const isArticle = url.startsWith('/article/'); - const isHome = url === '/'; - const isPageNumber = url.includes('/page/'); - const isProject = url.startsWith('/projets/'); - const isSearch = url.startsWith('/recherche'); - const isThematic = url.startsWith('/thematique/'); - const isTopic = url.startsWith('/sujet/'); - - const homeLabel = intl.formatMessage({ - defaultMessage: 'Home', - description: 'Breadcrumb: home label', - id: 'j5k9Fe', - }); - const items: BreadcrumbItem[] = [{ id: 'home', name: homeLabel, url: '/' }]; - const schema: BreadcrumbList['itemListElement'][] = [ - { - '@type': 'ListItem', - position: 1, - name: homeLabel, - item: website.url, - }, - ]; - - if (isHome) return { items, schema }; - - if (isArticle || isPageNumber || isSearch || isThematic || isTopic) { - const blogLabel = intl.formatMessage({ - defaultMessage: 'Blog', - description: 'Breadcrumb: blog label', - id: 'Es52wh', - }); - items.push({ id: 'blog', name: blogLabel, url: '/blog' }); - schema.push({ - '@type': 'ListItem', - position: 2, - name: blogLabel, - item: `${website.url}/blog`, - }); - } - - if (isProject) { - const projectsLabel = intl.formatMessage({ - defaultMessage: 'Projects', - description: 'Breadcrumb: projects label', - id: '28GZdv', - }); - items.push({ id: 'blog', name: projectsLabel, url: '/projets' }); - schema.push({ - '@type': 'ListItem', - position: 2, - name: projectsLabel, - item: `${website.url}/projets`, - }); - } - - items.push({ id: slugify(title), name: title, url }); - schema.push({ - '@type': 'ListItem', - position: schema.length + 1, - name: title, - item: `${website.url}${url}`, - }); - - return { items, schema }; -}; -- cgit v1.2.3