aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/pages/sujet/[slug].tsx96
-rw-r--r--src/services/graphql/taxonomies.ts186
-rw-r--r--src/styles/pages/Subject.module.scss26
-rw-r--r--src/ts/types/taxonomies.ts41
4 files changed, 344 insertions, 5 deletions
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx
new file mode 100644
index 0000000..a6acf2b
--- /dev/null
+++ b/src/pages/sujet/[slug].tsx
@@ -0,0 +1,96 @@
+import Layout from '@components/Layouts/Layout';
+import PostPreview from '@components/PostPreview/PostPreview';
+import { t } from '@lingui/macro';
+import {
+ fetchAllSubjectsSlug,
+ getSubjectBySlug,
+} from '@services/graphql/taxonomies';
+import { NextPageWithLayout } from '@ts/types/app';
+import { SubjectProps } from '@ts/types/taxonomies';
+import { loadTranslation } from '@utils/helpers/i18n';
+import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
+import Image from 'next/image';
+import { ParsedUrlQuery } from 'querystring';
+import { ReactElement } from 'react';
+import styles from '@styles/pages/Subject.module.scss';
+
+const Subject: NextPageWithLayout<SubjectProps> = ({ subject }) => {
+ const getPostsList = () => {
+ return subject.posts.reverse().map((post) => (
+ <li key={post.id} className={styles.item}>
+ <PostPreview post={post} titleLevel={3} />
+ </li>
+ ));
+ };
+
+ return (
+ <article>
+ <header>
+ <h1 className={styles.title}>
+ {subject.featuredImage && (
+ <span className={styles.cover}>
+ <Image
+ src={subject.featuredImage.sourceUrl}
+ alt={subject.featuredImage.altText}
+ layout="fill"
+ />
+ </span>
+ )}
+ {subject.title}
+ </h1>
+ {subject.officialWebsite && (
+ <dl>
+ <dt>{t`Official website:`}</dt>
+ <dd>{subject.officialWebsite}</dd>
+ </dl>
+ )}
+ <div dangerouslySetInnerHTML={{ __html: subject.intro }}></div>
+ </header>
+ <div dangerouslySetInnerHTML={{ __html: subject.content }}></div>
+ {subject.posts.length > 0 && (
+ <div>
+ <h2>{t`All posts in ${subject.title}`}</h2>
+ <ol className={styles.list}>{getPostsList()}</ol>
+ </div>
+ )}
+ </article>
+ );
+};
+
+Subject.getLayout = function getLayout(page: ReactElement) {
+ return <Layout>{page}</Layout>;
+};
+
+interface PostParams extends ParsedUrlQuery {
+ slug: string;
+}
+
+export const getStaticProps: GetStaticProps = async (
+ context: GetStaticPropsContext
+) => {
+ console.log(context);
+ const translation = await loadTranslation(
+ context.locale!,
+ process.env.NODE_ENV === 'production'
+ );
+ const { slug } = context.params as PostParams;
+ const subject = await getSubjectBySlug(slug);
+
+ return {
+ props: {
+ subject,
+ translation,
+ },
+ };
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const allSlugs = await fetchAllSubjectsSlug();
+
+ return {
+ paths: allSlugs.map((post) => `/sujet/${post.slug}`),
+ fallback: true,
+ };
+};
+
+export default Subject;
diff --git a/src/services/graphql/taxonomies.ts b/src/services/graphql/taxonomies.ts
index a14b7cb..ee73dc8 100644
--- a/src/services/graphql/taxonomies.ts
+++ b/src/services/graphql/taxonomies.ts
@@ -1,9 +1,12 @@
import { ArticlePreview } from '@ts/types/articles';
import {
- AllTaxonomiesSlugResponse,
+ AllSubjectsSlugResponse,
+ AllThematicsSlugResponse,
FetchAllTaxonomiesSlugReturn,
+ FetchSubjectByReturn,
FetchThematicByReturn,
GetTaxonomyByReturn,
+ Subject,
Taxonomy,
} from '@ts/types/taxonomies';
import { gql } from 'graphql-request';
@@ -157,10 +160,189 @@ export const fetchAllThematicsSlug: FetchAllTaxonomiesSlugReturn = async () => {
`;
try {
- const response: AllTaxonomiesSlugResponse = await client.request(query);
+ const response: AllThematicsSlugResponse = await client.request(query);
return response.thematics.nodes;
} catch (error) {
console.error(error, undefined, 2);
process.exit(1);
}
};
+
+export const fetchSubjectBySlug: FetchSubjectByReturn = async (
+ slug: string
+) => {
+ const client = getGraphQLClient();
+ const query = gql`
+ query SubjectBySlug($slug: String!) {
+ subjectBy(slug: $slug) {
+ acfSubjects {
+ officialWebsite
+ postsInSubject {
+ ... 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
+ featuredImage {
+ node {
+ altText
+ sourceUrl
+ title
+ }
+ }
+ 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 getSubjectBySlug: GetTaxonomyByReturn = async (slug: string) => {
+ const rawSubject = await fetchSubjectBySlug(slug);
+
+ const content = rawSubject.subjectBy.contentParts.afterMore;
+ const cover = rawSubject.subjectBy.featuredImage
+ ? rawSubject.subjectBy.featuredImage.node
+ : null;
+ const intro = rawSubject.subjectBy.contentParts.beforeMore;
+ const rawPosts = rawSubject.subjectBy.acfSubjects.postsInSubject;
+ console.log(rawPosts);
+
+ // WP GraphQL return empty objects instead of filtering posts that do not
+ // belong to the queried post type so I need to filter them.
+ const formattedPosts: ArticlePreview[] = rawPosts
+ .filter((post) => Object.getOwnPropertyNames(post).length > 0)
+ .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 formattedSubject: Subject = {
+ ...rawSubject.subjectBy,
+ content,
+ featuredImage: cover,
+ intro,
+ posts: formattedPosts,
+ };
+
+ console.log(formattedSubject);
+
+ return formattedSubject;
+};
+
+export const fetchAllSubjectsSlug: FetchAllTaxonomiesSlugReturn = async () => {
+ const client = getGraphQLClient();
+
+ // 10 000 is an arbitrary number for small websites.
+ const query = gql`
+ query AllSubjectsSlug {
+ subjects(first: 10000) {
+ nodes {
+ slug
+ }
+ }
+ }
+ `;
+
+ try {
+ const response: AllSubjectsSlugResponse = await client.request(query);
+ return response.subjects.nodes;
+ } catch (error) {
+ console.error(error, undefined, 2);
+ process.exit(1);
+ }
+};
diff --git a/src/styles/pages/Subject.module.scss b/src/styles/pages/Subject.module.scss
new file mode 100644
index 0000000..01c3cb8
--- /dev/null
+++ b/src/styles/pages/Subject.module.scss
@@ -0,0 +1,26 @@
+@use "@styles/abstracts/functions" as fun;
+@use "@styles/abstracts/placeholders";
+
+.title {
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.cover {
+ display: block;
+ width: fun.convert-px(50);
+ height: fun.convert-px(50);
+ position: relative;
+}
+
+.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 d090e2d..a8c372c 100644
--- a/src/ts/types/taxonomies.ts
+++ b/src/ts/types/taxonomies.ts
@@ -1,5 +1,5 @@
import { ArticlePreview, ArticlePreviewResponse } from './articles';
-import { Cover } from './cover';
+import { Cover, CoverResponse } from './cover';
type TaxonomyPreview = {
databaseId: number;
@@ -17,7 +17,7 @@ export type Taxonomy = TaxonomyPreview & {
};
export type SubjectPreview = TaxonomyPreview & {
- cover: Cover;
+ featuredImage: Cover;
};
export type ThematicPreview = TaxonomyPreview;
@@ -42,7 +42,7 @@ export type AllTaxonomiesSlug = {
slug: string;
};
-export type AllTaxonomiesSlugResponse = {
+export type AllThematicsSlugResponse = {
thematics: {
nodes: AllTaxonomiesSlug[];
};
@@ -59,3 +59,38 @@ export type FetchThematicByReturn = (
export type GetTaxonomyByReturn = (slug: string) => Promise<Taxonomy>;
export type FetchAllTaxonomiesSlugReturn = () => Promise<AllTaxonomiesSlug[]>;
+
+export type Subject = Taxonomy & {
+ featuredImage: Cover;
+ officialWebsite: string;
+};
+
+export type SubjectResponse = SubjectPreview & {
+ acfSubjects: {
+ postsInSubject: ArticlePreviewResponse[];
+ };
+ contentParts: {
+ afterMore: string;
+ beforeMore: string;
+ };
+ date: string;
+ featuredImage: CoverResponse;
+ modified: string;
+ officialWebsite: string;
+};
+
+export type SubjectProps = {
+ subject: Subject;
+};
+
+export type SubjectByResponse = {
+ subjectBy: SubjectResponse;
+};
+
+export type FetchSubjectByReturn = (slug: string) => Promise<SubjectByResponse>;
+
+export type AllSubjectsSlugResponse = {
+ subjects: {
+ nodes: AllTaxonomiesSlug[];
+ };
+};