Skip to content

Commit 57db0d6

Browse files
authored
Merge pull request #138 from pagers-org/carpe/article-comments
feat(article): 아티클 상세화면에 진입 시 실제 아티클 댓글 데이터를 받아 렌더링하도록 합니다.
2 parents 811d6b8 + 862dcee commit 57db0d6

File tree

6 files changed

+103
-50
lines changed

6 files changed

+103
-50
lines changed

apps/react-world/src/apis/article/ArticleService.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { isAxiosError } from 'axios';
2-
import { api } from '../apiInstances';
2+
import { api } from '@apis/apiInstances';
33
import type {
44
ArticleDetailResponse,
55
ArticleDetailErrorResponse,
6+
ArticleCommentsResponse,
7+
ArticleCommentsErrorResponse,
68
} from './ArticleService.types';
79

810
class ArticleService {
@@ -21,6 +23,22 @@ class ArticleService {
2123
throw error;
2224
}
2325
}
26+
27+
static async fetchArticleComments(
28+
slug: string,
29+
): Promise<ArticleCommentsResponse> {
30+
try {
31+
const response = await api.get(`/articles/${slug}/comments`);
32+
return response.data;
33+
} catch (error) {
34+
if (isAxiosError(error) && error.response) {
35+
console.error('Axios error occurred:', error.response.data);
36+
throw error.response.data as ArticleCommentsErrorResponse;
37+
}
38+
console.error('An unexpected error occurred:', error);
39+
throw error;
40+
}
41+
}
2442
}
2543

2644
export default ArticleService;

apps/react-world/src/apis/article/ArticleService.types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ArticleAuthor } from './Article.types';
22

3+
// Article Detail
34
export interface ArticleDetailData {
45
slug: string;
56
title: string;
@@ -22,3 +23,22 @@ export interface ArticleDetailErrorResponse {
2223
body: string[];
2324
};
2425
}
26+
27+
// Article Comment
28+
export interface ArticleCommentData {
29+
id: number;
30+
createdAt: string;
31+
updatedAt: string;
32+
body: string;
33+
author: ArticleAuthor;
34+
}
35+
36+
export interface ArticleCommentsResponse {
37+
comments: ArticleCommentData[];
38+
}
39+
40+
export interface ArticleCommentsErrorResponse {
41+
errors: {
42+
body: string[];
43+
};
44+
}

