Skip to content

Commit e594474

Browse files
committed
feature 대댓글
1 parent 26f5913 commit e594474

File tree

15 files changed

+339
-143
lines changed

15 files changed

+339
-143
lines changed

services/ahhachul.com/src/app/stackflow/layers/sharedLayers/index.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import CommentInner from 'pages/_shared-pages/comment/ui/Page/Page';
23

34
const AllServices = React.lazy(
45
() =>
@@ -25,13 +26,6 @@ const CarSharing = React.lazy(
2526
),
2627
);
2728

28-
const CommentInner = React.lazy(
29-
() =>
30-
import(
31-
/* webpackPrefetch: true */ 'pages/_shared-pages/comment/ui/Page/Page'
32-
),
33-
);
34-
3529
export const sharedLayers = {
3630
AllServices,
3731
SubwayNotices,

services/ahhachul.com/src/features/articles/ui/BaseArticleTemplate.css.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { Interpolation, Theme } from '@emotion/react';
22
import cssUtils from 'shared/utils.css';
33

4+
export const article = {
5+
paddingBottom: '224px',
6+
};
7+
48
export const thumbnailContainer = [
59
cssUtils.posRel,
610
cssUtils.maxWidth,
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Interpolation, Theme, css } from '@emotion/react';
2+
import cssUtils from 'shared/utils.css';
3+
4;
4+
export const commentListWrap = [
5+
cssUtils.fullWidth,
6+
cssUtils.flexColumn,
7+
{
8+
borderTop: '1px solid rgb(196, 212, 252, 0.37)',
9+
10+
'& > li:not(:last-of-type)': {
11+
borderBottom: '1px solid hsla(0, 0%, 100%, .06)',
12+
},
13+
},
14+
] as Interpolation<Theme>;
15+
16+
export const emptyComment = [
17+
cssUtils.fullWidth,
18+
cssUtils.flexColumn,
19+
cssUtils.flexCenterCenter,
20+
({ color: { text }, typography: { fontSize } }: Theme) => ({
21+
color: text[50],
22+
fontSize: fontSize[14],
23+
minHeight: '240px',
24+
borderTop: '1px solid rgb(196, 212, 252, 0.37)',
25+
}),
26+
] as Interpolation<Theme>;
27+
28+
export const contentBody = [
29+
cssUtils.fullWidth,
30+
({ color: { text }, typography: { fontSize, fontWeight } }: Theme) => ({
31+
color: text[50],
32+
fontSize: fontSize[14],
33+
fontWeight: fontWeight[400],
34+
padding: '24px 20px 32px 20px',
35+
}),
36+
] as Interpolation<Theme>;
37+
38+
export const articleCardContentParser = css`
39+
padding: 0;
40+
margin-top: 12px;
41+
42+
& > div {
43+
border: none;
44+
}
45+
46+
.editor-input {
47+
min-height: unset !important;
48+
max-height: 46px !important;
49+
50+
p {
51+
margin-bottom: 4px !important;
52+
}
53+
}
54+
`;
Lines changed: 124 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,155 @@
1-
import React, { useRef, useCallback, useMemo } from 'react';
1+
import React, { useRef, useCallback, Suspense, useMemo } from 'react';
22
import { ActivityComponentType } from '@stackflow/react';
33
import { Layout } from 'widgets';
44
import { WithArticleId } from 'features/articles';
55
import { TypeActivities } from 'app/stackflow';
6-
import { getQueryKeys } from 'shared/api';
7-
import { LOST_FOUND_QUERY_KEY } from 'pages/lost-found/api/query-key';
8-
import { queryClient } from 'app/lib/react-query';
9-
import { AxiosResponse } from 'axios';
10-
import { IResponse } from 'entities/with-server';
11-
import { CommentList } from 'features/comments/model';
126
import { CommentCard } from 'widgets/comments/ui/CommentCard';
137
import CommentTextField from 'widgets/comments/ui/CommentTextField';
148
import { EditorState } from 'lexical';
9+
import { ArticleContentParser } from 'features/articles/ui/ArticleContentParser';
10+
import { Loading } from 'entities/app-loaders';
11+
import { useAuthStore } from 'entities/app-authentications/slice';
12+
import { isEmptyContent } from 'features/articles/lib/has-content-error';
13+
import { useGetLostFoundComments } from 'pages/lost-found/api/get-comments';
14+
import * as styles from 'features/articles/ui/BaseArticleTemplate.css';
15+
import * as pageStyles from './Page.css';
16+
import { usePostComment } from 'pages/lost-found/api/post-comment';
1517

16-
const CommentInnerPage: ActivityComponentType<
18+
const CommentInnerPage: React.FC<
1719
WithArticleId & {
1820
commentId: number;
19-
from: Extract<
20-
KeyOf<TypeActivities>,
21-
'CommunityDetail' | 'ComplaintDetail' | 'LostFoundDetail'
22-
>;
2321
mode: 'reply' | 'edit';
2422
}
25-
> = ({ params: { articleId, commentId, from, mode } }) => {
26-
console.log('mode:', mode);
27-
const commentQueryData = useMemo(() => {
28-
switch (from) {
29-
case 'LostFoundDetail':
30-
return queryClient.getQueryData(
31-
getQueryKeys(LOST_FOUND_QUERY_KEY).comments(articleId),
32-
) as AxiosResponse<IResponse<CommentList>>;
33-
default:
34-
return null;
35-
}
36-
}, [from]);
23+
> = ({ articleId, commentId, mode }) => {
24+
const { auth } = useAuthStore();
25+
const disabled = !auth;
3726

38-
const targetedCommentMap = useMemo(() => {
39-
if (!commentQueryData) return null;
40-
return commentQueryData.data.result.comments.find(
41-
(item) => item.parentComment.id === commentId,
42-
);
43-
}, [commentQueryData]);
27+
const { data: commentQueryData } = useGetLostFoundComments(articleId);
28+
const { mutate: submitComment } = usePostComment(articleId, false);
29+
30+
const targetCommentMap = useMemo(
31+
() =>
32+
commentQueryData.comments.find((item) => {
33+
return (
34+
item.parentComment.id === commentId ||
35+
item.childComments.some((child) => child.id === commentId)
36+
);
37+
}),
38+
[commentQueryData.comments, commentId],
39+
);
40+
41+
const parentComment = targetCommentMap.parentComment;
42+
const targetComment =
43+
parentComment.id === commentId
44+
? parentComment
45+
: targetCommentMap.childComments.find(
46+
(childComment) => childComment.id === commentId,
47+
);
4448

45-
const bottomRef = useRef<HTMLDivElement | null>(null);
49+
const bottomRef = useRef<HTMLDivElement>(null);
4650
const handleHitBottom = useCallback(() => {
47-
bottomRef.current.scrollIntoView({
51+
bottomRef.current?.scrollIntoView({
4852
block: 'end',
4953
behavior: 'smooth',
5054
});
5155
}, []);
5256

5357
const content = useRef<string>('');
5458
const handleSubmit = () => {
55-
handleHitBottom();
59+
if (isEmptyContent(content.current)) {
60+
window.alert('댓글을 입력해주세요.');
61+
return;
62+
}
63+
if (!targetComment) {
64+
console.error('Selected comment not found');
65+
return;
66+
}
67+
if (mode === 'edit') {
68+
window.alert('댓글 수정 API 나오면 작업할게요');
69+
return;
70+
}
71+
const upperCommentId =
72+
mode === 'reply' ? targetComment.id : targetComment.upperCommentId;
73+
submitComment(
74+
{
75+
postId: articleId,
76+
content: content.current,
77+
upperCommentId,
78+
isPrivate: null,
79+
},
80+
{
81+
onSuccess: () => {
82+
setTimeout(handleHitBottom, 100);
83+
},
84+
},
85+
);
5686
};
5787

58-
if (!targetedCommentMap) return <></>;
88+
return (
89+
<>
90+
<article css={styles.article}>
91+
<div css={styles.articleBasicInfos}>
92+
<h2>{parentComment.writer || 'LOST112'}</h2>
93+
<time>{parentComment.createdAt}</time>
94+
<span>자유</span>
95+
</div>
96+
97+
<ArticleContentParser
98+
content={parentComment.content}
99+
isPlainContent={false}
100+
/>
59101

102+
{targetCommentMap.childComments.length > 0 ? (
103+
<ul css={pageStyles.commentListWrap}>
104+
{targetCommentMap.childComments.map((childComment) => (
105+
<CommentCard
106+
showEllipsis={false}
107+
key={childComment.id}
108+
comment={childComment}
109+
/>
110+
))}
111+
</ul>
112+
) : (
113+
<div css={pageStyles.emptyComment}>댓글을 남겨주세요.</div>
114+
)}
115+
<CommentTextField
116+
disabled={disabled}
117+
shouldFocusOnMount
118+
placeholder="댓글을 남겨주세요."
119+
initialState={mode === 'edit' && targetComment.content}
120+
onSubmit={handleSubmit}
121+
onChange={(val: EditorState) => {
122+
content.current = JSON.stringify(val.toJSON());
123+
}}
124+
/>
125+
</article>
126+
<div ref={bottomRef} css={{ height: '1px' }} />
127+
</>
128+
);
129+
};
130+
131+
const CommentInnerPageWrap: ActivityComponentType<
132+
WithArticleId & {
133+
commentId: number;
134+
// 추후 from을 통해 커뮤니티, 민원 페이지도 수용할 수 있게끔 리팩토링
135+
from: Extract<
136+
KeyOf<TypeActivities>,
137+
'CommunityDetail' | 'ComplaintDetail' | 'LostFoundDetail'
138+
>;
139+
mode: 'reply' | 'edit';
140+
}
141+
> = ({ params: { articleId, commentId, mode } }) => {
60142
return (
61143
<Layout>
62-
<CommentCard comment={targetedCommentMap.parentComment} />
63-
{targetedCommentMap.childComments.map((childComment) => (
64-
<CommentCard asChild key={childComment.id} comment={childComment} />
65-
))}
66-
<CommentTextField
67-
disabled={false}
68-
placeholder="댓글을 남겨주세요."
69-
onSubmit={handleSubmit}
70-
onChange={(val: EditorState) => {
71-
content.current = JSON.stringify(val.toJSON());
72-
}}
73-
/>
74-
<div ref={bottomRef} css={{ height: '1px' }} />
144+
<Suspense fallback={<Loading />}>
145+
<CommentInnerPage
146+
articleId={articleId}
147+
commentId={commentId}
148+
mode={mode}
149+
/>
150+
</Suspense>
75151
</Layout>
76152
);
77153
};
78154

79-
export default CommentInnerPage;
155+
export default CommentInnerPageWrap;

services/ahhachul.com/src/pages/lost-found/api/get-comments.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@ import type { IResponse } from 'entities/with-server';
33
import type { CommentList } from 'features/comments/model';
44
import { TIMESTAMP } from 'shared/lib/config/timestamp';
55
import { LOST_FOUND_QUERY_KEY } from './query-key';
6-
import type { ParamsOfLostFoundDetail } from '../model/params';
76

8-
const getLostFoundComments = (params: ParamsOfLostFoundDetail) =>
7+
const getLostFoundComments = (articleId: number) =>
98
base.get<IResponse<CommentList>>(
10-
`${routes['lost-found']}/${params.articleId}/comments`,
9+
`${routes['lost-found']}/${articleId}/comments`,
1110
);
1211

13-
export const useGetLostFoundComments = (params: ParamsOfLostFoundDetail) =>
12+
export const useGetLostFoundComments = (articleId: number) =>
1413
useAuthQuery({
15-
queryKey: getQueryKeys(LOST_FOUND_QUERY_KEY).comments(params.articleId),
16-
queryFn: () => getLostFoundComments(params),
14+
queryKey: getQueryKeys(LOST_FOUND_QUERY_KEY).comments(articleId),
15+
queryFn: () => getLostFoundComments(articleId),
1716
options: {
1817
suspense: true,
1918
staleTime: 5 * TIMESTAMP.MINUTE, // default: 5분, 글 수정 시에는 따로 업데이트 관리

services/ahhachul.com/src/pages/lost-found/api/get-detail.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@ import type { IResponse } from 'entities/with-server';
33
import { TIMESTAMP } from 'shared/lib/config/timestamp';
44
import { LOST_FOUND_QUERY_KEY } from './query-key';
55
import type { LostFoundDetail } from '../model';
6-
import type { ParamsOfLostFoundDetail } from '../model/params';
76

8-
const getLostFoundDetail = (params: ParamsOfLostFoundDetail) =>
9-
base.get<IResponse<LostFoundDetail>>(
10-
`${routes['lost-found']}/${params.articleId}`,
11-
);
7+
const getLostFoundDetail = (articleId: number) =>
8+
base.get<IResponse<LostFoundDetail>>(`${routes['lost-found']}/${articleId}`);
129

13-
export const useGetLostFoundDetail = (params: ParamsOfLostFoundDetail) =>
10+
export const useGetLostFoundDetail = (articleId: number) =>
1411
useAuthQuery({
15-
queryKey: getQueryKeys(LOST_FOUND_QUERY_KEY).detail(params.articleId),
16-
queryFn: () => getLostFoundDetail(params),
12+
queryKey: getQueryKeys(LOST_FOUND_QUERY_KEY).detail(articleId),
13+
queryFn: () => getLostFoundDetail(articleId),
1714
options: {
1815
suspense: true,
1916
staleTime: 5 * TIMESTAMP.MINUTE, // default: 5분, 글 수정 시에는 따로 업데이트 관리

services/ahhachul.com/src/pages/lost-found/api/post-comment.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,18 @@ export const postComment = async (data: {
2323
return response.data;
2424
};
2525

26-
export const usePostComment = (articleId: number) => {
26+
export const usePostComment = (articleId: number, showLoading = false) => {
2727
const { setEnableGlobalLoading, setDisableGlobalLoading } = useLoadingStore();
2828

29-
const afterSubmitSuccess = (
30-
res: IResponse<Pick<Comment, 'id' | 'upperCommentId' | 'content'>>,
31-
) => {
32-
console.log('res:', res);
33-
setDisableGlobalLoading();
29+
const afterSubmitSuccess = () => {
30+
showLoading && setDisableGlobalLoading();
3431

3532
queryClient.invalidateQueries({
3633
queryKey: getQueryKeys(LOST_FOUND_QUERY_KEY).comments(articleId),
3734
});
38-
39-
// setTimeout(() => {}, 500);
4035
};
4136
const afterSubmitFailed = (error: Error) => {
42-
setDisableGlobalLoading();
37+
showLoading && setDisableGlobalLoading();
4338
// 토스트 띄어주고 뒤로 가기
4439
console.log('error with toast:', error, '토스트 띄어주고 뒤로 가기');
4540
window.alert('댓글 작성하다가 에러 발생');
@@ -49,7 +44,7 @@ export const usePostComment = (articleId: number) => {
4944
mutationFn: postComment,
5045
options: {
5146
onError: afterSubmitFailed,
52-
onMutate: setEnableGlobalLoading,
47+
onMutate: showLoading ? setEnableGlobalLoading : () => {},
5348
onSuccess: afterSubmitSuccess,
5449
},
5550
});

services/ahhachul.com/src/pages/lost-found/model/params.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,3 @@ interface LostFoundListParams
1212
}
1313

1414
export type ParamsOfLostFoundList = Partial<LostFoundListParams>;
15-
export interface ParamsOfLostFoundDetail extends WithArticleId {}

services/ahhachul.com/src/pages/lost-found/ui/_common/ArticleCommentList/ArticleCommentList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const ArticleCommentList = ({
2525
};
2626

2727
export const ArticleCommentListInner = ({ articleId }: WithArticleId) => {
28-
const { data } = useGetLostFoundComments({ articleId });
28+
const { data } = useGetLostFoundComments(articleId);
2929

3030
return <BaseCommentList commentsMap={data.comments} />;
3131
};

0 commit comments

Comments
 (0)