-
Couldn't load subscription status.
- Fork 4
✨ feat: 게시글 상세 댓글ui추가 #364
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { useState } from "react"; | ||
|
|
||
| export default function CommentAction({ id, handleModify }: { id: number; handleModify: (id: number) => void }) { | ||
| const [isOpen, setIsOpen] = useState<boolean>(false); | ||
| const handleOpen = () => { | ||
| setIsOpen(!isOpen); | ||
| }; | ||
| return ( | ||
| <div className="flex gap-2 text-sm"> | ||
| <button onClick={() => handleModify(id)} className="text-gray-400"> | ||
| 수정 | ||
| </button> | ||
| <button onClick={handleOpen} className="text-gray-400"> | ||
| 삭제 | ||
| </button> | ||
| {isOpen && <DeleteButton id={id} handleOpen={handleOpen} />} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function DeleteButton({ id, handleOpen }: { id: number; handleOpen: () => void }) { | ||
| return ( | ||
| <div className="w-[100%] h-[100%] absolute top-0 left-0 z-[1000] bg-white/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"> | ||
| <div className="flex flex-col fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] w-full max-w-xs md:max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg"> | ||
| <header className="flex flex-col space-y-2 text-center sm:text-left"> | ||
| <span className="text-lg font-semibold">댓글 삭제</span> | ||
| </header> | ||
| <section> | ||
| <p className="text-sm text-muted-foreground text-center md:text-start">댓글을 정말로 삭제하시겠습니까?</p> | ||
| </section> | ||
| <footer className="flex flex-row justify-end space-x-2"> | ||
| <button onClick={handleOpen} className="py-2 px-4 rounded-sm hover:bg-gray-100"> | ||
| 취소 | ||
| </button> | ||
| <button className="bg-primary py-2 px-4 text-white rounded-sm hover:bg-primary/90">확인</button> | ||
| </footer> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import { useState } from "react"; | ||
|
|
||
| import { Heart } from "lucide-react"; | ||
|
|
||
| import CommentAction from "@/components/common/Card/detail/CommentAction"; | ||
| import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; | ||
|
|
||
| import { timeAgo } from "@/utils/timeago"; | ||
|
|
||
| import { PostCommentType } from "@/types/post"; | ||
|
|
||
| type PostCommentProps = { | ||
| comments: PostCommentType[]; | ||
| }; | ||
| export default function PostComment({ comments }: PostCommentProps) { | ||
| const [modifyId, setModifyId] = useState<number | null>(null); | ||
| const handleModify = (id: number | null) => { | ||
| setModifyId(id); | ||
| }; | ||
| return ( | ||
| <div className="w-full space-y-6"> | ||
| {/* 댓글 입력 영역 */} | ||
| <div className="bg-gray-50 rounded-lg shadow-sm border border-gray-200 overflow-hidden"> | ||
| <div className="p-4"> | ||
| <div className="flex items-start gap-3"> | ||
| <Avatar className="w-10 h-10"> | ||
| <AvatarImage src="https://github.com/shadcn.png" alt="사용자 프로필" /> | ||
| <AvatarFallback>CN</AvatarFallback> | ||
| </Avatar> | ||
| <textarea | ||
| placeholder="댓글을 입력하세요..." | ||
| className="flex-1 bg-transparent p-2 rounded-md h-20 focus:outline-none focus:ring-2 focus:ring-gray-300 focus:border-transparent resize-none" | ||
| ></textarea> | ||
| </div> | ||
| </div> | ||
| <div className="flex justify-end px-4 pb-4"> | ||
| <button className="bg-primary hover:bg-primary/90 text-white font-medium py-2 px-4 rounded-full transition-colors"> | ||
| 등록 | ||
| </button> | ||
| </div> | ||
| </div> | ||
jungmyunggi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| {/* 댓글 목록 헤더 */} | ||
| <div className="flex items-center border-b border-gray-200 pb-2"> | ||
| <h3 className="font-bold text-lg"> | ||
| 댓글 <span className="text-yellow-500">{comments.length}</span> | ||
| </h3> | ||
| </div> | ||
|
|
||
| {/* 댓글 목록 */} | ||
| <ul className="space-y-4"> | ||
| {comments | ||
| .sort((a, b) => Number(new Date(b.createdAt)) - Number(new Date(a.createdAt))) | ||
| .map((comment) => ( | ||
| <li key={comment.id} className="border-b border-gray-100 pb-4"> | ||
| <div className="flex items-start gap-3"> | ||
| <Avatar className="w-8 h-8"> | ||
| <AvatarImage src={comment.authorImage} alt={comment.author} /> | ||
| <AvatarFallback>{comment.author.substring(0, 2)}</AvatarFallback> | ||
| </Avatar> | ||
| <div className="flex-1"> | ||
| <div className="flex items-center gap-2"> | ||
| <div className="flex justify-between w-full"> | ||
| <div className="flex gap-2 items-center"> | ||
| <p className="font-semibold text-sm">{comment.author}</p> | ||
| <p className="text-sm text-gray-400">{timeAgo(comment.createdAt)}</p> | ||
| <span className="flex text-[10px] items-center gap-1 border rounded-sm p-1 hover:bg-red-300"> | ||
| <Heart size={15} color="red" fill={comment.isLiked ? `red` : "#fff"} /> | ||
| {comment.likes} | ||
| </span> | ||
| </div> | ||
| {modifyId !== comment.id && <CommentAction id={comment.id} handleModify={handleModify} />} | ||
| </div> | ||
| </div> | ||
| {modifyId !== comment.id ? ( | ||
| <p className="mt-1 text-gray-800">{comment.content}</p> | ||
| ) : ( | ||
| <div className=""> | ||
| <textarea className="w-[100%] mt-2 flex-1 bg-transparent p-2 rounded-md h-20 outline-none ring-2 ring-gray-300 border-transparent resize-none"> | ||
| {comment.content} | ||
| </textarea> | ||
| <div className="flex justify-end gap-3 text-sm"> | ||
| <button onClick={() => handleModify(null)} className="hover:bg-gray-200 py-2 px-4 rounded-lg"> | ||
| 취소 | ||
| </button> | ||
| <button className="bg-primary hover:bg-primary/80 py-2 px-4 text-white rounded-lg"> | ||
| 댓글 수정 | ||
| </button> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </div> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
|
|
||
| {/* 더보기 버튼 */} | ||
| {comments.length > 1 && ( | ||
| <div className="flex justify-center"> | ||
| <button className="px-4 py-2 border border-gray-200 rounded-full text-sm text-black hover:bg-gray-200 transition-colors"> | ||
| 댓글 더보기 | ||
| </button> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,10 +2,13 @@ import React from "react"; | |
| import Markdown from "react-markdown"; | ||
|
|
||
| import LikeButton from "@/components/common/Card/detail/LikeButton"; | ||
| import PostComment from "@/components/common/Card/detail/PostComment"; | ||
| import ShareButton from "@/components/common/Card/detail/ShareButton"; | ||
|
|
||
| import { usePostCardActions } from "@/hooks/common/usePostCardActions"; | ||
|
|
||
| import { POST_COMMENT_DATA } from "@/constants/dummyData"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P5) 혹시 실제로 배포된 페이지에서 댓글이 사용될 때도 dummy data가 쓰이는걸까요? |
||
|
|
||
| import { useMediaStore } from "@/store/useMediaStore"; | ||
| import { Post } from "@/types/post"; | ||
|
|
||
|
|
@@ -51,10 +54,11 @@ export const PostContent = React.memo(({ post }: PostContentProps) => { | |
| <p className="text-gray-400">💡 인공지능이 요약한 내용입니다. 오류가 포함될 수 있으니 참고 바랍니다.</p> | ||
| )} | ||
| </div> | ||
| <div className="flex gap-3"> | ||
| <div className="flex gap-3 border-b pb-5"> | ||
| <LikeButton /> | ||
| <ShareButton post={post} /> | ||
| </div> | ||
| <PostComment comments={POST_COMMENT_DATA} /> | ||
| </div> | ||
| ); | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| export function timeAgo(dateString: string) { | ||
| const now = new Date(); | ||
| const past = new Date(dateString); | ||
| const diff = Number(now) - Number(past); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P5) 파일명과 함수명이 달라요 ! |
||
|
|
||
| const diffMin = Math.floor(diff / (1000 * 60)); | ||
| const diffHour = Math.floor(diff / (1000 * 60 * 60)); | ||
| const diffDay = Math.floor(diff / (1000 * 60 * 60 * 24)); | ||
|
|
||
| if (diffMin < 60) return `${diffMin}분 전`; | ||
| if (diffHour < 24) return `${diffHour}시간 전`; | ||
| return `${diffDay}일 전`; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.