summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2021-12-20 00:15:20 +0100
committerArmand Philippot <git@armandphilippot.com>2021-12-20 00:15:20 +0100
commitfa6adedc42e9c6ec39cc30df16b54900c220b094 (patch)
tree6bb498beadaa382245cecb86ce56931580313c6f /src
parent2ff898626c5c0abc6b8195224067b992403e313b (diff)
refactor: rewrite types and services
I was repeating myself a lot in services. So I rewrited the different functions to improve readability and I extracted some formatting functions to put them in utils. I also rewrited/reorganized some types to keep consistent names.
Diffstat (limited to 'src')
-rw-r--r--src/components/CommentForm/CommentForm.tsx21
-rw-r--r--src/components/PostHeader/PostHeader.tsx11
-rw-r--r--src/components/PostPreview/PostPreview.tsx6
-rw-r--r--src/pages/article/[slug].tsx9
-rw-r--r--src/pages/blog/index.tsx2
-rw-r--r--src/pages/contact.tsx10
-rw-r--r--src/pages/cv.tsx4
-rw-r--r--src/pages/index.tsx2
-rw-r--r--src/pages/mentions-legales.tsx4
-rw-r--r--src/pages/sujet/[slug].tsx11
-rw-r--r--src/pages/thematique/[slug].tsx10
-rw-r--r--src/services/graphql/api.ts27
-rw-r--r--src/services/graphql/blog.ts158
-rw-r--r--src/services/graphql/client.ts11
-rw-r--r--src/services/graphql/comments.ts68
-rw-r--r--src/services/graphql/contact.ts49
-rw-r--r--src/services/graphql/homepage.ts36
-rw-r--r--src/services/graphql/mutations.ts82
-rw-r--r--src/services/graphql/pages.ts60
-rw-r--r--src/services/graphql/post.ts150
-rw-r--r--src/services/graphql/queries.ts496
-rw-r--r--src/services/graphql/taxonomies.ts348
-rw-r--r--src/ts/types/app.ts75
-rw-r--r--src/ts/types/articles.ts90
-rw-r--r--src/ts/types/blog.ts48
-rw-r--r--src/ts/types/comments.ts38
-rw-r--r--src/ts/types/contact.ts16
-rw-r--r--src/ts/types/cover.ts2
-rw-r--r--src/ts/types/homepage.ts15
-rw-r--r--src/ts/types/pages.ts16
-rw-r--r--src/ts/types/pagination.ts4
-rw-r--r--src/ts/types/taxonomies.ts114
-rw-r--r--src/utils/helpers/format.ts226
-rw-r--r--src/utils/helpers/sort.ts4
34 files changed, 1106 insertions, 1117 deletions
diff --git a/src/components/CommentForm/CommentForm.tsx b/src/components/CommentForm/CommentForm.tsx
index 988468c..b2d538f 100644
--- a/src/components/CommentForm/CommentForm.tsx
+++ b/src/components/CommentForm/CommentForm.tsx
@@ -2,7 +2,7 @@ import { ButtonSubmit } from '@components/Buttons';
import { Form, FormItem, Input, TextArea } from '@components/Form';
import Notice from '@components/Notice/Notice';
import { t } from '@lingui/macro';
-import { createComment } from '@services/graphql/comments';
+import { createComment } from '@services/graphql/mutations';
import { useState } from 'react';
const CommentForm = ({
@@ -30,15 +30,16 @@ const CommentForm = ({
e.preventDefault();
if (name && email && message && articleId) {
- const createdComment = await createComment(
- name,
- email,
- website,
- message,
- parentId,
- articleId,
- 'createComment'
- );
+ const data = {
+ author: name,
+ authorEmail: email,
+ authorUrl: website,
+ content: message,
+ parent: parentId,
+ commentOn: articleId,
+ mutationId: 'createComment',
+ };
+ const createdComment = await createComment(data);
if (createdComment.success) setIsSuccess(true);
if (isSuccess) {
diff --git a/src/components/PostHeader/PostHeader.tsx b/src/components/PostHeader/PostHeader.tsx
index 5c5aff4..3ee6705 100644
--- a/src/components/PostHeader/PostHeader.tsx
+++ b/src/components/PostHeader/PostHeader.tsx
@@ -1,5 +1,6 @@
import { t } from '@lingui/macro';
-import { ArticleAuthor, ArticleDates } from '@ts/types/articles';
+import { Dates } from '@ts/types/app';
+import { ArticleAuthor } from '@ts/types/articles';
import { ThematicPreview } from '@ts/types/taxonomies';
import Link from 'next/link';
import { useRouter } from 'next/router';
@@ -7,13 +8,13 @@ import styles from './PostHeader.module.scss';
const PostHeader = ({
author,
- date,
+ dates,
intro,
title,
thematics,
}: {
author: ArticleAuthor;
- date: ArticleDates;
+ dates: Dates;
intro: string;
title: string;
thematics: ThematicPreview[];
@@ -52,9 +53,9 @@ const PostHeader = ({
<h1>{title}</h1>
<ul className={styles.meta}>
<li>{t`Written by ${getAuthor()} on ${getLocaleDate(
- date.publication
+ dates.publication
)}.`}</li>
- <li>{t`Last update on ${getLocaleDate(date.update)}.`}</li>
+ <li>{t`Last update on ${getLocaleDate(dates.update)}.`}</li>
{thematics.length > 0 && (
<li>
<dl>
diff --git a/src/components/PostPreview/PostPreview.tsx b/src/components/PostPreview/PostPreview.tsx
index 95aca97..f5c6d9b 100644
--- a/src/components/PostPreview/PostPreview.tsx
+++ b/src/components/PostPreview/PostPreview.tsx
@@ -38,7 +38,7 @@ const PostPreview = ({
</header>
<div
className={styles.body}
- dangerouslySetInnerHTML={{ __html: post.content }}
+ dangerouslySetInnerHTML={{ __html: post.intro }}
></div>
<footer className={styles.footer}>
<Link href={`/article/${post.slug}`}>
@@ -54,8 +54,8 @@ const PostPreview = ({
</footer>
<PostMeta
commentCount={post.commentCount}
- publicationDate={post.date.publication}
- updateDate={post.date.update}
+ publicationDate={post.dates.publication}
+ updateDate={post.dates.update}
thematics={post.thematics}
/>
</article>
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index 55753c3..bb11220 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -4,8 +4,7 @@ import Layout from '@components/Layouts/Layout';
import PostFooter from '@components/PostFooter/PostFooter';
import PostHeader from '@components/PostHeader/PostHeader';
import { t } from '@lingui/macro';
-import { fetchAllPostsSlug } from '@services/graphql/blog';
-import { getPostBySlug } from '@services/graphql/post';
+import { getAllPostsSlug, getPostBySlug } from '@services/graphql/queries';
import { NextPageWithLayout } from '@ts/types/app';
import { ArticleProps } from '@ts/types/articles';
import { loadTranslation } from '@utils/helpers/i18n';
@@ -19,7 +18,7 @@ const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => {
author,
comments,
content,
- date,
+ dates,
intro,
seo,
subjects,
@@ -36,7 +35,7 @@ const SingleArticle: NextPageWithLayout<ArticleProps> = ({ post }) => {
<article>
<PostHeader
author={author}
- date={date}
+ dates={dates}
intro={intro}
title={title}
thematics={thematics}
@@ -81,7 +80,7 @@ export const getStaticProps: GetStaticProps = async (
};
export const getStaticPaths: GetStaticPaths = async () => {
- const allSlugs = await fetchAllPostsSlug();
+ const allSlugs = await getAllPostsSlug();
return {
paths: allSlugs.map((post) => `/article/${post.slug}`),
diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx
index 29e7770..7d34763 100644
--- a/src/pages/blog/index.tsx
+++ b/src/pages/blog/index.tsx
@@ -5,13 +5,13 @@ import { t } from '@lingui/macro';
import Layout from '@components/Layouts/Layout';
import { seo } from '@config/seo';
import { config } from '@config/website';
-import { getPublishedPosts } from '@services/graphql/blog';
import { NextPageWithLayout } from '@ts/types/app';
import { BlogPageProps, PostsList as PostsListData } from '@ts/types/blog';
import { loadTranslation } from '@utils/helpers/i18n';
import PostsList from '@components/PostsList/PostsList';
import useSWRInfinite from 'swr/infinite';
import { Button } from '@components/Buttons';
+import { getPublishedPosts } from '@services/graphql/queries';
const Blog: NextPageWithLayout<BlogPageProps> = ({ fallback }) => {
const getKey = (pageIndex: number, previousData: PostsListData) => {
diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx
index bfdd681..ff60188 100644
--- a/src/pages/contact.tsx
+++ b/src/pages/contact.tsx
@@ -3,7 +3,7 @@ import { Form, FormItem, Input, TextArea } from '@components/Form';
import Layout from '@components/Layouts/Layout';
import { seo } from '@config/seo';
import { t } from '@lingui/macro';
-import { sendMail } from '@services/graphql/contact';
+import { sendMail } from '@services/graphql/mutations';
import { NextPageWithLayout } from '@ts/types/app';
import { loadTranslation } from '@utils/helpers/i18n';
import { GetStaticProps, GetStaticPropsContext } from 'next';
@@ -28,7 +28,13 @@ const ContactPage: NextPageWithLayout = () => {
e.preventDefault();
const body = `Message received from ${name} <${email}> on ArmandPhilippot.com.\n\n${message}`;
const replyTo = `${name} <${email}>`;
- const mail = await sendMail(subject, body, replyTo, 'contact');
+ const data = {
+ body,
+ mutationId: 'contact',
+ replyTo,
+ subject,
+ };
+ const mail = await sendMail(data);
if (mail.sent) {
setStatus(
diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx
index 5b913f3..150e29a 100644
--- a/src/pages/cv.tsx
+++ b/src/pages/cv.tsx
@@ -1,6 +1,6 @@
import Layout from '@components/Layouts/Layout';
import { seo } from '@config/seo';
-import { getCVPage } from '@services/graphql/pages';
+import { getPageByUri } from '@services/graphql/queries';
import { NextPageWithLayout } from '@ts/types/app';
import { PageProps } from '@ts/types/pages';
import { loadTranslation } from '@utils/helpers/i18n';
@@ -35,7 +35,7 @@ export const getStaticProps: GetStaticProps = async (
context.locale!,
process.env.NODE_ENV === 'production'
);
- const page = await getCVPage();
+ const page = await getPageByUri('/cv/');
return {
props: {
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index f51dec9..4146f34 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -3,10 +3,10 @@ import { GetStaticProps } from 'next';
import Head from 'next/head';
import Layout from '@components/Layouts/Layout';
import { seo } from '@config/seo';
-import { getHomePage } from '@services/graphql/homepage';
import { NextPageWithLayout } from '@ts/types/app';
import { HomePage, HomePageProps } from '@ts/types/homepage';
import { loadTranslation } from '@utils/helpers/i18n';
+import { getHomePage } from '@services/graphql/queries';
const Home: NextPageWithLayout<HomePageProps> = ({ data }) => {
return (
diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx
index 4ff4104..c90b8d6 100644
--- a/src/pages/mentions-legales.tsx
+++ b/src/pages/mentions-legales.tsx
@@ -1,6 +1,6 @@
import Layout from '@components/Layouts/Layout';
import { seo } from '@config/seo';
-import { getLegalNoticePage } from '@services/graphql/pages';
+import { getPageByUri } from '@services/graphql/queries';
import { NextPageWithLayout } from '@ts/types/app';
import { PageProps } from '@ts/types/pages';
import { loadTranslation } from '@utils/helpers/i18n';
@@ -35,7 +35,7 @@ export const getStaticProps: GetStaticProps = async (
context.locale!,
process.env.NODE_ENV === 'production'
);
- const page = await getLegalNoticePage();
+ const page = await getPageByUri('/mentions-legales/');
return {
props: {
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx
index a6acf2b..4dc4e9b 100644
--- a/src/pages/sujet/[slug].tsx
+++ b/src/pages/sujet/[slug].tsx
@@ -1,10 +1,6 @@
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';
@@ -13,6 +9,10 @@ import Image from 'next/image';
import { ParsedUrlQuery } from 'querystring';
import { ReactElement } from 'react';
import styles from '@styles/pages/Subject.module.scss';
+import {
+ getAllSubjectsSlug,
+ getSubjectBySlug,
+} from '@services/graphql/queries';
const Subject: NextPageWithLayout<SubjectProps> = ({ subject }) => {
const getPostsList = () => {
@@ -68,7 +68,6 @@ interface PostParams extends ParsedUrlQuery {
export const getStaticProps: GetStaticProps = async (
context: GetStaticPropsContext
) => {
- console.log(context);
const translation = await loadTranslation(
context.locale!,
process.env.NODE_ENV === 'production'
@@ -85,7 +84,7 @@ export const getStaticProps: GetStaticProps = async (
};
export const getStaticPaths: GetStaticPaths = async () => {
- const allSlugs = await fetchAllSubjectsSlug();
+ const allSlugs = await getAllSubjectsSlug();
return {
paths: allSlugs.map((post) => `/sujet/${post.slug}`),
diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx
index 1919b59..2e7c346 100644
--- a/src/pages/thematique/[slug].tsx
+++ b/src/pages/thematique/[slug].tsx
@@ -1,10 +1,6 @@
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';
@@ -12,6 +8,10 @@ import { GetStaticPaths, GetStaticProps, GetStaticPropsContext } from 'next';
import { ParsedUrlQuery } from 'querystring';
import { ReactElement } from 'react';
import styles from '@styles/pages/Thematic.module.scss';
+import {
+ getAllThematicsSlug,
+ getThematicBySlug,
+} from '@services/graphql/queries';
const Thematic: NextPageWithLayout<ThematicProps> = ({ thematic }) => {
const getPostsList = () => {
@@ -66,7 +66,7 @@ export const getStaticProps: GetStaticProps = async (
};
export const getStaticPaths: GetStaticPaths = async () => {
- const allSlugs = await fetchAllThematicsSlug();
+ const allSlugs = await getAllThematicsSlug();
return {
paths: allSlugs.map((post) => `/thematique/${post.slug}`),
diff --git a/src/services/graphql/api.ts b/src/services/graphql/api.ts
new file mode 100644
index 0000000..de8024f
--- /dev/null
+++ b/src/services/graphql/api.ts
@@ -0,0 +1,27 @@
+import { RequestType, VariablesType } from '@ts/types/app';
+import { GraphQLClient } from 'graphql-request';
+
+export const getGraphQLClient = (): GraphQLClient => {
+ const apiUrl: string = process.env.NEXT_PUBLIC_GRAPHQL_API || '';
+
+ if (!apiUrl) throw new Error('API URL not defined.');
+
+ const graphQLClient = new GraphQLClient(apiUrl);
+
+ return graphQLClient;
+};
+
+export const fetchApi = async <T extends RequestType>(
+ query: string,
+ variables: VariablesType<T>
+): Promise<T> => {
+ const client = getGraphQLClient();
+
+ try {
+ const response = await client.request(query, variables);
+ return response;
+ } catch (error) {
+ console.error(error, undefined, 2);
+ process.exit(1);
+ }
+};
diff --git a/src/services/graphql/blog.ts b/src/services/graphql/blog.ts
deleted file mode 100644
index 27b972b..0000000
--- a/src/services/graphql/blog.ts
+++ /dev/null
@@ -1,158 +0,0 @@
-import { ArticlePreview } from '@ts/types/articles';
-import {
- AllPostsSlugResponse,
- FetchAllPostsSlugReturn,
- FetchPostsListReturn,
- GetPostsListReturn,
- PostsListResponse,
-} from '@ts/types/blog';
-import { gql } from 'graphql-request';
-import { getGraphQLClient } from './client';
-
-export const fetchPublishedPosts: FetchPostsListReturn = async (
- first = 10,
- after = ''
-) => {
- const client = getGraphQLClient();
- const query = gql`
- query AllPublishedPosts($first: Int, $after: String) {
- posts(
- after: $after
- first: $first
- where: { status: PUBLISH, orderby: { field: DATE, order: DESC } }
- ) {
- edges {
- cursor
- node {
- acfPosts {
- postsInSubject {
- ... on Subject {
- databaseId
- featuredImage {
- node {
- altText
- sourceUrl
- title
- }
- }
- id
- slug
- title
- }
- }
- postsInThematic {
- ... on Thematic {
- databaseId
- id
- slug
- title
- }
- }
- }
- commentCount
- contentParts {
- beforeMore
- }
- date
- featuredImage {
- node {
- altText
- sourceUrl
- title
- }
- }
- id
- databaseId
- modified
- slug
- title
- }
- }
- pageInfo {
- endCursor
- hasNextPage
- }
- }
- }
- `;
-
- const variables = { first, after };
-
- try {
- const response: PostsListResponse = await client.request(query, variables);
- return response;
- } catch (error) {
- console.error(JSON.stringify(error, undefined, 2));
- process.exit(1);
- }
-};
-
-export const getPublishedPosts: GetPostsListReturn = async ({
- first = 10,
- after = '',
-}) => {
- const rawPostsList = await fetchPublishedPosts(first, after);
- const postsList: ArticlePreview[] = rawPostsList.posts.edges.map((post) => {
- const {
- acfPosts,
- commentCount,
- contentParts,
- databaseId,
- date,
- featuredImage,
- id,
- modified,
- slug,
- title,
- } = post.node;
- const content = contentParts.beforeMore;
- const cover = featuredImage ? featuredImage.node : null;
- const dates = { publication: date, update: modified };
- const subjects =
- acfPosts.postsInSubject && acfPosts.postsInSubject?.length > 0
- ? acfPosts.postsInSubject
- : [];
- const thematics =
- acfPosts.postsInThematic && acfPosts.postsInThematic?.length > 0
- ? acfPosts.postsInThematic
- : [];
-
- return {
- commentCount,
- content,
- databaseId,
- date: dates,
- featuredImage: cover,
- id,
- slug,
- subjects,
- thematics,
- title,
- };
- });
-
- return { posts: postsList, pageInfo: rawPostsList.posts.pageInfo };
-};
-
-export const fetchAllPostsSlug: FetchAllPostsSlugReturn = async () => {
- const client = getGraphQLClient();
-
- // 10 000 is an arbitrary number for small websites.
- const query = gql`
- query AllPostsSlug {
- posts(first: 10000) {
- nodes {
- slug
- }
- }
- }
- `;
-
- try {
- const response: AllPostsSlugResponse = await client.request(query);
- return response.posts.nodes;
- } catch (error) {
- console.error(JSON.stringify(error, undefined, 2));
- process.exit(1);
- }
-};
diff --git a/src/services/graphql/client.ts b/src/services/graphql/client.ts
deleted file mode 100644
index a58441f..0000000
--- a/src/services/graphql/client.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { GraphQLClient } from 'graphql-request';
-
-export const getGraphQLClient = () => {
- const apiUrl: string = process.env.NEXT_PUBLIC_GRAPHQL_API || '';
-
- if (!apiUrl) throw new Error('API URL not defined.');
-
- const graphQLClient = new GraphQLClient(apiUrl);
-
- return graphQLClient;
-};
diff --git a/src/services/graphql/comments.ts b/src/services/graphql/comments.ts
deleted file mode 100644
index b7a9ed2..0000000
--- a/src/services/graphql/comments.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import {
- CreatedCommentResponse,
- CreatedCommentReturn,
-} from '@ts/types/comments';
-import { gql } from 'graphql-request';
-import { getGraphQLClient } from './client';
-
-export const createComment: CreatedCommentReturn = async (
- author: string,
- authorEmail: string,
- authorUrl: string,
- content: string,
- parent: number,
- commentOn: number,
- mutationId: string
-) => {
- const client = getGraphQLClient();
- const mutation = gql`
- mutation CreateComment(
- $author: String!
- $authorEmail: String!
- $authorUrl: String!
- $content: String!
- $parent: ID!
- $commentOn: Int!
- $mutationId: String!
- ) {
- createComment(
- input: {
- author: $author
- authorEmail: $authorEmail
- authorUrl: $authorUrl
- content: $content
- parent: $parent
- commentOn: $commentOn
- clientMutationId: $mutationId
- }
- ) {
- clientMutationId
- success
- comment {
- approved
- }
- }
- }
- `;
-
- const variables = {
- author,
- authorEmail,
- authorUrl,
- content,
- parent,
- commentOn,
- mutationId,
- };
-
- try {
- const response: CreatedCommentResponse = await client.request(
- mutation,
- variables
- );
- return response.createComment;
- } catch (error) {
- console.error(error, undefined, 2);
- throw new Error(`An uncaught exception has occurred: ${error}`);
- }
-};
diff --git a/src/services/graphql/contact.ts b/src/services/graphql/contact.ts
deleted file mode 100644
index 4699688..0000000
--- a/src/services/graphql/contact.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { SendMailReturn, SentEmailResponse } from '@ts/types/contact';
-import { gql } from 'graphql-request';
-import { getGraphQLClient } from './client';
-
-export const sendMail: SendMailReturn = async (
- subject: string,
- body: string,
- replyTo: string,
- mutationId: string
-) => {
- const client = getGraphQLClient();
- const mutation = gql`
- mutation SendEmail(
- $subject: String!
- $body: String!
- $replyTo: String!
- $mutationId: String!
- ) {
- sendEmail(
- input: {
- clientMutationId: $mutationId
- body: $body
- replyTo: $replyTo
- subject: $subject
- }
- ) {
- clientMutationId
- message
- sent
- origin
- replyTo
- to
- }
- }
- `;
-
- const variables = { subject, body, replyTo, mutationId };
-
- try {
- const response: SentEmailResponse = await client.request(
- mutation,
- variables
- );
- return response.sendEmail;
- } catch (error) {
- console.error(error, undefined, 2);
- process.exit(1);
- }
-};
diff --git a/src/services/graphql/homepage.ts b/src/services/graphql/homepage.ts
deleted file mode 100644
index 6ea71ac..0000000
--- a/src/services/graphql/homepage.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { gql } from 'graphql-request';
-import {
- fetchHomePageReturn,
- getHomePageReturn,
- HomePage,
- HomePageResponse,
-} from '@ts/types/homepage';
-import { getGraphQLClient } from './client';
-
-export const fetchHomepage: fetchHomePageReturn = async () => {
- const client = getGraphQLClient();
- const query = gql`
- query HomePage {
- nodeByUri(uri: "/") {
- ... on Page {
- id
- content
- }
- }
- }
- `;
-
- try {
- const response: HomePageResponse = await client.request(query);
- return response;
- } catch (error) {
- console.error(JSON.stringify(error, undefined, 2));
- process.exit(1);
- }
-};
-
-export const getHomePage: getHomePageReturn = async () => {
- const rawHomePage = await fetchHomepage();
- const homePage: HomePage = rawHomePage.nodeByUri;
- return homePage;
-};
diff --git a/src/services/graphql/mutations.ts b/src/services/graphql/mutations.ts
new file mode 100644
index 0000000..c697835
--- /dev/null
+++ b/src/services/graphql/mutations.ts
@@ -0,0 +1,82 @@
+import { CommentData, CreateComment, CreatedComment } from '@ts/types/comments';
+import { ContactData, SendEmail } from '@ts/types/contact';
+import { gql } from 'graphql-request';
+import { fetchApi } from './api';
+
+//==============================================================================
+// Comment mutation
+//==============================================================================
+
+export const createComment = async (
+ data: CommentData
+): Promise<CreatedComment> => {
+ const mutation = gql`
+ mutation CreateComment(
+ $author: String!
+ $authorEmail: String!
+ $authorUrl: String!
+ $content: String!
+ $parent: ID!
+ $commentOn: Int!
+ $mutationId: String!
+ ) {
+ createComment(
+ input: {
+ author: $author
+ authorEmail: $authorEmail
+ authorUrl: $authorUrl
+ content: $content
+ parent: $parent
+ commentOn: $commentOn
+ clientMutationId: $mutationId
+ }
+ ) {
+ clientMutationId
+ success
+ comment {
+ approved
+ }
+ }
+ }
+ `;
+
+ const variables = { ...data };
+ const response = await fetchApi<CreateComment>(mutation, variables);
+
+ return response.createComment;
+};
+
+//==============================================================================
+// Contact mutation
+//==============================================================================
+
+export const sendMail = async (data: ContactData) => {
+ const mutation = gql`
+ mutation SendEmail(
+ $subject: String!
+ $body: String!
+ $replyTo: String!
+ $mutationId: String!
+ ) {
+ sendEmail(
+ input: {
+ clientMutationId: $mutationId
+ body: $body
+ replyTo: $replyTo
+ subject: $subject
+ }
+ ) {
+ clientMutationId
+ message
+ sent
+ origin
+ replyTo
+ to
+ }
+ }
+ `;
+
+ const variables = { ...data };
+ const response = await fetchApi<SendEmail>(mutation, variables);
+ return response.sendEmail;
+};
diff --git a/src/services/graphql/pages.ts b/src/services/graphql/pages.ts
deleted file mode 100644
index 0781d44..0000000
--- a/src/services/graphql/pages.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import {
- FetchPageByUriReturn,
- GetPageReturn,
- Page,
- PageResponse,
- RawPage,
-} from '@ts/types/pages';
-import { gql } from 'graphql-request';
-import { getGraphQLClient } from './client';
-
-const fetchPageByUri: FetchPageByUriReturn = async (uri: string) => {
- const client = getGraphQLClient();
- const query = gql`
- query PageByUri($uri: String!) {
- pageBy(uri: $uri) {
- contentParts {
- afterMore
- beforeMore
- }
- date
- modified
- title
- }
- }
- `;
-
- const variables = { uri };
-
- try {
- const response: PageResponse = await client.request(query, variables);
- return response.pageBy;
- } catch (error) {
- console.error(JSON.stringify(error, undefined, 2));
- process.exit(1);
- }
-};
-
-const getFormattedPage = (page: RawPage) => {
- const formattedPage: Page = {
- ...page,
- content: page.contentParts.afterMore,
- intro: page.contentParts.beforeMore,
- };
-
- return formattedPage;
-};
-
-export const getCVPage: GetPageReturn = async () => {
- const rawCV = await fetchPageByUri('/cv/');
- const formattedCV = getFormattedPage(rawCV);
-
- return formattedCV;
-};
-
-export const getLegalNoticePage: GetPageReturn = async () => {
- const rawLegalNotice = await fetchPageByUri('/mentions-legales');
- const formattedLegalNotice = getFormattedPage(rawLegalNotice);
-
- return formattedLegalNotice;
-};
diff --git a/src/services/graphql/post.ts b/src/services/graphql/post.ts
deleted file mode 100644
index 08411bf..0000000
--- a/src/services/graphql/post.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import {
- Article,
- FetchPostByReturn,
- GetPostByReturn,
- PostByResponse,
-} from '@ts/types/articles';
-import { gql } from 'graphql-request';
-import { getGraphQLClient } from './client';
-
-const fetchPostBySlug: FetchPostByReturn = async (slug: string) => {
- const client = getGraphQLClient();
- const query = gql`
- query PostBySlug($slug: String!) {
- postBy(slug: $slug) {
- acfPosts {
- postsInSubject {
- ... on Subject {
- id
- featuredImage {
- node {
- altText
- sourceUrl
- title
- }
- }
- slug
- title
- }
- }
- postsInThematic {
- ... on Thematic {
- id
- slug
- title
- }
- }
- }
- author {
- node {
- firstName
- lastName
- name
- }
- }
- commentCount
- comments {
- nodes {
- approved
- author {
- node {
- gravatarUrl
- name
- url
- }
- }
- commentId
- content
- date
- id
- parentDatabaseId
- parentId
- }
- }
- contentParts {
- afterMore
- beforeMore
- }
- databaseId
- date
- featuredImage {
- node {
- altText
- sourceUrl
- title
- }
- }
- modified
- seo {
- title
- metaDesc
- opengraphAuthor
- opengraphDescription
- opengraphImage {
- altText
- sourceUrl
- srcSet
- }
- opengraphModifiedTime
- opengraphPublishedTime
- opengraphPublisher
- opengraphSiteName
- opengraphTitle
- opengraphType
- opengraphUrl
- readingTime
- }
- title
- }
- }
- `;
-
- const variables = { slug };
-
- try {
- const response: PostByResponse = await client.request(query, variables);
- return response;
- } catch (error) {
- console.error(JSON.stringify(error, undefined, 2));
- process.exit(1);
- }
-};
-
-export const getPostBySlug: GetPostByReturn = async (slug: string) => {
- const rawPost = await fetchPostBySlug(slug);
-
- const author = rawPost.postBy.author.node;
- const comments = rawPost.postBy.comments.nodes.reverse().map((comment) => {
- const author = comment.author.node;
- return { ...comment, author: author, replies: [] };
- });
- const content = rawPost.postBy.contentParts.afterMore;
- const featuredImage = rawPost.postBy.featuredImage
- ? rawPost.postBy.featuredImage.node
- : null;
- const date = {
- publication: rawPost.postBy.date,
- update: rawPost.postBy.modified,
- };
- const intro = rawPost.postBy.contentParts.beforeMore;
- const subjects = rawPost.postBy.acfPosts.postsInSubject
- ? rawPost.postBy.acfPosts.postsInSubject
- : [];
- const thematics = rawPost.postBy.acfPosts.postsInThematic
- ? rawPost.postBy.acfPosts.postsInThematic
- : [];
-
- const formattedPost: Article = {
- ...rawPost.postBy,
- author,
- comments,
- content,
- featuredImage,
- date,
- intro,
- subjects,
- thematics,
- };
-
- return formattedPost;
-};
diff --git a/src/services/graphql/queries.ts b/src/services/graphql/queries.ts
new file mode 100644
index 0000000..b449612
--- /dev/null
+++ b/src/services/graphql/queries.ts
@@ -0,0 +1,496 @@
+import { Slug } from '@ts/types/app';
+import { Article, PostBy } from '@ts/types/articles';
+import { AllPostsSlug, PostsList, RawPostsList } from '@ts/types/blog';
+import { HomePage, HomePageBy } from '@ts/types/homepage';
+import { Page, PageBy } from '@ts/types/pages';
+import {
+ AllSubjectsSlug,
+ AllThematicsSlug,
+ Subject,
+ SubjectBy,
+ Thematic,
+ ThematicBy,
+} from '@ts/types/taxonomies';
+import {
+ getFormattedPage,
+ getFormattedPost,
+ getFormattedPostPreview,
+ getFormattedSubject,
+ getFormattedThematic,
+} from '@utils/helpers/format';
+import { gql } from 'graphql-request';
+import { fetchApi } from './api';
+
+//==============================================================================
+// Posts list queries
+//==============================================================================
+
+export const getPublishedPosts = async ({
+ first = 10,
+ after = '',
+}): Promise<PostsList> => {
+ const query = gql`
+ query AllPublishedPosts($first: Int, $after: String) {
+ posts(
+ after: $after
+ first: $first
+ where: { status: PUBLISH, orderby: { field: DATE, order: DESC } }
+ ) {
+ edges {
+ cursor
+ node {
+ acfPosts {
+ postsInSubject {
+ ... on Subject {
+ databaseId
+ featuredImage {
+ node {
+ altText
+ sourceUrl
+ title
+ }
+ }
+ id
+ slug
+ title
+ }
+ }
+ postsInThematic {
+ ... on Thematic {
+ databaseId
+ id
+ slug
+ title
+ }
+ }
+ }
+ commentCount
+ contentParts {
+ beforeMore
+ }
+ date
+ featuredImage {
+ node {
+ altText
+ sourceUrl
+ title
+ }
+ }
+ id
+ databaseId
+ modified
+ slug
+ title
+ }
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ }
+ `;
+
+ const variables = { first, after };
+ const response = await fetchApi<RawPostsList>(query, variables);
+ const formattedPosts = response.posts.edges.map((post) => {
+ const formattedPost = getFormattedPostPreview(post.node);
+
+ return formattedPost;
+ });
+
+ const postsList = {
+ posts: formattedPosts,
+ pageInfo: response.posts.pageInfo,
+ };
+
+ return postsList;
+};
+
+export const getAllPostsSlug = async (): Promise<Slug[]> => {
+ // 10 000 is an arbitrary number that I use for small websites.
+ const query = gql`
+ query AllPostsSlug {
+ posts(first: 10000) {
+ nodes {
+ slug
+ }
+ }
+ }
+ `;
+
+ const response = await fetchApi<AllPostsSlug>(query, null);
+ return response.posts.nodes;
+};
+
+//==============================================================================
+// Single Post query
+//==============================================================================
+
+export const getPostBySlug = async (slug: string): Promise<Article> => {
+ const query = gql`
+ query PostBySlug($slug: String!) {
+ postBy(slug: $slug) {
+ acfPosts {
+ postsInSubject {
+ ... on Subject {
+ id
+ featuredImage {
+ node {
+ altText
+ sourceUrl
+ title
+ }
+ }
+ slug
+ title
+ }
+ }
+ postsInThematic {
+ ... on Thematic {
+ id
+ slug
+ title
+ }
+ }
+ }
+ author {
+ node {
+ firstName
+ lastName
+ name
+ }
+ }
+ commentCount
+ comments {
+ nodes {
+ approved
+ author {
+ node {
+ gravatarUrl
+ name
+ url
+ }
+ }
+ commentId
+ content
+ date
+ id
+ parentDatabaseId
+ parentId
+ }
+ }
+ contentParts {
+ afterMore
+ beforeMore
+ }
+ databaseId
+ date
+ featuredImage {
+ node {
+ altText
+ sourceUrl
+ title
+ }
+ }
+ id
+ modified
+ seo {
+ title
+ metaDesc
+ opengraphAuthor
+ opengraphDescription
+ opengraphImage {
+ altText
+ sourceUrl
+ srcSet
+ }
+ opengraphModifiedTime
+ opengraphPublishedTime
+ opengraphPublisher
+ opengraphSiteName
+ opengraphTitle
+ opengraphType
+ opengraphUrl
+ readingTime
+ }
+ title
+ }
+ }
+ `;
+ const variables = { slug };
+ const response = await fetchApi<PostBy>(query, variables);
+ const post = getFormattedPost(response.postBy);
+
+ return post;
+};
+
+//==============================================================================
+// Pages query
+//==============================================================================
+
+export const getHomePage = async (): Promise<HomePage> => {
+ const query = gql`
+ query HomePage {
+ nodeByUri(uri: "/") {
+ ... on Page {
+ id
+ content
+ }
+ }
+ }
+ `;
+
+ const response = await fetchApi<HomePageBy>(query, null);
+ const homepage = response.nodeByUri;
+
+ return homepage;
+};
+
+export const getPageByUri = async (slug: string): Promise<Page> => {
+ const query = gql`
+ query PageByUri($slug: String!) {
+ pageBy(uri: $slug) {
+ contentParts {
+ afterMore
+ beforeMore
+ }
+ date
+ modified
+ title
+ }
+ }
+ `;
+
+ const variables = { slug };
+ const response = await fetchApi<PageBy>(query, variables);
+ const page = getFormattedPage(response.pageBy);
+
+ return page;
+};
+
+//==============================================================================
+// Subject query
+//==============================================================================
+
+export const getSubjectBySlug = async (slug: string): Promise<Subject> => {
+ 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
+ }
+ databaseId
+ date
+ featuredImage {
+ node {
+ altText
+ sourceUrl
+ title
+ }
+ }
+ id
+ modified
+ seo {
+ metaDesc
+ opengraphAuthor
+ opengraphDescription
+ opengraphImage {
+ altText
+ sourceUrl
+ srcSet
+ }
+ opengraphModifiedTime
+ opengraphPublishedTime
+ opengraphPublisher
+ opengraphSiteName
+ opengraphTitle
+ opengraphType
+ opengraphUrl
+ readingTime
+ title
+ }
+ title
+ }
+ }
+ `;
+ const variables = { slug };
+ const response = await fetchApi<SubjectBy>(query, variables);
+ const subject = getFormattedSubject(response.subjectBy);
+
+ return subject;
+};
+
+export const getAllSubjectsSlug = async (): Promise<Slug[]> => {
+ // 10 000 is an arbitrary number that I use for small websites.
+ const query = gql`
+ query AllSubjectsSlug {
+ subjects(first: 10000) {
+ nodes {
+ slug
+ }
+ }
+ }
+ `;
+ const response = await fetchApi<AllSubjectsSlug>(query, null);
+ return response.subjects.nodes;
+};
+
+//==============================================================================
+// Thematic query
+//==============================================================================
+
+export const getThematicBySlug = async (slug: string): Promise<Thematic> => {
+ 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
+ }
+ databaseId
+ date
+ id
+ modified
+ seo {
+ metaDesc
+ opengraphAuthor
+ opengraphDescription
+ opengraphImage {
+ altText
+ sourceUrl
+ srcSet
+ }
+ opengraphModifiedTime
+ opengraphPublishedTime
+ opengraphPublisher
+ opengraphSiteName
+ opengraphTitle
+ opengraphType
+ opengraphUrl
+ readingTime
+ title
+ }
+ title
+ }
+ }
+ `;
+ const variables = { slug };
+ const response = await fetchApi<ThematicBy>(query, variables);
+ const thematic = getFormattedThematic(response.thematicBy);
+
+ return thematic;
+};
+
+export const getAllThematicsSlug = async (): Promise<Slug[]> => {
+ // 10 000 is an arbitrary number that I use for small websites.
+ const query = gql`
+ query AllThematicsSlug {
+ thematics(first: 10000) {
+ nodes {
+ slug
+ }
+ }
+ }
+ `;
+ const response = await fetchApi<AllThematicsSlug>(query, null);
+ return response.thematics.nodes;
+};
diff --git a/src/services/graphql/taxonomies.ts b/src/services/graphql/taxonomies.ts
deleted file mode 100644
index ee73dc8..0000000
--- a/src/services/graphql/taxonomies.ts
+++ /dev/null
@@ -1,348 +0,0 @@
-import { ArticlePreview } from '@ts/types/articles';
-import {
- AllSubjectsSlugResponse,
- AllThematicsSlugResponse,
- FetchAllTaxonomiesSlugReturn,
- FetchSubjectByReturn,
- FetchThematicByReturn,
- GetTaxonomyByReturn,
- Subject,
- 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: 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/ts/types/app.ts b/src/ts/types/app.ts
index 488fe6e..ba28416 100644
--- a/src/ts/types/app.ts
+++ b/src/ts/types/app.ts
@@ -1,6 +1,22 @@
import { NextPage } from 'next';
import { AppProps } from 'next/app';
import { ReactElement, ReactNode } from 'react';
+import { PostBy } from './articles';
+import { AllPostsSlug, RawPostsList } from './blog';
+import { CommentData, CreateComment } from './comments';
+import { ContactData, SendEmail } from './contact';
+import { HomePageBy } from './homepage';
+import { PageBy } from './pages';
+import {
+ AllSubjectsSlug,
+ AllThematicsSlug,
+ SubjectBy,
+ ThematicBy,
+} from './taxonomies';
+
+//==============================================================================
+// Next
+//==============================================================================
export type NextPageWithLayout<P = {}> = NextPage<P> & {
getLayout?: (page: ReactElement) => ReactNode;
@@ -9,3 +25,62 @@ export type NextPageWithLayout<P = {}> = NextPage<P> & {
export type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
+
+//==============================================================================
+// API
+//==============================================================================
+
+export type VariablesType<T> = T extends
+ | PageBy
+ | PostBy
+ | SubjectBy
+ | ThematicBy
+ ? Slug
+ : T extends RawPostsList
+ ? CursorPagination
+ : T extends CreateComment
+ ? CommentData
+ : T extends SendEmail
+ ? ContactData
+ : null;
+
+export type RequestType =
+ | AllPostsSlug
+ | AllSubjectsSlug
+ | AllThematicsSlug
+ | CreateComment
+ | HomePageBy
+ | PageBy
+ | PostBy
+ | SubjectBy
+ | ThematicBy
+ | RawPostsList
+ | SendEmail;
+
+//==============================================================================
+// Globals
+//==============================================================================
+
+export type ContentParts = {
+ afterMore: string;
+ beforeMore: string;
+};
+
+export type CursorPagination = {
+ first: number;
+ after: string;
+};
+
+export type Dates = {
+ publication: string;
+ update: string;
+};
+
+export type PageInfo = {
+ endCursor: string;
+ hasNextPage: boolean;
+};
+
+export type Slug = {
+ slug: string;
+};
diff --git a/src/ts/types/articles.ts b/src/ts/types/articles.ts
index afaa3e3..e6a40ef 100644
--- a/src/ts/types/articles.ts
+++ b/src/ts/types/articles.ts
@@ -1,80 +1,68 @@
-import { Comment, CommentsResponse } from './comments';
-import { Cover, CoverResponse } from './cover';
+import { ContentParts, Dates } from './app';
+import { Comment, CommentsNode } from './comments';
+import { Cover, RawCover } from './cover';
import { SEO } from './seo';
import { SubjectPreview, ThematicPreview } from './taxonomies';
-export type ArticleDates = {
- publication: string;
- update: string;
-};
-
export type ArticleAuthor = {
firstName: string;
lastName: string;
name: string;
};
-export type ArticlePreviewResponse = {
- acfPosts: {
- postsInSubject: SubjectPreview[] | null;
- postsInThematic: ThematicPreview[] | null;
- };
- commentCount: number | null;
- contentParts: {
- beforeMore: string;
- };
- databaseId: number;
- date: string;
- featuredImage: CoverResponse;
- id: string;
- modified: string;
- slug: string;
- title: string;
+export type ACFPosts = {
+ postsInSubject: SubjectPreview[] | null;
+ postsInThematic: ThematicPreview[] | null;
};
-export type ArticlePreview = {
+export type Article = {
+ author: ArticleAuthor;
commentCount: number | null;
+ comments: Comment[];
content: string;
databaseId: number;
- date: ArticleDates;
- featuredImage: Cover | null;
+ dates: Dates;
id: string;
- slug: string;
+ intro: string;
+ seo: SEO;
subjects: SubjectPreview[] | [];
thematics: ThematicPreview[] | [];
title: string;
};
-export type ArticleResponse = ArticlePreviewResponse & {
- author: {
- node: ArticleAuthor;
- };
- comments: CommentsResponse;
- contentParts: {
- afterMore: string;
- };
- seo: SEO;
+export type RawArticle = Pick<
+ Article,
+ 'commentCount' | 'databaseId' | 'id' | 'seo' | 'title'
+> & {
+ acfPosts: ACFPosts;
+ author: { node: ArticleAuthor };
+ comments: CommentsNode;
+ contentParts: ContentParts;
+ date: string;
+ modified: string;
};
-export type Article = ArticlePreview & {
- author: ArticleAuthor;
- comments: Comment[];
- intro: string;
- seo: SEO;
-};
+export type ArticlePreview = Pick<
+ Article,
+ 'commentCount' | 'dates' | 'id' | 'intro' | 'thematics' | 'title'
+> & { featuredImage: Cover; slug: string };
-export type PostByResponse = {
- postBy: ArticleResponse;
+export type RawArticlePreview = Pick<
+ Article,
+ 'commentCount' | 'id' | 'title'
+> & {
+ acfPosts: Pick<ACFPosts, 'postsInThematic'>;
+ contentParts: Pick<ContentParts, 'beforeMore'>;
+ date: string;
+ featuredImage: RawCover;
+ modified: string;
+ slug: string;
};
-export type FetchPostByReturn = (slug: string) => Promise<PostByResponse>;
-
-export type GetPostByReturn = (slug: string) => Promise<Article>;
+export type PostBy = {
+ postBy: RawArticle;
+};
export type ArticleProps = {
post: Article;
};
-
-export type ArticleSlug = {
- slug: string;
-};
diff --git a/src/ts/types/blog.ts b/src/ts/types/blog.ts
index 32fa9b8..7325ddf 100644
--- a/src/ts/types/blog.ts
+++ b/src/ts/types/blog.ts
@@ -1,47 +1,29 @@
-import {
- ArticlePreview,
- ArticlePreviewResponse,
- ArticleSlug,
-} from './articles';
-import { PageInfo } from './pagination';
-
-export type PostsListEdge = {
- cursor: string;
- node: ArticlePreviewResponse;
-};
-
-export type PostsListResponse = {
- posts: {
- edges: PostsListEdge[];
- pageInfo: PageInfo;
- };
-};
+import { PageInfo, Slug } from './app';
+import { ArticlePreview, RawArticlePreview } from './articles';
export type PostsList = {
posts: ArticlePreview[];
pageInfo: PageInfo;
};
-export type FetchPostsListReturn = (
- first?: number,
- after?: string
-) => Promise<PostsListResponse>;
-
-type PostsListProps = {
- first?: number;
- after?: string;
+export type PostsListEdges = {
+ cursor: string;
+ node: RawArticlePreview;
};
-export type GetPostsListReturn = (props: PostsListProps) => Promise<PostsList>;
-
-export type BlogPageProps = {
- fallback: PostsList;
+export type RawPostsList = {
+ posts: {
+ edges: PostsListEdges[];
+ pageInfo: PageInfo;
+ };
};
-export type AllPostsSlugResponse = {
+export type AllPostsSlug = {
posts: {
- nodes: ArticleSlug[];
+ nodes: Slug[];
};
};
-export type FetchAllPostsSlugReturn = () => Promise<ArticleSlug[]>;
+export type BlogPageProps = {
+ fallback: PostsList;
+};
diff --git a/src/ts/types/comments.ts b/src/ts/types/comments.ts
index a1bb120..d5c0052 100644
--- a/src/ts/types/comments.ts
+++ b/src/ts/types/comments.ts
@@ -1,10 +1,14 @@
+//==============================================================================
+// Comments query
+//==============================================================================
+
export type CommentAuthor = {
gravatarUrl: string;
name: string;
url: string;
};
-export type CommentAuthorResponse = {
+export type RawCommentAuthor = {
node: CommentAuthor;
};
@@ -19,14 +23,28 @@ export type Comment = {
replies: Comment[];
};
-export type RawComment = Omit<Comment, 'author'> & {
- author: CommentAuthorResponse;
+export type RawComment = Omit<Comment, 'author' | 'replies'> & {
+ author: RawCommentAuthor;
};
-export type CommentsResponse = {
+export type CommentsNode = {
nodes: RawComment[];
};
+//==============================================================================
+// Comment mutations
+//==============================================================================
+
+export type CommentData = {
+ author: string;
+ authorEmail: string;
+ authorUrl: string;
+ content: string;
+ parent: number;
+ commentOn: number;
+ mutationId: string;
+};
+
export type CreatedComment = {
clientMutationId: string;
success: boolean;
@@ -35,16 +53,6 @@ export type CreatedComment = {
};
};
-export type CreatedCommentResponse = {
+export type CreateComment = {
createComment: CreatedComment;
};
-
-export type CreatedCommentReturn = (
- author: string,
- authorEmail: string,
- authorUrl: string,
- content: string,
- parent: number,
- commentOn: number,
- mutationId: string
-) => Promise<CreatedComment>;
diff --git a/src/ts/types/contact.ts b/src/ts/types/contact.ts
index c0f23e0..ef6847a 100644
--- a/src/ts/types/contact.ts
+++ b/src/ts/types/contact.ts
@@ -1,3 +1,10 @@
+export type ContactData = {
+ body: string;
+ mutationId: string;
+ replyTo: string;
+ subject: string;
+};
+
export type SentEmail = {
clientMutationId: string;
message: string;
@@ -7,13 +14,6 @@ export type SentEmail = {
to: string;
};
-export type SentEmailResponse = {
+export type SendEmail = {
sendEmail: SentEmail;
};
-
-export type SendMailReturn = (
- subject: string,
- body: string,
- replyTo: string,
- mutationId: string
-) => Promise<SentEmail>;
diff --git a/src/ts/types/cover.ts b/src/ts/types/cover.ts
index 2a565ef..4df898e 100644
--- a/src/ts/types/cover.ts
+++ b/src/ts/types/cover.ts
@@ -4,6 +4,6 @@ export type Cover = {
title: string;
} | null;
-export type CoverResponse = {
+export type RawCover = {
node: Cover;
} | null;
diff --git a/src/ts/types/homepage.ts b/src/ts/types/homepage.ts
index 404aa38..8ff2ccb 100644
--- a/src/ts/types/homepage.ts
+++ b/src/ts/types/homepage.ts
@@ -1,19 +1,12 @@
-export type fetchHomePageReturn = () => Promise<HomePageResponse>;
-
-export type HomePageResponse = {
- nodeByUri: {
- id: string;
- content: string;
- };
-};
-
-export type getHomePageReturn = () => Promise<HomePage>;
-
export type HomePage = {
id: string;
content: string;
};
+export type HomePageBy = {
+ nodeByUri: HomePage;
+};
+
export type HomePageProps = {
data: HomePage;
};
diff --git a/src/ts/types/pages.ts b/src/ts/types/pages.ts
index 4b38ff4..93ff62e 100644
--- a/src/ts/types/pages.ts
+++ b/src/ts/types/pages.ts
@@ -1,29 +1,23 @@
+import { ContentParts, Dates } from './app';
+
export type Page = {
content: string;
- date: string;
+ dates: Dates;
intro: string;
- modified: string;
title: string;
};
export type RawPage = {
- contentParts: {
- afterMore: string;
- beforeMore: string;
- };
+ contentParts: ContentParts;
date: string;
modified: string;
title: string;
};
-export type PageResponse = {
+export type PageBy = {
pageBy: RawPage;
};
-export type FetchPageByUriReturn = (uri: string) => Promise<RawPage>;
-
-export type GetPageReturn = () => Promise<Page>;
-
export type PageProps = {
page: Page;
};
diff --git a/src/ts/types/pagination.ts b/src/ts/types/pagination.ts
deleted file mode 100644
index 45830d9..0000000
--- a/src/ts/types/pagination.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export type PageInfo = {
- endCursor: string;
- hasNextPage: boolean;
-};
diff --git a/src/ts/types/taxonomies.ts b/src/ts/types/taxonomies.ts
index a8c372c..3945934 100644
--- a/src/ts/types/taxonomies.ts
+++ b/src/ts/types/taxonomies.ts
@@ -1,96 +1,90 @@
-import { ArticlePreview, ArticlePreviewResponse } from './articles';
-import { Cover, CoverResponse } from './cover';
+import { ContentParts, Dates, Slug } from './app';
+import { ArticlePreview, RawArticlePreview } from './articles';
+import { Cover, RawCover } from './cover';
-type TaxonomyPreview = {
+//==============================================================================
+// Taxonomies base
+//==============================================================================
+
+type Taxonomy = {
+ content: string;
databaseId: number;
+ dates: Dates;
id: string;
- slug: string;
+ intro: string;
+ posts: ArticlePreview[];
title: string;
};
-export type Taxonomy = TaxonomyPreview & {
- content: string;
- date: string;
- intro: string;
- modified: string;
- posts: ArticlePreview[];
+type TaxonomyPreview = Pick<Taxonomy, 'databaseId' | 'id' | 'title'> & {
+ slug: string;
};
-export type SubjectPreview = TaxonomyPreview & {
+//==============================================================================
+// Subjects
+//==============================================================================
+
+export type Subject = Taxonomy & {
featuredImage: Cover;
+ officialWebsite: string;
};
-export type ThematicPreview = TaxonomyPreview;
+export type SubjectPreview = TaxonomyPreview & {
+ featuredImage: Cover;
+};
-export type ThematicResponse = TaxonomyPreview & {
- acfThematics: {
- postsInThematic: ArticlePreviewResponse[];
- };
- contentParts: {
- afterMore: string;
- beforeMore: string;
+export type RawSubject = SubjectPreview & {
+ acfSubjects: {
+ officialWebsite: string;
+ postsInSubject: RawArticlePreview[];
};
+ contentParts: ContentParts;
date: string;
+ featuredImage: RawCover;
modified: string;
};
-export type ThematicProps = {
- thematic: Taxonomy;
-};
-
-export type AllTaxonomiesSlug = {
- slug: string;
+export type SubjectBy = {
+ subjectBy: RawSubject;
};
-export type AllThematicsSlugResponse = {
- thematics: {
- nodes: AllTaxonomiesSlug[];
+export type AllSubjectsSlug = {
+ subjects: {
+ nodes: Slug[];
};
};
-export type ThematicByResponse = {
- thematicBy: ThematicResponse;
-};
+//==============================================================================
+// Thematics
+//==============================================================================
-export type FetchThematicByReturn = (
- slug: string
-) => Promise<ThematicByResponse>;
+export type Thematic = Taxonomy;
-export type GetTaxonomyByReturn = (slug: string) => Promise<Taxonomy>;
-
-export type FetchAllTaxonomiesSlugReturn = () => Promise<AllTaxonomiesSlug[]>;
-
-export type Subject = Taxonomy & {
- featuredImage: Cover;
- officialWebsite: string;
-};
+export type ThematicPreview = TaxonomyPreview;
-export type SubjectResponse = SubjectPreview & {
- acfSubjects: {
- postsInSubject: ArticlePreviewResponse[];
- };
- contentParts: {
- afterMore: string;
- beforeMore: string;
+export type RawThematic = TaxonomyPreview & {
+ acfThematics: {
+ postsInThematic: RawArticlePreview[];
};
+ contentParts: ContentParts;
date: string;
- featuredImage: CoverResponse;
modified: string;
- officialWebsite: string;
};
-export type SubjectProps = {
- subject: Subject;
+export type ThematicBy = {
+ thematicBy: RawThematic;
};
-export type SubjectByResponse = {
- subjectBy: SubjectResponse;
+export type AllThematicsSlug = {
+ thematics: {
+ nodes: Slug[];
+ };
};
-export type FetchSubjectByReturn = (slug: string) => Promise<SubjectByResponse>;
+export type SubjectProps = {
+ subject: Subject;
+};
-export type AllSubjectsSlugResponse = {
- subjects: {
- nodes: AllTaxonomiesSlug[];
- };
+export type ThematicProps = {
+ thematic: Thematic;
};
diff --git a/src/utils/helpers/format.ts b/src/utils/helpers/format.ts
new file mode 100644
index 0000000..8c5e545
--- /dev/null
+++ b/src/utils/helpers/format.ts
@@ -0,0 +1,226 @@
+import {
+ Article,
+ ArticlePreview,
+ RawArticle,
+ RawArticlePreview,
+} from '@ts/types/articles';
+import { Comment, RawComment } from '@ts/types/comments';
+import { Page, RawPage } from '@ts/types/pages';
+import {
+ RawSubject,
+ RawThematic,
+ Subject,
+ Thematic,
+} from '@ts/types/taxonomies';
+
+/**
+ * Format a post preview from RawArticlePreview to ArticlePreview type.
+ * @param rawPost - A post preview coming from WP GraphQL.
+ * @returns A formatted post preview.
+ */
+export const getFormattedPostPreview = (rawPost: RawArticlePreview) => {
+ const {
+ acfPosts,
+ commentCount,
+ contentParts,
+ date,
+ featuredImage,
+ id,
+ modified,
+ slug,
+ title,
+ } = rawPost;
+
+ const dates = {
+ publication: date,
+ update: modified,
+ };
+
+ const thematics = acfPosts.postsInThematic ? acfPosts.postsInThematic : [];
+
+ const formattedPost: ArticlePreview = {
+ commentCount,
+ dates,
+ featuredImage: featuredImage ? featuredImage.node : null,
+ id,
+ intro: contentParts.beforeMore,
+ slug,
+ thematics,
+ title,
+ };
+
+ return formattedPost;
+};
+
+/**
+ * Format an array of posts list from RawArticlePreview to ArticlePreview type.
+ * @param rawPosts - A posts list coming from WP GraphQL.
+ * @returns A formatted posts list.
+ */
+export const getFormattedPostsList = (
+ rawPosts: RawArticlePreview[]
+): ArticlePreview[] => {
+ const formattedPosts = rawPosts
+ .filter((post) => Object.getOwnPropertyNames(post).length > 0)
+ .map((post) => {
+ const formattedPost = getFormattedPostPreview(post);
+
+ return formattedPost;
+ });
+
+ return formattedPosts;
+};
+
+/**
+ * Format a subject from RawSubject to Subject type.
+ * @param rawSubject - A subject coming from WP GraphQL.
+ * @returns A formatted subject.
+ */
+export const getFormattedSubject = (rawSubject: RawSubject): Subject => {
+ const {
+ acfSubjects,
+ contentParts,
+ databaseId,
+ date,
+ featuredImage,
+ id,
+ modified,
+ title,
+ } = rawSubject;
+
+ const dates = {
+ publication: date,
+ update: modified,
+ };
+
+ const posts = getFormattedPostsList(acfSubjects.postsInSubject);
+
+ const formattedSubject: Subject = {
+ content: contentParts.afterMore,
+ databaseId,
+ dates,
+ featuredImage: featuredImage ? featuredImage.node : null,
+ id,
+ intro: contentParts.beforeMore,
+ officialWebsite: acfSubjects.officialWebsite,
+ posts,
+ title,
+ };
+
+ return formattedSubject;
+};
+
+/**
+ * Format a thematic from RawThematic to Thematic type.
+ * @param rawThematic - A thematic coming from wP GraphQL.
+ * @returns A formatted thematic.
+ */
+export const getFormattedThematic = (rawThematic: RawThematic): Thematic => {
+ const { acfThematics, contentParts, databaseId, date, id, modified, title } =
+ rawThematic;
+
+ const dates = {
+ publication: date,
+ update: modified,
+ };
+
+ const posts = getFormattedPostsList(acfThematics.postsInThematic);
+
+ const formattedThematic: Thematic = {
+ content: contentParts.afterMore,
+ databaseId,
+ dates,
+ id,
+ intro: contentParts.beforeMore,
+ posts,
+ title,
+ };
+
+ return formattedThematic;
+};
+
+/**
+ * Format a comments list from RawComment to Comment type.
+ * @param rawComments - A comments list coming from WP GraphQL.
+ * @returns A formatted comments list.
+ */
+export const getFormattedComments = (rawComments: RawComment[]): Comment[] => {
+ const formattedComments: Comment[] = rawComments.map((comment) => {
+ const formattedComment: Comment = {
+ ...comment,
+ author: comment.author.node,
+ replies: [],
+ };
+
+ return formattedComment;
+ });
+
+ return formattedComments;
+};
+
+/**
+ * Format an article from RawArticle to Article type.
+ * @param rawPost - An article coming from WP GraphQL.
+ * @returns A formatted article.
+ */
+export const getFormattedPost = (rawPost: RawArticle): Article => {
+ const {
+ acfPosts,
+ author,
+ commentCount,
+ comments,
+ contentParts,
+ databaseId,
+ date,
+ id,
+ modified,
+ seo,
+ title,
+ } = rawPost;
+
+ const dates = {
+ publication: date,
+ update: modified,
+ };
+
+ const formattedComments = getFormattedComments(comments.nodes);
+
+ const formattedPost: Article = {
+ author: author.node,
+ commentCount,
+ comments: formattedComments,
+ content: contentParts.afterMore,
+ databaseId,
+ dates,
+ id,
+ intro: contentParts.beforeMore,
+ seo,
+ subjects: acfPosts.postsInSubject ? acfPosts.postsInSubject : [],
+ thematics: acfPosts.postsInThematic ? acfPosts.postsInThematic : [],
+ title,
+ };
+
+ return formattedPost;
+};
+
+/**
+ * Format a page from RawPage to Page type.
+ * @param page - A page coming from WP GraphQL.
+ * @returns A formatted page.
+ */
+export const getFormattedPage = (rawPage: RawPage): Page => {
+ const { date, modified } = rawPage;
+ const dates = {
+ publication: date,
+ update: modified,
+ };
+
+ const formattedPage: Page = {
+ ...rawPage,
+ content: rawPage.contentParts.afterMore,
+ dates,
+ intro: rawPage.contentParts.beforeMore,
+ };
+
+ return formattedPage;
+};
diff --git a/src/utils/helpers/sort.ts b/src/utils/helpers/sort.ts
index ade82d0..c1ee35d 100644
--- a/src/utils/helpers/sort.ts
+++ b/src/utils/helpers/sort.ts
@@ -10,7 +10,9 @@ export const sortPostsByYear = (data: PostsList[]) => {
data.forEach((page) => {
page.posts.forEach((post) => {
- const postYear = new Date(post.date.publication).getFullYear().toString();
+ const postYear = new Date(post.dates.publication)
+ .getFullYear()
+ .toString();
yearCollection[postYear] = [...(yearCollection[postYear] || []), post];
});
});