aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/components/templates/page/page-layout.tsx4
-rw-r--r--src/pages/blog/index.tsx2
-rw-r--r--src/pages/blog/page/[number].tsx2
-rw-r--r--src/services/graphql/api.ts312
-rw-r--r--src/services/graphql/articles.query.ts2
-rw-r--r--src/services/graphql/articles.ts42
-rw-r--r--src/services/graphql/comments.ts47
-rw-r--r--src/services/graphql/contact.ts8
-rw-r--r--src/services/graphql/thematics.ts33
-rw-r--r--src/services/graphql/topics.ts32
-rw-r--r--src/ts/types/graphql/generics.ts25
-rw-r--r--src/ts/types/graphql/mutations.ts61
-rw-r--r--src/ts/types/graphql/queries.ts147
-rw-r--r--src/ts/types/raw-data.ts10
-rw-r--r--src/utils/helpers/pages.ts18
-rw-r--r--src/utils/hooks/use-article.tsx6
-rw-r--r--src/utils/hooks/use-comments.tsx6
-rw-r--r--src/utils/hooks/use-pagination.tsx7
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;