diff --git a/.eslintignore b/.eslintignore
index c71e206..5724ba5 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,3 +2,6 @@
/craco.config.js
/__tests__
/babel.config.js
+build/
+src/
+*.js
\ No newline at end of file
diff --git a/src/components/article/ButtonsWIthAccess.tsx b/src/components/article/ButtonsWIthAccess.tsx
index 18c4536..5a88ca6 100644
--- a/src/components/article/ButtonsWIthAccess.tsx
+++ b/src/components/article/ButtonsWIthAccess.tsx
@@ -34,6 +34,14 @@ const ButtonsWIthAccess = ({ articleInfo }: IButtonsWIthAccessProps) => {
Edit Article
+
+
diff --git a/src/components/article/Revision.tsx b/src/components/article/Revision.tsx
new file mode 100644
index 0000000..13fda60
--- /dev/null
+++ b/src/components/article/Revision.tsx
@@ -0,0 +1,84 @@
+import { useRevertArticleUpdateMutation } from '@/queries/articles.query';
+import { useGetUserQuery } from '@/queries/user.query';
+import useInputs from '@/lib/hooks/useInputs';
+import queryClient from '@/queries/queryClient';
+import { QUERY_ARTICLE_REVISIONS_KEY, QUERY_COMMENTS_KEY } from '@/constants/query.constant';
+import convertToDate from '@/lib/utils/convertToDate';
+import { IArticleRevision } from '@/interfaces/main';
+import { useLocation, useNavigate } from 'react-router-dom';
+
+interface IRevisionProps {
+ revisionsInfo: {
+ articlesRevisions: IArticleRevision[];
+ articlesRevisionsCount: number;
+ };
+ slug: string;
+}
+
+const Revision = ({ revisionsInfo, slug }: IRevisionProps) => {
+ const { articlesRevisions } = revisionsInfo;
+ const revertArticleUpdateMutation = useRevertArticleUpdateMutation();
+ const navigate = useNavigate();
+ const onRevert = (slug: string, revision: number, newSlug: string) => {
+ revertArticleUpdateMutation.mutate(
+ { slug, revision },
+ {
+ onSuccess: (_) => {
+ queryClient.invalidateQueries({ queryKey: [QUERY_ARTICLE_REVISIONS_KEY] });
+ alert('Article reverted successfully!');
+ navigate(`/article/${newSlug}`, { state: newSlug });
+ },
+ },
+ );
+ };
+ return (
+
+
Updates History
+ {articlesRevisions.length === 0 ? (
+
Loading...
+ ) : (
+ articlesRevisions?.map((revision, index) => (
+
+
+
Update {revision.id}
+
Date: {convertToDate(revision.createdAt)}
+
+
+
+
+
+ | Title |
+ {revision.articleData.title} |
+
+
+ | Slug |
+ {revision.articleData.slug} |
+
+
+ | Body |
+ {revision.articleData.body} |
+
+
+ | Description |
+ {revision.articleData.description} |
+
+
+ | Created At |
+ {convertToDate(revision.articleData.createdAt)} |
+
+
+
+
+
+
+
+
+ ))
+ )}
+
+ );
+};
+
+export default Revision;
diff --git a/src/constants/query.constant.ts b/src/constants/query.constant.ts
index db8ddc3..ac053a2 100644
--- a/src/constants/query.constant.ts
+++ b/src/constants/query.constant.ts
@@ -4,3 +4,4 @@ export const QUERY_ARTICLE_KEY = 'article';
export const QUERY_COMMENTS_KEY = 'comments';
export const QUERY_PROFILE_KEY = 'profile';
export const QUERY_TAG_KEY = 'tags';
+export const QUERY_ARTICLE_REVISIONS_KEY = 'revisions';
diff --git a/src/interfaces/main.d.ts b/src/interfaces/main.d.ts
index 7ffc461..595b144 100644
--- a/src/interfaces/main.d.ts
+++ b/src/interfaces/main.d.ts
@@ -16,6 +16,22 @@ export interface IArticle {
};
}
+export interface IArticleRevision {
+ id: number;
+ articleId: number;
+ createdAt: string;
+ updatedAt: string;
+ articleData: {
+ id: number;
+ slug: string;
+ title: string;
+ description: string;
+ body: string;
+ createdAt: string;
+ updatedAt: string;
+ };
+}
+
export interface IComment {
id: number;
createdAt: string;
diff --git a/src/lib/routerMeta.ts b/src/lib/routerMeta.ts
index 39856e0..8eca098 100644
--- a/src/lib/routerMeta.ts
+++ b/src/lib/routerMeta.ts
@@ -42,6 +42,12 @@ const routerMeta: RouterMetaType = {
path: '/article/:slug',
isShow: false,
},
+ ArticleRevisionPage: {
+ name: 'Article Revisions',
+ path: '/article/:slug/revisions',
+ isShow: false,
+ isAuth: true,
+ },
ProfilePage: {
name: 'Profile',
path: '/profile/:username/*',
@@ -59,6 +65,7 @@ const routerMeta: RouterMetaType = {
isShow: true,
isAuth: false,
},
+
NotFoundPage: {
path: '/*',
isShow: false,
diff --git a/src/pages/ArticleRevisionPage.tsx b/src/pages/ArticleRevisionPage.tsx
new file mode 100644
index 0000000..3dd1aa7
--- /dev/null
+++ b/src/pages/ArticleRevisionPage.tsx
@@ -0,0 +1,84 @@
+import { useGetArticleRevisionsQueries, useGetArticleQueries } from '@/queries/articles.query';
+import { Link, useLocation } from 'react-router-dom';
+import ReactMarkdown from 'react-markdown';
+import remarkGfm from 'remark-gfm';
+import ButtonSelector from '@/components/article/ButtonSelector';
+import { useContext } from 'react';
+import { UserContext } from '@/contexts/UserContextProvider';
+import Revision from '@/components/article/Revision';
+import routerMeta from '@/lib/routerMeta';
+import convertToDate from '@/lib/utils/convertToDate';
+
+const ArticleRevisionPage = () => {
+ const { state } = useLocation();
+ const [revisionInfo] = useGetArticleRevisionsQueries(state.slug);
+
+ const { isLogin } = useContext(UserContext);
+ console.log(state);
+ console.log(revisionInfo);
+
+ return (
+
+
+
+
{state.title}
+
+
+
+

+
+
+
+
+ {state.author.username}
+
+ {convertToDate(state.updatedAt)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+ {state.author.username}
+
+ {convertToDate(state.updatedAt)}
+
+ {/* {isLogin ?
: <>>} */}
+
+
+
+
+
+ {isLogin ? (
+
+ ) : (
+
+ Sign in
+ or
+ Sign up
+ to add comments on this article.
+
+ )}
+
+
+
+
+ );
+};
+
+export default ArticleRevisionPage;
diff --git a/src/queries/articles.query.ts b/src/queries/articles.query.ts
index 55eeba4..5786e96 100644
--- a/src/queries/articles.query.ts
+++ b/src/queries/articles.query.ts
@@ -1,7 +1,14 @@
-import { QUERY_ARTICLES_KEY, QUERY_ARTICLE_KEY, QUERY_COMMENTS_KEY, QUERY_TAG_KEY } from '@/constants/query.constant';
+import {
+ QUERY_ARTICLES_KEY,
+ QUERY_ARTICLE_KEY,
+ QUERY_ARTICLE_REVISIONS_KEY,
+ QUERY_COMMENTS_KEY,
+ QUERY_TAG_KEY,
+} from '@/constants/query.constant';
import {
getArticle,
getArticles,
+ getArticleRevisions,
createArticle,
updateArticle,
deleteArticle,
@@ -9,6 +16,7 @@ import {
createComment,
deleteComment,
favoriteArticle,
+ restoreArticleRevision,
unfavoriteArticle,
} from '@/repositories/articles/articlesRepository';
import { getTags } from '@/repositories/tags/tagsRepository';
@@ -48,6 +56,18 @@ export const useGetArticleQueries = (slug: string) => {
});
};
+export const useGetArticleRevisionsQueries = (slug: string) => {
+ return useQueries({
+ queries: [
+ {
+ queryKey: [QUERY_ARTICLE_KEY, slug],
+ queryFn: () => getArticleRevisions({ slug }).then((res) => res.data.revisions),
+ staleTime: 20000,
+ },
+ ],
+ });
+};
+
export const useCreateArticleMutation = () => useMutation(createArticle);
export const useUpdateArticleMutation = () => useMutation(updateArticle);
@@ -61,3 +81,5 @@ export const useDeleteCommentMutation = () => useMutation(deleteComment);
export const useFavoriteArticleMutation = () => useMutation(favoriteArticle);
export const useUnfavoriteArticleMutation = () => useMutation(unfavoriteArticle);
+
+export const useRevertArticleUpdateMutation = () => useMutation(restoreArticleRevision);
diff --git a/src/repositories/apiClient.ts b/src/repositories/apiClient.ts
index f42976f..2df8ddc 100644
--- a/src/repositories/apiClient.ts
+++ b/src/repositories/apiClient.ts
@@ -2,7 +2,7 @@ import { ACCESS_TOKEN_KEY } from '@/constants/token.contant';
import token from '@/lib/token';
import axios, { AxiosResponse, InternalAxiosRequestConfig, AxiosError } from 'axios';
-const host = 'https://api.realworld.io/api';
+const host = 'http://127.0.0.1:8000/api';
const apiClient = axios.create({
baseURL: host,
@@ -19,7 +19,7 @@ apiClient.interceptors.request.use((request) => {
const { method, url } = request;
if (jwtToken) {
- request.headers['Authorization'] = `Token ${jwtToken}`;
+ request.headers['Authorization'] = `Bearer ${jwtToken}`;
}
logOnDev(`🚀 [${method?.toUpperCase()}] ${url} | Request`, request);
diff --git a/src/repositories/articles/articlesRepository.param.ts b/src/repositories/articles/articlesRepository.param.ts
index d8309ab..5eed55b 100644
--- a/src/repositories/articles/articlesRepository.param.ts
+++ b/src/repositories/articles/articlesRepository.param.ts
@@ -46,3 +46,12 @@ export interface deleteCommentParam {
export interface favoriteParam {
slug: string;
}
+
+export interface getArticleRevisionParam {
+ slug: string;
+ revision: number;
+}
+
+export interface getArticleRevisionsParam {
+ slug: string;
+}
diff --git a/src/repositories/articles/articlesRepository.ts b/src/repositories/articles/articlesRepository.ts
index acad4a3..8dccd51 100644
--- a/src/repositories/articles/articlesRepository.ts
+++ b/src/repositories/articles/articlesRepository.ts
@@ -9,6 +9,8 @@ import {
createCommentParam,
deleteCommentParam,
favoriteParam,
+ getArticleRevisionsParam,
+ getArticleRevisionParam,
} from './articlesRepository.param';
import { UNIT_PER_PAGE } from '@/constants/units.constants';
@@ -104,3 +106,24 @@ export const unfavoriteArticle = async ({ slug }: favoriteParam) => {
url: `/articles/${slug}/favorite`,
});
};
+
+export const getArticleRevisions = async ({ slug }: getArticleRevisionsParam) => {
+ return await apiClient({
+ method: 'get',
+ url: `/articles/${slug}/revisions`,
+ });
+};
+
+export const getArticleRevision = async ({ slug, revision }: getArticleRevisionParam) => {
+ return await apiClient({
+ method: 'get',
+ url: `/articles/${slug}/revisions/${revision}`,
+ });
+};
+
+export const restoreArticleRevision = async ({ slug, revision }: getArticleRevisionParam) => {
+ return await apiClient({
+ method: 'post',
+ url: `/articles/${slug}/revisions/${revision}/revert`,
+ });
+};