Skip to content

Commit f57732c

Browse files
authored
Merge pull request #412 from TripInfoWeb/dev_comment
Feat: 댓글 기능 구현
2 parents eede0be + ffbbd96 commit f57732c

File tree

15 files changed

+544
-157
lines changed

15 files changed

+544
-157
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import {
2+
CreateInformationCommentRequestDto,
3+
UpdateInformationCommentRequestDto,
4+
} from "@/types/InformationCommentDto";
5+
import { NextRequest } from "next/server";
6+
7+
/**
8+
* @method GET
9+
* @url /api/informations/comments/:informationId?page=0
10+
* @description 댓글 목록 조회
11+
*/
12+
export async function GET(
13+
request: NextRequest,
14+
{ params }: { params: { id: string } },
15+
) {
16+
const cookie = request.cookies.get("access_token");
17+
const searchParams = request.nextUrl.searchParams;
18+
const page = searchParams.get("page") ?? "0";
19+
20+
return await fetch(
21+
`${process.env.BACKEND_URL}/api/informations/comments/${params.id}?page=${page}`,
22+
{
23+
method: "GET",
24+
headers: {
25+
"Content-Type": "application/json",
26+
Cookie: `${cookie?.name}=${cookie?.value}`,
27+
},
28+
cache: "no-store",
29+
},
30+
);
31+
}
32+
33+
/**
34+
* @method POST
35+
* @url /api/informations/comments/:informationId
36+
* @description 댓글 작성
37+
*/
38+
export async function POST(
39+
request: NextRequest,
40+
{ params }: { params: { id: number } },
41+
) {
42+
const cookie = request.cookies.get("access_token");
43+
const body: CreateInformationCommentRequestDto = await request.json();
44+
return await fetch(
45+
`${process.env.BACKEND_URL}/api/informations/comments/${params.id}`,
46+
{
47+
method: "POST",
48+
headers: {
49+
"Content-Type": "application/json",
50+
Cookie: `${cookie?.name}=${cookie?.value}`,
51+
},
52+
body: JSON.stringify(body),
53+
cache: "no-store",
54+
},
55+
);
56+
}
57+
58+
/**
59+
* @method PUT
60+
* @url /api/informations/comments/:informationId
61+
* @description 댓글 수정
62+
*/
63+
export async function PUT(
64+
request: NextRequest,
65+
{ params }: { params: { id: number } },
66+
) {
67+
const cookie = request.cookies.get("access_token");
68+
const body: UpdateInformationCommentRequestDto = await request.json();
69+
return await fetch(
70+
`${process.env.BACKEND_URL}/api/informations/comments/${params.id}`,
71+
{
72+
method: "PUT",
73+
headers: {
74+
"Content-Type": "application/json",
75+
Cookie: `${cookie?.name}=${cookie?.value}`,
76+
},
77+
body: JSON.stringify(body),
78+
cache: "no-store",
79+
},
80+
);
81+
}
82+
83+
/**
84+
* @method DELETE
85+
* @url /api/informations/comments/:informationCommentId
86+
* @description 댓글 삭제
87+
*/
88+
export async function DELETE(
89+
request: NextRequest,
90+
{ params }: { params: { id: number } },
91+
) {
92+
const cookie = request.cookies.get("access_token");
93+
return await fetch(
94+
`${process.env.BACKEND_URL}/api/informations/comments/${params.id}`,
95+
{
96+
method: "DELETE",
97+
headers: {
98+
Cookie: `${cookie?.name}=${cookie?.value}`,
99+
},
100+
cache: "no-store",
101+
},
102+
);
103+
}

src/app/informations/(detail)/[id]/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import Breadcrumbs from "@/components/common/Breadcrumb";
22
import InformationViewer from "@/components/informations/detail/InformationViewer";
33
import RecommendationList from "@/components/informations/detail/RecommendationList";
44
import CommentListContainer from "@/containers/informations/detail/comment/CommentListContainer";
5-
import CommentPaginationContainer from "@/containers/informations/detail/comment/CommentPaginationContainer";
65
import { InformationDetailDto } from "@/types/InformationDto";
76
import { cookies } from "next/headers";
87

