diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-13 17:45:59 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-13 17:45:59 +0100 |
| commit | 56878f647ea0f1066fa3e222d7aa0d83057f496d (patch) | |
| tree | 26f673a062741414bfa7db5d37990936ce115f49 /src/pages | |
| parent | 599b70cd2390d08ce26ee44174b3f39c6587110c (diff) | |
refactor(components): rewrite PostsList component
* remove NoResults component and move logic to Search page
* add a usePostsList hook
* remove Pagination from PostsList (it is only used if javascript is
disabled and not on every posts list)
* replace `byYear` prop with `sortByYear`
* replace `loadMore` prop with `onLoadMore`
* remove `showLoadMoreBtn` (we can use `loadMore` prop instead to
determine if we need to display the button)
* replace `titleLevel` prop with `headingLvl`
* add `firstNewResult` prop to handle focus on the new results when
loading more article (we should not focus a useless span but the item
directly)
Diffstat (limited to 'src/pages')
| -rw-r--r-- | src/pages/blog/index.tsx | 101 | ||||
| -rw-r--r-- | src/pages/blog/page/[number].tsx | 71 | ||||
| -rw-r--r-- | src/pages/recherche/index.tsx | 69 | ||||
| -rw-r--r-- | src/pages/sujet/[slug].tsx | 10 | ||||
| -rw-r--r-- | src/pages/thematique/[slug].tsx | 9 |
5 files changed, 215 insertions, 45 deletions
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index d74124e..678b75a 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -3,6 +3,7 @@ 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 { useIntl } from 'react-intl'; import { getLayout, @@ -12,6 +13,9 @@ import { Notice, PageLayout, PostsList, + Pagination, + type RenderPaginationLink, + type RenderPaginationItemAriaLabel, } from '../../components'; import { getArticles, @@ -21,6 +25,7 @@ import { getTotalThematics, getTotalTopics, } from '../../services/graphql'; +import styles from '../../styles/pages/blog.module.scss'; import type { EdgesResponse, NextPageWithLayout, @@ -34,12 +39,16 @@ import { getBlogSchema, getLinksListItems, getPageLinkFromRawData, - getPostsList, getSchemaJson, getWebPageSchema, } from '../../utils/helpers'; import { loadTranslation, type Messages } from '../../utils/helpers/server'; -import { useBreadcrumb, usePagination, useSettings } from '../../utils/hooks'; +import { + useBreadcrumb, + useIsMounted, + usePostsList, + useSettings, +} from '../../utils/hooks'; type BlogPageProps = { articles: EdgesResponse<RawArticle>; @@ -68,7 +77,8 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ title, url: ROUTES.BLOG, }); - + const postsListRef = useRef<HTMLDivElement>(null); + const isMounted = useIsMounted(postsListRef); const { blog, website } = useSettings(); const { asPath } = useRouter(); const page = { @@ -105,14 +115,15 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); const { - data, error, + firstNewResultIndex, isLoading, isLoadingMore, isRefreshing, hasNextPage, loadMore, - } = usePagination<RawArticle>({ + posts, + } = usePostsList({ fallback: [articles], fetcher: getArticles, perPage: blog.postsPerPage, @@ -129,7 +140,54 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ description: 'BlogPage: topics list widget title', id: '2D9tB5', }); - const postsListBaseUrl = `${ROUTES.BLOG}/page/`; + const renderPaginationLink: RenderPaginationLink = useCallback( + (pageNum) => `${ROUTES.BLOG}/page/${pageNum}`, + [] + ); + const renderPaginationLabel: RenderPaginationItemAriaLabel = useCallback( + ({ kind, pageNumber: number, isCurrentPage }) => { + switch (kind) { + case 'backward': + return intl.formatMessage( + { + defaultMessage: 'Go to previous page, page {number}', + description: 'BlogPage: previous page label', + id: 'faO6BQ', + }, + { number } + ); + case 'forward': + return intl.formatMessage( + { + defaultMessage: 'Go to next page, page {number}', + description: 'BlogPage: next page label', + id: 'oq3BzP', + }, + { number } + ); + case 'number': + default: + return isCurrentPage + ? intl.formatMessage( + { + defaultMessage: 'Current page, page {number}', + description: 'BlogPage: current page label', + id: 'JL6G22', + }, + { number } + ) + : intl.formatMessage( + { + defaultMessage: 'Go to page {number}', + description: 'BlogPage: page number label', + id: 'IVczxR', + }, + { number } + ); + } + }, + [intl] + ); const headerMeta: MetaItemData[] = totalArticles ? [ @@ -153,6 +211,12 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ ] : []; + const paginationAriaLabel = intl.formatMessage({ + defaultMessage: 'Pagination', + description: 'BlogPage: pagination accessible name', + id: 'AXe1Iz', + }); + return ( <> <Head> @@ -206,17 +270,28 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ />, ]} > - {data ? ( + {posts ? ( <PostsList - baseUrl={postsListBaseUrl} - byYear={true} + className={styles.list} + firstNewResult={firstNewResultIndex} isLoading={isLoading || isLoadingMore || isRefreshing} - loadMore={loadMore} - posts={getPostsList(data)} - showLoadMoreBtn={hasNextPage} - total={totalArticles} + onLoadMore={hasNextPage && isMounted ? loadMore : undefined} + posts={posts} + ref={postsListRef} + sortByYear + total={isMounted ? totalArticles : undefined} /> ) : null} + {isMounted ? null : ( + <Pagination + aria-label={paginationAriaLabel} + current={1} + isCentered + renderItemAriaLabel={renderPaginationLabel} + renderLink={renderPaginationLink} + total={totalArticles} + /> + )} {error ? ( <Notice // eslint-disable-next-line react/jsx-no-literals -- Kind allowed diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx index 1c723f1..842c2b8 100644 --- a/src/pages/blog/page/[number].tsx +++ b/src/pages/blog/page/[number].tsx @@ -4,6 +4,7 @@ import type { GetStaticPaths, GetStaticProps } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; import Script from 'next/script'; +import { useCallback } from 'react'; import { useIntl } from 'react-intl'; import { getLayout, @@ -12,6 +13,9 @@ import { type MetaItemData, PageLayout, PostsList, + Pagination, + type RenderPaginationLink, + type RenderPaginationItemAriaLabel, } from '../../../components'; import { getArticles, @@ -131,7 +135,54 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ description: 'BlogPage: topics list widget title', id: '2D9tB5', }); - const postsListBaseUrl = `${ROUTES.BLOG}/page/`; + const renderPaginationLink: RenderPaginationLink = useCallback( + (pageNum) => `${ROUTES.BLOG}/page/${pageNum}`, + [] + ); + const renderPaginationLabel: RenderPaginationItemAriaLabel = useCallback( + ({ kind, pageNumber: number, isCurrentPage }) => { + switch (kind) { + case 'backward': + return intl.formatMessage( + { + defaultMessage: 'Go to previous page, page {number}', + description: 'BlogPage: previous page label', + id: 'faO6BQ', + }, + { number } + ); + case 'forward': + return intl.formatMessage( + { + defaultMessage: 'Go to next page, page {number}', + description: 'BlogPage: next page label', + id: 'oq3BzP', + }, + { number } + ); + case 'number': + default: + return isCurrentPage + ? intl.formatMessage( + { + defaultMessage: 'Current page, page {number}', + description: 'BlogPage: current page label', + id: 'JL6G22', + }, + { number } + ) + : intl.formatMessage( + { + defaultMessage: 'Go to page {number}', + description: 'BlogPage: page number label', + id: 'IVczxR', + }, + { number } + ); + } + }, + [intl] + ); const headerMeta: MetaItemData[] = totalArticles ? [ @@ -155,6 +206,12 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ ] : []; + const paginationAriaLabel = intl.formatMessage({ + defaultMessage: 'Pagination', + description: 'BlogPage: pagination accessible name', + id: 'AXe1Iz', + }); + return ( <> <Head> @@ -208,11 +265,13 @@ const BlogPage: NextPageWithLayout<BlogPageProps> = ({ />, ]} > - <PostsList - baseUrl={postsListBaseUrl} - byYear={true} - pageNumber={pageNumber} - posts={getPostsList([articles])} + <PostsList posts={getPostsList([articles])} sortByYear /> + <Pagination + aria-label={paginationAriaLabel} + current={pageNumber} + isCentered + renderItemAriaLabel={renderPaginationLabel} + renderLink={renderPaginationLink} total={totalArticles} /> </PageLayout> diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx index a0e5057..effd087 100644 --- a/src/pages/recherche/index.tsx +++ b/src/pages/recherche/index.tsx @@ -3,6 +3,7 @@ import type { GetStaticProps } from 'next'; import Head from 'next/head'; import { useRouter } from 'next/router'; import Script from 'next/script'; +import { useCallback } from 'react'; import { useIntl } from 'react-intl'; import { getLayout, @@ -13,6 +14,8 @@ import { PageLayout, PostsList, Spinner, + SearchForm, + type SearchFormSubmit, } from '../../components'; import { getArticles, @@ -22,9 +25,9 @@ import { getTotalThematics, getTotalTopics, } from '../../services/graphql'; +import styles from '../../styles/pages/blog.module.scss'; import type { NextPageWithLayout, - RawArticle, RawThematicPreview, RawTopicPreview, } from '../../types'; @@ -33,7 +36,6 @@ import { getBlogSchema, getLinksListItems, getPageLinkFromRawData, - getPostsList, getSchemaJson, getWebPageSchema, } from '../../utils/helpers'; @@ -41,7 +43,7 @@ import { loadTranslation, type Messages } from '../../utils/helpers/server'; import { useBreadcrumb, useDataFromAPI, - usePagination, + usePostsList, useSettings, } from '../../utils/hooks'; @@ -59,7 +61,7 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ topicsList, }) => { const intl = useIntl(); - const { asPath, query } = useRouter(); + const { asPath, query, push: routerPush } = useRouter(); const title = query.s ? intl.formatMessage( { @@ -116,14 +118,15 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ const schemaJsonLd = getSchemaJson([webpageSchema, blogSchema]); const { - data, error, + firstNewResultIndex, isLoading, isLoadingMore, isRefreshing, hasNextPage, loadMore, - } = usePagination<RawArticle>({ + posts, + } = usePostsList({ fallback: [], fetcher: getArticles, perPage: blog.postsPerPage, @@ -167,13 +170,33 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ description: 'SearchPage: topics list widget title', id: 'N804XO', }); - const postsListBaseUrl = `${ROUTES.SEARCH}/page/`; const loadingResults = intl.formatMessage({ defaultMessage: 'Loading the search results...', description: 'SearchPage: loading search results message', id: 'EeCqAE', }); + 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', + }), + }, + validator: (value) => value.query.length > 1, + }; + + routerPush({ pathname: ROUTES.SEARCH, query: { s: searchQuery } }); + + return undefined; + }, + [intl, routerPush] + ); + return ( <> <Head> @@ -227,18 +250,34 @@ const SearchPage: NextPageWithLayout<SearchPageProps> = ({ />, ]} > - {data && data.length > 0 ? ( + {posts ? null : <Spinner>{loadingResults}</Spinner>} + {posts?.length ? ( <PostsList - baseUrl={postsListBaseUrl} - byYear={true} + className={styles.list} + firstNewResult={firstNewResultIndex} isLoading={isLoading || isLoadingMore || isRefreshing} - loadMore={loadMore} - posts={getPostsList(data)} - showLoadMoreBtn={hasNextPage} - total={totalArticles ?? 0} + onLoadMore={hasNextPage ? loadMore : undefined} + posts={posts} + sortByYear /> ) : ( - <Spinner>{loadingResults}</Spinner> + <> + <p> + {intl.formatMessage({ + defaultMessage: 'No results found.', + description: 'SearchPage: no results', + id: 'YV//MH', + })} + </p> + <p> + {intl.formatMessage({ + defaultMessage: 'Would you like to try a new search?', + description: 'SearchPage: try a new search message', + id: 'vtDLzG', + })} + </p> + <SearchForm isLabelHidden onSubmit={searchSubmitHandler} /> + </> )} {error ? ( <Notice diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx index 9094703..66c3d02 100644 --- a/src/pages/sujet/[slug].tsx +++ b/src/pages/sujet/[slug].tsx @@ -21,7 +21,7 @@ import { getTopicsPreview, getTotalTopics, } from '../../services/graphql'; -import styles from '../../styles/pages/topic.module.scss'; +import styles from '../../styles/pages/blog.module.scss'; import type { NextPageWithLayout, PageLink, Topic } from '../../types'; import { ROUTES } from '../../utils/constants'; import { @@ -156,7 +156,6 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ </> ); const pageUrl = `${website.url}${asPath}`; - const postsListBaseUrl = `${ROUTES.TOPICS}/page/`; return ( <> @@ -225,11 +224,10 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({ )} </Heading> <PostsList - baseUrl={postsListBaseUrl} - byYear={true} + className={styles.list} posts={getPostsWithUrl(articles)} - titleLevel={3} - total={articles.length} + headingLvl={3} + sortByYear /> </> ) : null} diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx index bb97f47..61d105e 100644 --- a/src/pages/thematique/[slug].tsx +++ b/src/pages/thematique/[slug].tsx @@ -20,6 +20,7 @@ import { getThematicsPreview, getTotalThematics, } from '../../services/graphql'; +import styles from '../../styles/pages/blog.module.scss'; import type { NextPageWithLayout, PageLink, Thematic } from '../../types'; import { ROUTES } from '../../utils/constants'; import { @@ -128,7 +129,6 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ id: '/42Z0z', }); const pageUrl = `${website.url}${asPath}`; - const postsListBaseUrl = `${ROUTES.THEMATICS.INDEX}/page/`; return ( <> @@ -197,11 +197,10 @@ const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({ )} </Heading> <PostsList - baseUrl={postsListBaseUrl} - byYear={true} + className={styles.list} posts={getPostsWithUrl(articles)} - titleLevel={3} - total={articles.length} + headingLvl={3} + sortByYear /> </> ) : null} |
