Skip to content

Commit 435ce13

Browse files
authored
Merge pull request #147 from FE9-2/refactor/albatalk
refactor: ์•Œ๋ฐ” ํ† ํฌ ๊ด€๋ จ ์ˆ˜์ • ์ค‘
2 parents 89b3dad + abb0161 commit 435ce13

File tree

29 files changed

+1278
-1055
lines changed

29 files changed

+1278
-1055
lines changed
Lines changed: 8 additions & 299 deletions
Original file line numberDiff line numberDiff line change
@@ -1,311 +1,20 @@
11
"use client";
22

3-
import { useState, useEffect, useRef, useCallback } from "react";
43
import { useParams } from "next/navigation";
5-
import CommentDetail from "@/app/components/card/board/CommentDetail";
6-
import BaseTextArea from "@/app/components/input/textarea/BaseTextArea";
7-
import Button from "@/app/components/button/default/Button";
8-
import { usePostActions } from "@/hooks/usePostActions";
9-
import { Post } from "@/types/post";
10-
import Image from "next/image";
11-
import { format } from "date-fns";
12-
import { useUser } from "@/hooks/queries/user/me/useUser";
13-
import EditPostModal from "@/app/components/modal/modals/edit/EditPost";
4+
import { PostDetailSection } from "./sections/PostDetailSection";
5+
import { CommentsSection } from "./sections/CommentsSection";
146

