diff options
| -rw-r--r-- | src/components/templates/page/page-layout.tsx | 4 | ||||
| -rw-r--r-- | src/pages/blog/index.tsx | 2 | ||||
| -rw-r--r-- | src/pages/blog/page/[number].tsx | 2 | ||||
| -rw-r--r-- | src/services/graphql/api.ts | 312 | ||||
| -rw-r--r-- | src/services/graphql/articles.query.ts | 2 | ||||
| -rw-r--r-- | src/services/graphql/articles.ts | 42 | ||||
| -rw-r--r-- | src/services/graphql/comments.ts | 47 | ||||
| -rw-r--r-- | src/services/graphql/contact.ts | 8 | ||||
| -rw-r--r-- | src/services/graphql/thematics.ts | 33 | ||||
| -rw-r--r-- | src/services/graphql/topics.ts | 32 | ||||
| -rw-r--r-- | src/ts/types/graphql/generics.ts | 25 | ||||
| -rw-r--r-- | src/ts/types/graphql/mutations.ts | 61 | ||||
| -rw-r--r-- | src/ts/types/graphql/queries.ts | 147 | ||||
| -rw-r--r-- | src/ts/types/raw-data.ts | 10 | ||||
| -rw-r--r-- | src/utils/helpers/pages.ts | 18 | ||||
| -rw-r--r-- | src/utils/hooks/use-article.tsx | 6 | ||||
| -rw-r--r-- | src/utils/hooks/use-comments.tsx | 6 | ||||
| -rw-r--r-- | src/utils/hooks/use-pagination.tsx | 7 |
18 files changed, 380 insertions, 384 deletions
diff --git a/src/components/templates/page/page-layout.tsx b/src/components/templates/page/page-layout.tsx index f96666e..d53f53d 100644 --- a/src/components/templates/page/page-layout.tsx +++ b/src/components/templates/page/page-layout.tsx @@ -18,8 +18,8 @@ import CommentsList, { type CommentsListProps, } from '@components/organisms/layout/comments-list'; import TableOfContents from '@components/organisms/widgets/table-of-contents'; -import { type SendCommentVars } from '@services/graphql/api'; import { sendComment } from '@services/graphql/comments'; +import { SendCommentInput } from '@ts/types/graphql/mutations'; import useIsMounted from '@utils/hooks/use-is-mounted'; import Script from 'next/script'; import { FC, HTMLAttributes, ReactNode, useRef, useState } from 'react'; @@ -130,7 +130,7 @@ const PageLayout: FC<PageLayoutProps> = ({ if (!id) throw new Error('Page id missing. Cannot save comment.'); const { comment: commentBody, email, name, parentId, website } = data; - const commentData: SendCommentVars = { + const commentData: SendCommentInput = { author: name, authorEmail: email, authorUrl: website || '', diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index 3f7eefd..3be8a99 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -3,7 +3,6 @@ import PostsList from '@components/organisms/layout/posts-list'; import LinksListWidget from '@components/organisms/widgets/links-list-widget'; import { getLayout } from '@components/templates/layout/layout'; import PageLayout from '@components/templates/page/page-layout'; -import { type EdgesResponse } from '@services/graphql/api'; import { getArticles, getTotalArticles } from '@services/graphql/articles'; import { getThematicsPreview, @@ -11,6 +10,7 @@ import { } from '@services/graphql/thematics'; import { getTopicsPreview, getTotalTopics } from '@services/graphql/topics'; import { type NextPageWithLayout } from '@ts/types/app'; +import { EdgesResponse } from '@ts/types/graphql/queries'; import { type RawArticle, type RawThematicPreview, diff --git a/src/pages/blog/page/[number].tsx b/src/pages/blog/page/[number].tsx index 1e1240a..e72eb9a 100644 --- a/src/pages/blog/page/[number].tsx +++ b/src/pages/blog/page/[number].tsx @@ -2,7 +2,6 @@ import PostsList from '@components/organisms/layout/posts-list'; import LinksListWidget from '@components/organisms/widgets/links-list-widget'; import { getLayout } from '@components/templates/layout/layout'; import PageLayout from '@components/templates/page/page-layout'; -import { type EdgesResponse } from '@services/graphql/api'; import { getArticles, getArticlesEndCursor, @@ -14,6 +13,7 @@ import { } from '@services/graphql/thematics'; import { getTopicsPreview, getTotalTopics } from '@services/graphql/topics'; import { type NextPageWithLayout } from '@ts/types/app'; +import { EdgesResponse } from '@ts/types/graphql/queries'; import { type RawArticle, type RawThematicPreview, diff --git a/src/services/graphql/api.ts b/src/services/graphql/api.ts index 009aea4..e587ccc 100644 --- a/src/services/graphql/api.ts +++ b/src/services/graphql/api.ts @@ -1,271 +1,60 @@ -import { settings } from '@utils/config'; -import { - articleBySlugQuery, - articlesCardQuery, - articlesEndCursor, - articlesQuery, - articlesSlugQuery, - totalArticlesQuery, -} from './articles.query'; -import { sendCommentMutation } from './comments.mutation'; -import { commentsQuery } from './comments.query'; -import { sendMailMutation } from './contact.mutation'; import { - thematicBySlugQuery, - thematicsListQuery, - thematicsSlugQuery, - totalThematicsQuery, -} from './thematics.query'; + Mutations, + MutationsInputMap, + MutationsResponseMap, +} from '@ts/types/graphql/mutations'; import { - topicBySlugQuery, - topicsListQuery, - topicsSlugQuery, - totalTopicsQuery, -} from './topics.query'; - -export type Mutations = typeof sendMailMutation | typeof sendCommentMutation; - -export type Queries = - | typeof articlesQuery - | typeof articleBySlugQuery - | typeof articlesCardQuery - | typeof articlesEndCursor - | typeof articlesSlugQuery - | typeof commentsQuery - | typeof thematicBySlugQuery - | typeof thematicsListQuery - | typeof thematicsSlugQuery - | typeof topicBySlugQuery - | typeof topicsListQuery - | typeof topicsSlugQuery - | typeof totalArticlesQuery - | typeof totalThematicsQuery - | typeof totalTopicsQuery; - -export type ArticleResponse<T> = { - post: T; -}; - -export type ArticlesResponse<T> = { - posts: T; -}; - -export type CommentsResponse<T> = { - comments: T; -}; - -export type SendCommentResponse<T> = { - createComment: T; -}; - -export type SendMailResponse<T> = { - sendEmail: T; -}; - -export type ThematicResponse<T> = { - thematic: T; -}; - -export type ThematicsResponse<T> = { - thematics: T; -}; - -export type TopicResponse<T> = { - topic: T; -}; - -export type TopicsResponse<T> = { - topics: T; -}; - -export type PageInfo = { - endCursor: string; - hasNextPage: boolean; - total: number; -}; - -export type Edges<T> = { - cursor: string; - node: T; -}; - -export type EdgesResponse<T> = { - edges: Edges<T>[]; - pageInfo: PageInfo; -}; - -export type NodeResponse<T> = { - node: T; -}; - -export type NodesResponse<T> = { - nodes: T[]; -}; - -export type EndCursor = Pick< - EdgesResponse<Pick<PageInfo, 'endCursor'>>, - 'pageInfo' ->; - -export type ResponseMap<T> = { - [articleBySlugQuery]: ArticleResponse<T>; - [articlesCardQuery]: ArticlesResponse<NodesResponse<T>>; - [articlesEndCursor]: ArticlesResponse<EndCursor>; - [articlesQuery]: ArticlesResponse<EdgesResponse<T>>; - [articlesSlugQuery]: ArticlesResponse<EdgesResponse<T>>; - [commentsQuery]: CommentsResponse<NodesResponse<T>>; - [sendCommentMutation]: SendCommentResponse<T>; - [sendMailMutation]: SendMailResponse<T>; - [thematicBySlugQuery]: ThematicResponse<T>; - [thematicsListQuery]: ThematicsResponse<EdgesResponse<T>>; - [thematicsSlugQuery]: ThematicsResponse<EdgesResponse<T>>; - [topicBySlugQuery]: TopicResponse<T>; - [topicsListQuery]: TopicsResponse<EdgesResponse<T>>; - [topicsSlugQuery]: TopicsResponse<EdgesResponse<T>>; - [totalArticlesQuery]: ArticlesResponse<T>; - [totalThematicsQuery]: ThematicsResponse<T>; - [totalTopicsQuery]: TopicsResponse<T>; -}; + Queries, + QueriesInputMap, + QueriesResponseMap, +} from '@ts/types/graphql/queries'; +import { settings } from '@utils/config'; -export type GraphQLResponse< - T extends keyof ResponseMap<U>, - U -> = ResponseMap<U>[T]; +/** + * Retrieve the API url from settings. + * + * @returns {string} The API url. + */ +export const getAPIUrl = (): string => { + const { url } = settings.api; -export type BySlugVar = { - /** - * A slug. - */ - slug: string; -}; + if (!url) { + throw new Error('API url is not defined.'); + } -export type EdgesVars = { - /** - * A cursor. - */ - after?: string; - /** - * The number of items to return. - */ - first: number; - /** - * A search query. - */ - search?: string; + return url; }; -export type ByContentIdVar = { - /** - * An article id. - */ - contentId: number; -}; +export type ResponseMap<T, K extends Mutations | Queries> = K extends Mutations + ? MutationsResponseMap<T> + : QueriesResponseMap<T>; -export type SearchVar = { - /** - * A search term. - */ - search?: string; -}; +export type InputMap<T extends Mutations | Queries> = T extends Mutations + ? MutationsInputMap + : QueriesInputMap; -export type SendCommentVars = { - /** - * The author name. - */ - author: string; - /** - * The author e-mail address. - */ - authorEmail: string; - /** - * The author website. - */ - authorUrl: string; - /** - * A mutation id. - */ - clientMutationId: string; - /** - * A post or page id. - */ - commentOn: number; - /** - * The comment body. - */ - content: string; - /** - * The comment parent. - */ - parent?: number; -}; +type FetchAPIVariables<T> = T extends Queries + ? QueriesInputMap[T] + : T extends Mutations + ? MutationsInputMap[T] + : never; -export type SendMailVars = { - /** - * The mail body. - */ - body: string; - /** - * A mutation id. - */ - clientMutationId: string; - /** - * The reply to e-mail address. - */ - replyTo: string; - /** - * The mail subject. - */ - subject: string; +type FetchAPIProps<Q extends Queries | Mutations, V = FetchAPIVariables<Q>> = { + query: Q; + variables?: V; }; -export type VariablesMap = { - [articleBySlugQuery]: BySlugVar; - [articlesCardQuery]: EdgesVars; - [articlesEndCursor]: EdgesVars; - [articlesQuery]: EdgesVars; - [articlesSlugQuery]: EdgesVars; - [commentsQuery]: ByContentIdVar; - [sendCommentMutation]: SendCommentVars; - [sendMailMutation]: SendMailVars; - [thematicBySlugQuery]: BySlugVar; - [thematicsListQuery]: EdgesVars; - [thematicsSlugQuery]: EdgesVars; - [topicBySlugQuery]: BySlugVar; - [topicsListQuery]: EdgesVars; - [topicsSlugQuery]: EdgesVars; - [totalArticlesQuery]: SearchVar; - [totalThematicsQuery]: null; - [totalTopicsQuery]: null; -}; +type FetchAPIResponse<T, K extends Queries | Mutations> = K extends Queries + ? QueriesResponseMap<T>[K] + : K extends Mutations + ? MutationsResponseMap<T>[K] + : never; -export type FetchAPIProps<T extends Queries | Mutations> = { - /** - * A GraphQL API URL. - */ - api: string; - /** - * A GraphQL query. - */ - query: T; - /** - * (Optional) The query variables. - */ - variables?: VariablesMap[T]; -}; - -/** - * Fetch a GraphQL API. - * @param {object} obj - An object. - * @param {string} obj.api - A GraphQL API URL. - * @param {Queries} obj.query - A GraphQL query. - * @param {object} [obj.variables] - The query variables. - */ -export async function fetchAPI<T, U extends Queries | Mutations>({ - api, +export const fetchAPI = async <T, K extends Queries | Mutations>({ query, variables, -}: FetchAPIProps<U>): Promise<GraphQLResponse<U, T>> { - const response = await fetch(api, { +}: FetchAPIProps<K>): Promise<FetchAPIResponse<T, K>> => { + const response = await fetch(getAPIUrl(), { method: 'POST', headers: { 'content-type': 'application/json;charset=UTF-8', @@ -277,7 +66,7 @@ export async function fetchAPI<T, U extends Queries | Mutations>({ }); type JSONResponse = { - data?: GraphQLResponse<U, T>; + data?: FetchAPIResponse<T, K>; errors?: Array<{ message: string }>; }; @@ -294,19 +83,4 @@ export async function fetchAPI<T, U extends Queries | Mutations>({ ); return Promise.reject(error); } -} - -/** - * Retrieve the API url from settings. - * - * @returns {string} The API url. - */ -export const getAPIUrl = (): string => { - const { url } = settings.api; - - if (!url) { - throw new Error('API url is not defined.'); - } - - return url; }; diff --git a/src/services/graphql/articles.query.ts b/src/services/graphql/articles.query.ts index 3e1f575..46e3df6 100644 --- a/src/services/graphql/articles.query.ts +++ b/src/services/graphql/articles.query.ts @@ -181,7 +181,7 @@ export const totalArticlesQuery = `query PostsTotal($search: String = "") { /** * Query the end cursor based on the queried posts number. */ -export const articlesEndCursor = `query EndCursorAfter($first: Int) { +export const articlesEndCursorQuery = `query EndCursorAfter($first: Int) { posts(first: $first) { pageInfo { hasNextPage diff --git a/src/services/graphql/articles.ts b/src/services/graphql/articles.ts index 27406ac..1a7b2e0 100644 --- a/src/services/graphql/articles.ts +++ b/src/services/graphql/articles.ts @@ -1,4 +1,6 @@ -import { Slug, type Article, type ArticleCard } from '@ts/types/app'; +import { type Article, type ArticleCard, type Slug } from '@ts/types/app'; +import { GraphQLEdgesInput, GraphQLPageInfo } from '@ts/types/graphql/generics'; +import { EdgesResponse, EndCursorResponse } from '@ts/types/graphql/queries'; import { type RawArticle, type RawArticlePreview, @@ -7,18 +9,11 @@ import { import { getAuthorFromRawData } from '@utils/helpers/author'; import { getImageFromRawData } from '@utils/helpers/images'; import { getPageLinkFromRawData } from '@utils/helpers/pages'; -import { - EdgesResponse, - EdgesVars, - EndCursor, - fetchAPI, - getAPIUrl, - PageInfo, -} from './api'; +import { fetchAPI } from './api'; import { articleBySlugQuery, articlesCardQuery, - articlesEndCursor, + articlesEndCursorQuery, articlesQuery, articlesSlugQuery, totalArticlesQuery, @@ -31,7 +26,6 @@ import { */ export const getTotalArticles = async (search?: string): Promise<number> => { const response = await fetchAPI<TotalItems, typeof totalArticlesQuery>({ - api: getAPIUrl(), query: totalArticlesQuery, variables: { search }, }); @@ -41,7 +35,7 @@ export const getTotalArticles = async (search?: string): Promise<number> => { export type GetArticlesReturn = { articles: Article[]; - pageInfo: PageInfo; + pageInfo: GraphQLPageInfo; }; /** @@ -97,14 +91,13 @@ export const getArticleFromRawData = (data: RawArticle): Article => { /** * Retrieve the given number of articles from API. * - * @param {EdgesVars} props - An object of GraphQL variables. + * @param {GraphQLEdgesInput} props - An object of GraphQL variables. * @returns {Promise<EdgesResponse<RawArticle>>} The articles data. */ export const getArticles = async ( - props: EdgesVars + props: GraphQLEdgesInput ): Promise<EdgesResponse<RawArticle>> => { const response = await fetchAPI<RawArticle, typeof articlesQuery>({ - api: getAPIUrl(), query: articlesQuery, variables: { ...props }, }); @@ -133,15 +126,14 @@ const getArticleCardFromRawData = (data: RawArticlePreview): ArticleCard => { /** * Retrieve the given number of article cards from API. * - * @param {EdgesVars} obj - An object. + * @param {GraphQLEdgesInput} obj - An object. * @param {number} obj.first - The number of articles. * @returns {Promise<ArticleCard[]>} - The article cards data. */ export const getArticlesCard = async ({ first, -}: EdgesVars): Promise<ArticleCard[]> => { +}: GraphQLEdgesInput): Promise<ArticleCard[]> => { const response = await fetchAPI<RawArticlePreview, typeof articlesCardQuery>({ - api: getAPIUrl(), query: articlesCardQuery, variables: { first }, }); @@ -157,7 +149,6 @@ export const getArticlesCard = async ({ */ export const getArticleBySlug = async (slug: string): Promise<Article> => { const response = await fetchAPI<RawArticle, typeof articleBySlugQuery>({ - api: getAPIUrl(), query: articleBySlugQuery, variables: { slug }, }); @@ -173,7 +164,6 @@ export const getArticleBySlug = async (slug: string): Promise<Article> => { export const getAllArticlesSlugs = async (): Promise<string[]> => { const totalArticles = await getTotalArticles(); const response = await fetchAPI<Slug, typeof articlesSlugQuery>({ - api: getAPIUrl(), query: articlesSlugQuery, variables: { first: totalArticles }, }); @@ -184,15 +174,17 @@ export const getAllArticlesSlugs = async (): Promise<string[]> => { /** * Retrieve the last cursor. * - * @param {EdgesVars} props - An object of GraphQL variables. + * @param {GraphQLEdgesInput} props - An object of GraphQL variables. * @returns {Promise<string>} - The end cursor. */ export const getArticlesEndCursor = async ( - props: EdgesVars + props: GraphQLEdgesInput ): Promise<string> => { - const response = await fetchAPI<EndCursor, typeof articlesEndCursor>({ - api: getAPIUrl(), - query: articlesEndCursor, + const response = await fetchAPI< + EndCursorResponse, + typeof articlesEndCursorQuery + >({ + query: articlesEndCursorQuery, variables: { ...props }, }); diff --git a/src/services/graphql/comments.ts b/src/services/graphql/comments.ts index 28ddfd0..86b6a35 100644 --- a/src/services/graphql/comments.ts +++ b/src/services/graphql/comments.ts @@ -1,10 +1,33 @@ import { Comment } from '@ts/types/app'; +import { GraphQLEdgesInput } from '@ts/types/graphql/generics'; +import { SendCommentInput, SentComment } from '@ts/types/graphql/mutations'; +import { ContentId } from '@ts/types/graphql/queries'; import { RawComment } from '@ts/types/raw-data'; import { getAuthorFromRawData } from '@utils/helpers/author'; -import { fetchAPI, getAPIUrl, SendCommentVars } from './api'; +import { fetchAPI } from './api'; import { sendCommentMutation } from './comments.mutation'; import { commentsQuery } from './comments.query'; +type FetchCommentsInput = ContentId & + Pick<GraphQLEdgesInput, 'after' | 'first'>; + +/** + * Retrieve the comments list from GraphQL. + * + * @param {FetchCommentsInput} variables - An object of variables. + * @returns {Promise<RawComment[]>} The raw comments. + */ +export const fetchComments = async ( + variables: FetchCommentsInput +): Promise<RawComment[]> => { + const response = await fetchAPI<RawComment, typeof commentsQuery>({ + query: commentsQuery, + variables, + }); + + return response.comments.nodes; +}; + /** * Create a comments tree with replies. * @@ -62,27 +85,12 @@ export const getCommentFromRawData = (comment: RawComment): Comment => { * @returns {Promise<Comment[]>} The comments list. */ export const getPostComments = async (id: number): Promise<Comment[]> => { - const response = await fetchAPI<RawComment, typeof commentsQuery>({ - api: getAPIUrl(), - query: commentsQuery, - variables: { contentId: id }, - }); - - const comments = response.comments.nodes.map((comment) => - getCommentFromRawData(comment) - ); + const rawComments = await fetchComments({ contentId: id }); + const comments = rawComments.map((comment) => getCommentFromRawData(comment)); return buildCommentsTree(comments); }; -export type SentComment = { - clientMutationId: string; - success: boolean; - comment: { - approved: boolean; - } | null; -}; - /** * Send a comment using GraphQL API. * @@ -90,10 +98,9 @@ export type SentComment = { * @returns {Promise<SentEmail>} The mutation response. */ export const sendComment = async ( - data: SendCommentVars + data: SendCommentInput ): Promise<SentComment> => { const response = await fetchAPI<SentComment, typeof sendCommentMutation>({ - api: getAPIUrl(), query: sendCommentMutation, variables: { ...data }, }); diff --git a/src/services/graphql/contact.ts b/src/services/graphql/contact.ts index 00c6ca2..de078b9 100644 --- a/src/services/graphql/contact.ts +++ b/src/services/graphql/contact.ts @@ -1,4 +1,5 @@ -import { fetchAPI, getAPIUrl, SendMailVars } from './api'; +import { SendMailInput } from '@ts/types/graphql/mutations'; +import { fetchAPI } from './api'; import { sendMailMutation } from './contact.mutation'; export type SentEmail = { @@ -12,12 +13,11 @@ export type SentEmail = { /** * Send an email using GraphQL API. * - * @param {sendMailVars} data - The mail data. + * @param {SendMailInput} data - The mail data. * @returns {Promise<SentEmail>} The mutation response. */ -export const sendMail = async (data: SendMailVars): Promise<SentEmail> => { +export const sendMail = async (data: SendMailInput): Promise<SentEmail> => { const response = await fetchAPI<SentEmail, typeof sendMailMutation>({ - api: getAPIUrl(), query: sendMailMutation, variables: { ...data }, }); diff --git a/src/services/graphql/thematics.ts b/src/services/graphql/thematics.ts index 4dc69e7..508fc2f 100644 --- a/src/services/graphql/thematics.ts +++ b/src/services/graphql/thematics.ts @@ -1,4 +1,6 @@ import { PageLink, Slug, Thematic } from '@ts/types/app'; +import { GraphQLEdgesInput } from '@ts/types/graphql/generics'; +import { EdgesResponse } from '@ts/types/graphql/queries'; import { RawArticle, RawThematic, @@ -6,8 +8,11 @@ import { TotalItems, } from '@ts/types/raw-data'; import { getImageFromRawData } from '@utils/helpers/images'; -import { getPageLinkFromRawData } from '@utils/helpers/pages'; -import { EdgesResponse, EdgesVars, fetchAPI, getAPIUrl } from './api'; +import { + getPageLinkFromRawData, + sortPageLinksByName, +} from '@utils/helpers/pages'; +import { fetchAPI } from './api'; import { getArticleFromRawData } from './articles'; import { thematicBySlugQuery, @@ -23,7 +28,6 @@ import { */ export const getTotalThematics = async (): Promise<number> => { const response = await fetchAPI<TotalItems, typeof totalThematicsQuery>({ - api: getAPIUrl(), query: totalThematicsQuery, }); @@ -33,16 +37,16 @@ export const getTotalThematics = async (): Promise<number> => { /** * Retrieve the given number of thematics from API. * - * @param {EdgesVars} props - An object of GraphQL variables. + * @param {GraphQLEdgesInput} props - An object of GraphQL variables. * @returns {Promise<EdgesResponse<RawThematicPreview>>} The thematics data. */ export const getThematicsPreview = async ( - props: EdgesVars + props: GraphQLEdgesInput ): Promise<EdgesResponse<RawThematicPreview>> => { const response = await fetchAPI< RawThematicPreview, typeof thematicsListQuery - >({ api: getAPIUrl(), query: thematicsListQuery, variables: props }); + >({ query: thematicsListQuery, variables: props }); return response.thematics; }; @@ -88,21 +92,8 @@ export const getThematicFromRawData = (data: RawThematic): Thematic => { const uniqueTopics = topics.filter( ({ id }, index) => !topicsIds.includes(id, index + 1) ); - const sortTopicByName = (a: PageLink, b: PageLink) => { - var nameA = a.name.toUpperCase(); // ignore upper and lowercase - var nameB = b.name.toUpperCase(); // ignore upper and lowercase - if (nameA < nameB) { - return -1; - } - if (nameA > nameB) { - return 1; - } - - // names must be equal - return 0; - }; - return uniqueTopics.sort(sortTopicByName); + return uniqueTopics.sort(sortPageLinksByName); }; return { @@ -137,7 +128,6 @@ export const getThematicFromRawData = (data: RawThematic): Thematic => { */ export const getThematicBySlug = async (slug: string): Promise<Thematic> => { const response = await fetchAPI<RawThematic, typeof thematicBySlugQuery>({ - api: getAPIUrl(), query: thematicBySlugQuery, variables: { slug }, }); @@ -153,7 +143,6 @@ export const getThematicBySlug = async (slug: string): Promise<Thematic> => { export const getAllThematicsSlugs = async (): Promise<string[]> => { const totalThematics = await getTotalThematics(); const response = await fetchAPI<Slug, typeof thematicsSlugQuery>({ - api: getAPIUrl(), query: thematicsSlugQuery, variables: { first: totalThematics }, }); diff --git a/src/services/graphql/topics.ts b/src/services/graphql/topics.ts index 0b1971b..5448d89 100644 --- a/src/services/graphql/topics.ts +++ b/src/services/graphql/topics.ts @@ -1,4 +1,6 @@ import { PageLink, Slug, Topic } from '@ts/types/app'; +import { GraphQLEdgesInput } from '@ts/types/graphql/generics'; +import { EdgesResponse } from '@ts/types/graphql/queries'; import { RawArticle, RawTopic, @@ -6,8 +8,11 @@ import { TotalItems, } from '@ts/types/raw-data'; import { getImageFromRawData } from '@utils/helpers/images'; -import { getPageLinkFromRawData } from '@utils/helpers/pages'; -import { EdgesResponse, EdgesVars, fetchAPI, getAPIUrl } from './api'; +import { + getPageLinkFromRawData, + sortPageLinksByName, +} from '@utils/helpers/pages'; +import { fetchAPI } from './api'; import { getArticleFromRawData } from './articles'; import { topicBySlugQuery, @@ -23,7 +28,6 @@ import { */ export const getTotalTopics = async (): Promise<number> => { const response = await fetchAPI<TotalItems, typeof totalTopicsQuery>({ - api: getAPIUrl(), query: totalTopicsQuery, }); @@ -33,14 +37,13 @@ export const getTotalTopics = async (): Promise<number> => { /** * Retrieve the given number of topics from API. * - * @param {EdgesVars} props - An object of GraphQL variables. + * @param {GraphQLEdgesInput} props - An object of GraphQL variables. * @returns {Promise<EdgesResponse<RawTopicPreview>>} The topics data. */ export const getTopicsPreview = async ( - props: EdgesVars + props: GraphQLEdgesInput ): Promise<EdgesResponse<RawTopicPreview>> => { const response = await fetchAPI<RawTopicPreview, typeof topicsListQuery>({ - api: getAPIUrl(), query: topicsListQuery, variables: props, }); @@ -89,21 +92,8 @@ export const getTopicFromRawData = (data: RawTopic): Topic => { const uniqueThematics = thematics.filter( ({ id }, index) => !thematicsIds.includes(id, index + 1) ); - const sortThematicByName = (a: PageLink, b: PageLink) => { - var nameA = a.name.toUpperCase(); // ignore upper and lowercase - var nameB = b.name.toUpperCase(); // ignore upper and lowercase - if (nameA < nameB) { - return -1; - } - if (nameA > nameB) { - return 1; - } - - // names must be equal - return 0; - }; - return uniqueThematics.sort(sortThematicByName); + return uniqueThematics.sort(sortPageLinksByName); }; return { @@ -139,7 +129,6 @@ export const getTopicFromRawData = (data: RawTopic): Topic => { */ export const getTopicBySlug = async (slug: string): Promise<Topic> => { const response = await fetchAPI<RawTopic, typeof topicBySlugQuery>({ - api: getAPIUrl(), query: topicBySlugQuery, variables: { slug }, }); @@ -155,7 +144,6 @@ export const getTopicBySlug = async (slug: string): Promise<Topic> => { export const getAllTopicsSlugs = async (): Promise<string[]> => { const totalTopics = await getTotalTopics(); const response = await fetchAPI<Slug, typeof topicsSlugQuery>({ - api: getAPIUrl(), query: topicsSlugQuery, variables: { first: totalTopics }, }); diff --git a/src/ts/types/graphql/generics.ts b/src/ts/types/graphql/generics.ts new file mode 100644 index 0000000..dec5f10 --- /dev/null +++ b/src/ts/types/graphql/generics.ts @@ -0,0 +1,25 @@ +export type GraphQLPageInfo = { + endCursor: string; + hasNextPage: boolean; + total: number; +}; + +export type GraphQLEdges<T> = { + cursor: string; + node: T; +}; + +export type GraphQLEdgesInput = { + after?: string; + before?: string; + first?: number; + last?: number; +}; + +export type GraphQLNode<T> = { + node: T; +}; + +export type GraphQLNodes<T> = { + nodes: T[]; +}; diff --git a/src/ts/types/graphql/mutations.ts b/src/ts/types/graphql/mutations.ts new file mode 100644 index 0000000..10bdbd1 --- /dev/null +++ b/src/ts/types/graphql/mutations.ts @@ -0,0 +1,61 @@ +import { sendCommentMutation } from '@services/graphql/comments.mutation'; +import { sendMailMutation } from '@services/graphql/contact.mutation'; + +//=========================================================================== +// Existing mutations list +//=========================================================================== + +export type Mutations = typeof sendMailMutation | typeof sendCommentMutation; + +//=========================================================================== +// Mutations response types +//=========================================================================== + +export type SendCommentResponse<T> = { + createComment: T; +}; + +export type SendMailResponse<T> = { + sendEmail: T; +}; + +export type MutationsResponseMap<T> = { + [sendCommentMutation]: SendCommentResponse<T>; + [sendMailMutation]: SendMailResponse<T>; +}; + +export type Approved = { + approved: boolean; +}; + +export type SentComment = { + clientMutationId: string; + success: boolean; + comment: Approved | null; +}; + +//=========================================================================== +// Mutations input types +//=========================================================================== + +export type SendCommentInput = { + author: string; + authorEmail: string; + authorUrl: string; + clientMutationId: string; + commentOn: number; + content: string; + parent?: number; +}; + +export type SendMailInput = { + body: string; + clientMutationId: string; + replyTo: string; + subject: string; +}; + +export type MutationsInputMap = { + [sendCommentMutation]: SendCommentInput; + [sendMailMutation]: SendMailInput; +}; diff --git a/src/ts/types/graphql/queries.ts b/src/ts/types/graphql/queries.ts new file mode 100644 index 0000000..cc7b62b --- /dev/null +++ b/src/ts/types/graphql/queries.ts @@ -0,0 +1,147 @@ +import { + articleBySlugQuery, + articlesCardQuery, + articlesEndCursorQuery, + articlesQuery, + articlesSlugQuery, + totalArticlesQuery, +} from '@services/graphql/articles.query'; +import { commentsQuery } from '@services/graphql/comments.query'; +import { + thematicBySlugQuery, + thematicsListQuery, + thematicsSlugQuery, + totalThematicsQuery, +} from '@services/graphql/thematics.query'; +import { + topicBySlugQuery, + topicsListQuery, + topicsSlugQuery, + totalTopicsQuery, +} from '@services/graphql/topics.query'; +import { Slug } from '../app'; +import { RawComment } from '../raw-data'; +import { + GraphQLEdges, + GraphQLEdgesInput, + GraphQLNodes, + GraphQLPageInfo, +} from './generics'; + +//=========================================================================== +// Existing queries list +//=========================================================================== + +export type Queries = + | typeof articlesQuery + | typeof articleBySlugQuery + | typeof articlesCardQuery + | typeof articlesEndCursorQuery + | typeof articlesSlugQuery + | typeof commentsQuery + | typeof thematicBySlugQuery + | typeof thematicsListQuery + | typeof thematicsSlugQuery + | typeof topicBySlugQuery + | typeof topicsListQuery + | typeof topicsSlugQuery + | typeof totalArticlesQuery + | typeof totalThematicsQuery + | typeof totalTopicsQuery; + +//=========================================================================== +// Queries response types +//=========================================================================== + +export type ArticleResponse<T> = { + post: T; +}; + +export type ArticlesResponse<T> = { + posts: T; +}; + +export type CommentsResponse<T> = { + comments: T; +}; + +export type ThematicResponse<T> = { + thematic: T; +}; + +export type ThematicsResponse<T> = { + thematics: T; +}; + +export type TopicResponse<T> = { + topic: T; +}; + +export type TopicsResponse<T> = { + topics: T; +}; + +export type EdgesResponse<T> = { + edges: GraphQLEdges<T>[]; + pageInfo: GraphQLPageInfo; +}; + +export type EndCursorResponse = { + pageInfo: Pick<GraphQLPageInfo, 'endCursor'>; +}; + +export type QueriesResponseMap<T> = { + [articleBySlugQuery]: ArticleResponse<T>; + [articlesCardQuery]: ArticlesResponse<GraphQLNodes<T>>; + [articlesEndCursorQuery]: ArticlesResponse<EndCursorResponse>; + [articlesQuery]: ArticlesResponse<EdgesResponse<T>>; + [articlesSlugQuery]: ArticlesResponse<EdgesResponse<T>>; + [commentsQuery]: CommentsResponse<GraphQLNodes<T>>; + [thematicBySlugQuery]: ThematicResponse<T>; + [thematicsListQuery]: ThematicsResponse<EdgesResponse<T>>; + [thematicsSlugQuery]: ThematicsResponse<EdgesResponse<T>>; + [topicBySlugQuery]: TopicResponse<T>; + [topicsListQuery]: TopicsResponse<EdgesResponse<T>>; + [topicsSlugQuery]: TopicsResponse<EdgesResponse<T>>; + [totalArticlesQuery]: ArticlesResponse<T>; + [totalThematicsQuery]: ThematicsResponse<T>; + [totalTopicsQuery]: TopicsResponse<T>; +}; + +//=========================================================================== +// Queries input types +//=========================================================================== + +export type QueryEdges = Pick<GraphQLEdgesInput, 'after' | 'first'>; + +export type ContentId = { + contentId: number; +}; + +export type Search = { + search?: string; +}; + +export type QueriesInputMap = { + [articleBySlugQuery]: Slug; + [articlesCardQuery]: QueryEdges & Search; + [articlesEndCursorQuery]: QueryEdges & Search; + [articlesQuery]: QueryEdges & Search; + [articlesSlugQuery]: QueryEdges & Search; + [commentsQuery]: ContentId; + [thematicBySlugQuery]: Slug; + [thematicsListQuery]: QueryEdges & Search; + [thematicsSlugQuery]: QueryEdges & Search; + [topicBySlugQuery]: Slug; + [topicsListQuery]: QueryEdges & Search; + [topicsSlugQuery]: QueryEdges & Search; + [totalArticlesQuery]: Search; + [totalThematicsQuery]: null; + [totalTopicsQuery]: null; +}; + +export type CommentPage = { + comments: RawComment[]; + hasNextPage: boolean; + endCursor: string; +}; diff --git a/src/ts/types/raw-data.ts b/src/ts/types/raw-data.ts index dc3db90..ae7f7c6 100644 --- a/src/ts/types/raw-data.ts +++ b/src/ts/types/raw-data.ts @@ -2,8 +2,8 @@ * Types for raw data coming from GraphQL API. */ -import { NodeResponse, PageInfo } from '@services/graphql/api'; import { ContentKind } from './app'; +import { GraphQLNode, GraphQLPageInfo } from './graphql/generics'; export type ACFPosts = { postsInThematic?: RawThematicPreview[]; @@ -37,7 +37,7 @@ export type RawAuthor<T extends ContentKind> = { export type RawComment = { approved: boolean; - author: NodeResponse<RawAuthor<'comment'>>; + author: GraphQLNode<RawAuthor<'comment'>>; content: string; databaseId: number; date: string; @@ -65,11 +65,11 @@ export type RawArticlePreview = Pick< >; export type RawPage = { - author?: NodeResponse<RawAuthor<'page'>>; + author?: GraphQLNode<RawAuthor<'page'>>; contentParts: ContentParts; databaseId: number; date: string; - featuredImage: NodeResponse<RawCover> | null; + featuredImage: GraphQLNode<RawCover> | null; info: Info; modified: string; seo?: RawSEO; @@ -101,5 +101,5 @@ export type RawTopicPreview = Pick< >; export type TotalItems = { - pageInfo: Pick<PageInfo, 'total'>; + pageInfo: Pick<GraphQLPageInfo, 'total'>; }; diff --git a/src/utils/helpers/pages.ts b/src/utils/helpers/pages.ts index 773d454..eb4453b 100644 --- a/src/utils/helpers/pages.ts +++ b/src/utils/helpers/pages.ts @@ -1,8 +1,8 @@ import { type Post } from '@components/organisms/layout/posts-list'; import { type LinksListItems } from '@components/organisms/widgets/links-list-widget'; -import { type EdgesResponse } from '@services/graphql/api'; import { getArticleFromRawData } from '@services/graphql/articles'; import { type Article, type PageLink } from '@ts/types/app'; +import { EdgesResponse } from '@ts/types/graphql/queries'; import { type RawArticle, type RawThematicPreview, @@ -36,6 +36,22 @@ export const getPageLinkFromRawData = ( }; /** + * Method to sort PageLink objects by name. + * + * @param {PageLink} a - A PageLink object. + * @param {PageLink} b - Another PageLink object. + * @returns {1 | -1 | 0} + */ +export const sortPageLinksByName = (a: PageLink, b: PageLink) => { + const nameA = a.name.toUpperCase(); + const nameB = b.name.toUpperCase(); + + if (nameA < nameB) return -1; + if (nameA > nameB) return 1; + return 0; +}; + +/** * Convert page link data to an array of links items. * * @param {PageLink[]} links - An array of page links. diff --git a/src/utils/hooks/use-article.tsx b/src/utils/hooks/use-article.tsx index 6281a54..e658407 100644 --- a/src/utils/hooks/use-article.tsx +++ b/src/utils/hooks/use-article.tsx @@ -1,4 +1,4 @@ -import { fetchAPI, getAPIUrl } from '@services/graphql/api'; +import { fetchAPI } from '@services/graphql/api'; import { getArticleFromRawData } from '@services/graphql/articles'; import { articleBySlugQuery } from '@services/graphql/articles.query'; import { Article } from '@ts/types/app'; @@ -22,9 +22,7 @@ const useArticle = ({ fallback, }: UseArticleConfig): Article | undefined => { const { data } = useSWR( - slug - ? { api: getAPIUrl(), query: articleBySlugQuery, variables: { slug } } - : null, + slug ? { query: articleBySlugQuery, variables: { slug } } : null, fetchAPI<RawArticle, typeof articleBySlugQuery> ); diff --git a/src/utils/hooks/use-comments.tsx b/src/utils/hooks/use-comments.tsx index 9076888..cb0848b 100644 --- a/src/utils/hooks/use-comments.tsx +++ b/src/utils/hooks/use-comments.tsx @@ -1,4 +1,4 @@ -import { fetchAPI, getAPIUrl } from '@services/graphql/api'; +import { fetchAPI } from '@services/graphql/api'; import { buildCommentsTree, getCommentFromRawData, @@ -24,9 +24,7 @@ const useComments = ({ fallback, }: UseCommentsConfig): Comment[] | undefined => { const { data } = useSWR( - contentId - ? { api: getAPIUrl(), query: commentsQuery, variables: { contentId } } - : null, + contentId ? { query: commentsQuery, variables: { contentId } } : null, fetchAPI<RawComment, typeof commentsQuery> ); diff --git a/src/utils/hooks/use-pagination.tsx b/src/utils/hooks/use-pagination.tsx index a80a539..f17b6ff 100644 --- a/src/utils/hooks/use-pagination.tsx +++ b/src/utils/hooks/use-pagination.tsx @@ -1,4 +1,5 @@ -import { type EdgesResponse, type EdgesVars } from '@services/graphql/api'; +import { GraphQLEdgesInput } from '@ts/types/graphql/generics'; +import { EdgesResponse, Search } from '@ts/types/graphql/queries'; import useSWRInfinite, { SWRInfiniteKeyLoader } from 'swr/infinite'; export type UsePaginationProps<T> = { @@ -9,7 +10,7 @@ export type UsePaginationProps<T> = { /** * A function to fetch more data. */ - fetcher: (props: EdgesVars) => Promise<EdgesResponse<T>>; + fetcher: (props: GraphQLEdgesInput & Search) => Promise<EdgesResponse<T>>; /** * The number of results per page. */ @@ -74,7 +75,7 @@ const usePagination = <T extends object>({ const getKey: SWRInfiniteKeyLoader = ( pageIndex: number, previousData: EdgesResponse<T> - ): EdgesVars | null => { + ): (GraphQLEdgesInput & Search) | null => { // Reached the end. if (previousData && !previousData.edges.length) return null; |
