From 2cc983064467fdef5630eeabc1a87d454afdb58d Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 8 Dec 2023 19:19:37 +0100 Subject: refactor(pages): refine 404 page * refresh topics and thematics list using hooks * add Cypress tests --- src/components/templates/page/page.module.scss | 9 +- src/i18n/en.json | 30 ++-- src/i18n/fr.json | 30 ++-- src/pages/404.tsx | 194 +++++++++++++++---------- src/styles/pages/blog.module.scss | 2 +- tests/cypress/e2e/pages/404.cy.ts | 31 ++++ 6 files changed, 193 insertions(+), 103 deletions(-) create mode 100644 tests/cypress/e2e/pages/404.cy.ts diff --git a/src/components/templates/page/page.module.scss b/src/components/templates/page/page.module.scss index e7d3587..91a1b58 100644 --- a/src/components/templates/page/page.module.scss +++ b/src/components/templates/page/page.module.scss @@ -78,9 +78,8 @@ padding-bottom: var(--spacing-md); } -.body > * + * { - margin-top: var(--spacing-sm); - margin-bottom: var(--spacing-sm); +.body > * { + margin-block: var(--spacing-sm); } .footer { @@ -143,6 +142,10 @@ } } +.body > *:first-child { + margin-block-start: var(--spacing-md); +} + :where(.footer) { .btn { margin-inline-end: var(--spacing-2xs); diff --git a/src/i18n/en.json b/src/i18n/en.json index 935dcdc..820902b 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -99,10 +99,6 @@ "defaultMessage": "Written by:", "description": "PostPreviewMeta: author label" }, - "310o3F": { - "defaultMessage": "Error 404: Page not found - {websiteName}", - "description": "404Page: SEO - Page title" - }, "3Pipok": { "defaultMessage": "Thanks. Your message was successfully sent. I will answer it as soon as possible.", "description": "Contact: success message" @@ -111,9 +107,9 @@ "defaultMessage": "Repositories:", "description": "ProjectOverview: repositories label" }, - "48Ww//": { - "defaultMessage": "Page not found.", - "description": "404Page: SEO - Meta description" + "3u29G5": { + "defaultMessage": "Query must be longer than one character.", + "description": "Error404Page: invalid query message" }, "4M71hp": { "defaultMessage": "{starsCount, plural, =0 {No stars} one {# star} other {# stars}}", @@ -135,6 +131,10 @@ "defaultMessage": "Copy", "description": "usePrism: copy button text (not clicked)" }, + "6IAJYx": { + "defaultMessage": "Thematics are loading...", + "description": "Error404Page: loading thematics message" + }, "701ggm": { "defaultMessage": "Illustration of {projectName}", "description": "ProjectOverview: cover accessible name" @@ -223,10 +223,6 @@ "defaultMessage": "Failed to load.", "description": "BlogPage: failed to load text" }, - "C6oK7h": { - "defaultMessage": "Query must be longer than one character.", - "description": "404Page: invalid query message" - }, "Dq6+WH": { "defaultMessage": "Thematics", "description": "SearchPage: thematics list widget title" @@ -263,6 +259,10 @@ "defaultMessage": "Share on Journal du Hacker", "description": "SharingWidget: Journal du Hacker sharing link" }, + "HnMf0i": { + "defaultMessage": "Topics are loading...", + "description": "Error404Page: loading topics message" + }, "HohQPh": { "defaultMessage": "Thematics", "description": "Error404Page: thematics list widget title" @@ -647,6 +647,10 @@ "defaultMessage": "Go to next page, page {number}", "description": "BlogPage: next page label" }, + "pNIIU1": { + "defaultMessage": "Error 404: Page not found - {websiteName}", + "description": "Error404Page: SEO - Page title" + }, "pT5nHk": { "defaultMessage": "Published on:", "description": "HomePage: publication date label" @@ -739,6 +743,10 @@ "defaultMessage": "Thematics are loading...", "description": "BlogPage: loading thematics message" }, + "yKoGqg": { + "defaultMessage": "Page not found.", + "description": "Error404Page: SEO - Meta description" + }, "yN5P+m": { "defaultMessage": "Message:", "description": "ContactForm: message label" diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 6e65be0..3628763 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -99,10 +99,6 @@ "defaultMessage": "Écrit par :", "description": "PostPreviewMeta: author label" }, - "310o3F": { - "defaultMessage": "Erreur 404 : Page non trouvée - {websiteName}", - "description": "404Page: SEO - Page title" - }, "3Pipok": { "defaultMessage": "Merci. Votre message a été envoyé avec succès. J’y répondrai dès que possible.", "description": "Contact: success message" @@ -111,9 +107,9 @@ "defaultMessage": "Dépôts :", "description": "ProjectOverview: repositories label" }, - "48Ww//": { - "defaultMessage": "Page non trouvée.", - "description": "404Page: SEO - Meta description" + "3u29G5": { + "defaultMessage": "Query must be longer than one character.", + "description": "Error404Page: invalid query message" }, "4M71hp": { "defaultMessage": "{starsCount, plural, =0 {0 étoile} one {# étoile} other {# étoiles}}", @@ -135,6 +131,10 @@ "defaultMessage": "Copier", "description": "usePrism: copy button text (not clicked)" }, + "6IAJYx": { + "defaultMessage": "Les thématiques sont en cours de chargement…", + "description": "Error404Page: loading thematics message" + }, "701ggm": { "defaultMessage": "Illustration de {projectName}", "description": "ProjectOverview: cover accessible name" @@ -223,10 +223,6 @@ "defaultMessage": "Échec du chargement.", "description": "BlogPage: failed to load text" }, - "C6oK7h": { - "defaultMessage": "Les mots-clés doivent être plus longs qu'un caractère.", - "description": "404Page: invalid query message" - }, "Dq6+WH": { "defaultMessage": "Thématiques", "description": "SearchPage: thematics list widget title" @@ -263,6 +259,10 @@ "defaultMessage": "Partager sur le Journal du Hacker", "description": "SharingWidget: Journal du Hacker sharing link" }, + "HnMf0i": { + "defaultMessage": "Les sujets sont en cours de chargement…", + "description": "Error404Page: loading topics message" + }, "HohQPh": { "defaultMessage": "Thématiques", "description": "Error404Page: thematics list widget title" @@ -647,6 +647,10 @@ "defaultMessage": "Aller à la page suivante, page {number}", "description": "BlogPage: next page label" }, + "pNIIU1": { + "defaultMessage": "Erreur 404 : Page non trouvée - {websiteName}", + "description": "Error404Page: SEO - Page title" + }, "pT5nHk": { "defaultMessage": "Publié le :", "description": "HomePage: publication date label" @@ -739,6 +743,10 @@ "defaultMessage": "Les thématiques sont en cours de chargement…", "description": "BlogPage: loading thematics message" }, + "yKoGqg": { + "defaultMessage": "Page non trouvée.", + "description": "Error404Page: SEO - Meta description" + }, "yN5P+m": { "defaultMessage": "Message :", "description": "ContactForm: message label" diff --git a/src/pages/404.tsx b/src/pages/404.tsx index a98931f..6ef0c55 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,4 +1,3 @@ -/* eslint-disable max-statements */ import type { GetStaticProps } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; @@ -15,6 +14,7 @@ import { PageHeader, PageSidebar, SearchForm, + Spinner, type SearchFormSubmit, } from '../components'; import { @@ -25,7 +25,9 @@ import { fetchTopicsCount, fetchTopicsList, } from '../services/graphql'; +import styles from '../styles/pages/blog.module.scss'; import type { + GraphQLConnection, NextPageWithLayout, WPThematicPreview, WPTopicPreview, @@ -34,82 +36,100 @@ import { CONFIG } from '../utils/config'; import { ROUTES } from '../utils/constants'; import { getLinksItemData } from '../utils/helpers'; import { loadTranslation, type Messages } from '../utils/helpers/server'; -import { useBreadcrumb } from '../utils/hooks'; +import { useBreadcrumb, useThematicsList, useTopicsList } from '../utils/hooks'; + +const link = (chunks: ReactNode) => {chunks}; type Error404PageProps = { - thematicsList: WPThematicPreview[]; - topicsList: WPTopicPreview[]; + data: { + thematics: GraphQLConnection; + topics: GraphQLConnection; + }; translation: Messages; }; /** * Error 404 page. */ -const Error404Page: NextPageWithLayout = ({ - thematicsList, - topicsList, -}) => { +const Error404Page: NextPageWithLayout = ({ data }) => { const router = useRouter(); const intl = useIntl(); - const title = intl.formatMessage({ - defaultMessage: 'Page not found', - description: 'Error404Page: page title', - id: 'KnWeKh', + const { isLoading: areThematicsLoading, thematics } = useThematicsList({ + fallback: data.thematics, + input: { first: data.thematics.pageInfo.total }, }); - const body = intl.formatMessage( - { - defaultMessage: - 'Sorry, it seems that the page your are looking for does not exist. If you think this path should work, feel free to contact me with the necessary information so that I can fix the problem.', - id: '9sGNKq', - description: 'Error404Page: page body', + const { isLoading: areTopicsLoading, topics } = useTopicsList({ + fallback: data.topics, + input: { first: data.topics.pageInfo.total }, + }); + const messages = { + loading: { + thematicsList: intl.formatMessage({ + defaultMessage: 'Thematics are loading...', + description: 'Error404Page: loading thematics message', + id: '6IAJYx', + }), + topicsList: intl.formatMessage({ + defaultMessage: 'Topics are loading...', + description: 'Error404Page: loading topics message', + id: 'HnMf0i', + }), }, - { - link: (chunks: ReactNode) => {chunks}, - } - ); + page: { + title: intl.formatMessage({ + defaultMessage: 'Page not found', + description: 'Error404Page: page title', + id: 'KnWeKh', + }), + }, + seo: { + title: intl.formatMessage( + { + defaultMessage: 'Error 404: Page not found - {websiteName}', + description: 'Error404Page: SEO - Page title', + id: 'pNIIU1', + }, + { websiteName: CONFIG.name } + ), + metaDesc: intl.formatMessage({ + defaultMessage: 'Page not found.', + description: 'Error404Page: SEO - Meta description', + id: 'yKoGqg', + }), + }, + widgets: { + thematicsListTitle: intl.formatMessage({ + defaultMessage: 'Thematics', + description: 'Error404Page: thematics list widget title', + id: 'HohQPh', + }), + topicsListTitle: intl.formatMessage({ + defaultMessage: 'Topics', + description: 'Error404Page: topics list widget title', + id: 'GVpTIl', + }), + }, + }; const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ - title, + title: messages.page.title, url: ROUTES.NOT_FOUND, }); - const pageTitle = intl.formatMessage( - { - defaultMessage: 'Error 404: Page not found - {websiteName}', - description: '404Page: SEO - Page title', - id: '310o3F', - }, - { websiteName: CONFIG.name } - ); - const pageDescription = intl.formatMessage({ - defaultMessage: 'Page not found.', - description: '404Page: SEO - Meta description', - id: '48Ww//', - }); - const thematicsListTitle = intl.formatMessage({ - defaultMessage: 'Thematics', - description: 'Error404Page: thematics list widget title', - id: 'HohQPh', - }); - const topicsListTitle = intl.formatMessage({ - defaultMessage: 'Topics', - description: 'Error404Page: topics list widget title', - id: 'GVpTIl', - }); const searchSubmitHandler: SearchFormSubmit = useCallback( - ({ query }) => { + async ({ query }) => { if (!query) return { messages: { error: intl.formatMessage({ defaultMessage: 'Query must be longer than one character.', - description: '404Page: invalid query message', - id: 'C6oK7h', + description: 'Error404Page: invalid query message', + id: '3u29G5', }), }, validator: (value) => value.query.length > 1, }; - router.push({ pathname: ROUTES.SEARCH, query: { s: query } }); + await router.push({ pathname: ROUTES.SEARCH, query: { s: query } }); return undefined; }, @@ -119,9 +139,9 @@ const Error404Page: NextPageWithLayout = ({ return ( - {pageTitle} + {messages.seo.title} {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} - +