From 2cc983064467fdef5630eeabc1a87d454afdb58d Mon Sep 17 00:00:00 2001
From: Armand Philippot <git@armandphilippot.com>
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/pages/404.tsx | 194 ++++++++++++++++++++++++++++++++----------------------
 1 file changed, 117 insertions(+), 77 deletions(-)

(limited to 'src/pages/404.tsx')

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) => <Link href={ROUTES.CONTACT}>{chunks}</Link>;
 
 type Error404PageProps = {
-  thematicsList: WPThematicPreview[];
-  topicsList: WPTopicPreview[];
+  data: {
+    thematics: GraphQLConnection<WPThematicPreview>;
+    topics: GraphQLConnection<WPTopicPreview>;
+  };
   translation: Messages;
 };
 
 /**
  * Error 404 page.
  */
-const Error404Page: NextPageWithLayout<Error404PageProps> = ({
-  thematicsList,
-  topicsList,
-}) => {
+const Error404Page: NextPageWithLayout<Error404PageProps> = ({ 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 <link>contact me</link> 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) => <Link href={ROUTES.CONTACT}>{chunks}</Link>,
-    }
-  );
+    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<Error404PageProps> = ({
   return (
     <Page breadcrumbs={breadcrumbItems}>
       <Head>
-        <title>{pageTitle}</title>
+        <title>{messages.seo.title}</title>
         {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */}
-        <meta name="description" content={pageDescription} />
+        <meta name="description" content={messages.seo.metaDesc} />
       </Head>
       <Script
         dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
@@ -129,9 +149,19 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({
         id="schema-breadcrumb"
         type="application/ld+json"
       />
-      <PageHeader heading={title} />
-      <PageBody>
-        {body}
+      <PageHeader heading={messages.page.title} />
+      <PageBody className={styles['no-results']}>
+        <p>
+          {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 <link>contact me</link> with the necessary information so that I can fix the problem.',
+              id: '9sGNKq',
+              description: 'Error404Page: page body',
+            },
+            { link }
+          )}
+        </p>
         <p>
           {intl.formatMessage({
             defaultMessage: 'You can also try a search:',
@@ -142,26 +172,34 @@ const Error404Page: NextPageWithLayout<Error404PageProps> = ({
         <SearchForm isLabelHidden onSubmit={searchSubmitHandler} />
       </PageBody>
       <PageSidebar>
-        <LinksWidget
-          heading={
-            <Heading isFake level={3}>
-              {thematicsListTitle}
-            </Heading>
-          }
-          items={getLinksItemData(
-            thematicsList.map(convertWPThematicPreviewToPageLink)
-          )}
-        />
-        <LinksWidget
-          heading={
-            <Heading isFake level={3}>
-              {topicsListTitle}
-            </Heading>
-          }
-          items={getLinksItemData(
-            topicsList.map(convertWPTopicPreviewToPageLink)
-          )}
-        />
+        {areThematicsLoading ? (
+          <Spinner>{messages.loading.thematicsList}</Spinner>
+        ) : (
+          <LinksWidget
+            heading={
+              <Heading level={2}>{messages.widgets.thematicsListTitle}</Heading>
+            }
+            items={getLinksItemData(
+              thematics.edges.map((edge) =>
+                convertWPThematicPreviewToPageLink(edge.node)
+              )
+            )}
+          />
+        )}
+        {areTopicsLoading ? (
+          <Spinner>{messages.loading.topicsList}</Spinner>
+        ) : (
+          <LinksWidget
+            heading={
+              <Heading level={2}>{messages.widgets.topicsListTitle}</Heading>
+            }
+            items={getLinksItemData(
+              topics.edges.map((edge) =>
+                convertWPTopicPreviewToPageLink(edge.node)
+              )
+            )}
+          />
+        )}
       </PageSidebar>
     </Page>
   );
@@ -180,8 +218,10 @@ export const getStaticProps: GetStaticProps<Error404PageProps> = async ({
 
   return {
     props: {
-      thematicsList: thematics.edges.map((edge) => edge.node),
-      topicsList: topics.edges.map((edge) => edge.node),
+      data: {
+        thematics,
+        topics,
+      },
       translation,
     },
   };
-- 
cgit v1.2.3

