diff --git a/lionblog-taehyeong/src/App.js b/lionblog-taehyeong/src/App.js index 59c5f9c9..87f2c086 100644 --- a/lionblog-taehyeong/src/App.js +++ b/lionblog-taehyeong/src/App.js @@ -16,7 +16,7 @@ export const DarkModeContext = createContext(false); export const PostsDataContext = createContext(null); function App() { - const [darkMode, setDarkMode] = useState(false); + const [darkMode, setDarkMode] = useState(true); const darkModeToggle = () => setDarkMode(!darkMode); diff --git a/lionblog-taehyeong/src/components/Comment/CommentElement.jsx b/lionblog-taehyeong/src/components/Comment/CommentElement.jsx new file mode 100644 index 00000000..cb651fb4 --- /dev/null +++ b/lionblog-taehyeong/src/components/Comment/CommentElement.jsx @@ -0,0 +1,84 @@ +import { useState, useEffect } from "react"; + +const CommentElement = ({ comment, handleCommentDelete }) => { + const [isEditMode, setIsEditMode] = useState(false); + const [content, setContent] = useState(comment.content); + const [editInputValue, setEditInputValue] = useState(""); + + // comment created_at 전처리 + const date = new Intl.DateTimeFormat("en-CA", { + formatString: "yyyy.mm.dd", + + year: "numeric", + month: "2-digit", + day: "2-digit", + }) + .format(new Date(comment.created_at)) + .replaceAll("-", "."); + + const handleEditComment = (e) => { + e.preventDefault(); + setContent(editInputValue); + setIsEditMode(false); + setEditInputValue(""); + }; + + const toEditMode = (e) => { + e.preventDefault(); + setIsEditMode(true); + }; + + const toShowMode = (e) => { + e.preventDefault(); + setIsEditMode(false); + }; + + useEffect(() => { + // add api call to check if user is the author of the comment + }, []); + + return ( +
+
+ {isEditMode ? ( + setEditInputValue(e.target.value)} + /> + ) : ( +

{content}

+ )} + + {date} +
+ +
+ {isEditMode ? ( + <> + + + + ) : ( + <> + + + + )} +
+
+ ); +}; +export default CommentElement; diff --git a/lionblog-taehyeong/src/components/Comment/index.jsx b/lionblog-taehyeong/src/components/Comment/index.jsx new file mode 100644 index 00000000..57ba3662 --- /dev/null +++ b/lionblog-taehyeong/src/components/Comment/index.jsx @@ -0,0 +1,83 @@ +import { useState, useEffect } from "react"; +import comments from "../../data/comment"; // dummy data +import CommentElement from "./CommentElement"; + +const Comment = ({ postId }) => { + const [commentsData, setCommentsData] = useState([]); + const [commentInputValue, setCommentInputValue] = useState(""); + + const handleCommentInputChange = (e) => { + const { value } = e.target; + setCommentInputValue(value); + }; + + const handleCommentSubmit = (e) => { + e.preventDefault(); + if (commentInputValue.length === 0) return; + + const newId = + commentsData.length > 0 + ? commentsData.sort((a, b) => a.id - b.id)[commentsData.length - 1].id + + 1 + : 1; + + setCommentsData([ + ...commentsData, + { + id: newId, + content: commentInputValue, + created_at: new Date(), + post: 1, + author: { + id: 2, + username: "user2", + }, + }, + ]); + + setCommentInputValue(""); + }; + + const handleCommentDelete = (commentId) => (e) => { + e.preventDefault(); + const updatedComments = commentsData.filter( + (comment) => comment.id !== commentId + ); + setCommentsData([...updatedComments]); + console.log(commentId); + }; + + useEffect(() => { + setCommentsData( + comments.filter((comment) => comment.post === parseInt(postId)) + ); + }, [postId]); + + return ( +
+

Comments

