From d4d6ec58ec42ad7a9eceb8a8e616ad1e4616dc9f Mon Sep 17 00:00:00 2001 From: minseo Date: Fri, 31 May 2024 00:05:51 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AD=90=EA=B0=80=20=EB=AC=B8=EC=A0=9C?= =?UTF-8?q?=EC=95=BC=20=E3=85=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 93 +++++++++++++++- package.json | 2 + src/apis/api.js | 128 ++++++++++++++++++++++ src/apis/axios.js | 52 +++++++++ src/components/Comment/CommentElement.jsx | 124 +++++++++++++-------- src/components/Comment/index.jsx | 112 +++++++++++-------- src/components/Header/index.jsx | 24 +++- src/routes/HomePage.jsx | 73 ++++++++---- src/routes/PostCreatePage.jsx | 43 +++----- src/routes/PostDetailPage.jsx | 61 ++++++++--- src/routes/PostEditPage.jsx | 48 +++++--- src/routes/SignInPage.jsx | 98 +++++++++-------- src/routes/SignUpPage.jsx | 13 ++- src/utils/cookie.js | 19 ++++ 14 files changed, 654 insertions(+), 236 deletions(-) create mode 100644 src/apis/api.js create mode 100644 src/apis/axios.js create mode 100644 src/utils/cookie.js 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 ( +
+
+ {isEdit ? ( + setOnChangeValue(e.target.value)} + /> + ) : ( +

{content}

+ )} - return ( -
-
- {isEdit ? ( - setOnChangeValue(e.target.value)} /> - ) : ( -

{content}

- )} + + {year}.{month}.{day} + +
- {year}.{month}.{day} -
- -
- {isEdit ? ( - <> - - - - ) : ( - <> - - - - )} -
-
- ); +
+ {user?.id === comment?.author.id ? ( + isEdit ? ( + <> + + + + ) : ( + <> + + + + ) + ) : null} +
+
+ ); }; export default CommentElement; diff --git a/src/components/Comment/index.jsx b/src/components/Comment/index.jsx index c8c4a27e..5614ec19 100644 --- a/src/components/Comment/index.jsx +++ b/src/components/Comment/index.jsx @@ -1,53 +1,79 @@ -import { useState } from "react"; -import comments from "../../data/comments"; // dummy data +import { useState, useEffect } from "react"; import CommentElement from "./CommentElement"; +import { + createComment, + getComments, + getUser, + deleteComment, +} from "../../apis/api"; +import { getCookie } from "../../utils/cookie"; const Comment = ({ postId }) => { - const [commentList, setCommentList] = useState(comments); // state for comments - const [newContent, setNewContent] = useState(""); // state for new comment + const [commentList, setCommentList] = useState([]); // state for comments + const [newContent, setNewContent] = useState(""); // state for new comment + const [isLoggedIn, setIsLoggedIn] = useState(); - const handleCommentSubmit = (e) => { - e.preventDefault(); - setCommentList([ // TODO: add api call for creating comment - ...commentList, - { - id: commentList.length + 1, - content: newContent, - created_at: new Date().toISOString(), - post: postId, - author: { - id: 1, - username: "user1" - } - } - ]); - console.log({ - post: postId, - content: newContent - }); - setNewContent(""); + useEffect(() => { + const getCommentsAPI = async () => { + const comments = await getComments(postId); + setCommentList(comments); }; + getCommentsAPI(); + const loggedIn = getCookie("access_token") ? true : false; + setIsLoggedIn(loggedIn); + }, []); - const handleCommentDelete = (commentId) => { - console.log("comment: ", commentId); - setCommentList(commentList.filter((comment) => comment.id !== commentId)); // TODO: add api call for deleting comment - }; + const handleCommentSubmit = (e) => { + e.preventDefault(); // event 가져올 때 기존의 빈 요청이 보내지는 것을 막는 ? 용도 + createComment({ + content: newContent, + post: postId, + }); + setNewContent(""); // 댓글 submit시 댓글을 비워주는 애 + }; + + const handleCommentDelete = async (commentId) => { + const confirmDelete = window.confirm("정말 삭제하시겠습니까?"); + if (!confirmDelete) return; + try { + await deleteComment(commentId); + } catch (error) { + console.error(error); + } + }; + + return ( +
+

Comments

