From e9d5a40432c451090e17859c764e52a96120b712 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Mon, 4 Dec 2023 19:36:34 +0100 Subject: refactor(pages): refine Search page * extract NoResults component to improve readability * provide a different message when the url does not contain a query * use hooks to refresh Thematics and Topics lists * remove useDataFromApi hook --- src/i18n/en.json | 50 +++-- src/i18n/fr.json | 50 +++-- src/pages/recherche/index.tsx | 365 ++++++++++++++++++++-------------- src/styles/pages/blog.module.scss | 4 + src/utils/hooks/index.ts | 1 - src/utils/hooks/use-data-from-api.tsx | 21 -- tests/cypress/e2e/pages/search.cy.ts | 28 +++ 7 files changed, 305 insertions(+), 214 deletions(-) delete mode 100644 src/utils/hooks/use-data-from-api.tsx create mode 100644 tests/cypress/e2e/pages/search.cy.ts diff --git a/src/i18n/en.json b/src/i18n/en.json index b1768a8..67880a2 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -167,6 +167,10 @@ "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.", "description": "Error404Page: page body" }, + "A0TsHP": { + "defaultMessage": "Please use the form below to start searching:", + "description": "SearchPage: search for message" + }, "A8hGaK": { "defaultMessage": "Comment:", "description": "CommentForm: comment label" @@ -219,9 +223,9 @@ "defaultMessage": "Thematics", "description": "SearchPage: thematics list widget title" }, - "EeCqAE": { - "defaultMessage": "Loading the search results...", - "description": "SearchPage: loading search results message" + "E+ROR5": { + "defaultMessage": "No results found. Would you like to try a new search?", + "description": "SearchPage: no results" }, "Es52wh": { "defaultMessage": "Blog", @@ -307,6 +311,10 @@ "defaultMessage": "CV", "description": "SiteNavbar: main nav - cv link" }, + "N+3eau": { + "defaultMessage": "Search results for \"{query}\"", + "description": "SearchPage: SEO - Page title" + }, "N804XO": { "defaultMessage": "Topics", "description": "SearchPage: topics list widget title" @@ -319,6 +327,10 @@ "defaultMessage": "{commentsCount, plural, =0 {No comments} one {# comment} other {# comments}} about {title}", "description": "PostPreviewMeta: comments count" }, + "NqVQYo": { + "defaultMessage": "Search - {websiteName}", + "description": "SearchPage: SEO - Page title" + }, "Nx8Jo5": { "defaultMessage": "Github profile", "description": "ProjectsPage: Github profile link" @@ -371,6 +383,10 @@ "defaultMessage": "Main navigation", "description": "SiteNavbar: main nav accessible name" }, + "QRDdye": { + "defaultMessage": "Search results for {query} - {websiteName}", + "description": "SearchPage: SEO - Page title" + }, "Qa9twM": { "defaultMessage": "Reply", "description": "CommentsList: reply button" @@ -411,10 +427,6 @@ "defaultMessage": "Send", "description": "ContactForm: send button" }, - "VkfO7t": { - "defaultMessage": "Query must be longer than one character.", - "description": "NoResults: invalid query message" - }, "Vrw5/h": { "defaultMessage": "{website} logo", "description": "SiteBranding: logo title" @@ -447,10 +459,6 @@ "defaultMessage": "Leave a comment", "description": "PageComments: the section title of the comment form" }, - "YV//MH": { - "defaultMessage": "No results found.", - "description": "SearchPage: no results" - }, "Ygea7s": { "defaultMessage": "Light theme", "description": "ThemeToggle: light theme label" @@ -471,10 +479,6 @@ "defaultMessage": "You can't load more articles without Javascript, please use the pagination instead.", "description": "BlogPage: pagination no script message" }, - "ZNBhDP": { - "defaultMessage": "Search results for {query}", - "description": "SearchPage: SEO - Page title" - }, "ZcFroC": { "defaultMessage": "Thanks, your comment was successfully sent.", "description": "PageComments: comment form success message" @@ -523,6 +527,10 @@ "defaultMessage": "Blog: development, open source - Page {number} - {websiteName}", "description": "BlogPage: SEO - Page title" }, + "e3ppRI": { + "defaultMessage": "Query must be longer than one character.", + "description": "SearchPage: invalid query message" + }, "eys2uX": { "defaultMessage": "Table of Contents", "description": "PageLayout: table of contents title" @@ -647,6 +655,10 @@ "defaultMessage": "Discover search results for {query} on {websiteName}.", "description": "SearchPage: SEO - Meta description" }, + "qFqWQH": { + "defaultMessage": "Thematics are loading...", + "description": "SearchPage: loading thematics message" + }, "rVoW4G": { "defaultMessage": "Thematics are loading...", "description": "ThematicPage: loading thematics message" @@ -675,6 +687,10 @@ "defaultMessage": "Partial", "description": "AckeeToggle: partial option name" }, + "tLflgC": { + "defaultMessage": "Topics are loading...", + "description": "SearchPage: loading topics message" + }, "tsWh8x": { "defaultMessage": "Light theme", "description": "PrismThemeToggle: light theme label" @@ -699,10 +715,6 @@ "defaultMessage": "On", "description": "MotionToggle: activate reduce motion label" }, - "vtDLzG": { - "defaultMessage": "Would you like to try a new search?", - "description": "SearchPage: try a new search message" - }, "w+BpPg": { "defaultMessage": "No comments yet. Be the first!", "description": "PageComments: no comments text" diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 50c9ca7..39ae75c 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -167,6 +167,10 @@ "defaultMessage": "Désolé, il semble que la page demandée n’existe pas. Si vous pensez que le chemin devrait exister, n’hésitez pas à me contacter avec les informations nécessaires pour que je puisse corriger le problème.", "description": "Error404Page: page body" }, + "A0TsHP": { + "defaultMessage": "Veuillez utiliser le formulaire ci-dessous pour commencer une recherche :", + "description": "SearchPage: search for message" + }, "A8hGaK": { "defaultMessage": "Commentaire :", "description": "CommentForm: comment label" @@ -219,9 +223,9 @@ "defaultMessage": "Thématiques", "description": "SearchPage: thematics list widget title" }, - "EeCqAE": { - "defaultMessage": "Chargement des résultats…", - "description": "SearchPage: loading search results message" + "E+ROR5": { + "defaultMessage": "Aucun résultat. Souhaitez-vous tenter une nouvelle rechercher ?", + "description": "SearchPage: no results" }, "Es52wh": { "defaultMessage": "Blog", @@ -307,6 +311,10 @@ "defaultMessage": "CV", "description": "SiteNavbar: main nav - cv link" }, + "N+3eau": { + "defaultMessage": "Résultats de la recherche pour « {query} »", + "description": "SearchPage: SEO - Page title" + }, "N804XO": { "defaultMessage": "Sujets", "description": "SearchPage: topics list widget title" @@ -319,6 +327,10 @@ "defaultMessage": "{commentsCount, plural, =0 {0 commentaire} one {# commentaire} other {# commentaires}} à propos de {title}", "description": "PostPreviewMeta: comments count" }, + "NqVQYo": { + "defaultMessage": "Recherche - {websiteName}", + "description": "SearchPage: SEO - Page title" + }, "Nx8Jo5": { "defaultMessage": "Profil Github", "description": "ProjectsPage: Github profile link" @@ -371,6 +383,10 @@ "defaultMessage": "Navigation principale", "description": "SiteNavbar: main nav accessible name" }, + "QRDdye": { + "defaultMessage": "Résultats de la recherche pour {query} - {websiteName}", + "description": "SearchPage: SEO - Page title" + }, "Qa9twM": { "defaultMessage": "Répondre", "description": "CommentsList: reply button" @@ -411,10 +427,6 @@ "defaultMessage": "Envoyer", "description": "ContactForm: send button" }, - "VkfO7t": { - "defaultMessage": "Les mots-clés doivent être plus longs qu'un caractère.", - "description": "NoResults: invalid query message" - }, "Vrw5/h": { "defaultMessage": "Logo d’{website}", "description": "SiteBranding: logo title" @@ -447,10 +459,6 @@ "defaultMessage": "Laisser un commentaire", "description": "PageComments: the section title of the comment form" }, - "YV//MH": { - "defaultMessage": "Aucun résultat.", - "description": "SearchPage: no results" - }, "Ygea7s": { "defaultMessage": "Thème clair", "description": "ThemeToggle: light theme label" @@ -471,10 +479,6 @@ "defaultMessage": "Vous ne pouvez pas charger plus d’articles sans Javascript, veuillez utiliser la pagination.", "description": "BlogPage: pagination no script message" }, - "ZNBhDP": { - "defaultMessage": "Résultats de la recherche pour {query}", - "description": "SearchPage: SEO - Page title" - }, "ZcFroC": { "defaultMessage": "Merci, votre commentaire a été envoyé avec succès.", "description": "PageComments: comment form success message" @@ -523,6 +527,10 @@ "defaultMessage": "Blog: développement, libre et open-source - Page {number} - {websiteName}", "description": "BlogPage: SEO - Page title" }, + "e3ppRI": { + "defaultMessage": "Les mots-clés doivent être plus longs qu'un caractère.", + "description": "SearchPage: invalid query message" + }, "eys2uX": { "defaultMessage": "Table des matières", "description": "PageLayout: table of contents title" @@ -647,6 +655,10 @@ "defaultMessage": "Découvrez les résultats de recherche pour {query} sur {websiteName}.", "description": "SearchPage: SEO - Meta description" }, + "qFqWQH": { + "defaultMessage": "Les thématiques sont en cours de chargement…", + "description": "SearchPage: loading thematics message" + }, "rVoW4G": { "defaultMessage": "Les thématiques sont en cours de chargement…", "description": "ThematicPage: loading thematics message" @@ -675,6 +687,10 @@ "defaultMessage": "Partiel", "description": "AckeeToggle: partial option name" }, + "tLflgC": { + "defaultMessage": "Les sujets sont en cours de chargement…", + "description": "SearchPage: loading topics message" + }, "tsWh8x": { "defaultMessage": "Thème clair", "description": "PrismThemeToggle: light theme label" @@ -699,10 +715,6 @@ "defaultMessage": "Marche", "description": "MotionToggle: activate reduce motion label" }, - "vtDLzG": { - "defaultMessage": "Souhaitez-vous essayer une nouvelle recherche ?", - "description": "SearchPage: try a new search message" - }, "w+BpPg": { "defaultMessage": "Aucun commentaire pour le moment. Soyez le premier !", "description": "PageComments: no comments text" diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx index 2bcb1c0..9eaecba 100644 --- a/src/pages/recherche/index.tsx +++ b/src/pages/recherche/index.tsx @@ -22,7 +22,6 @@ import { import { convertWPThematicPreviewToPageLink, convertWPTopicPreviewToPageLink, - fetchPostsCount, fetchThematicsCount, fetchThematicsList, fetchTopicsCount, @@ -30,6 +29,7 @@ import { } from '../../services/graphql'; import styles from '../../styles/pages/blog.module.scss'; import type { + GraphQLConnection, NextPageWithLayout, WPThematicPreview, WPTopicPreview, @@ -47,78 +47,70 @@ import { loadTranslation, type Messages } from '../../utils/helpers/server'; import { useArticlesList, useBreadcrumb, - useDataFromAPI, + useThematicsList, + useTopicsList, } from '../../utils/hooks'; +const NoResults = () => { + const intl = useIntl(); + const router = useRouter(); + + const searchSubmitHandler: SearchFormSubmit = useCallback( + async ({ query: searchQuery }) => { + if (!searchQuery) + return { + messages: { + error: intl.formatMessage({ + defaultMessage: 'Query must be longer than one character.', + description: 'SearchPage: invalid query message', + id: 'e3ppRI', + }), + }, + validator: (value) => value.query.length > 1, + }; + + await router.push({ pathname: ROUTES.SEARCH, query: { s: searchQuery } }); + + return undefined; + }, + [intl, router] + ); + + return ( +
+

+ {router.query.s + ? intl.formatMessage({ + defaultMessage: + 'No results found. Would you like to try a new search?', + description: 'SearchPage: no results', + id: 'E+ROR5', + }) + : intl.formatMessage({ + defaultMessage: 'Please use the form below to start searching:', + description: 'SearchPage: search for message', + id: 'A0TsHP', + })} +

+ +
+ ); +}; + type SearchPageProps = { - thematicsList: WPThematicPreview[]; - topicsList: WPTopicPreview[]; + data: { + thematics: GraphQLConnection; + topics: GraphQLConnection; + }; translation: Messages; }; /** * Search page. */ -const SearchPage: NextPageWithLayout = ({ - thematicsList, - topicsList, -}) => { +const SearchPage: NextPageWithLayout = ({ data }) => { const intl = useIntl(); - const { asPath, query, push: routerPush } = useRouter(); - const title = query.s - ? intl.formatMessage( - { - defaultMessage: 'Search results for {query}', - description: 'SearchPage: SEO - Page title', - id: 'ZNBhDP', - }, - { query: query.s as string } - ) - : intl.formatMessage({ - defaultMessage: 'Search', - description: 'SearchPage: SEO - Page title', - id: 'WDwNDl', - }); - const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ - title, - url: ROUTES.SEARCH, - }); - - const page = { - title: `${title} - ${CONFIG.name}`, - url: `${CONFIG.url}${asPath}`, - }; - const pageDescription = query.s - ? intl.formatMessage( - { - defaultMessage: - 'Discover search results for {query} on {websiteName}.', - description: 'SearchPage: SEO - Meta description', - id: 'pg26sn', - }, - { query: query.s as string, websiteName: CONFIG.name } - ) - : intl.formatMessage( - { - defaultMessage: 'Search for a post on {websiteName}.', - description: 'SearchPage: SEO - Meta description', - id: 'npisb3', - }, - { websiteName: CONFIG.name } - ); - const webpageSchema = getWebPageSchema({ - description: pageDescription, - locale: CONFIG.locales.defaultLocale, - slug: asPath, - title: page.title, - }); - const blogSchema = getBlogSchema({ - isSinglePage: false, - locale: CONFIG.locales.defaultLocale, - slug: asPath, - }); - const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); - + const { asPath, query } = useRouter(); const { articles, error, @@ -129,68 +121,127 @@ const SearchPage: NextPageWithLayout = ({ hasNextPage, loadMore, } = useArticlesList({ - fallback: [], perPage: CONFIG.postsPerPage, - searchQuery: query.s as string, + searchQuery: typeof query.s === 'string' ? query.s : undefined, }); - - const totalArticles = useDataFromAPI(async () => - fetchPostsCount({ search: query.s as string }) - ); - - const thematicsListTitle = intl.formatMessage({ - defaultMessage: 'Thematics', - description: 'SearchPage: thematics list widget title', - id: 'Dq6+WH', - }); - - const topicsListTitle = intl.formatMessage({ - defaultMessage: 'Topics', - description: 'SearchPage: topics list widget title', - id: 'N804XO', + const { isLoading: areThematicsLoading, thematics } = useThematicsList({ + fallback: data.thematics, + input: { first: data.thematics.pageInfo.total }, }); - const loadingResults = intl.formatMessage({ - defaultMessage: 'Loading the search results...', - description: 'SearchPage: loading search results message', - id: 'EeCqAE', + const { isLoading: areTopicsLoading, topics } = useTopicsList({ + fallback: data.topics, + input: { first: data.topics.pageInfo.total }, }); - const searchSubmitHandler: SearchFormSubmit = useCallback( - ({ query: searchQuery }) => { - if (!searchQuery) - return { - messages: { - error: intl.formatMessage({ - defaultMessage: 'Query must be longer than one character.', - description: 'NoResults: invalid query message', - id: 'VkfO7t', - }), + const messages = { + loading: { + thematicsList: intl.formatMessage({ + defaultMessage: 'Thematics are loading...', + description: 'SearchPage: loading thematics message', + id: 'qFqWQH', + }), + topicsList: intl.formatMessage({ + defaultMessage: 'Topics are loading...', + description: 'SearchPage: loading topics message', + id: 'tLflgC', + }), + }, + pageTitle: query.s + ? intl.formatMessage( + { + defaultMessage: 'Search results for "{query}"', + description: 'SearchPage: SEO - Page title', + id: 'N+3eau', }, - validator: (value) => value.query.length > 1, - }; + { query: query.s as string } + ) + : intl.formatMessage({ + defaultMessage: 'Search', + description: 'SearchPage: SEO - Page title', + id: 'WDwNDl', + }), + seo: { + metaDesc: query.s + ? intl.formatMessage( + { + defaultMessage: + 'Discover search results for {query} on {websiteName}.', + description: 'SearchPage: SEO - Meta description', + id: 'pg26sn', + }, + { query: query.s as string, websiteName: CONFIG.name } + ) + : intl.formatMessage( + { + defaultMessage: 'Search for a post on {websiteName}.', + description: 'SearchPage: SEO - Meta description', + id: 'npisb3', + }, + { websiteName: CONFIG.name } + ), + title: query.s + ? intl.formatMessage( + { + defaultMessage: 'Search results for {query} - {websiteName}', + description: 'SearchPage: SEO - Page title', + id: 'QRDdye', + }, + { query: query.s as string, websiteName: CONFIG.name } + ) + : intl.formatMessage( + { + defaultMessage: 'Search - {websiteName}', + description: 'SearchPage: SEO - Page title', + id: 'NqVQYo', + }, + { websiteName: CONFIG.name } + ), + }, + widgets: { + thematicsListTitle: intl.formatMessage({ + defaultMessage: 'Thematics', + description: 'SearchPage: thematics list widget title', + id: 'Dq6+WH', + }), + topicsListTitle: intl.formatMessage({ + defaultMessage: 'Topics', + description: 'SearchPage: topics list widget title', + id: 'N804XO', + }), + }, + }; - routerPush({ pathname: ROUTES.SEARCH, query: { s: searchQuery } }); + const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ + title: messages.pageTitle, + url: ROUTES.SEARCH, + }); - return undefined; - }, - [intl, routerPush] - ); + const webpageSchema = getWebPageSchema({ + description: messages.seo.metaDesc, + locale: CONFIG.locales.defaultLocale, + slug: asPath, + title: messages.pageTitle, + }); + const blogSchema = getBlogSchema({ + isSinglePage: false, + locale: CONFIG.locales.defaultLocale, + slug: asPath, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); - const foundArticles = articles?.flatMap((p) => - p.edges.map((edge) => edge.node) - ); + const pageUrl = `${CONFIG.url}${asPath}`; return ( - {page.title} + {messages.seo.title} {/*eslint-disable-next-line react/jsx-no-literals -- Name allowed */} - - + + {/*eslint-disable-next-line react/jsx-no-literals -- Content allowed */} - - + +