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];      });    }); | 