157
export default function PostDetailPage() {
16-
const { talkId } = useParams();
17-
const [isLoading, setIsLoading] = useState(true);
18-
const [newComment, setNewComment] = useState("");
19-
const [initialPost, setInitialPost] = useState<Post | null>(null);
20-
const [initialComments, setInitialComments] = useState<any[]>([]);
21-
const [page, setPage] = useState(1);
22-
const [hasMore, setHasMore] = useState(true);
23-
const [showOptions, setShowOptions] = useState(false);
24-
const [showEditModal, setShowEditModal] = useState(false);
25-
const [authorImageError, setAuthorImageError] = useState(false); // Added state for image error handling
26-
const optionsRef = useRef<HTMLDivElement>(null);
27-
const observer = useRef<IntersectionObserver>();
8+
const { albatalkId } = useParams();
289

29-
const { user } = useUser();
30-
31-
const lastCommentElementRef = useCallback(
32-
(node: HTMLDivElement) => {
33-
if (isLoading) return;
34-
if (observer.current) observer.current.disconnect();
35-
observer.current = new IntersectionObserver((entries) => {
36-
if (entries[0].isIntersecting && hasMore) {
37-
setPage((prevPage) => prevPage + 1);
38-
}
39-
});
40-
if (node) observer.current.observe(node);
41-
},
42-
[isLoading, hasMore]
43-
);
44-
45-
useEffect(() => {
46-
const handleClickOutside = (event: MouseEvent) => {
47-
if (optionsRef.current && !optionsRef.current.contains(event.target as Node)) {
48-
setShowOptions(false);
49-
}
50-
};
51-
52-
document.addEventListener("mousedown", handleClickOutside);
53-
return () => document.removeEventListener("mousedown", handleClickOutside);
54-
}, []);
55-
56-
const {
57-
post,
58-
comments = [],
59-
handleLike,
60-
handleDeletePost,
61-
handleAddComment,
62-
handleEditComment,
63-
handleDeleteComment,
64-
isPendingLike,
65-
} = usePostActions(initialPost, initialComments);
66-
67-
useEffect(() => {
68-
const fetchPostAndComments = async () => {
69-
try {
70-
const postResponse = await fetch(`/api/posts/${talkId}`);
71-
const postData = await postResponse.json();
72-
setInitialPost(postData);
73-
74-
const commentsResponse = await fetch(`/api/posts/${talkId}/comments?page=${page}&pageSize=10`);
75-
const commentsData = await commentsResponse.json();
76-
setInitialComments((prev) => {
77-
const newComments = page === 1 ? commentsData.data : [...prev, ...commentsData.data];
78-
return newComments.map((comment: any) => ({
79-
...comment,
80-
userName: comment.writer.nickname,
81-
userImageUrl: comment.writer.imageUrl,
82-
isAuthor: comment.writer.id === user?.id,
83-
}));
84-
});
85-
setHasMore(commentsData.data.length > 0);
86-
setIsLoading(false);
87-
} catch (error) {
88-
console.error("Error fetching data:", error);
89-
setIsLoading(false);
90-
}
91-
};
92-
93-
if (user) {
94-
fetchPostAndComments();
95-
}
96-
}, [talkId, page, user]);
97-
98-
if (isLoading || !post) {
99-
return <div>๋กœ๋”ฉ ์ค‘...</div>;
100-
}
101-
102-
console.log("Author image URL:", post.writer.imageUrl); // Added console log for image URL
103-
104-
const formatDate = (dateString: string) => {
105-
return format(new Date(dateString), "yyyy. MM. dd");
106-
};
107-
108-
const handleLikeClick = () => {
109-
handleLike();
110-
};
10+
// ์—๋Ÿฌ ์ฒ˜๋ฆฌ
11111

11212
return (
113-
<div className="min-h-screen bg-white py-12">
13+
<main className="min-h-screen bg-white py-12">
11414
<div className="mx-auto flex w-full max-w-[1480px] flex-col items-center px-4 lg:px-8">
115-
{/* Post Content Box */}
116-
<div className="mb-12 flex h-[372px] w-full max-w-[327px] flex-col lg:h-[356px]">
117-
<div className="flex h-full flex-col">
118-
{/* Title and Profile Section */}
119-
<div className="flex h-[98px] flex-col justify-between lg:h-[128px]">
120-
<div className="mb-4 flex items-center justify-between">
121-
<h1 className="text-[16px] font-semibold lg:text-[24px]">{post.title}</h1>
122-
{post.writer.id === user?.id && (
123-
<div className="relative" ref={optionsRef}>
124-
<button onClick={() => setShowOptions(!showOptions)} className="text-grayscale-500">
125-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
126-
<path
127-
d="M12 13C12.5523 13 13 12.5523 13 12C13 11.4477 12.5523 11 12 11C11.4477 11 11 11.4477 11 12C11 12.5523 11.4477 13 12 13Z"
128-
stroke="currentColor"
129-
strokeWidth="2"
130-
strokeLinecap="round"
131-
strokeLinejoin="round"
132-
/>
133-
<path
134-
d="M19 13C19.5523 13 20 12.5523 20 12C20 11.4477 19.5523 11 19 11C18.4477 11 18 11.4477 18 12C18 12.5523 18.4477 13 19 13Z"
135-
stroke="currentColor"
136-
strokeWidth="2"
137-
strokeLinecap="round"
138-
strokeLinejoin="round"
139-
/>
140-
<path
141-
d="M5 13C5.55228 13 6 12.5523 6 12C6 11.4477 5.55228 11 5 11C4.44772 11 4 11.4477 4 12C4 12.5523 4.44772 13 5 13Z"
142-
stroke="currentColor"
143-
strokeWidth="2"
144-
strokeLinecap="round"
145-
strokeLinejoin="round"
146-
/>
147-
</svg>
148-
</button>
149-
{showOptions && (
150-
<div className="absolute right-0 mt-2 w-[80px] rounded-lg bg-white shadow-lg lg:w-[132px]">
151-
<div className="flex h-[68px] flex-col justify-center gap-2 p-2 lg:h-[104px]">
152-
<button
153-
onClick={() => {
154-
setShowEditModal(true);
155-
setShowOptions(false);
156-
}}
157-
className="rounded-md bg-grayscale-50 p-2 text-xs text-grayscale-400 hover:bg-orange-50 hover:text-black-400 lg:text-sm"
158-
>
159-
์ˆ˜์ •ํ•˜๊ธฐ
160-
</button>
161-
<button
162-
onClick={() => {
163-
if (confirm("์ •๋ง๋กœ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?")) {
164-
handleDeletePost();
165-
}
166-
setShowOptions(false);
167-
}}
168-
className="rounded-md bg-grayscale-50 p-2 text-xs text-grayscale-400 hover:bg-orange-50 hover:text-black-400 lg:text-sm"
169-
>
170-
์‚ญ์ œํ•˜๊ธฐ
171-
</button>
172-
</div>
173-
</div>
174-
)}
175-
</div>
176-
)}
177-
</div>
178-
<hr className="mb-4 border-t border-line-200" />
179-
<div className="flex items-center justify-between">
180-
<div className="flex items-center gap-2">
181-
{authorImageError ? (
182-
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-gray-300">
183-
<span className="text-sm font-semibold text-gray-600">
184-
{post.writer.nickname.charAt(0).toUpperCase()}
185-
</span>
186-
</div>
187-
) : (
188-
<Image
189-
src={post.writer.imageUrl || "/icons/user/user-profile-sm.svg"}
190-
alt="User Icon"
191-
width={24}
192-
height={24}
193-
className="rounded-full"
194-
onError={() => setAuthorImageError(true)}
195-
/>
196-
)}
197-
<span className="text-xs text-grayscale-500 lg:text-base">{post.writer.nickname}</span>
198-
<span className="text-xs text-grayscale-500 lg:text-base">|</span>
199-
<span className="text-xs text-grayscale-500 lg:text-base">{formatDate(post.createdAt)}</span>
200-
</div>
201-
<div className="flex items-center gap-4">
202-
<div className="flex items-center gap-1 sm:gap-2">
203-
<Image
204-
src="/icons/comment/comment-sm.svg"
205-
alt="Comments"
206-
width={24}
207-
height={24}
208-
className="h-[22px] w-[22px] lg:h-6 lg:w-6"
209-
/>
210-
<span className="text-xs text-grayscale-500 lg:text-base">{post.commentCount}</span>
211-
</div>
212-
<div className="flex items-center gap-1 lg:gap-2">
213-
<button onClick={handleLikeClick} disabled={isPendingLike}>
214-
<Image
215-
src={post.isLiked ? "/icons/like/like-sm-active.svg" : "/icons/like/like-sm.svg"}
216-
alt="Like"
217-
width={24}
218-
height={24}
219-
className="h-[22px] w-[22px] lg:h-6 lg:w-6"
220-
/>
221-
</button>
222-
<span className="text-xs text-grayscale-500 lg:text-base">{post.likeCount}</span>
223-
</div>
224-
</div>
225-
</div>
226-
</div>
227-
{/* Content Section */}
228-
<div className="mt-auto h-[210px] overflow-y-auto whitespace-pre-wrap text-xs text-black-400 lg:h-[140px] lg:text-base">
229-
{post.content}
230-
</div>
231-
</div>
232-
</div>
233-
234-
{/* Comment Section */}
235-
<div className="mb-12 flex w-full max-w-[327px] flex-col lg:max-w-[1480px]">
236-
<h2 className="mb-4 text-[16px] font-semibold sm:text-[20px] lg:text-[24px]">๋Œ“๊ธ€({post.commentCount})</h2>
237-
<hr className="mb-4 border-t border-line-200" />
238-
<div className="mb-[7px] flex-grow lg:mb-[10px]"></div>
239-
{/* Comment Input Box */}
240-
<div className="mt-auto">
241-
<div className="relative mb-4">
242-
<BaseTextArea
243-
name="newComment"
244-
variant="white"
245-
placeholder="๋Œ“๊ธ€์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
246-
value={newComment}
247-
onChange={(e) => setNewComment(e.target.value)}
248-
size="w-full h-[132px] lg:h-[160px]"
249-
/>
250-
</div>
251-
<div className="flex justify-end">
252-
<Button
253-
onClick={() => {
254-
if (newComment.trim()) {
255-
handleAddComment(newComment);
256-
setNewComment("");
257-
}
258-
}}
259-
className="h-[52px] w-[108px] text-base lg:h-[64px] lg:w-[214px] lg:text-xl"
260-
>
261-
๋“ฑ๋กํ•˜๊ธฐ
262-
</Button>
263-
</div>
264-
</div>
265-
</div>
266-
267-
{/* Comments List or Empty State */}
268-
<div className="w-full max-w-[327px]">
269-
{comments.length > 0 ? (
270-
<div className="space-y-4">
271-
{comments.map((comment, index) => (
272-
<div
273-
key={comment.id}
274-
ref={index === comments.length - 1 ? lastCommentElementRef : undefined}
275-
className="w-full"
276-
>
277-
<CommentDetail
278-
key={comment.id}
279-
id={comment.id}
280-
userName={comment.userName}
281-
userImageUrl={comment.userImageUrl}
282-
date={formatDate(comment.createdAt)}
283-
comment={comment.content}
284-
isAuthor={comment.isAuthor}
285-
onEdit={(id, newContent) => handleEditComment({ commentId: id, newContent })}
286-
onDelete={handleDeleteComment}
287-
/>
288-
</div>
289-
))}
290-
{isLoading && <div className="text-center">๋กœ๋”ฉ ์ค‘...</div>}
291-
</div>
292-
) : (
293-
<div className="mt-8 flex justify-center">
294-
<Image src={`/images/emptyComment-md.svg`} alt="No comments" width={206} height={204} />
295-
</div>
296-
)}
297-
</div>
298-
{showEditModal && (
299-
<EditPostModal
300-
post={post}
301-
onClose={() => setShowEditModal(false)}
302-
onUpdate={(updatedPost) => {
303-
setInitialPost(updatedPost);
304-
setShowEditModal(false);
305-
}}
306-
/>
307-
)}
15+
<PostDetailSection postId={albatalkId.toString()} />
16+
<CommentsSection postId={albatalkId.toString()} />
30817
</div>
309-
</div>
18+
</main>
31019
);
31120
}

0 commit comments

Comments
ย (0)