+ {commentList.map((comment) => { + return ( + + ); + })} - return ( -
-

Comments

- {commentList.map((comment) => { - return ( - - ); - })} - -
- setNewContent(e.target.value)} /> - -
-
- ); +
+ setNewContent(e.target.value)} + /> + +
+
+ ); }; export default Comment; diff --git a/src/components/Header/index.jsx b/src/components/Header/index.jsx index c71dbebf..1ac1c51d 100644 --- a/src/components/Header/index.jsx +++ b/src/components/Header/index.jsx @@ -1,17 +1,23 @@ import { useState, useEffect } from "react"; import { Link } from "react-router-dom"; +import { getCookie, removeCookie } from "../../utils/cookie"; import lion from "../../assets/images/lion.jpeg"; const Header = () => { const [isUserLoggedIn, setIsUserLoggedIn] = useState(false); // 로그인 여부 상태, 우선 false로 초기화 + // getCookie를 통해 access token을 가져올 수 있으면 로그인 된 것으로 설정 useEffect(() => { - // TODO: 이후 api 연결 시 유효한 token이 있다면 setIsUserLoggedIn(true)로 상태 변경하는 코드 작성 + const loggedIn = getCookie("access_token") ? true : false; + setIsUserLoggedIn(loggedIn); }, []); + // 로그아웃 시 cookie에서 access_token, refresh_token 제거 const handleSignOut = () => { - // TODO: 이후 api 연결 시 token 제거 + removeCookie("access_token"); + removeCookie("refresh_token"); + window.location.href = "/"; // 새로고침 - 로그아웃 되었다는 것을 인지시켜주기 위해 }; return ( @@ -21,9 +27,15 @@ const Header = () => {
SNULION BLOG
- {isUserLoggedIn ? - sign out - : + {isUserLoggedIn ? ( + + sign out + + ) : ( <> sign in @@ -32,7 +44,7 @@ const Header = () => { sign up - } + )}
); diff --git a/src/routes/HomePage.jsx b/src/routes/HomePage.jsx index 10caaf8e..85c9c977 100644 --- a/src/routes/HomePage.jsx +++ b/src/routes/HomePage.jsx @@ -2,22 +2,35 @@ import { useState, useEffect } from "react"; import { SmallPost } from "../components/Posts"; import { Link } from "react-router-dom"; import posts from "../data/posts"; +import { getPosts, getTags } from "../apis/api"; // Post들을 전부 받아오는 getPosts api 불러오기 +import { getCookie } from "../utils/cookie"; const HomePage = () => { - const [postList, setPostList] = useState(posts); - const [tags, setTags] = useState([]); const [searchTags, setSearchTags] = useState([]); const [searchValue, setSearchValue] = useState(""); + const [postList, setPostList] = useState([]); // 처음에는 빈 리스트로 설정 + useEffect(() => { - const tagList = posts.reduce((acc, post) => { - for (let tag of post.tags) { - acc.add(tag.content); - } - return acc; - }, new Set()); - setTags([...tagList]); - setSearchTags([...tagList]); + const getPostsAPI = async () => { + const posts = await getPosts(); + setPostList(posts); + }; + getPostsAPI(); + // 처음 한 번만 실행 + // 작성했던 getPosts()를 호출한 후, setPostList를 통해 postList에 저장 + + const getTagsAPI = async () => { + const tags = await getTags(); + const tagContents = tags.map((tag) => { + return tag.content; + }); + setTags(tagContents); + setSearchTags(tagContents); + }; + getTagsAPI(); + // getTags() 이용해서 tag들 불러오고 tags.map을 이용해서 tagContents에 + // tag.content만 저장한 후, tags와 searchTags에 저장 }, []); const handleChange = (e) => { @@ -25,20 +38,19 @@ const HomePage = () => { const newTags = tags.filter((tag) => tag.includes(value)); setSearchTags(newTags); }; + const handleTagFilter = (e) => { const { innerText } = e.target; if (searchValue === innerText.substring(1)) { setSearchValue(""); - setPostList(posts); } else { const activeTag = innerText.substring(1); setSearchValue(activeTag); - const newPosts = posts.filter((post) => - post.tags.find((tag) => tag.content === activeTag) - ); - setPostList(newPosts); } }; + // 태그를 눌렀을 때는 searchValue만 바뀜 + + console.log(getCookie("access token")); return (
@@ -66,16 +78,29 @@ const HomePage = () => { ); })}
-
- {postList.map((post) => ( - - ))} -
-
- - 작성 - +
+ {postList + .filter((post) => + searchValue + ? post.tags.find((tag) => tag.content === searchValue) + : post + ) + .map((post) => ( + + ))}
+ + {/* searchValue가 있으면 postList에서 필터링 후, map함수를 이용해 SmallPost + 리턴 */} + + {getCookie("access_token") ? ( +
+ + Post + +
+ ) : null} + {/* 로그인해야지만 Post 버튼 보이도록 설정 */}
); }; diff --git a/src/routes/PostCreatePage.jsx b/src/routes/PostCreatePage.jsx index 45bf5225..34fec1cc 100644 --- a/src/routes/PostCreatePage.jsx +++ b/src/routes/PostCreatePage.jsx @@ -1,17 +1,14 @@ import { useState, useEffect } from "react"; import posts from "../data/posts"; import { BigPost } from "../components/Posts"; +import { getTags, createPost } from "../apis/api"; +import { useNavigate } from "react-router-dom"; const PostCreatePage = () => { - const [isSubmitted, setIsSubmitted] = useState(false); const [post, setPost] = useState({ - id: posts.length, title: "", content: "", - author: { id: posts.length, username: "아기사자" }, tags: [], - like_users: [], - created_at: "2024-02-04T07:42:50.658501Z", }); const [tagInputValue, setTagInputValue] = useState(""); @@ -20,14 +17,14 @@ const PostCreatePage = () => { const [tags, setTags] = useState([]); useEffect(() => { - const duplicatedTagList = posts.reduce((acc, post) => { - for (let tag of post.tags) { - acc.add(tag.content); - } - return acc; - }, new Set()); - const tagList = [...duplicatedTagList]; - setTags([...tagList]); + const getTagsAPI = async () => { + const tags = await getTags(); + const tagContents = tags.map((tag) => { + return tag.content; + }); + setTags(tagContents); + }; + getTagsAPI(); }, []); const handleChange = (e) => { @@ -80,26 +77,14 @@ const PostCreatePage = () => { }); }; + const navigate = useNavigate(); + const onSubmit = (e) => { e.preventDefault(); - const createdPost = { - ...post, - like_users: [], - tags: post.tags.map((tag, idx) => { - return { id: idx + 1, content: tag }; - }), - }; - setPost(createdPost); - setIsSubmitted(true); - alert("게시글을 등록합니다."); - //TODO : api connect + createPost(post, navigate); }; - return isSubmitted ? ( -
- -
- ) : ( + return (

게시글 작성

diff --git a/src/routes/PostDetailPage.jsx b/src/routes/PostDetailPage.jsx index 26d11e4d..eaeba3de 100644 --- a/src/routes/PostDetailPage.jsx +++ b/src/routes/PostDetailPage.jsx @@ -2,23 +2,46 @@ import { useState, useEffect } from "react"; import { useParams, Link, useNavigate } from "react-router-dom"; import { BigPost } from "../components/Posts"; import Comment from "../components/Comment"; - -import posts from "../data/posts"; +import { getPost, getUser, deletePost } from "../apis/api"; +import { getCookie } from "../utils/cookie"; const PostDetailPage = () => { const { postId } = useParams(); + const [post, setPost] = useState(); + const [user, setUser] = useState(); // user의 정보를 담아둘 수 있기 위해서 + const [CommentList, setCommentList] = useState([]); - const [post, setPost] = useState(null); useEffect(() => { - const post = posts.find((post) => post.id === parseInt(postId)); - setPost(post); + const getPostAPI = async () => { + const post = await getPost(postId); + setPost(post); + }; + getPostAPI(); }, [postId]); + // 작성했던 getPost()를 호출한 후, setPostList를 통해 postList에 저장 + + useEffect(() => { + // access_token이 있으면 유저 정보 가져옴 + if (getCookie("access_token")) { + const getUserAPI = async () => { + const user = await getUser(); + setUser(user); + }; + getUserAPI(); + } + }, []); + // 처음 한번만 실행 const navigate = useNavigate(); - const onClickDelete = () => { - alert("게시물을 삭제합니다."); - navigate("/"); - // add api call for deleting post + + const onClickDelete = async () => { + const confirmDelete = window.confirm("정말 삭제하시겠습니까?"); + if (!confirmDelete) return; + try { + await deletePost(postId, navigate); + } catch (error) { + console.error(error); + } }; return ( @@ -28,12 +51,20 @@ const PostDetailPage = () => {
- - - - + {user?.id === post?.author.id ? ( + <> + + + + + + ) : null} + {/* user와 post.author가 동일하면 버튼을 리턴, 아니면 null */}
) diff --git a/src/routes/PostEditPage.jsx b/src/routes/PostEditPage.jsx index 5a1a5742..19071bad 100644 --- a/src/routes/PostEditPage.jsx +++ b/src/routes/PostEditPage.jsx @@ -2,21 +2,33 @@ import { useEffect, useState } from "react"; import { useParams } from "react-router-dom"; import posts from "../data/posts"; import { BigPost } from "../components/Posts"; +import { getTags, getPost } from "../apis/api"; +import { updatePost } from "../apis/api"; +import { useNavigate } from "react-router-dom"; const PostEditPage = () => { const { postId } = useParams(); const [isSubmitted, setIsSubmitted] = useState(false); + const [tags, setTags] = useState([]); + useEffect(() => { + const getTagsAPI = async () => { + const tags = await getTags(); + const tagContents = tags.map((tag) => { + return tag.content; + }); + setTags(tagContents); + }; + getTagsAPI(); + }, []); + const [tagInputValue, setTagInputValue] = useState(""); const [autoCompletes, setAutoCompletes] = useState([]); + const [post, setPost] = useState({ - id: posts.length, title: "", content: "", - author: { id: posts.length, username: "아기사자" }, tags: [], - like_users: [], - created_at: "2024-02-04T07:42:50.658501Z", }); useEffect(() => { @@ -30,10 +42,17 @@ const PostEditPage = () => { setTags([...tagList]); }, []); + // post를 받아와서 넣어주기 useEffect(() => { - const post = posts.find((post) => post.id === parseInt(postId)); - const originalPost = { ...post, tags: post.tags.map((tag) => tag.content) }; - setPost(originalPost); + const getPostAPI = async () => { + const post = await getPost(postId); + const postFormData = { + ...post, + tags: post.tags.map((tag) => tag.content), + }; + setPost(postFormData); + }; + getPostAPI(); }, [postId]); const handleChange = (e) => { @@ -83,18 +102,11 @@ const PostEditPage = () => { }); }; + const navigate = useNavigate(); + const onSubmit = (e) => { - const editedPost = { - ...post, - like_users: [], - tags: post.tags.map((tag, idx) => { - return { id: idx + 1, content: tag }; - }), - }; - setPost(editedPost); - setIsSubmitted(true); - alert("게시물을 수정합니다."); - // TODO : api connect(edit post) + e.preventDefault(); + updatePost(postId, post, navigate); }; return isSubmitted ? ( diff --git a/src/routes/SignInPage.jsx b/src/routes/SignInPage.jsx index 04cd8232..722a79d1 100644 --- a/src/routes/SignInPage.jsx +++ b/src/routes/SignInPage.jsx @@ -1,53 +1,63 @@ import { useState } from "react"; +import { getCookie } from "../utils/cookie"; +import axios from "axios"; +import { signIn } from "../apis/api"; const SignInPage = () => { - const [signInData, setSignInData] = useState({ - username: "", - password: "", - }); + const [signInData, setSignInData] = useState({ + username: "", + password: "", + }); - const handleSignInData = (e) => { - const { id, value } = e.target; - setSignInData({ ...signInData, [id]: value }); - }; + const handleSignInData = (e) => { + const { id, value } = e.target; + setSignInData({ ...signInData, [id]: value }); + }; - const handleSignInSubmit = (e) => { - e.preventDefault(); // to prevent reloading the page - console.log(signInData); - alert("로그인 하기"); // TODO: add api call for sign in - }; + const handleSignInSubmit = (e) => { + e.preventDefault(); // to prevent reloading the page + signIn(signInData); + }; - return ( -
-

로그인

- - - - - - - -
- - -
- -
- ); + return ( +
+

로그인

+
+ + + + + + +
+ + +
+
+
+ ); }; export default SignInPage; diff --git a/src/routes/SignUpPage.jsx b/src/routes/SignUpPage.jsx index 4b4c893e..0b9ab7c8 100644 --- a/src/routes/SignUpPage.jsx +++ b/src/routes/SignUpPage.jsx @@ -1,4 +1,6 @@ import { useState } from "react"; +import axios from "axios"; +import { signUp } from "../apis/api"; const SignUpPage = () => { const [signUpData, setSignUpData] = useState({ @@ -15,11 +17,11 @@ const SignUpPage = () => { setSignUpData({ ...signUpData, [id]: value }); }; - const handleSignUpSubmit = (e) => { + const handleSignUpSubmit = async (e) => { e.preventDefault(); // to prevent reloading the page - console.log(signUpData); - alert("회원가입 하기"); // TODO: add api call for sign up + signUp(signUpData); }; + // signUp함수에 formData를 전달하여 회원가입 요청 return (
@@ -32,8 +34,8 @@ const SignUpPage = () => { required type="email" id="email" - className="input" - value={signUpData.email} + className="input" + value={signUpData.email} onChange={handleSignUpData} /> @@ -61,7 +63,6 @@ const SignUpPage = () => { onChange={handleSignUpData} /> - diff --git a/src/utils/cookie.js b/src/utils/cookie.js new file mode 100644 index 00000000..158d8229 --- /dev/null +++ b/src/utils/cookie.js @@ -0,0 +1,19 @@ +import { Cookies } from "react-cookie"; + +const cookies = new Cookies(); + +// 쿠키 설정하는 함수 +// 궁금하실까봐 만들긴 했는데, 우리는 안 쓸거에요!! (쿠키에 토큰 넣어주는 건 서버에서 해주니까요) +export const setCookie = (name, value, option) => { + return cookies.set(name, value, { ...option }); +}; + +// 쿠키 정보 가져오는 함수 +export const getCookie = (name) => { + return cookies.get(name); +}; + +// 쿠키 정보 삭제하는 함수 +export const removeCookie = (name) => { + cookies.remove(name); +};