From 53b63ac27c2275262db9a04be02210a3287aa71d Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Fri, 1 Dec 2023 19:34:58 +0100 Subject: refactor(pages): refine Blog pages * replace usePostsList with useArticlesList to keep names coherent * remove useIsMounted hook * rewrite useRedirection hook * add redirect in getStaticProps to avoid unecessary fetching * move Pagination component in a noscript tag * use hooks to refresh thematics and topics lists * complete Cypress tests --- src/i18n/en.json | 24 +- src/i18n/fr.json | 24 +- src/pages/blog/index.tsx | 305 +++++++++++-------- src/pages/blog/page/[number].tsx | 334 ++++++++++++++------- src/pages/recherche/index.tsx | 10 +- src/styles/pages/Page.module.scss | 44 --- src/styles/pages/blog.module.scss | 16 + src/utils/hooks/index.ts | 3 +- src/utils/hooks/use-articles-list/index.ts | 1 + .../use-articles-list/use-articles-list.test.tsx | 109 +++++++ .../hooks/use-articles-list/use-articles-list.ts | 86 ++++++ src/utils/hooks/use-is-mounted.tsx | 17 -- src/utils/hooks/use-pagination/use-pagination.ts | 8 +- src/utils/hooks/use-posts-list/index.ts | 1 - .../hooks/use-posts-list/use-posts-list.test.tsx | 74 ----- src/utils/hooks/use-posts-list/use-posts-list.ts | 83 ----- src/utils/hooks/use-redirection.tsx | 31 -- src/utils/hooks/use-redirection/index.ts | 1 + .../hooks/use-redirection/use-redirection.test.ts | 80 +++++ src/utils/hooks/use-redirection/use-redirection.ts | 41 +++ tests/cypress/e2e/pages/blog.cy.ts | 13 + tests/fixtures/wp-posts.fixture.ts | 4 +- 22 files changed, 792 insertions(+), 517 deletions(-) delete mode 100644 src/styles/pages/Page.module.scss create mode 100644 src/utils/hooks/use-articles-list/index.ts create mode 100644 src/utils/hooks/use-articles-list/use-articles-list.test.tsx create mode 100644 src/utils/hooks/use-articles-list/use-articles-list.ts delete mode 100644 src/utils/hooks/use-is-mounted.tsx delete mode 100644 src/utils/hooks/use-posts-list/index.ts delete mode 100644 src/utils/hooks/use-posts-list/use-posts-list.test.tsx delete mode 100644 src/utils/hooks/use-posts-list/use-posts-list.ts delete mode 100644 src/utils/hooks/use-redirection.tsx create mode 100644 src/utils/hooks/use-redirection/index.ts create mode 100644 src/utils/hooks/use-redirection/use-redirection.test.ts create mode 100644 src/utils/hooks/use-redirection/use-redirection.ts diff --git a/src/i18n/en.json b/src/i18n/en.json index f760860..b1768a8 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -147,6 +147,10 @@ "defaultMessage": "{date} at {time}", "description": "Time: readable date and time" }, + "8xVO3Y": { + "defaultMessage": "Blog - Page {number}", + "description": "BlogPage: page title with number" + }, "9MTBCG": { "defaultMessage": "{thematicsCount, plural, =0 {Thematics:} one {Thematic:} other {Thematics:}}", "description": "PostPreviewMeta: thematics label" @@ -343,6 +347,10 @@ "defaultMessage": "Share by Email", "description": "SharingWidget: Email sharing link" }, + "OsclKU": { + "defaultMessage": "Topics are loading...", + "description": "BlogPage: loading topics message" + }, "PBdVsm": { "defaultMessage": "{starsCount, plural, =0 {No stars} one {# star} other {# stars}}", "description": "ProjectOverview: stars count" @@ -459,6 +467,10 @@ "defaultMessage": "Name:", "description": "CommentForm: name label" }, + "ZMES/E": { + "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" @@ -507,6 +519,10 @@ "defaultMessage": "{website} picture", "description": "SiteBranding: photo alternative text" }, + "dG3sT3": { + "defaultMessage": "Blog: development, open source - Page {number} - {websiteName}", + "description": "BlogPage: SEO - Page title" + }, "eys2uX": { "defaultMessage": "Table of Contents", "description": "PageLayout: table of contents title" @@ -703,14 +719,14 @@ "defaultMessage": "{minutesCount, plural, =0 {Less than one minute} one {# minute} other {# minutes}}", "description": "PostPreviewMeta: rounded minutes count" }, + "y37FuH": { + "defaultMessage": "Thematics are loading...", + "description": "BlogPage: loading thematics message" + }, "yN5P+m": { "defaultMessage": "Message:", "description": "ContactForm: message label" }, - "zbzlb1": { - "defaultMessage": "Page {number}", - "description": "BlogPage: page number" - }, "zhjPcZ": { "defaultMessage": "Settings form", "description": "SiteNavbar: an accessible name for the settings form in navbar" diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 9a098fc..50c9ca7 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -147,6 +147,10 @@ "defaultMessage": "{date} à {time}", "description": "Time: readable date and time" }, + "8xVO3Y": { + "defaultMessage": "Blog - Page {number}", + "description": "BlogPage: page title with number" + }, "9MTBCG": { "defaultMessage": "{thematicsCount, plural, =0 {Thématiques :} one {Thématique :} other {Thématiques :}}", "description": "PostPreviewMeta: thematics label" @@ -343,6 +347,10 @@ "defaultMessage": "Partager par email", "description": "SharingWidget: Email sharing link" }, + "OsclKU": { + "defaultMessage": "Les sujets sont en cours de chargement…", + "description": "BlogPage: loading topics message" + }, "PBdVsm": { "defaultMessage": "{starsCount, plural, =0 {0 étoile} one {# étoile} other {# étoiles}}", "description": "ProjectOverview: stars count" @@ -459,6 +467,10 @@ "defaultMessage": "Nom :", "description": "CommentForm: name label" }, + "ZMES/E": { + "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" @@ -507,6 +519,10 @@ "defaultMessage": "Photo d’{website}", "description": "SiteBranding: photo alternative text" }, + "dG3sT3": { + "defaultMessage": "Blog: développement, libre et open-source - Page {number} - {websiteName}", + "description": "BlogPage: SEO - Page title" + }, "eys2uX": { "defaultMessage": "Table des matières", "description": "PageLayout: table of contents title" @@ -703,14 +719,14 @@ "defaultMessage": "{minutesCount, plural, =0 {Moins d’une minute} one {# minute} other {# minutes}}", "description": "PostPreviewMeta: rounded minutes count" }, + "y37FuH": { + "defaultMessage": "Les thématiques sont en cours de chargement…", + "description": "BlogPage: loading thematics message" + }, "yN5P+m": { "defaultMessage": "Message :", "description": "ContactForm: message label" }, - "zbzlb1": { - "defaultMessage": "Page {number}", - "description": "BlogPage: page number" - }, "zhjPcZ": { "defaultMessage": "Formulaire des réglages", "description": "SiteNavbar: an accessible name for the settings form in navbar" diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index 12bc03e..df25cd2 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -1,9 +1,8 @@ /* eslint-disable max-statements */ import type { GetStaticProps } from 'next'; import Head from 'next/head'; -import { useRouter } from 'next/router'; import Script from 'next/script'; -import { useCallback, useRef } from 'react'; +import { useCallback } from 'react'; import { useIntl } from 'react-intl'; import { getLayout, @@ -18,11 +17,11 @@ import { PageHeader, PageBody, PageSidebar, + Spinner, } from '../../components'; import { convertWPThematicPreviewToPageLink, convertWPTopicPreviewToPageLink, - fetchPostsCount, fetchPostsList, fetchThematicsCount, fetchThematicsList, @@ -47,71 +46,30 @@ import { getWebPageSchema, } from '../../utils/helpers'; import { loadTranslation, type Messages } from '../../utils/helpers/server'; -import { useBreadcrumb, useIsMounted, usePostsList } from '../../utils/hooks'; +import { + useArticlesList, + useBreadcrumb, + useThematicsList, + useTopicsList, +} from '../../utils/hooks'; + +const renderPaginationLink: RenderPaginationLink = (pageNum) => + `${ROUTES.BLOG}/page/${pageNum}`; type BlogPageProps = { - posts: GraphQLConnection; - thematicsList: WPThematicPreview[]; - topicsList: WPTopicPreview[]; - totalArticles: number; + data: { + posts: GraphQLConnection; + thematics: GraphQLConnection; + topics: GraphQLConnection; + }; translation: Messages; }; /** * Blog index page. */ -const BlogPage: NextPageWithLayout = ({ - posts, - thematicsList, - topicsList, - totalArticles, -}) => { +const BlogPage: NextPageWithLayout = ({ data }) => { const intl = useIntl(); - const title = intl.formatMessage({ - defaultMessage: 'Blog', - description: 'BlogPage: page title', - id: '7TbbIk', - }); - const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ - title, - url: ROUTES.BLOG, - }); - const postsListRef = useRef(null); - const isMounted = useIsMounted(postsListRef); - const { asPath } = useRouter(); - const page = { - title: intl.formatMessage( - { - defaultMessage: 'Blog: development, open source - {websiteName}', - description: 'BlogPage: SEO - Page title', - id: '+Y+tLK', - }, - { websiteName: CONFIG.name } - ), - url: `${CONFIG.url}${asPath}`, - }; - const pageDescription = intl.formatMessage( - { - defaultMessage: - "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.", - description: 'BlogPage: SEO - Meta description', - id: '18h/t0', - }, - { websiteName: CONFIG.name } - ); - const webpageSchema = getWebPageSchema({ - description: pageDescription, - locale: CONFIG.locales.defaultLocale, - slug: asPath, - title, - }); - const blogSchema = getBlogSchema({ - isSinglePage: false, - locale: CONFIG.locales.defaultLocale, - slug: asPath, - }); - const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); - const { articles, error, @@ -121,27 +79,101 @@ const BlogPage: NextPageWithLayout = ({ isRefreshing, hasNextPage, loadMore, - } = usePostsList({ - fallback: [posts], - fetcher: fetchPostsList, + } = useArticlesList({ + fallback: [data.posts], perPage: CONFIG.postsPerPage, }); + const { isLoading: areThematicsLoading, thematics } = useThematicsList({ + fallback: data.thematics, + input: { first: data.thematics.pageInfo.total }, + }); + const { isLoading: areTopicsLoading, topics } = useTopicsList({ + fallback: data.topics, + input: { first: data.topics.pageInfo.total }, + }); - const thematicsListTitle = intl.formatMessage({ - defaultMessage: 'Thematics', - description: 'BlogPage: thematics list widget title', - id: 'HriY57', + const messages = { + loading: { + thematicsList: intl.formatMessage({ + defaultMessage: 'Thematics are loading...', + description: 'BlogPage: loading thematics message', + id: 'y37FuH', + }), + topicsList: intl.formatMessage({ + defaultMessage: 'Topics are loading...', + description: 'BlogPage: loading topics message', + id: 'OsclKU', + }), + }, + pageTitle: intl.formatMessage({ + defaultMessage: 'Blog', + description: 'BlogPage: page title', + id: '7TbbIk', + }), + pagination: { + noJS: intl.formatMessage({ + defaultMessage: + "You can't load more articles without Javascript, please use the pagination instead.", + description: 'BlogPage: pagination no script message', + id: 'ZMES/E', + }), + title: intl.formatMessage({ + defaultMessage: 'Pagination', + description: 'BlogPage: pagination accessible name', + id: 'AXe1Iz', + }), + }, + seo: { + metaDesc: intl.formatMessage( + { + defaultMessage: + "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.", + description: 'BlogPage: SEO - Meta description', + id: '18h/t0', + }, + { websiteName: CONFIG.name } + ), + title: intl.formatMessage( + { + defaultMessage: 'Blog: development, open source - {websiteName}', + description: 'BlogPage: SEO - Page title', + id: '+Y+tLK', + }, + { websiteName: CONFIG.name } + ), + }, + widgets: { + thematicsListTitle: intl.formatMessage({ + defaultMessage: 'Thematics', + description: 'BlogPage: thematics list widget title', + id: 'HriY57', + }), + topicsListTitle: intl.formatMessage({ + defaultMessage: 'Topics', + description: 'BlogPage: topics list widget title', + id: '2D9tB5', + }), + }, + }; + + const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ + title: messages.pageTitle, + url: ROUTES.BLOG, }); - const topicsListTitle = intl.formatMessage({ - defaultMessage: 'Topics', - description: 'BlogPage: topics list widget title', - id: '2D9tB5', + const webpageSchema = getWebPageSchema({ + description: messages.seo.metaDesc, + locale: CONFIG.locales.defaultLocale, + slug: ROUTES.BLOG, + title: messages.pageTitle, }); - const renderPaginationLink: RenderPaginationLink = useCallback( - (pageNum) => `${ROUTES.BLOG}/page/${pageNum}`, - [] - ); + const blogSchema = getBlogSchema({ + isSinglePage: false, + locale: CONFIG.locales.defaultLocale, + slug: ROUTES.BLOG, + }); + const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); + const renderPaginationLabel: RenderPaginationItemAriaLabel = useCallback( ({ kind, pageNumber: number, isCurrentPage }) => { switch (kind) { @@ -187,27 +219,19 @@ const BlogPage: NextPageWithLayout = ({ [intl] ); - const paginationAriaLabel = intl.formatMessage({ - defaultMessage: 'Pagination', - description: 'BlogPage: pagination accessible name', - id: 'AXe1Iz', - }); - - const blogArticles = articles?.flatMap((p) => - p.edges.map((edge) => edge.node) - ); + const pageUrl = `${CONFIG.url}${ROUTES.BLOG}`; 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 */} - - + +