diff --git a/package-lock.json b/package-lock.json index 0615b9d6..0ffa9ed5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,9 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.2", "react": "^18.2.0", + "react-cookie": "^7.1.4", "react-dom": "^18.2.0", "react-router-dom": "^6.22.0", "react-scripts": "5.0.1", @@ -4076,6 +4078,11 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "node_modules/@types/eslint": { "version": "8.56.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", @@ -4129,6 +4136,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -5425,6 +5441,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -8454,9 +8493,9 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==" }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -9079,6 +9118,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -14639,6 +14691,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -14788,6 +14845,19 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/react-cookie": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.1.4.tgz", + "integrity": "sha512-wDxxa/HYaSXSMlyWJvJ5uZTzIVtQTPf1gMksFgwAz/2/W3lCtY8r4OChCXMPE7wax0PAdMY97UkNJedGv7KnDw==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.5", + "hoist-non-react-statics": "^3.3.2", + "universal-cookie": "^7.0.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -17094,6 +17164,23 @@ "node": ">=8" } }, + "node_modules/universal-cookie": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.1.4.tgz", + "integrity": "sha512-Q+DVJsdykStWRMtXr2Pdj3EF98qZHUH/fXv/gwFz/unyToy1Ek1w5GsWt53Pf38tT8Gbcy5QNsj61Xe9TggP4g==", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0" + } + }, + "node_modules/universal-cookie/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/package.json b/package.json index dcf8eba4..47146efd 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.2", "react": "^18.2.0", + "react-cookie": "^7.1.4", "react-dom": "^18.2.0", "react-router-dom": "^6.22.0", "react-scripts": "5.0.1", diff --git a/src/apis/api.js b/src/apis/api.js new file mode 100644 index 00000000..49c9942e --- /dev/null +++ b/src/apis/api.js @@ -0,0 +1,128 @@ +import { instance, instanceWithToken } from "./axios"; + +// Account 관련 API들 +export const signIn = async (data) => { + const response = await instance.post("/account/signin/", data); + if (response.status === 200) { + window.location.href = "/"; + } else { + console.log("Error"); + } +}; + +export const signUp = async (data) => { + const response = await instance.post("/account/signup/", data); + if (response.status === 200 || response.status === 201) { + window.location.href = "/"; + } else { + console.log("Error"); + } + return response; +}; + +export const getUser = async () => { + const response = await instanceWithToken.get("/account/info/"); + if (response.status === 200) { + console.log("GET USER SUCCESS"); + } else { + console.log("[ERROR] error while updating comment"); + } + return response.data; +}; + +// POST 관련 API들 +export const getPosts = async () => { + const response = await instance.get("/post/"); + return response.data; +}; + +export const getPost = async (id) => { + const response = await instance.get(`/post/${id}/`); + return response.data; +}; + +export const createPost = async (data, navigate) => { + const response = await instanceWithToken.post("/post/", data); + if (response.status === 201) { + console.log("POST SUCCESS"); + navigate("/"); + } else { + console.log("[ERROR] error while creating post"); + } +}; + +export const updatePost = async (id, data, navigate) => { + const response = await instanceWithToken.put(`/post/${id}/`, data); + if (response.status === 200) { + console.log("POST UPDATE SUCCESS"); + navigate(-1); + } else { + console.log("[ERROR] error while updating post"); + } +}; + +export const deletePost = async (id, navigate) => { + const response = await instanceWithToken.delete(`/post/${id}/`); + if (response.status === 204) { + console.log("DELETE SUCCESS"); + navigate(-1); + } else { + console.log("[ERROR] error while deleting post"); + } +}; + +// 과제!! +export const likePost = async (postId) => {}; + +// Tag 관련 API들 +export const getTags = async () => { + const response = await instance.get("/tag/"); + return response.data; +}; + +export const createTag = async (data) => { + const response = await instanceWithToken.post("/tag/", data); + if (response.status === 201) { + console.log("TAG SUCCESS"); + } else { + console.log("[ERROR] error while creating tag"); + } + return response; // response 받아서 그 다음 처리 +}; + +// Comment 관련 API들 +export const getComments = async (postId) => { + const response = await instance.get(`/comment/?post=${postId}`); + return response.data; +}; + +export const createComment = async (data) => { + const response = await instanceWithToken.post("/comment/", data); + if (response.status === 201) { + console.log("COMMENT SUCCESS"); + window.location.reload(); // 새로운 코멘트 생성시 새로고침으로 반영 + } else { + console.log("[ERROR] error while creating comment"); + } +}; + +export const updateComment = async (id, data) => { + const response = await instanceWithToken.put(`/comment/${id}/`, data); // 혹시 patch로 구현했다면 .patch + if (response.status === 200) { + console.log("COMMENT UPDATE SUCCESS"); + window.location.reload(); + } else { + console.log("[ERROR] error while updating comment"); + } +}; + +// 과제 !! +export const deleteComment = async (id) => { + const response = await instanceWithToken.delete(`/comment/${id}/`); + if (response.status === 204) { + console.log("COMMENT DELETE SUCCESS"); + window.location.reload(); + } else { + console.log("[ERROR] error while deleting comment"); + } +}; diff --git a/src/apis/axios.js b/src/apis/axios.js new file mode 100644 index 00000000..857476d6 --- /dev/null +++ b/src/apis/axios.js @@ -0,0 +1,52 @@ +import axios from "axios"; +import { getCookie } from "../utils/cookie"; + +// baseURL, credential, 헤더 세팅 +axios.defaults.baseURL = "http://localhost:8000/api"; +axios.defaults.withCredentials = true; +axios.defaults.headers.post["Content-Type"] = "application/json"; +axios.defaults.headers.common["X-CSRFToken"] = getCookie("csrftoken"); + +// 누구나 접근 가능한 API들 +export const instance = axios.create(); + +// Token 있어야 접근 가능한 API들 - 얘는 토큰을 넣어줘야 해요 +export const instanceWithToken = axios.create(); + +// instanceWithToken에는 쿠키에서 토큰을 찾고 담아줍시다! +instanceWithToken.interceptors.request.use( + // 요청을 보내기전 수행할 일 + // 사실상 이번 세미나에 사용할 부분은 이거밖에 없어요 + (config) => { + const accessToken = getCookie("access_token"); + + if (!accessToken) { + // token 없으면 리턴 + return; + } else { + // token 있으면 헤더에 담아주기 (Authorization은 장고에서 JWT 토큰을 인식하는 헤더 key) + config.headers["Authorization"] = `Bearer ${accessToken}`; + } + return config; + }, + + // 클라이언트 요청 오류 났을 때 처리 + (error) => { + // 콘솔에 찍어주고, 요청을 보내지 않고 오류를 발생시킴 + console.log("Request Error!!"); + return Promise.reject(error); + } +); + +instanceWithToken.interceptors.response.use( + (response) => { + // 서버 응답 데이터를 프론트에 넘겨주기 전 수행할 일 + console.log("Interceptor Response!!"); + return response; + }, + (error) => { + // 서버가 오류를 응답했을 때 처리 - 콘솔 찍어주고, 프론트에게 보내지 않고 오류를 발생시킴 + console.log("Response Error!!"); + return Promise.reject(error); + } +); diff --git a/src/components/Comment/CommentElement.jsx b/src/components/Comment/CommentElement.jsx index fd48385e..883941ce 100644 --- a/src/components/Comment/CommentElement.jsx +++ b/src/components/Comment/CommentElement.jsx @@ -1,59 +1,87 @@ import { useState, useEffect } from "react"; +import { getCookie } from "../../utils/cookie"; +import { getUser } from "../../apis/api"; const CommentElement = (props) => { - const { comment, handleCommentDelete, postId } = props; - const [content, setContent] = useState(comment.content); - const [isEdit, setIsEdit] = useState(false); + const { comment, handleCommentDelete, postId } = props; + const [content, setContent] = useState(comment.content); + const [isEdit, setIsEdit] = useState(false); + const [onChangeValue, setOnChangeValue] = useState(content); // 수정 취소 시 직전 content 값으로 변경을 위한 state + const [user, setUser] = useState(); - const [onChangeValue, setOnChangeValue] = useState(content); // 수정 취소 시 직전 content 값으로 변경을 위한 state + // comment created_at 전처리 + const date = new Date(comment.created_at); + const year = date.getFullYear(); + let month = date.getMonth() + 1; + month = month < 10 ? `0${month}` : month; + let day = date.getDate(); + day = day < 10 ? `0${day}` : day; - // comment created_at 전처리 - const date = new Date(comment.created_at); - const year = date.getFullYear(); - let month = date.getMonth() + 1; - month = month < 10 ? `0${month}` : month; - let day = date.getDate(); - day = day < 10 ? `0${day}` : day; + const handleEditComment = () => { + // add api call for editing comment + setContent(onChangeValue); + setIsEdit(!isEdit); + console.log({ + post: postId, + comment: comment.id, + content: content, + }); + }; - const handleEditComment = () => { // add api call for editing comment - setContent(onChangeValue); - setIsEdit(!isEdit); - console.log({ - post: postId, - comment: comment.id, - content: content - }); - }; + useEffect(() => { + // add api call to check if user is the author of the comment + if (getCookie("access_token")) { + const getUserAPI = async () => { + const user = await getUser(); + setUser(user); + }; + getUserAPI(); + } + }, []); - useEffect(() => { // add api call to check if user is the author of the comment - }, []); + return ( +
{content}
+ )} - return ( -{content}
- )} + + {year}.{month}.{day} + +