aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2021-12-16 23:20:58 +0100
committerArmand Philippot <git@armandphilippot.com>2021-12-16 23:20:58 +0100
commit8a703cb39ff23ff3639b0da33f0d72f92f1cc55b (patch)
treee898046c472839f36e306d8ae9aa7efc58ba98cd
parent89bf1e53fda306d271676bda4605794567b7f3b6 (diff)
chore: create thematic page view
For now I have to repeat some markup/styles, I cannot reuse PostsList component. WP GraphQL does not offer edges for ACF Post2Post or filters to get thematic posts with another way. I should create custom fields in backend to improve the posts fetching.
-rw-r--r--src/components/PostPreview/PostPreview.tsx4
-rw-r--r--src/pages/thematique/[slug].tsx77
-rw-r--r--src/services/graphql/taxonomies.ts166
-rw-r--r--src/styles/pages/Thematic.module.scss11
-rw-r--r--src/ts/types/taxonomies.ts47
5 files changed, 303 insertions, 2 deletions
diff --git a/src/components/PostPreview/PostPreview.tsx b/src/components/PostPreview/PostPreview.tsx
index 3dfef73..95aca97 100644
--- a/src/components/PostPreview/PostPreview.tsx
+++ b/src/components/PostPreview/PostPreview.tsx
@@ -19,7 +19,7 @@ const PostPreview = ({
return (
<article className={styles.wrapper}>
- {post.featuredImage && (
+ {post.featuredImage && Object.keys(post.featuredImage).length > 0 && (
<div className={styles.cover}>
<Image
src={post.featuredImage.sourceUrl}
@@ -41,7 +41,7 @@ const PostPreview = ({
dangerouslySetInnerHTML={{ __html: post.content }}
></div>
<footer className={styles.footer}>
- <Link href={post.slug}>
+ <Link href={`/article/${post.slug}`}>
<a className={styles['read-more']}>
{t`Read more`}
<span className="screen-reader-text">
diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx
new file mode 100644
index 0000000..1919b59
--- /dev/null
+++ b/src/pages/thematique/[slug].tsx
@@ -0,0 +1,77 @@
+import Layout from '@components/Layouts/Layout';
+import PostPreview from '@components/PostPreview/PostPreview';
+import { t } from '@lingui/macro';
+import {
+ fetchAllThematicsSlug,
+ getThematicBySlug,
+} from '@services/graphql/taxonomies';
+import { NextPageWithLayout } from '@ts/types/app';
+import { ThematicProps } from '@ts/types/taxonomies';
+import { loadTranslation } from '@utils/helpers/i18n';
+import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
+import { ParsedUrlQuery } from 'querystring';
+import { ReactElement } from 'react';
+import styles from '@styles/pages/Thematic.module.scss';
+
+const Thematic: NextPageWithLayout<ThematicProps> = ({ thematic }) => {
+ const getPostsList = () => {
+ return thematic.posts.reverse().map((post) => (
+ <li key={post.id} className={styles.item}>
+ <PostPreview post={post} titleLevel={3} />
+ </li>
+ ));
+ };
+
+ return (
+ <article>
+ <header>
+ <h1>{thematic.title}</h1>
+ <div dangerouslySetInnerHTML={{ __html: thematic.intro }}></div>
+ </header>
+ <div dangerouslySetInnerHTML={{ __html: thematic.content }}></div>
+ {thematic.posts.length > 0 && (
+ <div>
+ <h2>{t`All posts in ${thematic.title}`}</h2>
+ <ol className={styles.list}>{getPostsList()}</ol>
+ </div>
+ )}
+ </article>
+ );
+};
+
+Thematic.getLayout = function getLayout(page: ReactElement) {
+ return <Layout>{page}</Layout>;
+};
+
+interface PostParams extends ParsedUrlQuery {
+ slug: string;
+}
+
+export const getStaticProps: GetStaticProps = async (
+ context: GetStaticPropsContext
+) => {
+ const translation = await loadTranslation(
+ context.locale!,
+ process.env.NODE_ENV === 'production'
+ );
+ const { slug } = context.params as PostParams;
+ const thematic = await getThematicBySlug(slug);
+
+ return {
+ props: {
+ thematic,
+ translation,
+ },
+ };
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const allSlugs = await fetchAllThematicsSlug();
+
+ return {
+ paths: allSlugs.map((post) => `/thematique/${post.slug}`),
+ fallback: true,
+ };
+};
+
+export default Thematic;
diff --git a/src/services/graphql/taxonomies.ts b/src/services/graphql/taxonomies.ts
new file mode 100644
index 0000000..a14b7cb
--- /dev/null
+++ b/src/services/graphql/taxonomies.ts
@@ -0,0 +1,166 @@
+import { ArticlePreview } from '@ts/types/articles';
+import {
+ AllTaxonomiesSlugResponse,
+ FetchAllTaxonomiesSlugReturn,
+ FetchThematicByReturn,
+ GetTaxonomyByReturn,
+ Taxonomy,
+} from '@ts/types/taxonomies';
+import { gql } from 'graphql-request';
+import { getGraphQLClient } from './client';
+
+export const fetchThematicBySlug: FetchThematicByReturn = async (
+ slug: string
+) => {
+ const client = getGraphQLClient();
+ const query = gql`
+ query ThematicBySlug($slug: String!) {
+ thematicBy(slug: $slug) {
+ acfThematics {
+ postsInThematic {
+ ... on Post {
+ acfPosts {
+ postsInSubject {
+ ... on Subject {
+ databaseId
+ featuredImage {
+ node {
+ altText
+ sourceUrl
+ title
+ }
+ }
+ id
+ slug
+ title
+ }
+ }
+ postsInThematic {
+ ... on Thematic {
+ databaseId
+ id
+ slug
+ title
+ }
+ }
+ }
+ id
+ commentCount
+ contentParts {
+ beforeMore
+ }
+ databaseId
+ date
+ featuredImage {
+ node {
+ altText
+ sourceUrl
+ title
+ }
+ }
+ modified
+ slug
+ title
+ }
+ }
+ }
+ contentParts {
+ afterMore
+ beforeMore
+ }
+ date
+ modified
+ seo {
+ metaDesc
+ opengraphAuthor
+ opengraphDescription
+ opengraphImage {
+ altText
+ sourceUrl
+ srcSet
+ }
+ opengraphModifiedTime
+ opengraphPublishedTime
+ opengraphPublisher
+ opengraphSiteName
+ opengraphTitle
+ opengraphType
+ opengraphUrl
+ readingTime
+ title
+ }
+ title
+ }
+ }
+ `;
+
+ const variables = { slug };
+
+ try {
+ const response = client.request(query, variables);
+ return response;
+ } catch (error) {
+ console.error(error, undefined, 2);
+ process.exit(1);
+ }
+};
+
+export const getThematicBySlug: GetTaxonomyByReturn = async (slug: string) => {
+ const rawThematic = await fetchThematicBySlug(slug);
+
+ const content = rawThematic.thematicBy.contentParts.afterMore;
+ const intro = rawThematic.thematicBy.contentParts.beforeMore;
+ const rawPosts = rawThematic.thematicBy.acfThematics.postsInThematic;
+ const formattedPosts: ArticlePreview[] = rawPosts.map((post) => {
+ const content = post.contentParts.beforeMore;
+ const cover = post.featuredImage ? post.featuredImage.node : null;
+ const dates = { publication: post.date, update: post.modified };
+ const subjects =
+ post.acfPosts.postsInSubject && post.acfPosts.postsInSubject?.length > 0
+ ? post.acfPosts.postsInSubject
+ : [];
+ const thematics =
+ post.acfPosts.postsInThematic && post.acfPosts.postsInThematic?.length > 0
+ ? post.acfPosts.postsInThematic
+ : [];
+
+ return {
+ ...post,
+ content,
+ featuredImage: cover,
+ date: dates,
+ subjects,
+ thematics,
+ };
+ });
+
+ const formattedThematic: Taxonomy = {
+ ...rawThematic.thematicBy,
+ content,
+ intro,
+ posts: formattedPosts,
+ };
+
+ return formattedThematic;
+};
+
+export const fetchAllThematicsSlug: FetchAllTaxonomiesSlugReturn = async () => {
+ const client = getGraphQLClient();
+ const query = gql`
+ query AllThematicsSlug {
+ thematics {
+ nodes {
+ slug
+ }
+ }
+ }
+ `;
+
+ try {
+ const response: AllTaxonomiesSlugResponse = await client.request(query);
+ return response.thematics.nodes;
+ } catch (error) {
+ console.error(error, undefined, 2);
+ process.exit(1);
+ }
+};
diff --git a/src/styles/pages/Thematic.module.scss b/src/styles/pages/Thematic.module.scss
new file mode 100644
index 0000000..0b9aa2d
--- /dev/null
+++ b/src/styles/pages/Thematic.module.scss
@@ -0,0 +1,11 @@
+@use "@styles/abstracts/placeholders";
+
+.list {
+ @extend %reset-ordered-list;
+
+ margin: var(--spacing-md) auto;
+}
+
+li.item {
+ margin: var(--spacing-md) 0;
+}
diff --git a/src/ts/types/taxonomies.ts b/src/ts/types/taxonomies.ts
index dd45852..d090e2d 100644
--- a/src/ts/types/taxonomies.ts
+++ b/src/ts/types/taxonomies.ts
@@ -1,3 +1,4 @@
+import { ArticlePreview, ArticlePreviewResponse } from './articles';
import { Cover } from './cover';
type TaxonomyPreview = {
@@ -7,8 +8,54 @@ type TaxonomyPreview = {
title: string;
};
+export type Taxonomy = TaxonomyPreview & {
+ content: string;
+ date: string;
+ intro: string;
+ modified: string;
+ posts: ArticlePreview[];
+};
+
export type SubjectPreview = TaxonomyPreview & {
cover: Cover;
};
export type ThematicPreview = TaxonomyPreview;
+
+export type ThematicResponse = TaxonomyPreview & {
+ acfThematics: {
+ postsInThematic: ArticlePreviewResponse[];
+ };
+ contentParts: {
+ afterMore: string;
+ beforeMore: string;
+ };
+ date: string;
+ modified: string;
+};
+
+export type ThematicProps = {
+ thematic: Taxonomy;
+};
+
+export type AllTaxonomiesSlug = {
+ slug: string;
+};
+
+export type AllTaxonomiesSlugResponse = {
+ thematics: {
+ nodes: AllTaxonomiesSlug[];
+ };
+};
+
+export type ThematicByResponse = {
+ thematicBy: ThematicResponse;
+};
+
+export type FetchThematicByReturn = (
+ slug: string
+) => Promise<ThematicByResponse>;
+
+export type GetTaxonomyByReturn = (slug: string) => Promise<Taxonomy>;
+
+export type FetchAllTaxonomiesSlugReturn = () => Promise<AllTaxonomiesSlug[]>;