Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions src/app/(pages)/albaTalk/[albatalkId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,10 @@ import { CommentsSection } from "./sections/CommentsSection";
export default function PostDetailPage() {
const { albatalkId } = useParams();

// 에러 처리

return (
<main className="min-h-screen bg-white py-12">
<div className="mx-auto flex w-full max-w-[1480px] flex-col items-center px-4 lg:px-8">
<PostDetailSection postId={albatalkId.toString()} />
<CommentsSection postId={albatalkId.toString()} />
</div>
</main>
<div className="mx-auto flex w-full min-w-[375px] flex-col items-center px-4 lg:w-[1024px] xl:w-[1480px]">
<PostDetailSection postId={albatalkId.toString()} />
<CommentsSection postId={albatalkId.toString()} />
</div>
);
}
102 changes: 54 additions & 48 deletions src/app/(pages)/albaTalk/[albatalkId]/sections/CommentsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import Button from "@/app/components/button/default/Button";
import { useAddComment } from "@/hooks/queries/post/comment/useAddComment";
import { useUser } from "@/hooks/queries/user/me/useUser";
import { useComments } from "@/hooks/queries/post/comment/useComments";
import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner";
import Pagination from "@/app/components/pagination/Pagination";
import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner";
import DotLoadingSpinner from "@/app/components/loading-spinner/DotLoadingSpinner";

interface CommentsSectionProps {
postId: string;
}

export function CommentsSection({ postId }: CommentsSectionProps) {
export function CommentsSection({ postId }: CommentsSectionProps): JSX.Element {
const [currentPage, setCurrentPage] = useState(1);
const [newComment, setNewComment] = useState("");

Expand All @@ -42,10 +43,13 @@ export function CommentsSection({ postId }: CommentsSectionProps) {
}
}, [addComment, newComment]);

const handlePageChange = (page: number) => {
const handlePageChange = useCallback((page: number) => {
setCurrentPage(page);
window.scrollTo({ top: 0, behavior: "smooth" });
};
const commentSection = document.querySelector("#comments-section");
if (commentSection) {
commentSection.scrollIntoView({ behavior: "smooth" });
}
}, []);

if (isCommentsLoading) {
return (
Expand All @@ -60,59 +64,61 @@ export function CommentsSection({ postId }: CommentsSectionProps) {
}

return (
<section className="w-full max-w-[327px]">
<h2 className="mb-4 text-[16px] font-semibold sm:text-[20px] lg:text-[24px]">
댓글({commentsData?.totalItemCount || 0})
</h2>

<hr className="mb-4 border-t border-line-200" />
<section className="w-full" aria-label="댓글 섹션">
<div className="mb-6 ml-6 text-[16px] font-semibold text-black-400 md:text-[20px] lg:text-[24px]">
댓글({commentsData?.totalItemCount ?? 0})
</div>

{/* 댓글 입력 영역 */}
<div className="mb-8">
<div className="p-4">
<BaseTextArea
name="newComment"
variant="white"
placeholder="댓글을 입력해주세요."
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
className="mb-4 h-[132px] w-full lg:h-[160px]"
className="h-[90px] w-[344px] resize-none rounded-lg bg-background-200 p-4 md:w-[742px] lg:h-[100px] lg:w-[994px] xl:w-[1410px]"
size="h-[120px] w-[375px] md:w-[768px] lg:w-[1024px] xl:w-[1440px] lg:h-[144px]"
variant="white"
/>
<div className="flex justify-end">
<Button
onClick={handleAddComment}
disabled={addComment.isPending || !newComment.trim()}
className="h-[52px] w-[108px] text-base lg:h-[64px] lg:w-[214px] lg:text-xl"
>
{addComment.isPending ? "등록 중..." : "등록하기"}
</Button>
</div>
</div>
<div className="flex justify-end p-4">
<Button
onClick={handleAddComment}
disabled={addComment.isPending || !newComment.trim()}
className="h-[64px] w-[140px] rounded-lg text-[14px] font-medium"
>
{addComment.isPending ? <DotLoadingSpinner /> : "등록하기"}
</Button>
</div>

{/* 댓글 목록 */}
{commentsData?.data && commentsData.data.length > 0 ? (
<div className="space-y-4">
{commentsData.data.map((comment) => (
<div key={comment.id}>
<Comment
id={comment.id.toString()}
nickname={comment.writer.nickname}
updatedAt={comment.createdAt}
content={comment.content}
isAuthor={comment.writer.id === user?.id}
/>
</div>
))}
</div>
) : (
<div className="mt-8 flex justify-center">
<Image src="/images/emptyComment-md.svg" alt="No comments" width={206} height={204} />
</div>
)}
<div className="mt-4">
{commentsData?.data?.length ? (
<>
{commentsData.data.map((comment) => (
<div key={comment.id} className="rounded-lg bg-white p-2">
<Comment
id={comment.id.toString()}
nickname={comment.writer.nickname}
updatedAt={comment.createdAt}
content={comment.content}
isAuthor={comment.writer.id === user?.id}
/>
</div>
))}
</>
) : (
<div className="flex min-h-[300px] flex-col items-center justify-center gap-4 rounded-lg bg-white p-5">
<Image src="/images/emptyComment-md.svg" alt="No comments" width={206} height={204} className="mb-4" />
</div>
)}
</div>

{/* 페이지네이션 */}
{commentsData?.totalPages && commentsData.totalPages > 0 && (
<div className="mt-8">
<Pagination currentPage={currentPage} totalPage={commentsData.totalPages} onPageChange={handlePageChange} />
{(commentsData?.totalPages ?? 0) > 1 && (
<div className="mt-12 flex justify-center">
<Pagination
currentPage={currentPage}
totalPage={commentsData?.totalPages ?? 0}
onPageChange={handlePageChange}
/>
</div>
)}
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import KebabDropdown from "@/app/components/button/dropdown/KebabDropdown";
import useModalStore from "@/store/modalStore";
import useWidth from "@/hooks/useWidth";
import { usePostDetail } from "@/hooks/queries/post/usePostDetail";
import { useRouter } from "next/navigation";

export function PostDetailSection({ postId }: { postId: string }) {
const { isDesktop } = useWidth();
const { user } = useUser();
const router = useRouter();
const { data: post } = usePostDetail(postId);
const { likePost, unlikePost } = useLikePost(postId);
const deletePost = useDeletePost(postId);
Expand Down Expand Up @@ -46,62 +48,82 @@ export function PostDetailSection({ postId }: { postId: string }) {
},
});
},
onCancel: () => {
openModal("customForm", { isOpen: false, title: "", content: "", onConfirm: () => {}, onCancel: () => {} });
},
onCancel: () =>
openModal("customForm", {
isOpen: false,
title: "",
content: "",
onConfirm: () => {},
onCancel: () => {},
}),
});
};

const dropdownOptions = [{ label: "삭제하기", onClick: handleDelete, disabled: deletePost.isPending }];
const handleEdit = () => {
router.push(`/albatalk/edit/${postId}`);
};

const dropdownOptions = [
{ label: "수정하기", onClick: handleEdit },
{ label: "삭제하기", onClick: handleDelete, disabled: deletePost.isPending },
];

return (
<article className="mb-12 w-full max-w-[327px] rounded-lg border border-line-200 bg-white p-4">
<section className="mb-12 w-full rounded-lg bg-white p-4 md:p-6 lg:p-8">
{/* Header */}
<div className="mb-4 flex items-center justify-between">
<h1 className="text-[16px] font-semibold lg:text-[24px]">{post?.title}</h1>
<h1 className="text-[16px] font-semibold md:text-[20px] lg:text-[24px]">{post?.title}</h1>
{post?.writer.id === user?.id && <KebabDropdown options={dropdownOptions} />}
</div>

{/* Divider Line */}
<div className="my-[9px] flex items-center justify-center lg:my-[16px]">
<div className="w-full bg-line-100" style={{ height: "1px" }}></div>
</div>

{/* Author Info */}
<div className="mb-4 flex items-center gap-2">
<Image
src="/icons/user/user-profile-sm.svg"
alt="User Icon"
className="rounded-full"
width={24}
height={24}
sizes="(max-width: 600px) 24px, (max-width: 1480px) 28px, 30px"
/>
<div className="flex items-center gap-1">
<span className="font-nexon text-[14px] font-medium text-black-400 md:text-[16px] lg:text-[18px]">
{post?.writer.nickname}
</span>
<Image src="/icons/user/user-profile-sm.svg" alt="User Icon" className="rounded-full" width={32} height={32} />
<div className="flex items-center gap-2">
<span className="font-medium text-black-400">{post?.writer.nickname}</span>
<span className="text-grayscale-400">|</span>
<span className="font-nexon text-[12px] text-grayscale-400 md:text-[14px] lg:text-[16px]">
{formatLocalDate(post?.createdAt || new Date())}
</span>
<span className="text-sm text-grayscale-400">{formatLocalDate(post?.createdAt || new Date())}</span>
</div>
</div>

{/* Content */}
<div className="mb-4 min-h-[200px] whitespace-pre-wrap break-words font-nexon text-[14px] leading-[1.6] text-black-400 md:text-[16px] lg:text-[18px]">
<div className="mb-6 whitespace-pre-wrap text-sm leading-[1.6] text-black-400 md:text-base lg:text-lg">
{post?.content}
</div>

{/* Image */}
<div className="mb-6">
{post?.imageUrl ? (
<div className="relative aspect-square w-full max-w-[600px] overflow-hidden rounded-lg">
<Image
src={post.imageUrl}
alt="Post Image"
fill
sizes="(max-width: 768px) 100vw, 600px"
className="object-contain"
priority
/>
</div>
) : null}
</div>

{/* Footer */}
<div className="flex items-center justify-end gap-2">
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<Image
src={`/icons/comment/${isDesktop ? "comment-md.svg" : "comment-sm.svg"}`}
alt="Comment Icon"
width={22}
height={22}
width={24}
height={24}
/>
<span className="font-nexon text-[14px] font-normal text-grayscale-500 lg:text-[16px]">
{post?.commentCount}
</span>
<span className="text-sm text-grayscale-500">{post?.commentCount}</span>
</div>
<div className="flex items-center gap-1">
<div className="flex cursor-pointer items-center gap-1" onClick={handleLikeClick}>
<Image
src={`/icons/like/${
post?.isLiked
Expand All @@ -113,16 +135,12 @@ export function PostDetailSection({ postId }: { postId: string }) {
: "like-sm.svg"
}`}
alt="Like Icon"
width={22}
height={22}
className="cursor-pointer"
onClick={handleLikeClick}
width={24}
height={24}
/>
<span className="font-nexon text-[14px] font-normal text-grayscale-500 lg:text-[16px]">
{post?.likeCount}
</span>
<span className="text-sm text-grayscale-500">{post?.likeCount}</span>
</div>
</div>
</article>
</section>
);
}
43 changes: 28 additions & 15 deletions src/app/(pages)/albaTalk/edit/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,15 @@ export default function EditTalk({ params }: { params: { id: string } }) {
});

