aboutsummaryrefslogtreecommitdiffstats
path: root/src/pages
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-03-09 00:38:02 +0100
committerGitHub <noreply@github.com>2022-03-09 00:38:02 +0100
commit5b6639a3cf9b6c63045cb82e6ef1a43b0742c367 (patch)
tree4e7cebf9f6b094d405e96febe743fea514cfca9f /src/pages
parentb0d9d8cb1c8c4a4d2b9234bbfdc7195fb563b21a (diff)
feat: provide pagination for users with js disabled (#13)
* chore: add a Pagination component * chore: add blog pages * chore: fallback to page number based navigation if JS disabled * chore: update translation
Diffstat (limited to 'src/pages')
-rw-r--r--src/pages/blog/index.tsx59
-rw-r--r--src/pages/blog/page/[id].tsx195
2 files changed, 226 insertions, 28 deletions
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index 543fad9..366fc28 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -1,5 +1,6 @@
import { Button } from '@components/Buttons';
import { getLayout } from '@components/Layouts/Layout';
+import Pagination from '@components/Pagination/Pagination';
import PaginationCursor from '@components/PaginationCursor/PaginationCursor';
import PostHeader from '@components/PostHeader/PostHeader';
import PostsList from '@components/PostsList/PostsList';
@@ -29,12 +30,17 @@ import useSWRInfinite from 'swr/infinite';
const Blog: NextPageWithLayout<BlogPageProps> = ({
allThematics,
allTopics,
- firstPosts,
+ posts,
totalPosts,
}) => {
const intl = useIntl();
const lastPostRef = useRef<HTMLSpanElement>(null);
const router = useRouter();
+ const [isMounted, setIsMounted] = useState<boolean>(false);
+
+ useEffect(() => {
+ if (typeof window !== undefined) setIsMounted(true);
+ }, []);
const getKey = (pageIndex: number, previousData: PostsListData) => {
if (previousData && !previousData.posts) return null;
@@ -50,7 +56,7 @@ const Blog: NextPageWithLayout<BlogPageProps> = ({
const { data, error, size, setSize } = useSWRInfinite(
getKey,
getPublishedPosts,
- { fallbackData: [firstPosts] }
+ { fallbackData: [posts] }
);
const [totalPostsCount, setTotalPostsCount] = useState<number>(totalPosts);
@@ -171,31 +177,28 @@ const Blog: NextPageWithLayout<BlogPageProps> = ({
<PostHeader title={title} meta={{ results: totalPostsCount }} />
<div className={styles.body}>
{getPostsList()}
- {hasNextPage && (
- <>
- <PaginationCursor
- current={loadedPostsCount}
- total={totalPostsCount}
- />
- <Button
- isDisabled={isLoadingMore}
- clickHandler={loadMorePosts}
- position="center"
- spacing={true}
- >
- {intl.formatMessage({
- defaultMessage: 'Load more?',
- description: 'BlogPage: load more text',
- })}
- </Button>
- <noscript>
- {intl.formatMessage({
- defaultMessage: 'Javascript is required to load more posts.',
- description: 'BlogPage: noscript tag',
- })}
- </noscript>
- </>
- )}
+ {hasNextPage &&
+ (isMounted ? (
+ <>
+ <PaginationCursor
+ current={loadedPostsCount}
+ total={totalPostsCount}
+ />
+ <Button
+ isDisabled={isLoadingMore}
+ clickHandler={loadMorePosts}
+ position="center"
+ spacing={true}
+ >
+ {intl.formatMessage({
+ defaultMessage: 'Load more?',
+ description: 'BlogPage: load more text',
+ })}
+ </Button>
+ </>
+ ) : (
+ <Pagination baseUrl="/blog" total={totalPostsCount} />
+ ))}
</div>
<Sidebar
position="right"
@@ -246,8 +249,8 @@ export const getStaticProps: GetStaticProps = async (
allThematics,
allTopics,
breadcrumbTitle,
- firstPosts,
locale,
+ posts: firstPosts,
totalPosts,
translation,
},
diff --git a/src/pages/blog/page/[id].tsx b/src/pages/blog/page/[id].tsx
new file mode 100644
index 0000000..3be058b
--- /dev/null
+++ b/src/pages/blog/page/[id].tsx
@@ -0,0 +1,195 @@
+import { getLayout } from '@components/Layouts/Layout';
+import Pagination from '@components/Pagination/Pagination';
+import PostHeader from '@components/PostHeader/PostHeader';
+import PostsList from '@components/PostsList/PostsList';
+import Sidebar from '@components/Sidebar/Sidebar';
+import { ThematicsList, TopicsList } from '@components/Widgets';
+import {
+ getAllThematics,
+ getAllTopics,
+ getEndCursor,
+ getPostsTotal,
+ getPublishedPosts,
+} from '@services/graphql/queries';
+import { NextPageWithLayout } from '@ts/types/app';
+import { BlogPageProps } from '@ts/types/blog';
+import { settings } from '@utils/config';
+import { getIntlInstance, loadTranslation } from '@utils/helpers/i18n';
+import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import Script from 'next/script';
+import { useIntl } from 'react-intl';
+import { Blog, Graph, WebPage } from 'schema-dts';
+import styles from '@styles/pages/Page.module.scss';
+import { getFormattedPageNumbers } from '@utils/helpers/format';
+import { useEffect } from 'react';
+
+const BlogPage: NextPageWithLayout<BlogPageProps> = ({
+ allThematics,
+ allTopics,
+ posts,
+ totalPosts,
+}) => {
+ const intl = useIntl();
+ const router = useRouter();
+ const pageNumber = Number(router.query.id);
+
+ useEffect(() => {
+ if (router.query.id === '1') router.push('/blog');
+ }, [router]);
+
+ const pageTitle = intl.formatMessage(
+ {
+ defaultMessage: `Blog - Page {number} - {websiteName}`,
+ description: 'BlogPage: SEO - Page title',
+ },
+ { number: pageNumber, websiteName: settings.name }
+ );
+ const pageDescription = intl.formatMessage(
+ {
+ defaultMessage:
+ "Discover {websiteName}'s writings. He talks about web development, Linux and open source mostly.",
+ description: 'BlogPage: SEO - Meta description',
+ },
+ { websiteName: settings.name }
+ );
+ const pageUrl = `${settings.url}${router.asPath}`;
+
+ const webpageSchema: WebPage = {
+ '@id': `${pageUrl}`,
+ '@type': 'WebPage',
+ breadcrumb: { '@id': `${settings.url}/#breadcrumb` },
+ name: pageTitle,
+ description: pageDescription,
+ inLanguage: settings.locales.defaultLocale,
+ reviewedBy: { '@id': `${settings.url}/#branding` },
+ url: `${settings.url}`,
+ isPartOf: {
+ '@id': `${settings.url}`,
+ },
+ };
+
+ const blogSchema: Blog = {
+ '@id': `${settings.url}/#blog`,
+ '@type': 'Blog',
+ author: { '@id': `${settings.url}/#branding` },
+ creator: { '@id': `${settings.url}/#branding` },
+ editor: { '@id': `${settings.url}/#branding` },
+ inLanguage: settings.locales.defaultLocale,
+ license: 'https://creativecommons.org/licenses/by-sa/4.0/deed.fr',
+ mainEntityOfPage: { '@id': `${pageUrl}` },
+ };
+
+ const schemaJsonLd: Graph = {
+ '@context': 'https://schema.org',
+ '@graph': [webpageSchema, blogSchema],
+ };
+
+ const title = intl.formatMessage({
+ defaultMessage: 'Blog',
+ description: 'BlogPage: page title',
+ });
+
+ return (
+ <>
+ <Head>
+ <title>{pageTitle}</title>
+ <meta name="description" content={pageDescription} />
+ <meta property="og:url" content={`${pageUrl}`} />
+ <meta property="og:type" content="website" />
+ <meta property="og:title" content={title} />
+ <meta property="og:description" content={pageDescription} />
+ </Head>
+ <Script
+ id="schema-blog"
+ type="application/ld+json"
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}
+ />
+ <article
+ id="blog"
+ className={`${styles.article} ${styles['article--no-comments']}`}
+ >
+ <PostHeader title={title} meta={{ results: totalPosts }} />
+ <div className={styles.body}>
+ <PostsList data={[posts]} showYears={true} />
+ <Pagination baseUrl="/blog" total={totalPosts} />
+ </div>
+ <Sidebar
+ position="right"
+ title={intl.formatMessage({
+ defaultMessage: 'Filter by:',
+ description: 'BlogPage: sidebar title',
+ })}
+ >
+ <ThematicsList
+ initialData={allThematics}
+ title={intl.formatMessage({
+ defaultMessage: 'Thematics',
+ description: 'BlogPage: thematics list widget title',
+ })}
+ />
+ <TopicsList
+ initialData={allTopics}
+ title={intl.formatMessage({
+ defaultMessage: 'Topics',
+ description: 'BlogPage: topics list widget title',
+ })}
+ />
+ </Sidebar>
+ </article>
+ </>
+ );
+};
+
+BlogPage.getLayout = getLayout;
+
+export const getStaticProps: GetStaticProps = async (
+ context: GetStaticPropsContext
+) => {
+ const intl = await getIntlInstance();
+ const breadcrumbTitle = intl.formatMessage({
+ defaultMessage: 'Blog',
+ description: 'BlogPage: breadcrumb item',
+ });
+ const { locale, params } = context;
+ const queriedPageNumber = params ? Number(params.id) : 1;
+ const queriedPostsNumber = settings.postsPerPage * queriedPageNumber;
+ const endCursor =
+ queriedPostsNumber === 1
+ ? undefined
+ : await getEndCursor({ first: queriedPostsNumber });
+ const posts = await getPublishedPosts({
+ first: settings.postsPerPage,
+ after: endCursor,
+ });
+ const totalPosts = await getPostsTotal();
+ const allThematics = await getAllThematics();
+ const allTopics = await getAllTopics();
+ const translation = await loadTranslation(locale);
+
+ return {
+ props: {
+ allThematics,
+ allTopics,
+ breadcrumbTitle,
+ locale,
+ posts,
+ totalPosts,
+ translation,
+ },
+ };
+};
+
+export default BlogPage;
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const totalPosts = await getPostsTotal();
+ const totalPages = Math.floor(totalPosts / settings.postsPerPage);
+ const paths = getFormattedPageNumbers(totalPages);
+
+ return {
+ paths,
+ fallback: true,
+ };
+};