diff --git a/src/components/Comment/CommentElement.jsx b/src/components/Comment/CommentElement.jsx index e69de29b..b0d31873 100644 --- a/src/components/Comment/CommentElement.jsx +++ b/src/components/Comment/CommentElement.jsx @@ -0,0 +1,85 @@ +import { useState, useEffect } from "react"; + +const CommentElement = (props) => { + /* TODO: props 받기 + Hint: src/components/Comment/index.jsx에서 어떠한 props를 넘겨주는지 확인해보세요! */ + const { comment, handleCommentDelete, handleCommentEdit } = props; + + /* TODO: 댓글을 수정하는 input의 value를 관리하기 위한 state 작성 + Hint: 댓글의 내용을 저장하는 state와 수정 중인지 여부를 저장하는 state를 따로 만드는 게 좋겠죠? */ + + const [content, setContent] = useState(comment.content); + const [isEdit, setIsEdit] = useState(false); + // 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 = (e) => { + // 댓글 수정 API 호출 + // alert("댓글 수정"); + setIsEdit(!isEdit); + handleCommentEdit(comment.id, content); + }; + + useEffect(() => { + // add api call to check if user is the author of the comment + }, []); + + return ( +
+
+ {/* // TODO: 수정 중일 때와 아닐 때를 나눠서 보여줘야 해요! + {수정 중 ? :

댓글 내용

} */} + {isEdit ? ( + { + setContent(e.target.value); + }} + /> + ) : ( +

{comment.content}

+ )} + + + {year}.{month}.{day} + +
+ +
+ {isEdit ? ( + <> + + + + ) : ( + <> + + + + )} +
+
+ ); +}; +export default CommentElement; diff --git a/src/components/Comment/index.jsx b/src/components/Comment/index.jsx index e86f5bfd..6428ae47 100644 --- a/src/components/Comment/index.jsx +++ b/src/components/Comment/index.jsx @@ -4,27 +4,79 @@ import CommentElement from "./CommentElement"; const Comment = ({ postId }) => { // TODO: comments를 저장하기 위한 state를 만들어주세요 + const [commentList, setCommentList] = useState(comments); // TODO: 새로운 댓글을 추가하기 위한 state를 만들어주세요 + const [newContent, setNewContent] = useState(""); const handleCommentSubmit = (e) => { e.preventDefault(); - alert("댓글 작성"); // add api call for creating comment - // TODO: 댓글을 추가했으니 새로운 댓글을 저장하는 state를 초기화해야겠죠? - }; + let maxId = 0; + commentList.forEach((comment) => { + if (comment.id > maxId) { + maxId = comment.id; + } + }); + + const newComment = { + id: maxId + 1, + postId, + content: newContent, + created_at: "2024-04-08T15:09:43Z", + }; + + setCommentList([...commentList, newComment]); + setNewContent(""); + }; const handleCommentDelete = (commentId) => { - console.log(commentId); - alert("댓글 삭제"); // add api call for deleting comment + // console.log(commentId); + // alert("댓글 삭제"); // add api call for deleting comment + const updatedComments = commentList.filter( + (comment) => comment.id !== commentId + ); + setCommentList(updatedComments); }; + const handleCommentEdit = (commentId, editedComment) => { + const filteredComment = commentList.map((comment) => { + if (comment.id === commentId) { + comment.content = editedComment; + return comment; + } + return comment; + }); + setCommentList(filteredComment); + }; return (

Comments

- // TODO: comments 더미데이터를 돌며 각 댓글마다 CommentElement를 - 만들어주세요! // Hint: CommentElement에는 댓글인 comment와 댓글 삭제를 + {/* // TODO: comments 더미데이터를 돌며 각 댓글마다 CommentElement를 만들어주세요! // Hint: CommentElement에는 댓글인 comment와 댓글 삭제를 하는 handleCommentDelete라는 props가 필요합니다. // TODO: 새로운 댓글을 - 작성하는 form을 만들어주세요! -
+ 작성하는 form을 만들어주세요! */} + {commentList.map((comment) => ( + + ))} +
+ setNewContent(e.target.value)} + /> + + +
); }; diff --git a/src/components/Header/index.jsx b/src/components/Header/index.jsx index 626bbc49..d777b30c 100644 --- a/src/components/Header/index.jsx +++ b/src/components/Header/index.jsx @@ -20,6 +20,7 @@ const Header = () => {
{isUserLoggedIn ? ( + sign out diff --git a/src/routes/HomePage.jsx b/src/routes/HomePage.jsx index d5cdf2e4..4a0b4334 100644 --- a/src/routes/HomePage.jsx +++ b/src/routes/HomePage.jsx @@ -19,7 +19,11 @@ const HomePage = () => { setTags([...tagList]); setSearchTags([...tagList]); }, []); - const handleChange = (e) => {}; + const handleChange = (e) => { + const { value } = e.target; + const newTags = tags.filter((tag) => tag.includes(value)); + setSearchTags(newTags); + }; const handleTagFilter = (e) => { const { innerText } = e.target; if (searchValue === innerText.substring(1)) { diff --git a/src/routes/PostDetailPage.jsx b/src/routes/PostDetailPage.jsx index 079fd151..6eccdba5 100644 --- a/src/routes/PostDetailPage.jsx +++ b/src/routes/PostDetailPage.jsx @@ -1,25 +1,32 @@ import { useState, useEffect } from "react"; -import { useParams, Link } from "react-router-dom"; + +import { useParams, Link, useNavigate } from "react-router-dom"; +import Comment from "../components/Comment"; import { BigPost } from "../components/Posts"; import posts from "../data/posts"; const PostDetailPage = () => { const { postId } = useParams(); + const [post, setPost] = useState(null); + useEffect(() => { const post = posts.find((post) => post.id === parseInt(postId)); setPost(post); }, [postId]); + const navigate = useNavigate(); const onClickDelete = () => { - alert("삭제"); - //TODO : api connect(delete post) + alert("게시물을 삭제합니다."); + navigate("/"); + // add api call for deleting post }; return ( post && (
+
diff --git a/src/routes/PostEditPage.jsx b/src/routes/PostEditPage.jsx index ff86e811..80d0cc9b 100644 --- a/src/routes/PostEditPage.jsx +++ b/src/routes/PostEditPage.jsx @@ -1,24 +1,104 @@ -import { useEffect, useState } from "react"; +import { useState, useEffect } from "react"; import { useParams } from "react-router-dom"; import posts from "../data/posts"; +import { BigPost } from "../components/Posts"; const PostEditPage = () => { - const { postId } = useParams(); - const [post, setPost] = useState({}); + const { postId } = useParams(); + const [isSubmitted, setIsSubmitted] = useState(false); + const [tagInputValue, setTagInputValue] = useState(""); + const [autoCompletes, setAutoCompletes] = useState([]); + const [tags, setTags] = 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(() => { + 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]); + }, []); + useEffect(() => { const post = posts.find((post) => post.id === parseInt(postId)); const originalPost = { ...post, tags: post.tags.map((tag) => tag.content) }; setPost(originalPost); }, [postId]); + const handleChange = (e) => { + setPost({ ...post, [e.target.id]: e.target.value }); + }; + + const handleTag = (e) => { + setTagInputValue(e.target.value); + if (e.target.value) { + const autoCompleteData = tags.filter((tag) => + tag.includes(e.target.value) + ); + setAutoCompletes(autoCompleteData); + } + }; + + const handleAutoCompletes = (autoComplete) => { + const selectedTag = tags.find((tag) => tag === autoComplete); + if (post.tags.includes(selectedTag)) return; + setPost({ + ...post, + tags: [...post.tags, selectedTag], + }); + setTagInputValue(""); + setAutoCompletes([]); + }; + + const addTag = (e) => { + e.preventDefault(); + if (post.tags.find((tag) => tag === tagInputValue)) return; + setPost({ + ...post, + tags: [...post.tags, tagInputValue], + }); + setTagInputValue(""); + setAutoCompletes([]); + }; + + const deleteTag = (tag) => { + setPost({ + ...post, + tags: post.tags.filter((t) => t !== tag), + }); + }; + const onSubmit = (e) => { + e.preventDefault(); + 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) + //TODO : api connect }; - return ( + return isSubmitted ? ( +
+ +
+ ) : (

게시글 수정

@@ -29,8 +109,9 @@ const PostEditPage = () => { type="text" placeholder="제목을 입력하세요" id="title" - defaultValue={post.title} + value={post.title} className="input" + onChange={handleChange} required />
+
+ {autoCompletes && + autoCompletes.map((autoComplete) => ( + + ))} +
{post.tags && (
{post.tags.map((tag) => ( @@ -69,8 +165,8 @@ const PostEditPage = () => {

#{tag}

))} @@ -84,4 +180,4 @@ const PostEditPage = () => { ); }; -export default PostEditPage; +export default PostEditPage; \ No newline at end of file