@@ -64,7 +63,6 @@ export default async function page({ params: { id } }: Props) {
6463
/>
6564
<InformationViewer informationId={informationId} data={data} />
6665
<CommentListContainer informationId={informationId} />
67-
<CommentPaginationContainer />
6866
<RecommendationList data={data} />
6967
</div>
7068
);

src/components/auth/UserDropDown.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,28 +68,24 @@ const UserDropDown = () => {
6868
<section
6969
ref={ref1}
7070
onClick={(e) => e.preventDefault()}
71-
className={
72-
"outline-primary-20 fixed right-0 top-[4rem] flex h-auto w-[20rem] cursor-default flex-col gap-y-4 rounded-2xl bg-white p-4 outline outline-[0.0625rem] outline-offset-[-0.0625rem] outline-gray2"
73-
}
71+
className="outline-primary-20 fixed right-0 top-[4rem] flex h-auto w-[20rem] cursor-default flex-col gap-y-4 rounded-2xl bg-white p-4 outline outline-[0.0625rem] outline-offset-[-0.0625rem] outline-gray2"
7472
>
7573
<div className="relative flex h-[12rem] w-full items-center justify-center p-4">
7674
<Image
77-
className="rounded-full"
75+
className="rounded-full border-[0.03125rem] border-[#B8EDD9] bg-lightGreen"
7876
src={authStore.userImage.address}
79-
alt={"유저 이미지"}
77+
alt="유저 이미지"
8078
width={140}
8179
height={140}
8280
/>
8381
</div>
8482
<Link
85-
href={"/mypage?mainCategory=정보&category=owner"}
86-
className={
87-
"outline-primary-20 flex items-center justify-center gap-x-2 rounded-[1rem] bg-white px-8 py-2 outline outline-[0.0625rem] outline-offset-[-0.0625rem] outline-gray3"
88-
}
83+
href="/mypage?mainCategory=정보&category=owner"
84+
className="outline-primary-20 flex items-center justify-center gap-x-2 rounded-[1rem] bg-white px-8 py-2 outline outline-[0.0625rem] outline-offset-[-0.0625rem] outline-gray3"
8985
onClick={() => modalState.closeModal()}
9086
prefetch={true}
9187
>
92-
<div className={"relative h-[1.25rem] w-[1.25rem]"}>
88+
<div className="relative h-[1.25rem] w-[1.25rem]">
9389
<Image
9490
className="aspect-square"
9591
src="/home/mypage-icon.svg"

src/components/informations/detail/comment/CommentItem.tsx

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
1+
import HashSpinner from "@/components/common/HashSpinner";
12
import ReportIcon from "@/components/common/icons/ReportIcon";
23
import CommentDeleteModalContainer from "@/containers/informations/detail/comment/CommentDeleteModalContainer";
3-
import { InformationCommentResponseDto } from "@/types/InformationDto";
4+
import { InformationCommentResponseDto } from "@/types/InformationCommentDto";
45
import Image from "next/image";
6+
import { Dispatch, FormEvent, SetStateAction } from "react";
57

68
interface CommentItemProps {
79
data: InformationCommentResponseDto;
810
modalVisible: boolean;
11+
editable: boolean;
12+
comment: string;
13+
loading: boolean;
14+
userId: number;
915
openModal: () => void;
1016
closeModal: () => void;
17+
setEditable: Dispatch<SetStateAction<boolean>>;
18+
setComment: (value: string) => void;
19+
onSubmit: (e: FormEvent<HTMLFormElement>) => Promise<void>;
1120
}
1221

1322
const CommentItem = ({
1423
data,
1524
modalVisible,
25+
editable,
26+
comment,
27+
loading,
28+
userId,
1629
openModal,
1730
closeModal,
31+
setEditable,
32+
setComment,
33+
onSubmit,
1834
}: CommentItemProps) => {
1935
return (
2036
<div className="flex flex-col gap-[0.625rem] border-b border-b-gray3">
@@ -24,40 +40,86 @@ const CommentItem = ({
2440
closeModal={closeModal}
2541
/>
2642
)}
43+
<HashSpinner loading={loading} />
2744
<div className="flex flex-row items-center justify-between">
2845
<div className="flex flex-row items-center gap-3">
2946
<Image
3047
className="rounded-full border-[0.03125rem] border-[#B8EDD9] bg-lightGreen"
31-
src={data.userImage}
48+
src={data.userProfile}
3249
alt="userImage"
3350
width={54}
3451
height={54}
3552
/>
3653
<div className="flex flex-col gap-1">
37-
<p className="text-xs font-semibold text-black">{data.nickname}</p>
54+
<p className="text-xs font-semibold text-black">
55+
{data.userNickname}
56+
</p>
3857
<p className="text-xs text-gray1">
39-
{`${new Date(data.createdDate).toLocaleDateString("ko-KR")}`}
58+
{`${new Date(data.updatedAt).toLocaleDateString("ko-KR")}`}
4059
</p>
4160
</div>
4261
</div>
4362
<button
44-
className="stroke-gray1 hover:stroke-main"
45-
onClick={() => openModal()}
63+
className="invisible stroke-gray1 hover:stroke-main"
64+
onClick={() => alert("TODO: 구현 예정")}
4665
>
4766
<ReportIcon className="mb-[0.5625rem] cursor-pointer stroke-inherit" />
4867
</button>
4968
</div>
50-
<div>
51-
<p className="h-[4.375rem] break-words pl-[4.125rem] text-[0.9375rem] text-black">
52-
{data.content}
53-
</p>
54-
<div className="h-8 w-full">
55-
<div className="flex flex-row items-center justify-end gap-4 text-sm text-gray1">
56-
<button className="hover:text-main">수정</button>
57-
<button className="hover:text-main">삭제</button>
69+
{editable ? (
70+
<form className="pl-[4.125rem]" onSubmit={onSubmit}>
71+
<div className="relative h-[4.375rem]">
72+
<input
73+
className={`${comment.length === 0 ? "border-red-500" : "border-gray3 hover:border-main focus:border-main"} h-[2.75rem] w-full rounded-3xl border px-6 text-sm text-black outline-none`}
74+
type="text"
75+
placeholder="자유롭게 소통을 해보세요."
76+
value={comment}
77+
maxLength={200}
78+
autoComplete="off"
79+
onChange={(e) => setComment(e.target.value)}
80+
/>
81+
{comment.length === 0 && (
82+
<p className="absolute bottom-1 left-4 mt-1 text-xs text-red-500">
83+
댓글을 입력해 주세요.
84+
</p>
85+
)}
86+
</div>
87+
<div className="flex h-8 w-full flex-row items-start justify-end gap-4 text-sm text-gray1">
88+
<button className="hover:text-main" type="submit">
89+
수정
90+
</button>
91+
<button
92+
className="hover:text-main"
93+
type="button"
94+
onClick={() => setEditable(false)}
95+
>
96+
취소
97+
</button>
5898
</div>
99+
</form>
100+
) : (
101+
<div>
102+
<p className="min-h-[4.375rem] break-words pl-[4.125rem] text-[0.9375rem] text-black">
103+
{data.content}
104+
</p>
105+
{userId === data.userId && (
106+
<div className="flex h-8 w-full flex-row items-start justify-end gap-4 text-sm text-gray1">
107+
<button
108+
className="hover:text-main"
109+
onClick={() => {
110+
setComment(data.content);
111+
setEditable(true);
112+
}}
113+
>
114+
수정
115+
</button>
116+
<button className="hover:text-main" onClick={() => openModal()}>
117+
삭제
118+
</button>
119+
</div>
120+
)}
59121
</div>
60-
</div>
122+
)}
61123
</div>
62124
);
63125
};

0 commit comments

Comments
 (0)