diff options
Diffstat (limited to 'src')
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]; }); }); |