if (post.imageUrl) {
const initialImages = post.imageUrl.split(",").map((url, index) => ({
file: null,
url,
id: `initial-${index}`,
}));
setImageList(initialImages);
setImageList([
{
file: null,
url: post.imageUrl,
id: "initial-image",
},
]);
} else {
setImageList([]);
}
}
}, [post, reset]);
Expand Down Expand Up @@ -151,14 +154,16 @@ export default function EditTalk({ params }: { params: { id: string } }) {
<div className="hidden space-x-1 font-semibold md:flex md:space-x-2 lg:space-x-4">
<Button
variant="solid"
className="bg-grayscale-100 text-grayscale-50 hover:bg-grayscale-200 md:h-[46px] md:w-[108px] md:text-[14px] lg:h-[58px] lg:w-[180px] lg:text-[18px]"
color="gray"
className="md:h-[46px] md:w-[108px] md:text-[14px] lg:h-[58px] lg:w-[180px] lg:text-[18px]"
onClick={() => router.push(`/albatalk/${postId}`)}
>
취소
</Button>
<Button
variant="solid"
className="bg-primary-orange-300 text-grayscale-50 hover:bg-orange-400 md:h-[46px] md:w-[108px] md:text-[14px] lg:h-[58px] lg:w-[180px] lg:text-[18px]"
color="orange"
className="md:h-[46px] md:w-[108px] md:text-[14px] lg:h-[58px] lg:w-[180px] lg:text-[18px]"
onClick={handleSubmit(onSubmit)}
disabled={isPending}
>
Expand Down Expand Up @@ -220,27 +225,35 @@ export default function EditTalk({ params }: { params: { id: string } }) {
>
이미지
</label>
<ImageInputPlaceHolder onImageUpload={uploadImage} onImagesChange={handleImagesChange} />
<ImageInputPlaceHolder
onImageUpload={uploadImage}
onImagesChange={handleImagesChange}
initialImages={imageList}
/>
</div>
</form>
</div>
</div>

{/* 모바일 버전 버튼 */}
<div className="fixed bottom-4 left-4 right-4 flex w-full flex-col items-center space-y-2 rounded-t-lg bg-white p-4 font-semibold md:hidden">
<button
className="mb-2 h-[58px] w-[327px] rounded-[8px] bg-grayscale-100 text-white hover:bg-grayscale-200"
<Button
variant="solid"
color="gray"
className="h-[58px] w-[327px] rounded-[8px]"
onClick={() => router.push(`/albatalk/${postId}`)}
>
취소
</button>
<button
className="h-[58px] w-[327px] rounded-[8px] bg-primary-orange-300 text-white hover:bg-orange-400"
</Button>
<Button
variant="solid"
color="orange"
className="h-[58px] w-[327px] rounded-[8px]"
onClick={handleSubmit(onSubmit)}
disabled={isPending}
>
{isPending ? <DotLoadingSpinner /> : "수정하기"}
</button>
</Button>
</div>
</>
);
Expand Down
Loading
Loading