diff --git a/apps/react-world/src/apis/article/ArticleService.ts b/apps/react-world/src/apis/article/ArticleService.ts index d7e9df71..13ce5e12 100644 --- a/apps/react-world/src/apis/article/ArticleService.ts +++ b/apps/react-world/src/apis/article/ArticleService.ts @@ -1,8 +1,10 @@ import { isAxiosError } from 'axios'; -import { api } from '../apiInstances'; +import { api } from '@apis/apiInstances'; import type { ArticleDetailResponse, ArticleDetailErrorResponse, + ArticleCommentsResponse, + ArticleCommentsErrorResponse, } from './ArticleService.types'; class ArticleService { @@ -21,6 +23,22 @@ class ArticleService { throw error; } } + + static async fetchArticleComments( + slug: string, + ): Promise { + try { + const response = await api.get(`/articles/${slug}/comments`); + return response.data; + } catch (error) { + if (isAxiosError(error) && error.response) { + console.error('Axios error occurred:', error.response.data); + throw error.response.data as ArticleCommentsErrorResponse; + } + console.error('An unexpected error occurred:', error); + throw error; + } + } } export default ArticleService; diff --git a/apps/react-world/src/apis/article/ArticleService.types.ts b/apps/react-world/src/apis/article/ArticleService.types.ts index 4f932a7f..049790bb 100644 --- a/apps/react-world/src/apis/article/ArticleService.types.ts +++ b/apps/react-world/src/apis/article/ArticleService.types.ts @@ -1,5 +1,6 @@ import type { ArticleAuthor } from './Article.types'; +// Article Detail export interface ArticleDetailData { slug: string; title: string; @@ -22,3 +23,22 @@ export interface ArticleDetailErrorResponse { body: string[]; }; } + +// Article Comment +export interface ArticleCommentData { + id: number; + createdAt: string; + updatedAt: string; + body: string; + author: ArticleAuthor; +} + +export interface ArticleCommentsResponse { + comments: ArticleCommentData[]; +} + +export interface ArticleCommentsErrorResponse { + errors: { + body: string[]; + }; +} diff --git a/apps/react-world/src/components/article/ArticleComments.tsx b/apps/react-world/src/components/article/ArticleComments.tsx index 61a60b8d..ffd4ba79 100644 --- a/apps/react-world/src/components/article/ArticleComments.tsx +++ b/apps/react-world/src/components/article/ArticleComments.tsx @@ -1,4 +1,12 @@ -const ArticleComments = () => { +import type { ArticleCommentData } from '@apis/article/ArticleService.types'; + +interface ArticleCommentsProps { + comments: ArticleCommentData[]; +} + +const ArticleComments = (props: ArticleCommentsProps) => { + const { comments } = props; + return (
@@ -10,6 +18,7 @@ const ArticleComments = () => { >
+ {/* This will likely be the logged-in user's image */} {
-
-
-

- With supporting text below as a natural lead-in to additional - content. -

-
-
- - - -   - - Jacob Schmidt - - Dec 29th -
-
- -
-
-

- With supporting text below as a natural lead-in to additional - content. -

-
-
- - - -   - - Jacob Schmidt - - Dec 29th - - - + {comments.map(comment => ( +
+
+

{comment.body}

+
+
+ + + +   + + {comment.author.username} + + + {new Date(comment.createdAt).toDateString()} + + {/* If the logged-in user has the right to delete this comment, display the delete button */} + {/* For now, I'm leaving it static but you'll likely need a condition */} + + + +
-
+ ))}
); diff --git a/apps/react-world/src/components/article/ArticlePageContainer.tsx b/apps/react-world/src/components/article/ArticlePageContainer.tsx index 6b04cae6..2d86afc2 100644 --- a/apps/react-world/src/components/article/ArticlePageContainer.tsx +++ b/apps/react-world/src/components/article/ArticlePageContainer.tsx @@ -4,6 +4,7 @@ import ArticleComments from './ArticleComments'; import ArticleContents from './ArticleContents'; import ArticleHeader from './ArticleHeader'; import useArticleDetailQuery from '@quries/useArticleDetailQuery'; +import useArticleCommentsQuery from '@quries/useArticleCommentsQuery'; interface ArticlePageContainerProps { articleSlug: string; @@ -12,6 +13,7 @@ interface ArticlePageContainerProps { const ArticlePageContainer = (props: ArticlePageContainerProps) => { const { articleSlug } = props; const { articleDetail } = useArticleDetailQuery(articleSlug); + const { articleComments } = useArticleCommentsQuery(articleSlug); if (!articleDetail) { return null; @@ -38,7 +40,7 @@ const ArticlePageContainer = (props: ArticlePageContainerProps) => { favorited={articleDetail.article.favorited} favoritesCount={articleDetail.article.favoritesCount} /> - + ); diff --git a/apps/react-world/src/quries/useArticleCommentsQuery.ts b/apps/react-world/src/quries/useArticleCommentsQuery.ts new file mode 100644 index 00000000..0aae650f --- /dev/null +++ b/apps/react-world/src/quries/useArticleCommentsQuery.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@tanstack/react-query'; +import type { ArticleCommentsResponse } from '@apis/article/ArticleService.types'; +import ArticleService from '@apis/article/ArticleService'; + +export const ARTICLE_COMMENTS_CACHE_KEY = '@article/comments'; + +const useArticleCommentsQuery = (slug: string) => { + const queryResult = useQuery( + [ARTICLE_COMMENTS_CACHE_KEY, slug], // 슬러그를 조합해 QueryKey 지정 + () => ArticleService.fetchArticleComments(slug), + ); + + return { + articleComments: queryResult.data, + isArticleCommentsLoading: queryResult.isLoading, + }; +}; + +export default useArticleCommentsQuery; diff --git a/apps/react-world/vite.config.ts b/apps/react-world/vite.config.ts index 7b471804..50750776 100644 --- a/apps/react-world/vite.config.ts +++ b/apps/react-world/vite.config.ts @@ -1,7 +1,5 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; -import { resolve } from 'path'; -import path from 'path'; // https://vitejs.dev/config/ export default defineConfig({