apps/react-world/src/components/article/ArticleComments.tsx

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
const ArticleComments = () => {
1+
import type { ArticleCommentData } from '@apis/article/ArticleService.types';
2+
3+
interface ArticleCommentsProps {
4+
comments: ArticleCommentData[];
5+
}
6+
7+
const ArticleComments = (props: ArticleCommentsProps) => {
8+
const { comments } = props;
9+
210
return (
311
<div className="row">
412
<div className="col-xs-12 col-md-8 offset-md-2">
@@ -10,6 +18,7 @@ const ArticleComments = () => {
1018
></textarea>
1119
</div>
1220
<div className="card-footer">
21+
{/* This will likely be the logged-in user's image */}
1322
<img
1423
src="http://i.imgur.com/Qr71crq.jpg"
1524
className="comment-author-img"
@@ -18,52 +27,39 @@ const ArticleComments = () => {
1827
</div>
1928
</form>
2029

21-
<div className="card">
22-
<div className="card-block">
23-
<p className="card-text">
24-
With supporting text below as a natural lead-in to additional
25-
content.
26-
</p>
27-
</div>
28-
<div className="card-footer">
29-
<a href="/profile/author" className="comment-author">
30-
<img
31-
src="http://i.imgur.com/Qr71crq.jpg"
32-
className="comment-author-img"
33-
/>
34-
</a>
35-
&nbsp;
36-
<a href="/profile/jacob-schmidt" className="comment-author">
37-
Jacob Schmidt
38-
</a>
39-
<span className="date-posted">Dec 29th</span>
40-
</div>
41-
</div>
42-
43-
<div className="card">
44-
<div className="card-block">
45-
<p className="card-text">
46-
With supporting text below as a natural lead-in to additional
47-
content.
48-
</p>
49-
</div>
50-
<div className="card-footer">
51-
<a href="/profile/author" className="comment-author">
52-
<img
53-
src="http://i.imgur.com/Qr71crq.jpg"
54-
className="comment-author-img"
55-
/>
56-
</a>
57-
&nbsp;
58-
<a href="/profile/jacob-schmidt" className="comment-author">
59-
Jacob Schmidt
60-
</a>
61-
<span className="date-posted">Dec 29th</span>
62-
<span className="mod-options">
63-
<i className="ion-trash-a"></i>
64-
</span>
30+
{comments.map(comment => (
31+
<div key={comment.id} className="card">
32+
<div className="card-block">
33+
<p className="card-text">{comment.body}</p>
34+
</div>
35+
<div className="card-footer">
36+
<a
37+
href={`/profile/${comment.author.username}`}
38+
className="comment-author"
39+
>
40+
<img
41+
src={comment.author.image}
42+
className="comment-author-img"
43+
/>
44+
</a>
45+
&nbsp;
46+
<a
47+
href={`/profile/${comment.author.username}`}
48+
className="comment-author"
49+
>
50+
{comment.author.username}
51+
</a>
52+
<span className="date-posted">
53+
{new Date(comment.createdAt).toDateString()}
54+
</span>
55+
{/* If the logged-in user has the right to delete this comment, display the delete button */}
56+
{/* For now, I'm leaving it static but you'll likely need a condition */}
57+
<span className="mod-options">
58+
<i className="ion-trash-a"></i>
59+
</span>
60+
</div>
6561
</div>
66-
</div>
62+
))}
6763
</div>
6864
</div>
6965
);

apps/react-world/src/components/article/ArticlePageContainer.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ArticleComments from './ArticleComments';
44
import ArticleContents from './ArticleContents';
55
import ArticleHeader from './ArticleHeader';
66
import useArticleDetailQuery from '@quries/useArticleDetailQuery';
7+
import useArticleCommentsQuery from '@quries/useArticleCommentsQuery';
78

89
interface ArticlePageContainerProps {
910
articleSlug: string;
@@ -12,6 +13,7 @@ interface ArticlePageContainerProps {
1213
const ArticlePageContainer = (props: ArticlePageContainerProps) => {
1314
const { articleSlug } = props;
1415
const { articleDetail } = useArticleDetailQuery(articleSlug);
16+
const { articleComments } = useArticleCommentsQuery(articleSlug);
1517

1618
if (!articleDetail) {
1719
return null;
@@ -38,7 +40,7 @@ const ArticlePageContainer = (props: ArticlePageContainerProps) => {
3840
favorited={articleDetail.article.favorited}
3941
favoritesCount={articleDetail.article.favoritesCount}
4042
/>
41-
<ArticleComments />
43+
<ArticleComments comments={articleComments?.comments || []} />
4244
</Container>
4345
</div>
4446
);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useQuery } from '@tanstack/react-query';
2+
import type { ArticleCommentsResponse } from '@apis/article/ArticleService.types';
3+
import ArticleService from '@apis/article/ArticleService';
4+
5+
export const ARTICLE_COMMENTS_CACHE_KEY = '@article/comments';
6+
7+
const useArticleCommentsQuery = (slug: string) => {
8+
const queryResult = useQuery<ArticleCommentsResponse, Error>(
9+
[ARTICLE_COMMENTS_CACHE_KEY, slug], // 슬러그를 조합해 QueryKey 지정
10+
() => ArticleService.fetchArticleComments(slug),
11+
);
12+
13+
return {
14+
articleComments: queryResult.data,
15+
isArticleCommentsLoading: queryResult.isLoading,
16+
};
17+
};
18+
19+
export default useArticleCommentsQuery;

apps/react-world/vite.config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { defineConfig } from 'vite';
22
import react from '@vitejs/plugin-react';
3-
import { resolve } from 'path';
4-
import path from 'path';
53

64
// https://vitejs.dev/config/
75
export default defineConfig({

0 commit comments

Comments
 (0)