From d14b9dde9a2bae6338f0ef0c80e9468ec78ae61d Mon Sep 17 00:00:00 2001 From: yeonghun Date: Sat, 6 Apr 2024 16:40:22 +0900 Subject: [PATCH 1/2] week5 --- src/components/Header/index.jsx | 30 +++++++-- src/components/Posts/index.jsx | 17 ++++- src/routes/HomePage.jsx | 49 +++++++++++++- src/routes/PostCreatePage.jsx | 108 ++++++++++++++++++++++++++++-- src/routes/PostDetailPage.jsx | 9 +-- src/routes/PostEditPage.jsx | 114 +++++++++++++++++++++++++++++--- src/routes/SignInPage.jsx | 76 +++++++++++++++------ src/routes/SignUpPage.jsx | 64 ++++++++++++++++-- 8 files changed, 413 insertions(+), 54 deletions(-) diff --git a/src/components/Header/index.jsx b/src/components/Header/index.jsx index 99e87e1b..951c8bbc 100644 --- a/src/components/Header/index.jsx +++ b/src/components/Header/index.jsx @@ -1,8 +1,18 @@ +import { useState, useEffect } from "react"; import { Link } from "react-router-dom"; import lion from "../../assets/images/lion.jpeg"; const Header = () => { + const [isUserLoggedIn, setIsUserLoggedIn] = useState(false); // 로그인 여부 상태, 우선 false로 초기화 + + useEffect(() => { + // TODO: 이후 api 연결 시 유효한 token이 있다면 setIsUserLoggedIn(true)로 상태 변경하는 코드 작성 + }, []); + + const handleSignOut = () => { + // TODO: 이후 api 연결 시 token 제거 + }; return (
@@ -10,12 +20,20 @@ const Header = () => {
SNULION BLOG
- - sign in - - - sign up - + {isUserLoggedIn ? ( + + sign out + + ) : ( + <> + + sign in + + + sign up + + + )}
); diff --git a/src/components/Posts/index.jsx b/src/components/Posts/index.jsx index 07686dcc..506a468e 100644 --- a/src/components/Posts/index.jsx +++ b/src/components/Posts/index.jsx @@ -1,6 +1,12 @@ import { Link } from "react-router-dom"; export const SmallPost = ({ post }) => { + const onClickLike = (e) => { + e.preventDefault(); + alert("나도 좋아!"); + // add api call for liking post here + }; + return ( { ))} - + {post.like_users.length > 0 && `❤️ ${post.like_users.length}`} @@ -23,6 +29,10 @@ export const SmallPost = ({ post }) => { }; export const BigPost = ({ post }) => { + const onClickLike = () => { + alert("나도 좋아!"); + // add api call for liking post here + }; return (
@@ -44,7 +54,10 @@ export const BigPost = ({ post }) => { ))}
- + ❤️ {post.like_users.length > 0 ? post.like_users.length : "0"}
diff --git a/src/routes/HomePage.jsx b/src/routes/HomePage.jsx index 027eae34..b97e8010 100644 --- a/src/routes/HomePage.jsx +++ b/src/routes/HomePage.jsx @@ -1,11 +1,43 @@ +import { useState, useEffect } from "react"; import { SmallPost } from "../components/Posts"; import { Link } from "react-router-dom"; import posts from "../data/posts"; const HomePage = () => { - const postList = posts; + const [postList, setPostList] = useState(posts); + const [tags, setTags] = useState([]); + const [searchTags, setSearchTags] = useState([]); + const [searchValue, setSearchValue] = useState(""); - const handleChange = (e) => {}; + 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 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); + } + }; + const handleChange = (e) => { + const { value } = e.target; + const newTags = tags.filter((tag) => tag.includes(value)); + setSearchTags(newTags); + }; return (
@@ -20,6 +52,19 @@ const HomePage = () => { className="border border-orange-400 outline-none rounded-2xl text-center py-2 px-20 text-orange-400 bg-transparent" />
+
+ {searchTags.map((tag) => { + return ( + + ); + })} +
{postList.map((post) => ( diff --git a/src/routes/PostCreatePage.jsx b/src/routes/PostCreatePage.jsx index a33c7af4..1dd42922 100644 --- a/src/routes/PostCreatePage.jsx +++ b/src/routes/PostCreatePage.jsx @@ -1,7 +1,13 @@ +import { useState, useEffect } from "react"; import posts from "../data/posts"; +import { BigPost } from "../components/Posts"; const PostCreatePage = () => { - const post = { + const [isSubmitted, setIsSubmitted] = useState(false); + const [tags, setTags] = useState([]); + const [tagInputValue, setTagInputValue] = useState(""); + const [autoCompletes, setAutoCompletes] = useState([]); + const [post, setPost] = useState({ id: posts.length, title: "", content: "", @@ -9,14 +15,82 @@ const PostCreatePage = () => { 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]); + }, []); + + 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 handleTag = (e) => { + setTagInputValue(e.target.value); + if (e.target.value) { + const autoCompleteData = tags.filter((tag) => + tag.includes(e.target.value) + ); + setAutoCompletes(autoCompleteData); + } else { + setAutoCompletes([]); + } + }; + const handleChange = (e) => { + setPost({ ...post, [e.target.id]: e.target.value }); }; 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(create post) }; - return ( + 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), + }); + }; + + return isSubmitted ? ( +
+ +
+ ) : (

게시글 작성

@@ -28,7 +102,9 @@ const PostCreatePage = () => { placeholder="제목을 입력하세요" defaultValue={post.title} id="title" + value={post.title} className="input" + onChange={handleChange} required />
+
+ {autoCompletes && + autoCompletes.map((autoComplete) => ( + + ))} +
{post.tags && (
{post.tags.map((tag) => ( @@ -66,7 +161,10 @@ const PostCreatePage = () => {

#{tag}

-
))} diff --git a/src/routes/PostDetailPage.jsx b/src/routes/PostDetailPage.jsx index 079fd151..27a57d0d 100644 --- a/src/routes/PostDetailPage.jsx +++ b/src/routes/PostDetailPage.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from "react"; -import { useParams, Link } from "react-router-dom"; +import { useParams, Link, useNavigate } from "react-router-dom"; import { BigPost } from "../components/Posts"; import posts from "../data/posts"; @@ -10,10 +10,11 @@ const PostDetailPage = () => { 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 ( diff --git a/src/routes/PostEditPage.jsx b/src/routes/PostEditPage.jsx index ff86e811..b28fbea4 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 [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}

))} diff --git a/src/routes/SignInPage.jsx b/src/routes/SignInPage.jsx index 537272d3..1a17be90 100644 --- a/src/routes/SignInPage.jsx +++ b/src/routes/SignInPage.jsx @@ -1,25 +1,61 @@ +import { useState } from "react"; + const SignInPage = () => { - const handleSignInSubmit = () => { - alert("로그인 하기"); // TODO: add api call for sign in - }; + const [signInData, setSignInData] = useState({ + username: "", + password: "", + }); + + 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 + }; + + return ( +
+

로그인

+ + + + + + - return ( -
-

로그인

- - - - - - - -
- - -
- -
- ); +
+ + +
+ +
+ ); }; export default SignInPage; diff --git a/src/routes/SignUpPage.jsx b/src/routes/SignUpPage.jsx index 9b87106b..dc91ac8a 100644 --- a/src/routes/SignUpPage.jsx +++ b/src/routes/SignUpPage.jsx @@ -1,5 +1,22 @@ +import { useState } from "react"; + const SignUpPage = () => { - const handleSignUpSubmit = () => { + const [signUpData, setSignUpData] = useState({ + email: "", + password: "", + confirm_password: "", + username: "", + college: "", + major: "", + }); + + const handleSignUpData = (e) => { + const { id, value } = e.target; + setSignUpData({ ...signUpData, [id]: value }); + }; + const handleSignUpSubmit = (e) => { + e.preventDefault(); // to prevent reloading the page + console.log(signUpData); alert("회원가입 하기"); // TODO: add api call for sign up }; @@ -10,17 +27,38 @@ const SignUpPage = () => { - + - + - +