diff --git a/client/src/App.tsx b/client/src/App.tsx index 6adc72f8..2c27c745 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -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 라이브러리가 실행되도록 명시하는 코드 @@ -61,6 +62,7 @@ function AppContent() { } /> } /> } /> + } /> diff --git a/client/src/apis/CommentApi.ts b/client/src/apis/CommentApi.ts new file mode 100644 index 00000000..598f99fe --- /dev/null +++ b/client/src/apis/CommentApi.ts @@ -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 => { + try { + const response = await eduApi.get( + `/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); + } +}; diff --git a/client/src/apis/StudyGroupApi.ts b/client/src/apis/StudyGroupApi.ts index 1fbf0ee1..8f3eb3b7 100644 --- a/client/src/apis/StudyGroupApi.ts +++ b/client/src/apis/StudyGroupApi.ts @@ -40,7 +40,7 @@ export interface StudyInfoDto { introduction: string; isRecruited: boolean; tags: { - [key: string]: string; + [key: string]: string[]; }; leader: { id: number; @@ -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( `${import.meta.env.VITE_APP_API_URL}/studygroup/${id}/member?join=true` diff --git a/client/src/apis/TokenRequestApi.ts b/client/src/apis/TokenRequestApi.ts index bc97e890..de1f88ac 100644 --- a/client/src/apis/TokenRequestApi.ts +++ b/client/src/apis/TokenRequestApi.ts @@ -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( @@ -52,4 +55,4 @@ tokenRequestApi.setAccessToken = (token): void => { } }; -export default tokenRequestApi; \ No newline at end of file +export default tokenRequestApi; diff --git a/client/src/components/StudyComment.tsx b/client/src/components/StudyComment.tsx index c0735902..0a2afb9d 100644 --- a/client/src/components/StudyComment.tsx +++ b/client/src/components/StudyComment.tsx @@ -1,28 +1,39 @@ 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) => { 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("댓글 등록에 실패했습니다."); + } } }; @@ -30,6 +41,7 @@ const StudyComment = () => { { + const isLoggedIn = useRecoilValue(LogInState); + const navigate = useNavigate(); + + const [comments, setComments] = useState([]); + const [comment, setComment] = useState(""); + const [patchId, setPatchId] = useState(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) => { + 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 ( + <> +
    + {comments.map((comment) => { + return ( + + +

    {comment.nickName}

    + <> + {isUpdateMode && patchId === comment.commentId ? ( + <> + + + + ) : ( + {comment.content} + )} + +
    + + + + +
    + ); + })} +
+ + ); +}; + +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; diff --git a/client/src/components/TagInput.tsx b/client/src/components/TagInput.tsx index 5bd078dd..1b67abae 100644 --- a/client/src/components/TagInput.tsx +++ b/client/src/components/TagInput.tsx @@ -8,14 +8,16 @@ const TagInput = ({ selectedCategory, tags, setTags, + viewTag, + setViewTag, }: { selectedCategory: string; tags: string[]; setTags: React.Dispatch>; + viewTag: boolean; + setViewTag: React.Dispatch>; }) => { - const [view, setView] = useState(false); - - const [defaultTag, setDefaultTag] = useState<{ [key: string]: string }>({}); + const [defaultTag, setDefaultTag] = useState<{ [key: string]: string[] }>({}); const [createdTag, setCreatedTag] = useState(""); const handleTag = (e: React.ChangeEvent) => { @@ -40,7 +42,6 @@ const TagInput = ({ }; useEffect(() => { - setView(false); setTags([]); setCreatedTag(""); const fetchData = async () => { @@ -69,13 +70,13 @@ const TagInput = ({
    { - setView(!view); + setViewTag(true); }} > - {selectedCategory} {view ? "⌃" : "⌄"} - {view && defaultTag && ( + {selectedCategory} {viewTag ? "⌃" : "⌄"} + {viewTag && defaultTag && ( diff --git a/client/src/pages/HTestPage.tsx b/client/src/pages/HTestPage.tsx new file mode 100644 index 00000000..80fc8122 --- /dev/null +++ b/client/src/pages/HTestPage.tsx @@ -0,0 +1,13 @@ +import StudyComment from "../components/StudyComment"; +import StudyCommentList from "../components/StudyCommentList"; +const HTestPage = () => { + return ( +
    +

    zz

    + + +
    + ); +}; + +export default HTestPage; diff --git a/client/src/pages/StudyPost.tsx b/client/src/pages/StudyPost.tsx index a862245d..471e4448 100644 --- a/client/src/pages/StudyPost.tsx +++ b/client/src/pages/StudyPost.tsx @@ -19,12 +19,14 @@ const StudyPost = () => { const [memberCountMax, setMemberCountMax] = useState(1); const [platform, setPlatform] = useState(""); const [tags, setTags] = useState([]); + const [viewTag, setViewTag] = useState(false); const [introduction, setIntroduction] = useState(""); const [selectedCategory, setSelectedCategory] = useState("프론트엔드"); const handleCategory = (e: React.ChangeEvent) => { setSelectedCategory(e.target.value); + setViewTag(false); }; const handleTitle = (e: React.ChangeEvent) => { @@ -180,6 +182,8 @@ const StudyPost = () => { selectedCategory={selectedCategory} tags={tags} setTags={setTags} + viewTag={viewTag} + setViewTag={setViewTag} />