+ {commentsData && + commentsData.map((comment) => ( + + ))} +
+ + +
+
+ ); +}; + +export default Comment; diff --git a/lionblog-taehyeong/src/components/Header/index.jsx b/lionblog-taehyeong/src/components/Header/index.jsx index b43c4fb7..caecd93b 100644 --- a/lionblog-taehyeong/src/components/Header/index.jsx +++ b/lionblog-taehyeong/src/components/Header/index.jsx @@ -1,11 +1,23 @@ import { Link } from "react-router-dom"; -import { useContext } from "react"; +import { useContext, useState, useEffect } from "react"; import { DarkModeContext } from "../../App"; +import { useMediaQuery } from "../../hooks/useMediaQuery"; import lion from "../../assets/images/lion.jpeg"; const Header = ({ darkModeToggle }) => { const darkMode = useContext(DarkModeContext); + const isMobile = useMediaQuery("(max-width: 640px)"); + + const [isUserLoggedIn, setIsUserLoggedIn] = useState(false); // 로그인 여부 상태, 우선 false로 초기화 + + const handleSignOut = () => { + // TODO: 이후 api 연결 시 token 제거 + }; + + useEffect(() => { + // TODO: 이후 api 연결 시 유효한 token이 있다면 setIsUserLoggedIn(true)로 상태 변경하는 코드 작성 + }, []); return (
{ lion
SNULION BLOG
-
- - sign in - - - sign up - - -
+ {isMobile ? null : ( +
+ {isUserLoggedIn ? ( + + sign out + + ) : ( + <> + + sign in + + + sign up + + + )} + +
+ )}
); }; diff --git a/lionblog-taehyeong/src/components/PostWriteTemplate/index.jsx b/lionblog-taehyeong/src/components/PostWriteTemplate/index.jsx index 724b790f..b9ed2dbf 100644 --- a/lionblog-taehyeong/src/components/PostWriteTemplate/index.jsx +++ b/lionblog-taehyeong/src/components/PostWriteTemplate/index.jsx @@ -44,7 +44,6 @@ export const PostWriteTemplate = ({ initial, mode }) => { const darkMode = useContext(DarkModeContext); const posts = useContext(PostsDataContext); - // const updateTempTag = (e) => setPost({ ...post, temp_tag: e.target.value }); const updateTags = () => { const data = { ...post }; const validTags = data.tags.filter((tag) => !!tag); @@ -52,24 +51,20 @@ export const PostWriteTemplate = ({ initial, mode }) => { data.tags.push({ id: tagSorted.length === 0 ? 1 : tagSorted[tagSorted.length - 1].id + 1, - content: post.temp_tag, + content: tagInputValue, }); - data.temp_tag = ""; setPost(data); + setTagInputValue(""); }; + const updateTitle = (e) => setPost({ ...post, title: e.target.value }); const updateContent = (e) => setPost({ ...post, content: e.target.value }); const deleteTag = (tagId) => { const data = { ...post }; const afterDeleted = data.tags.filter((tag) => tag && tag.id !== tagId); setPost({ ...post, tags: afterDeleted }); - // data.tags.forEach((tag, index, array) => { - // if (tag && tag.id === tagId) array[index] = null; - // }); }; - // tag autoComplete - const onSumbitMethod = (mode) => { if (mode === "작성") { alert("게시물을 등록합니다."); @@ -82,16 +77,17 @@ export const PostWriteTemplate = ({ initial, mode }) => { posts.push({ ...post, id: newId, - author: author, - created_at: created_at, + author, + created_at, }); + navigate(`/${newId}`); } else if (mode === "수정") { alert("게시물을 수정합니다."); posts.forEach((p, i, arr) => { if (p && p.id === post.id) arr[i] = { ...post }; }); + navigate(`/${initial.id}`); } - navigate("/"); }; useEffect(() => { @@ -102,7 +98,6 @@ export const PostWriteTemplate = ({ initial, mode }) => { }, new Set()); const tagList = [...duplicatedTagList]; setTags([...tagList]); - console.log("tagList", tagList); }, []); useEffect(() => { @@ -159,13 +154,14 @@ export const PostWriteTemplate = ({ initial, mode }) => { 추가 -
+
{autoCompletes && autoCompletes.map((autoComplete) => ( diff --git a/lionblog-taehyeong/src/data/comment.js b/lionblog-taehyeong/src/data/comment.js new file mode 100644 index 00000000..f95a23e4 --- /dev/null +++ b/lionblog-taehyeong/src/data/comment.js @@ -0,0 +1,35 @@ +// dummy data +const comments = [ + { + id: 1, + content: "새해 복 많이 받으세요^^", + created_at: "2024-01-01T15:09:43Z", + post: 1, + author: { + id: 2, + username: "user2", + }, + }, + { + id: 2, + content: "축구 2대 0;;;", + created_at: "2024-02-07T15:09:43Z", + post: 1, + author: { + id: 3, + username: "user3", + }, + }, + { + id: 3, + content: "망할 개강이야...", + created_at: "2024-03-02T15:09:43Z", + post: 1, + author: { + id: 4, + username: "user4", + }, + }, +]; + +export default comments; diff --git a/lionblog-taehyeong/src/hooks/useMediaQuery.jsx b/lionblog-taehyeong/src/hooks/useMediaQuery.jsx new file mode 100644 index 00000000..91faf71b --- /dev/null +++ b/lionblog-taehyeong/src/hooks/useMediaQuery.jsx @@ -0,0 +1,24 @@ +import { useState, useEffect, useCallback } from "react"; + +const getQueryMatches = (query) => { + if (typeof window !== "undefined") return window.matchMedia(query).matches; + return false; +}; + +export const useMediaQuery = (query) => { + const [matches, setMatches] = useState(getQueryMatches(query)); + + const handleChange = useCallback(() => { + setMatches(getQueryMatches(query)); + }, []); + + useEffect(() => { + const matchMedia = window.matchMedia(query); + + matchMedia.addEventListener("change", handleChange); + + return () => matchMedia.removeEventListener("change", handleChange); + }, [query]); + + return matches; +}; diff --git a/lionblog-taehyeong/src/index.css b/lionblog-taehyeong/src/index.css index ffc56dce..5dddb862 100644 --- a/lionblog-taehyeong/src/index.css +++ b/lionblog-taehyeong/src/index.css @@ -18,18 +18,23 @@ } /* 모든 버튼 일괄 적용 */ .button { - @apply bg-orange-400 text-white font-medium hover:text-black rounded-2xl text-lg px-7 py-2; + @apply bg-orange-400 text-white font-medium hover:text-black rounded-2xl text-lg px-7 py-2 shrink-0; } + +.button-no-bg { + @apply font-medium px-3 py-3 hover:opacity-50; +} + /* 모든 form 일괄 적용 */ .form { - @apply w-full flex flex-col items-center; + @apply w-full flex flex-col items-center relative; } .label { @apply self-start mt-5 mb-2 align-middle ml-3; } /* 모든 input 일괄 적용 */ .input { - @apply border-2 w-full px-6 py-3 rounded-2xl bg-transparent placeholder-opacity-50 focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-transparent; + @apply border-2 px-6 py-3 rounded-2xl bg-transparent placeholder-opacity-50 focus:outline-none focus:ring-2 focus:ring-orange-400 focus:border-transparent w-full shrink; } .input-dark { diff --git a/lionblog-taehyeong/src/routes/HomePage.jsx b/lionblog-taehyeong/src/routes/HomePage.jsx index eeff4a38..51a9d2d9 100644 --- a/lionblog-taehyeong/src/routes/HomePage.jsx +++ b/lionblog-taehyeong/src/routes/HomePage.jsx @@ -7,29 +7,25 @@ import { PostsDataContext } from "../App"; const HomePage = () => { // const postList = posts; const postList = useContext(PostsDataContext); - - // const [postList, setPostList] = useState(posts); + const [visiblePosts, setVisiblePosts] = useState(postList); const [tags, setTags] = useState([]); const [searchTags, setSearchTags] = useState([]); - const [searchValue, setSearchValue] = useState(""); + const [searchValue, setSearchValue] = useState([]); - 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)) { - // 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 handleTagFilter = (e) => { + const { innerText } = e.target; + const tag = innerText.substring(1); + searchValue.includes(tag) + ? setSearchValue([...searchValue.filter((v) => v !== tag)]) + : setSearchValue([...searchValue, tag]); + }; useEffect(() => { const tagList = postList.reduce((acc, post) => { @@ -40,6 +36,16 @@ const HomePage = () => { setSearchTags([...tagList]); }, []); + useEffect(() => { + if (searchValue.length === 0) setVisiblePosts(postList); + else { + const newPosts = postList.filter((post) => + searchValue.every((v) => post.tags.find((t) => t.content === v)) + ); + setVisiblePosts(newPosts); + } + }, [searchValue]); + return (
@@ -53,20 +59,23 @@ 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) => +
+ {visiblePosts.map((post) => post ? : null )}
diff --git a/lionblog-taehyeong/src/routes/PostDetailPage.jsx b/lionblog-taehyeong/src/routes/PostDetailPage.jsx index 6c773056..1779553f 100644 --- a/lionblog-taehyeong/src/routes/PostDetailPage.jsx +++ b/lionblog-taehyeong/src/routes/PostDetailPage.jsx @@ -3,6 +3,7 @@ import { BigPost } from "../components/Posts"; import { useEffect, useState, useContext } from "react"; import { useParams, Link, useNavigate } from "react-router-dom"; import { PostsDataContext } from "../App"; +import Comment from "../components/Comment"; const PostDetailPage = () => { const navigate = useNavigate(); @@ -26,6 +27,7 @@ const PostDetailPage = () => { useEffect(() => { const post = posts.find((post) => post && post.id === parseInt(postId)); if (post) setPost(post); + // console.log(postId); }, [postId]); useEffect(() => { @@ -45,6 +47,7 @@ const PostDetailPage = () => { {post && ( <> +