From 00b58d1e30f74bbc2b8d5fb2774a1c6547f315b5 Mon Sep 17 00:00:00 2001 From: parkseyoung Date: Thu, 7 Nov 2024 10:18:17 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/useGetComment.jsx | 4 +- src/api/useGetPost.jsx | 60 ++--------------------- src/api/useGetPosts.jsx | 42 ++++++++++------ src/component/ FilteredComment.jsx | 35 +++++++++++++ src/component/Comment.jsx | 7 ++- src/component/FilteredContent.jsx | 44 +++++++++++++++++ src/component/Postlist.jsx | 16 +++--- src/component/SimpleFilteredContent.jsx | 20 ++++++++ src/component/ViewPage.jsx | 9 +++- src/component/styles/Comment.scss | 5 +- src/component/styles/FilteredComment.scss | 44 +++++++++++++++++ src/component/styles/FilteredContent.scss | 58 ++++++++++++++++++++++ src/component/styles/ViewPage.scss | 2 + src/containers/Community.jsx | 4 +- src/containers/PostForm.jsx | 19 ++++--- src/containers/styles/ChangePass.scss | 1 + 16 files changed, 276 insertions(+), 94 deletions(-) create mode 100644 src/component/ FilteredComment.jsx create mode 100644 src/component/FilteredContent.jsx create mode 100644 src/component/SimpleFilteredContent.jsx create mode 100644 src/component/styles/FilteredComment.scss create mode 100644 src/component/styles/FilteredContent.scss diff --git a/src/api/useGetComment.jsx b/src/api/useGetComment.jsx index 2c97859..dd61976 100644 --- a/src/api/useGetComment.jsx +++ b/src/api/useGetComment.jsx @@ -80,12 +80,12 @@ export const getPostComments = async (postId) => { } }; -// transformCommentData 함수도 약간 수정 const transformCommentData = (commentData) => ({ id: commentData.id, postId: commentData.postId, parentId: commentData.parentId, content: commentData.content, + clean: commentData.clean ?? true, // clean 필드 추가, 기본값은 true depth: commentData.depth, createdAt: commentData.createdAt, updatedAt: commentData.updatedAt, @@ -94,5 +94,5 @@ const transformCommentData = (commentData) => ({ name: commentData.member.name, nameEnglish: commentData.member.nameEnglish, }, - replies: [], // 초기 replies 배열 추가 + replies: [], }); diff --git a/src/api/useGetPost.jsx b/src/api/useGetPost.jsx index 700377c..254f9de 100644 --- a/src/api/useGetPost.jsx +++ b/src/api/useGetPost.jsx @@ -23,6 +23,7 @@ export const getPost = async (postId) => { "createdAt", "updatedAt", "member", + "clean", // clean 필드로 수정 ]; for (const field of requiredFields) { @@ -45,9 +46,10 @@ export const getPost = async (postId) => { content: postData.content, createdAt: postData.createdAt, updatedAt: postData.updatedAt, - imageUrls: postData.cloudFrontPaths || [], // cloudFrontPath 사용 - s3ImageUrls: postData.s3ImagePaths || [], // s3ImagePaths 보관 + imageUrls: postData.cloudFrontPaths || [], + s3ImageUrls: postData.s3ImagePaths || [], likes: (postData.likes !== undefined ? postData.likes : 0).toString(), + clean: postData.clean, // clean 필드로 수정 author: { id: postData.member.id, role: postData.member.role, @@ -66,57 +68,3 @@ export const getPost = async (postId) => { throw error; } }; - -// useGetPosts.jsx -export const getPosts = async (pageNo = 0, pageSize = 10) => { - try { - const response = await api.get("/api/v1/posts", { - params: { - pageNo, - pageSize, - }, - }); - - const transformedData = { - content: response.data.content.map((post) => ({ - post_id: post.id, - post_title: post.title, - post_content: post.content, - created_at: post.createdAt, - updated_at: post.updatedAt, - member: { - member_id: post.member.id, - member_name: post.member.name, - member_name_english: post.member.nameEnglish, - course: post.member.course, - role: post.member.role, - }, - // cloudFrontPaths 사용 - imageUrls: post.cloudFrontPaths || [], - // 첫 번째 이미지를 대표 이미지로 사용 - mainImageUrl: post.cloudFrontPaths?.[0] || "", - // S3 URL도 보관 - s3ImageUrls: post.s3ImagePaths || [], - })), - totalPages: response.data.totalPages, - totalElements: response.data.totalElements, - size: response.data.size, - number: response.data.number, - first: response.data.first, - last: response.data.last, - }; - - return transformedData; - } catch (error) { - console.error("Error fetching posts:", error); - return { - content: [], - totalPages: 0, - totalElements: 0, - size: pageSize, - number: pageNo, - first: true, - last: true, - }; - } -}; diff --git a/src/api/useGetPosts.jsx b/src/api/useGetPosts.jsx index 5bb76ef..75b3c34 100644 --- a/src/api/useGetPosts.jsx +++ b/src/api/useGetPosts.jsx @@ -10,23 +10,31 @@ export const getPosts = async (pageNo = 0, pageSize = 10) => { }, }); - // API 응답을 데이터베이스 스키마에 맞게 변환 + console.log("API Response:", response.data); // API 응답 로깅 추가 + const transformedData = { - content: response.data.content.map((post) => ({ - post_id: post.id, - post_title: post.title, - post_content: post.content, - created_at: post.createdAt, - updated_at: post.updatedAt, - member: { - member_id: post.member.id, - member_name: post.member.name, - member_name_english: post.member.nameEnglish, - course: post.member.course, - role: post.member.role, - }, - imageUrls: post.imageUrls || [], - })), + content: response.data.content.map((post) => { + console.log("Individual post data:", post); // 개별 게시물 데이터 로깅 + return { + post_id: post.id, + post_title: post.title, + post_content: post.content, + created_at: post.createdAt, + updated_at: post.updatedAt, + clean: post.clean === undefined ? true : post.clean, // clean 필드 처리 수정 + member: { + member_id: post.member.id, + member_name: post.member.name, + member_name_english: post.member.nameEnglish, + course: post.member.course, + role: post.member.role, + }, + imageUrls: post.cloudFrontPaths || [], + mainImageUrl: post.cloudFrontPaths?.[0] || "", + s3ImageUrls: post.s3ImagePaths || [], + likes: post.likes?.toString() || "0", + }; + }), totalPages: response.data.totalPages, totalElements: response.data.totalElements, size: response.data.size, @@ -35,6 +43,8 @@ export const getPosts = async (pageNo = 0, pageSize = 10) => { last: response.data.last, }; + console.log("Transformed data:", transformedData); // 변환된 데이터 로깅 + return transformedData; } catch (error) { console.error("Error fetching posts:", error); diff --git a/src/component/ FilteredComment.jsx b/src/component/ FilteredComment.jsx new file mode 100644 index 0000000..f072772 --- /dev/null +++ b/src/component/ FilteredComment.jsx @@ -0,0 +1,35 @@ +import { useState } from "react"; +import PropTypes from "prop-types"; +import "./styles/FilteredComment.scss"; + +const FilteredCommentContent = ({ content, clean = true }) => { + const [isRevealed, setIsRevealed] = useState(false); + + if (clean) { + return
{content}
; + } + + return ( +
+ {!isRevealed ? ( +
+ + [구름봇에 의해 가려진 댓글입니다] + + +
+ ) : ( +
{content}
+ )} +
+ ); +}; + +FilteredCommentContent.propTypes = { + content: PropTypes.string.isRequired, + clean: PropTypes.bool, +}; + +export default FilteredCommentContent; diff --git a/src/component/Comment.jsx b/src/component/Comment.jsx index 2730888..8a73395 100644 --- a/src/component/Comment.jsx +++ b/src/component/Comment.jsx @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import { MoreHorizontal } from "lucide-react"; import CommentInput from "./CommentInput"; import { getCommentDetail } from "../api/useGetCommentDetail"; +import FilteredCommentContent from "./ FilteredComment"; import { useCreateComment, useEditComment, @@ -255,7 +256,10 @@ const Comment = ({ ) : ( <> -
{comment.content}
+
{formatDate(comment.createdAt)} @@ -310,6 +314,7 @@ Comment.propTypes = { comment: PropTypes.shape({ id: PropTypes.number.isRequired, content: PropTypes.string.isRequired, + clean: PropTypes.bool, // clean 필드 추가 createdAt: PropTypes.string.isRequired, parentId: PropTypes.number, depth: PropTypes.number.isRequired, diff --git a/src/component/FilteredContent.jsx b/src/component/FilteredContent.jsx new file mode 100644 index 0000000..7874fb6 --- /dev/null +++ b/src/component/FilteredContent.jsx @@ -0,0 +1,44 @@ +import { useState } from "react"; +import PropTypes from "prop-types"; +import "./styles/FilteredContent.scss"; + +const FilteredContent = ({ title, content, clean = true }) => { + const [isRevealed, setIsRevealed] = useState(false); + + if (clean) { + return ( +
+
{title}
+
{content}
+
+ ); + } + + return ( +
+ {!isRevealed ? ( +
+ + [구름봇에 의해 가려진 게시물입니다] + + +
+ ) : ( +
+
{title}
+
{content}
+
+ )} +
+ ); +}; + +FilteredContent.propTypes = { + title: PropTypes.string.isRequired, + content: PropTypes.string.isRequired, + clean: PropTypes.bool, +}; + +export default FilteredContent; diff --git a/src/component/Postlist.jsx b/src/component/Postlist.jsx index 57f646c..47d7332 100644 --- a/src/component/Postlist.jsx +++ b/src/component/Postlist.jsx @@ -1,8 +1,9 @@ import PropTypes from "prop-types"; import "./styles/Postlist.scss"; import Hearticon from "../image/Hearticon.svg"; -//pr test -const Postlist = ({ id, title, imageUrl, likes }) => { +import SimpleFilteredContent from "./SimpleFilteredContent"; + +const Postlist = ({ id, title, imageUrl = "", likes, clean = true }) => { return (
@@ -13,14 +14,16 @@ const Postlist = ({ id, title, imageUrl, likes }) => { onError={(e) => { console.error("Image load error:", imageUrl); e.target.onerror = null; - e.target.src = "/default-image.png"; // 기본 이미지 경로 + e.target.src = "/default-image.png"; }} /> ) : (
)}
-
{title}
+
+ +
하트 아이콘 @@ -35,10 +38,7 @@ Postlist.propTypes = { title: PropTypes.string.isRequired, imageUrl: PropTypes.string, likes: PropTypes.string.isRequired, -}; - -Postlist.defaultProps = { - imageUrl: "", + clean: PropTypes.bool, }; export default Postlist; diff --git a/src/component/SimpleFilteredContent.jsx b/src/component/SimpleFilteredContent.jsx new file mode 100644 index 0000000..b894659 --- /dev/null +++ b/src/component/SimpleFilteredContent.jsx @@ -0,0 +1,20 @@ +import PropTypes from "prop-types"; + +const SimpleFilteredContent = ({ content, clean = true }) => { + if (clean) { + return
{content}
; + } + + return ( +
+ [구름봇에 의해 가려졌습니다] +
+ ); +}; + +SimpleFilteredContent.propTypes = { + content: PropTypes.string.isRequired, + clean: PropTypes.bool, +}; + +export default SimpleFilteredContent; diff --git a/src/component/ViewPage.jsx b/src/component/ViewPage.jsx index 123ebc4..60802e4 100644 --- a/src/component/ViewPage.jsx +++ b/src/component/ViewPage.jsx @@ -10,6 +10,7 @@ import { deletePost } from "../api/useDeletePost"; import WriteForm from "../containers/WriteForm"; import CommentInput from "./CommentInput"; import { useCreateComment } from "../hooks/useComment"; +import FilteredContent from "./FilteredContent"; const ViewPage = ({ post, @@ -257,8 +258,11 @@ const ViewPage = ({ )}
-
제목: {post.title}
-
{post.content}
+
하트 아이콘 {post.likes} @@ -310,6 +314,7 @@ ViewPage.propTypes = { imageUrl: PropTypes.string, likes: PropTypes.string.isRequired, updatedAt: PropTypes.string.isRequired, + clean: PropTypes.bool.isRequired, // isClean에서 clean으로 변경 author: PropTypes.shape({ id: PropTypes.number.isRequired, role: PropTypes.string.isRequired, diff --git a/src/component/styles/Comment.scss b/src/component/styles/Comment.scss index c8be962..31a4385 100644 --- a/src/component/styles/Comment.scss +++ b/src/component/styles/Comment.scss @@ -5,7 +5,7 @@ margin-bottom: 15px; .comment-header { - margin-bottom: 8px; + margin-bottom: 0px; .author-line { display: flex; @@ -55,7 +55,8 @@ } .comment-content { - margin: 8px 0; + margin-top: 3px; + margin-bottom: 8px; word-break: break-word; } diff --git a/src/component/styles/FilteredComment.scss b/src/component/styles/FilteredComment.scss new file mode 100644 index 0000000..c02b245 --- /dev/null +++ b/src/component/styles/FilteredComment.scss @@ -0,0 +1,44 @@ +.filtered-comment { + background-color: #f8f9fa; + border-radius: 4px; + overflow: hidden; + + .filtered-message { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0; + + .warning-text { + color: #6c757d; + font-size: 0.9rem; + flex: 1; + } + + .reveal-button { + padding: 0.25rem 0.75rem; + background-color: #3f51b5; + border: none; + border-radius: 15px; + color: white; + font-size: 0.8rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease-in-out; + + &:hover { + background-color: #303f9f; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + &:active { + transform: translateY(1px); + } + } + } + + .comment-content { + padding: 0; + word-break: break-word; + } +} diff --git a/src/component/styles/FilteredContent.scss b/src/component/styles/FilteredContent.scss new file mode 100644 index 0000000..5899cc9 --- /dev/null +++ b/src/component/styles/FilteredContent.scss @@ -0,0 +1,58 @@ +// FilteredContent.scss +.filtered-content { + width: 100%; + background-color: white; + border-radius: 8px; + margin: 0; // 여백 제거 + padding: 0; // 여백 제거 + + .filtered-message { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + text-align: center; + padding: 1rem; // 내부 여백만 유지 + + .warning-text { + color: #6c757d; + font-size: 1rem; + font-weight: 500; + } + + .reveal-button { + padding: 0.5rem 1.5rem; + background-color: #3f51b5; + border: none; + border-radius: 20px; + color: white; + font-size: 0.9rem; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease-in-out; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + + &:hover { + background-color: #303f9f; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + } + + &:active { + transform: translateY(0); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + } + } +} + +// Postlist용 심플 스타일 +.simple-filtered-content { + padding: 0; + margin: 0; + + .warning-text { + color: #6c757d; + font-size: 0.9rem; + } +} diff --git a/src/component/styles/ViewPage.scss b/src/component/styles/ViewPage.scss index d9a7189..94d8de9 100644 --- a/src/component/styles/ViewPage.scss +++ b/src/component/styles/ViewPage.scss @@ -99,10 +99,12 @@ font-size: 1.2em; font-weight: bold; margin-bottom: 10px; + padding: 0; } .content { margin-bottom: 15px; + padding: 0; } .reaction { diff --git a/src/containers/Community.jsx b/src/containers/Community.jsx index 4dc88f5..1e88ccf 100644 --- a/src/containers/Community.jsx +++ b/src/containers/Community.jsx @@ -59,6 +59,7 @@ const Community = () => { post_likes: 0, created_at: newPost.createdAt, updated_at: newPost.updatedAt, + clean: newPost.clean, // clean 필드 추가 member: newPost.member, }; @@ -76,7 +77,6 @@ const Community = () => { setCurrentPage(0); }; - // Community.jsx const handleCommentsUpdate = async (updatedComments) => { try { console.log("Received updated comments:", updatedComments); @@ -139,6 +139,7 @@ const Community = () => { post_likes: post.post_likes, created_at: updatedPost.createdAt, updated_at: updatedPost.updatedAt, + clean: updatedPost.clean, // clean 필드 추가 member: updatedPost.member, } : post @@ -239,6 +240,7 @@ const Community = () => { ? post.post_likes : 0 ).toString()} + clean={post.clean} // clean prop 추가 />
)) diff --git a/src/containers/PostForm.jsx b/src/containers/PostForm.jsx index b8fb7b1..927fc88 100644 --- a/src/containers/PostForm.jsx +++ b/src/containers/PostForm.jsx @@ -6,11 +6,11 @@ import Commenticon from "../image/Commenticon.svg"; import FullHearticon from "../image/FullHearticon.svg"; import Comment from "../component/Comment"; import CommentInput from "../component/CommentInput"; +import FilteredContent from "../component/FilteredContent"; import { useCreateComment } from "../hooks/useComment"; import { getPostComments } from "../api/useGetComment"; const Post = ({ post, currentUserId }) => { - // currentUserId prop 추가 const [isLiked, setIsLiked] = useState(false); const [likeCount, setLikeCount] = useState(0); const [comments, setComments] = useState([]); @@ -151,7 +151,11 @@ const Post = ({ post, currentUserId }) => {
[{post.member.course}]
-
{post.post_content}
+ {post.imageUrls && post.imageUrls.length > 0 && (
{post.imageUrls.map((url, index) => ( @@ -217,8 +221,9 @@ Post.propTypes = { post_content: PropTypes.string.isRequired, created_at: PropTypes.string.isRequired, updated_at: PropTypes.string.isRequired, - imageUrls: PropTypes.arrayOf(PropTypes.string), // 이미지 URL 배열 추가 - s3ImageUrls: PropTypes.arrayOf(PropTypes.string), // S3 URL 배열 추가 + clean: PropTypes.bool, // clean 필드 추가 + imageUrls: PropTypes.arrayOf(PropTypes.string), + s3ImageUrls: PropTypes.arrayOf(PropTypes.string), member: PropTypes.shape({ member_id: PropTypes.number.isRequired, member_name: PropTypes.string.isRequired, @@ -232,8 +237,10 @@ Post.propTypes = { Post.defaultProps = { post: { - imageUrls: [], // 기본값 설정 - s3ImageUrls: [], // 기본값 설정 + imageUrls: [], + s3ImageUrls: [], + clean: true, // clean의 기본값 추가 }, }; + export default Post; diff --git a/src/containers/styles/ChangePass.scss b/src/containers/styles/ChangePass.scss index facdbe6..57d9a5b 100644 --- a/src/containers/styles/ChangePass.scss +++ b/src/containers/styles/ChangePass.scss @@ -17,6 +17,7 @@ } .content-wrapper { + width: 100%; h3 { text-align: center; margin: 20px 0; // 위아래 여백 추가