Skip to content

Commit

Permalink
Merge pull request #269 from codestates-seb/feat/coment-haeun
Browse files Browse the repository at this point in the history
[구현] 댓글 (comment) CRUD  구현 완료
  • Loading branch information
songhaeunsong authored May 23, 2023
2 parents ea12067 + a0e1fbc commit e7c776f
Show file tree
Hide file tree
Showing 9 changed files with 280 additions and 25 deletions.
2 changes: 2 additions & 0 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import useRefreshToken from "./hooks/useRefreshToken";
import Modal from "react-modal";
import TestPage from "./test/TestPage";
import Home from "./pages/Home";
import HTestPage from "./pages/HTestPage";

// import { worker } from "./mocks/browser";
// // 개발 모드로 실행되었을 때, mocking 라이브러리가 실행되도록 명시하는 코드
Expand Down Expand Up @@ -61,6 +62,7 @@ function AppContent() {
<Route path="/studypost" element={<StudyPost />} />
<Route path="/calendar" element={<ProfileCalendar />} />
<Route path="/test" element={<TestPage />} />
<Route path="/haeun" element={<HTestPage />} />
<Route />
</Routes>
</>
Expand Down
65 changes: 65 additions & 0 deletions client/src/apis/CommentApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import tokenRequestApi from "./TokenRequestApi";
import { eduApi } from "./EduApi";

// ====================== 댓글 등록 (post) ===========================

export interface CommentDto {
content: string;
studygroupId: number;
commentId: number;
nickName: string;
}

export const postComment = async (data: string) => {
try {
const jsonData = JSON.stringify({ content: data }); // 데이터를 JSON 문자열로 직렬화
await tokenRequestApi.post("/studygroup/31/comment", jsonData);
} catch (error) {
console.log(error);
throw new Error("댓글 등록 실패");
} //31 -> 변수로 나중에 바꿔야 함
};
// ====================== 댓글 수정 (patch) ===========================
export const patchComment = async (
studyGroupId: number,
patchId: number,
data: string
) => {
try {
const jsonData = JSON.stringify({ content: data }); // 데이터를 JSON 문자열로 직렬화
await tokenRequestApi.patch(
`/studygroup/${studyGroupId}/comment/${patchId}`,
jsonData
);
} catch (error) {
console.log(error);
throw new Error("댓글 수정 실패");
}
}; //31 -> 변수로 나중에 바꿔야 함

// ====================== 댓글 전부 조회 (get) ===========================
export const getComments = async (
studyGroupId: number
): Promise<CommentDto[]> => {
try {
const response = await eduApi.get<CommentDto[]>(
`/studygroup/${studyGroupId}/comments`
); //31 -> 변수로 나중에 바꿔야 함
return response.data;
} catch (error) {
console.log(error);
throw new Error("댓글 전부 조회 실패");
}
};

// ====================== 댓글 삭제 (DELETE) ===========================
export const deleteComment = async (studyGroupId: number, patchId: number) => {
try {
const response = await tokenRequestApi.delete(
`/studygroup/${studyGroupId}/comment/${patchId}`
);
console.log("댓글이 삭제되었습니다.", response);
} catch (error) {
console.error("댓글을 삭제하는데 실패했습니다. 권한을 확인하세요", error);
}
};
7 changes: 3 additions & 4 deletions client/src/apis/StudyGroupApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export interface StudyInfoDto {
introduction: string;
isRecruited: boolean;
tags: {
[key: string]: string;
[key: string]: string[];
};
leader: {
id: number;
Expand Down Expand Up @@ -238,9 +238,8 @@ export interface StudyGroupMemberListDto {
}

// TODO : StudyGroup에 가입된 멤버 리스트
export async function getStudyGroupMemberList (id: number, isLoggedIn : boolean) {
if (!isLoggedIn)
throw new Error("Access token is not defined.");
export async function getStudyGroupMemberList(id: number, isLoggedIn: boolean) {
if (!isLoggedIn) throw new Error("Access token is not defined.");
try {
const response = await axios.get<StudyGroupMemberListDto>(
`${import.meta.env.VITE_APP_API_URL}/studygroup/${id}/member?join=true`
Expand Down
5 changes: 4 additions & 1 deletion client/src/apis/TokenRequestApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { getRefreshToken } from "../pages/utils/Auth";
let accessToken: string | null = null;
const tokenRequestApi: AxiosInstance = axios.create({
baseURL: `${import.meta.env.VITE_APP_API_URL}`,
headers: {
"Content-Type": "application/json", // 요청 헤더(content type) 설정
},
});

tokenRequestApi.interceptors.request.use(
Expand Down Expand Up @@ -52,4 +55,4 @@ tokenRequestApi.setAccessToken = (token): void => {
}
};

export default tokenRequestApi;
export default tokenRequestApi;
36 changes: 24 additions & 12 deletions client/src/components/StudyComment.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
import styled from "styled-components";
import { useRecoilValue } from "recoil";
import { LogInState } from "../recoil/atoms/LogInState";
//import { useParams } from "react-router-dom";
import { useState } from "react";
// import { Link } from "react-router-dom";
import axios from "axios";
import { validateEmptyInput } from "../pages/utils/loginUtils";
import { postComment } from "../apis/CommentApi";
import { useNavigate } from "react-router-dom";

const StudyComment = () => {
//let { id } = useParams();
const isLoggedIn = useRecoilValue(LogInState);
const navigate = useNavigate();

const [comment, setComment] = useState("");

const handleComment = (e: React.ChangeEvent<HTMLInputElement>) => {
setComment(e.target.value);
//console.log(id);
};

const handleCommentButton = () => {
if (validateEmptyInput(comment)) {
const handleCommentButton = async () => {
if (!isLoggedIn) navigate("/login");
else if (validateEmptyInput(comment)) {
alert("댓글 내용을 입력해주세요");
} else {
axios
.post(`${import.meta.env.VITE_APP_API_URL}/comment`, {
comment,
})
.catch((error) => {
console.log(error);
alert("댓글 내용을 입력해주세요");
});
try {
await postComment(comment);
setComment("");

console.log("댓글이 성공적으로 등록되었습니다.");
} catch (error) {
console.log(error);
console.log("댓글 등록에 실패했습니다.");
}
}
};

return (
<StudyCommentContainer>
<CommentInput>
<input
value={comment}
onChange={handleComment}
type="text"
placeholder="댓글을 입력하세요."
Expand Down Expand Up @@ -86,4 +98,4 @@ const CommentButton = styled.button`
}
`;

export default StudyComment;
export default StudyComment;
156 changes: 156 additions & 0 deletions client/src/components/StudyCommentList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { useRecoilValue } from "recoil";
import { LogInState } from "../recoil/atoms/LogInState";
import { useEffect, useState } from "react";
import styled from "styled-components";
import {
CommentDto,
deleteComment,
getComments,
patchComment,
} from "../apis/CommentApi";
import { validateEmptyInput } from "../pages/utils/loginUtils";
import { useNavigate } from "react-router-dom";

const StudyCommentList = ({}) => {
const isLoggedIn = useRecoilValue(LogInState);
const navigate = useNavigate();

const [comments, setComments] = useState<CommentDto[]>([]);
const [comment, setComment] = useState("");
const [patchId, setPatchId] = useState<number | null>(null);
const [isUpdateMode, setIsUpdateMode] = useState(false);

const handleUpdate = (id: number, content: string) => {
if (!isLoggedIn) navigate("/login");
setIsUpdateMode(!isUpdateMode);
setPatchId(id);
setComment(content);
};

const handleDelete = async (patchId: number) => {
if (!isLoggedIn) navigate("/login");
try {
const studyGroupId = 31;
await deleteComment(studyGroupId, patchId);
} catch (error) {
console.log("댓글 삭제 실패", error);
}
};

const handleComment = (e: React.ChangeEvent<HTMLInputElement>) => {
setComment(e.target.value);
//console.log(id);
};

const handleUpdateButton = async () => {
if (!isLoggedIn) navigate("/login");

if (validateEmptyInput(comment)) {
alert("댓글 내용을 입력해주세요.");
} else {
try {
const studyGroupId = 31;
if (patchId) {
await patchComment(studyGroupId, patchId, comment);
setIsUpdateMode(false);
setPatchId(null);
setComment("");
}
} catch (error) {
console.log("댓글 등록 실패", error);
}
}
};
useEffect(() => {
const fetchData = async () => {
try {
const studyGroupId = 31;
const newComment = await getComments(studyGroupId);
setComments(newComment);
} catch (error) {
console.log(error);
}
};
fetchData();
}, [!isUpdateMode]); // post시 바로 변경될 수 있도록 의존성 배열 추가 예정
return (
<>
<ul>
{comments.map((comment) => {
return (
<CommentItemDiv key={comment.commentId}>
<ContentItem>
<p>{comment.nickName}</p>
<>
{isUpdateMode && patchId === comment.commentId ? (
<>
<input
defaultValue={comment.content}
onChange={handleComment}
></input>
<button onClick={handleUpdateButton}>완료</button>
</>
) : (
<span>{comment.content}</span>
)}
</>
</ContentItem>
<ButtonDiv>
<button
onClick={() =>
handleUpdate(comment.commentId, comment.content)
}
>
수정
</button>
<button onClick={() => handleDelete(comment.commentId)}>
삭제
</button>
</ButtonDiv>
</CommentItemDiv>
);
})}
</ul>
</>
);
};

const CommentItemDiv = styled.div`
width: 80%;
height: 70px;
padding: 10px 10px 10px 10px;
background-color: #ffffff;
display: flex;
justify-content: space-between;
border-bottom: solid #e9e9e9;
`;
const ContentItem = styled.div`
text-align: left;
button {
margin-left: 10px;
background-color: #858da8;
font-size: 13px;
padding: 3px;
color: #ffffff;
}
p {
font-size: 16px;
font-weight: bold;
color: #2759a2;
}
span {
font-size: 12px;
}
`;

const ButtonDiv = styled.div`
height: 100%;
display: flex;
align-items: flex-end;
button {
font-size: 12px;
padding: 3px;
color: #858da8;
}
`;
export default StudyCommentList;
17 changes: 9 additions & 8 deletions client/src/components/TagInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ const TagInput = ({
selectedCategory,
tags,
setTags,
viewTag,
setViewTag,
}: {
selectedCategory: string;
tags: string[];
setTags: React.Dispatch<React.SetStateAction<string[]>>;
viewTag: boolean;
setViewTag: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
const [view, setView] = useState(false);

const [defaultTag, setDefaultTag] = useState<{ [key: string]: string }>({});
const [defaultTag, setDefaultTag] = useState<{ [key: string]: string[] }>({});
const [createdTag, setCreatedTag] = useState<string>("");

const handleTag = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -40,7 +42,6 @@ const TagInput = ({
};

useEffect(() => {
setView(false);
setTags([]);
setCreatedTag("");
const fetchData = async () => {
Expand Down Expand Up @@ -69,13 +70,13 @@ const TagInput = ({

<ul
onClick={() => {
setView(!view);
setViewTag(true);
}}
>
{selectedCategory} {view ? "⌃" : "⌄"}
{view && defaultTag && (
{selectedCategory} {viewTag ? "⌃" : "⌄"}
{viewTag && defaultTag && (
<TagDropdown
defaultTags={[defaultTag[selectedCategory]]}
defaultTags={defaultTag[selectedCategory]}
setTags={setTags}
tags={tags}
/>
Expand Down
Loading

0 comments on commit e7c776f

Please sign in to comment.