diff --git a/src/apis/post-comment/dto.ts b/src/apis/post-comment/dto.ts index 5dcc4b1b..ce25c48d 100644 --- a/src/apis/post-comment/dto.ts +++ b/src/apis/post-comment/dto.ts @@ -29,7 +29,7 @@ export interface Comment { // 댓글 관련 User 정보 export interface CommentUser { - userId: number; + id: number; nickname: string; profilePictureUrl: string; } diff --git a/src/components/ClothingInfoItem/dto.ts b/src/components/ClothingInfoItem/dto.ts index d234be39..374d876b 100644 --- a/src/components/ClothingInfoItem/dto.ts +++ b/src/components/ClothingInfoItem/dto.ts @@ -5,5 +5,4 @@ export interface ClothingInfo extends PostClothing {} export interface ClothingInfoItemProps { clothingObj: ClothingInfo; onDelete?: (clothingObj: ClothingInfo) => void; - hasRightMargin?: boolean; } diff --git a/src/components/ClothingInfoItem/index.tsx b/src/components/ClothingInfoItem/index.tsx index 8bb56f57..6aa9162b 100644 --- a/src/components/ClothingInfoItem/index.tsx +++ b/src/components/ClothingInfoItem/index.tsx @@ -5,7 +5,7 @@ import Right from '../../assets/arrow/right.svg'; import { ClothingInfoItemProps } from './dto'; import { ClothingInfoItemContainer, ClothingInfoLeft, ClothingImage, ClothingInfoRight, ClothingModel } from './styles'; -const ClothingInfoItem: React.FC = ({ clothingObj, onDelete, hasRightMargin = false }) => { +const ClothingInfoItem: React.FC = ({ clothingObj, onDelete }) => { const handleClick = () => { if (clothingObj.url) { window.location.href = clothingObj.url; @@ -13,7 +13,7 @@ const ClothingInfoItem: React.FC = ({ clothingObj, onDele }; return ( - + ClothingImg diff --git a/src/components/ClothingInfoItem/styles.tsx b/src/components/ClothingInfoItem/styles.tsx index 586f6839..3a3d5139 100644 --- a/src/components/ClothingInfoItem/styles.tsx +++ b/src/components/ClothingInfoItem/styles.tsx @@ -8,21 +8,13 @@ export const ClothingInfoItemContainer = styled.li` align-items: center; border: 0.0625rem solid ${({ theme }) => theme.colors.pink2}; border-radius: 0.5rem; - padding: 0.5rem; + padding: 10px; min-width: 20.9375rem; - margin-bottom: 0.9375rem; box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.12), 0px 0px 1px 0px rgba(0, 0, 0, 0.08), 0px 0px 1px 0px rgba(0, 0, 0, 0.08); cursor: pointer; - - /* Post 안에 있을 때 첫 번째 아이템에만 margin-left 적용 */ - .post-mode > & { - &:first-child { - margin-left: 1.25rem; - } - } `; export const ClothingInfoLeft = styled.div` @@ -35,7 +27,7 @@ export const ClothingInfoLeft = styled.div` overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - width: 70%; + width: 75%; display: flex; flex-direction: column; justify-content: center; @@ -53,25 +45,26 @@ export const ClothingInfoLeft = styled.div` `; export const ClothingImage = styled.div` - width: 4.625rem; - height: 4.625rem; + width: 62px; + height: 62px; border-radius: 0.5rem; - margin-right: 0.9375rem; + margin-right: 10px; > img { - width: 4.625rem; - height: 4.625rem; + width: 100%; + height: 100%; object-fit: cover; border-radius: 0.5rem; } `; export const ClothingModel = styled(StyledText)` + display: -webkit-box; + -webkit-line-clamp: 2; /* 두 줄로 제한 */ + -webkit-box-orient: vertical; overflow: hidden; - white-space: nowrap; text-overflow: ellipsis; width: 100%; - display: inline-block; /* 텍스트 클리핑을 적용하기 위해 inline-block으로 설정 */ `; export const ClothingInfoRight = styled.div` diff --git a/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/dto.ts b/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/dto.ts index 34c57b87..2e5da179 100644 --- a/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/dto.ts +++ b/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/dto.ts @@ -3,5 +3,5 @@ import { Comment } from '../../../../apis/post-comment/dto'; export interface CommentItemProps { comment: Comment; handleUserClick: (userId: number) => void; - getPostCommentList: () => void; + handleMenuOpen: (comment: Comment, event: React.MouseEvent) => void; } diff --git a/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/index.tsx b/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/index.tsx index f5f1c5ee..ad42e400 100644 --- a/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/index.tsx +++ b/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/index.tsx @@ -1,15 +1,8 @@ import React, { useEffect, useState } from 'react'; -import { useRecoilState } from 'recoil'; import dayjs from 'dayjs'; import 'dayjs/locale/ko'; -import { IsBlockConfirmationModalOpenAtom, UserBlockAtom } from '../../../../recoil/Home/BlockBottomSheetAtom'; -import { - IsCommentDeleteConfirmationModalOpenAtom, - IsCommentReportModalOpenAtom, -} from '../../../../recoil/Post/PostCommentAtom'; - import theme from '../../../../styles/theme'; import { StyledBigUserProfile, @@ -20,133 +13,25 @@ import { } from './styles'; import { StyledText } from '../../../Text/StyledText'; -import MenuButtonList from '../MenuButtonList'; -import Modal from '../../../Modal'; -import { ModalProps } from '../../../Modal/dto'; import { CommentItemProps } from './dto'; import More from '../../../../assets/default/more.svg'; -import Delete from '../../../../assets/default/delete.svg'; -import Block from '../../../../assets/default/block.svg'; -import Report from '../../../../assets/default/report.svg'; -import X from '../../../../assets/default/x.svg'; - -import { deleteCommentApi } from '../../../../apis/post-comment'; -const CommentItem: React.FC = ({ comment, handleUserClick, getPostCommentList }) => { - const [showCommentMenuId, setShowCommentMenuId] = useState(null); - const [isCommentDeleteConfirmationModalOpen, setIsCommentDeleteConfirmationModalOpen] = useRecoilState( - IsCommentDeleteConfirmationModalOpenAtom, - ); - const [, setIsCommentReportModalOpen] = useRecoilState(IsCommentReportModalOpenAtom); - const [, setUserBlockAtom] = useRecoilState(UserBlockAtom); - const [, setIsBlockConfirmationModalOpen] = useRecoilState(IsBlockConfirmationModalOpenAtom); +const CommentItem: React.FC = ({ comment, handleUserClick, handleMenuOpen }) => { const [timeAgo, setTimeAgo] = useState(); - //const [isMenuVisible, setIsMenuVisible] = useState(false); - const [, setIsMenuVisible] = useState(false); - //const [menuPosition, setMenuPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); - - /* - useEffect(() => { - // 메뉴 위치 초기화 - setMenuPosition({ top: 0, left: 0 }); - }, [isMenuVisible]); - - const handleMenuOpen = (event: React.MouseEvent) => { - const rect = event.currentTarget.getBoundingClientRect(); - setMenuPosition({ top: rect.bottom + window.scrollY, left: rect.left + window.scrollX }); - setIsMenuVisible((prev) => !prev); - }; - */ - useEffect(() => { setTimeAgo(dayjs(comment.createdAt).locale('ko').fromNow()); }, [comment]); - // 댓글 삭제 - const deleteComment = async () => { - try { - await deleteCommentApi(comment.id); // 댓글 삭제 API 호출 - setIsCommentDeleteConfirmationModalOpen(false); // 모달 닫기 - getPostCommentList(); // 댓글 목록 갱신 - } catch (error) { - console.error('댓글 삭제 중 에러 발생:', error); - } - }; - - const deleteConfirmationModalProps: ModalProps = { - isCloseButtonVisible: true, - onClose: () => setIsCommentDeleteConfirmationModalOpen(false), - content: '정말 댓글을 삭제하시겠습니까?', - button: { - content: '삭제', - onClick: deleteComment, - }, - }; - - // 댓글 메뉴 클릭한 경우 - const handleCommentMenuClick = (commentId: number) => { - if (!commentId) return; - setShowCommentMenuId((prevId) => (prevId === commentId ? null : commentId)); - }; - - const menuItems = [ - ...(comment.isCommentWriter - ? [ - { - text: '삭제', - action: () => { - setIsCommentDeleteConfirmationModalOpen(true); - }, - icon: Delete, - color: 'red', - }, - ] - : [ - { - text: '신고하기', - action: () => { - setIsCommentReportModalOpen(true); - }, - icon: Report, - }, - { - text: '차단하기', - action: () => { - const storedUserId = localStorage.getItem('id'); - if (storedUserId) { - setUserBlockAtom({ - userId: Number(storedUserId), - friendId: comment.user.userId, - friendName: comment.user.nickname, - action: 'toggle', - }); - setIsBlockConfirmationModalOpen(true); - } - }, - icon: Block, - }, - ]), - { - text: '취소', - action: () => setShowCommentMenuId(null), - icon: X, - }, - ]; - return ( - handleUserClick(comment.user.userId)} - alt="user avatar" - /> + handleUserClick(comment.user.id)} alt="user avatar" /> - handleUserClick(comment.user.userId)} $textTheme={{ style: 'body2-medium' }}> + handleUserClick(comment.user.id)} $textTheme={{ style: 'body2-medium' }}> {comment.user.nickname} {comment.content} @@ -155,18 +40,10 @@ const CommentItem: React.FC = ({ comment, handleUserClick, get {timeAgo} - handleCommentMenuClick(comment.id)}> + handleMenuOpen(comment, event)}> more - {showCommentMenuId === comment.id && ( - setIsMenuVisible(false)} - /> - )} - {isCommentDeleteConfirmationModalOpen && } ); }; diff --git a/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/styles.tsx b/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/styles.tsx index 3d8b07ec..46347022 100644 --- a/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/styles.tsx +++ b/src/components/PostBase/LikeCommentBottomSheetContent/CommentItem/styles.tsx @@ -9,8 +9,8 @@ export const CommentItem = styled.div` `; export const StyledBigUserProfile = styled(BigUserProfile)` - width: 52px; - height: 52px; + width: 36px; + height: 36px; margin-bottom: auto; `; @@ -18,7 +18,7 @@ export const CommentContent = styled.div` margin-left: 8px; display: flex; flex-direction: column; - max-width: calc(100% - 60px); + max-width: calc(100% - 44px); `; export const RightContainer = styled.div` diff --git a/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/dto.ts b/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/dto.ts index 5784de45..5a1b93f5 100644 --- a/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/dto.ts +++ b/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/dto.ts @@ -1,12 +1,12 @@ +export interface MenuButtonListProps { + items: MenuButtonProps[]; + onClose: () => void; + position: { top: number; left: number }; +} + export interface MenuButtonProps { text: string; action: () => void; icon: string; color?: string; } - -export interface MenuButtonListProps { - items: MenuButtonProps[]; - isVisible: boolean; - onClose: () => void; -} diff --git a/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/index.tsx b/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/index.tsx index d6124568..7fa7b870 100644 --- a/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/index.tsx +++ b/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/index.tsx @@ -1,29 +1,21 @@ -import React, { useEffect, useRef } from 'react'; +import React from 'react'; import ReactDOM from 'react-dom'; import { MenuButtonListProps } from './dto'; import { MenuListWrapper, MenuListContainer, MenuButtonItem } from './styles'; import { StyledText } from '../../../Text/StyledText'; -const MenuButtonList: React.FC = ({ items, isVisible, onClose }) => { - const containerRef = useRef(null); +const MenuButtonList: React.FC = ({ items, onClose, position }) => { + const handleWrapperClick = () => { + onClose(); // Wrapper 클릭 시 닫기 + }; - // 외부 클릭 감지 - useEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (containerRef.current && !containerRef.current.contains(event.target as Node)) { - onClose(); - } - }; - - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, [onClose]); - - if (!isVisible) return null; + const handleContainerClick = (event: React.MouseEvent) => { + event.stopPropagation(); // Container 클릭 시 이벤트 중단 + }; return ReactDOM.createPortal( - - + + {items.map((item, index) => ( diff --git a/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/styles.tsx b/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/styles.tsx index c82adbe1..460d2321 100644 --- a/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/styles.tsx +++ b/src/components/PostBase/LikeCommentBottomSheetContent/MenuButtonList/styles.tsx @@ -7,17 +7,20 @@ export const MenuListWrapper = styled.div` width: 100%; height: 100%; z-index: 999; + background-color: rgba(0, 0, 0, 0.5); `; -export const MenuListContainer = styled.div` - z-index: 1000; +export const MenuListContainer = styled.div<{ $position: { top: number; left: number } }>` position: absolute; - right: 0; - top: 40; + top: ${({ $position }) => `${$position.top}px`}; + left: ${({ $position }) => `${$position.left}px`}; + z-index: 1000; + width: 120px; display: flex; flex-direction: column; border-radius: 10px; background-color: ${({ theme }) => theme.colors.gray1}; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); `; export const MenuButtonItem = styled.button<{ $color?: string }>` @@ -37,4 +40,8 @@ export const MenuButtonItem = styled.button<{ $color?: string }>` width: 16px; height: 16px; } + + &:last-child { + border-bottom: none; + } `; diff --git a/src/components/PostBase/LikeCommentBottomSheetContent/index.tsx b/src/components/PostBase/LikeCommentBottomSheetContent/index.tsx index f046f98f..fb211697 100644 --- a/src/components/PostBase/LikeCommentBottomSheetContent/index.tsx +++ b/src/components/PostBase/LikeCommentBottomSheetContent/index.tsx @@ -2,7 +2,11 @@ import React, { useEffect, useState, useRef, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useRecoilState } from 'recoil'; -import { UserBlockAtom } from '../../../recoil/Home/BlockBottomSheetAtom'; +import { + IsCommentDeleteConfirmationModalOpenAtom, + IsCommentReportModalOpenAtom, + selectedCommentAtom, +} from '../../../recoil/Post/PostCommentAtom'; import { TabContainer, Tab, ContentContainer, Content, BigUserProfile, LikeItem, InputLayout } from './styles'; @@ -11,27 +15,33 @@ import theme from '../../../styles/theme'; import Loading from '../../Loading'; import Modal from '../../Modal'; import CommentItem from './CommentItem'; +import MenuButtonList from './MenuButtonList'; import { LikeCommentBottomSheetProps } from '../dto'; import { ModalProps } from '../../Modal/dto'; import { GetPostLikeListResponse } from '../../../apis/post-like/dto'; -import { GetCommentListResponse } from '../../../apis/post-comment/dto'; +import { Comment, GetCommentListResponse } from '../../../apis/post-comment/dto'; + +import Delete from '../../../assets/default/delete.svg'; +import Block from '../../../assets/default/block.svg'; +import Report from '../../../assets/default/report.svg'; +import X from '../../../assets/default/x.svg'; import { getPostLikeListApi } from '../../../apis/post-like'; import { postUserBlockApi } from '../../../apis/user'; import { PostUserBlockRequest } from '../../../apis/user/dto'; -import { createCommentApi, getCommentListApi } from '../../../apis/post-comment'; +import { createCommentApi, getCommentListApi, deleteCommentApi } from '../../../apis/post-comment'; import { handleError } from '../../../apis/util/handleError'; const LikeCommentBottomSheetContent: React.FC = ({ tab, likeCount, commentCount }) => { const [activeTab, setActiveTab] = useState<'likes' | 'comments'>(tab); const { postId } = useParams<{ postId: string }>(); + const [likes, setLikes] = useState([]); const [postLikeCount, setPostLikeCount] = useState(likeCount); const [comments, setComments] = useState([]); const [postCommentCount, setPostCommentCount] = useState(commentCount); - const [userBlock] = useRecoilState(UserBlockAtom); - const [isBlockModalOpen, setIsBlockModalOpen] = useState(false); + const [isBlockConfirmationModalOpen, setIsBlockConfirmationModalOpen] = useState(false); const [isStatusModalOpen, setIsStatusModalOpen] = useState(false); const [modalContent, setModalContent] = useState('알 수 없는 오류입니다.\n관리자에게 문의해 주세요.'); @@ -43,6 +53,16 @@ const LikeCommentBottomSheetContent: React.FC = ({ const [inputValue, setInputValue] = useState(''); const textareaRef = useRef(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const [selectedComment, setSelectedComment] = useRecoilState(selectedCommentAtom); + const [isMenuVisible, setIsMenuVisible] = useState(false); + const [menuPosition, setMenuPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); + + const [isCommentDeleteConfirmationModalOpen, setIsCommentDeleteConfirmationModalOpen] = useRecoilState( + IsCommentDeleteConfirmationModalOpenAtom, + ); + const [, setIsCommentReportModalOpen] = useRecoilState(IsCommentReportModalOpenAtom); const nav = useNavigate(); @@ -84,7 +104,7 @@ const LikeCommentBottomSheetContent: React.FC = ({ }; }, [page, reachedEnd, loadMoreRef.current, activeTab]); - // 좋아요 리스트 불러오기 + // 좋아요 리스트 불러오기 api const getPostLikeList = async (currentPage: number) => { if (reachedEnd || isLoading) return; @@ -119,7 +139,7 @@ const LikeCommentBottomSheetContent: React.FC = ({ } }; - // 댓글 리스트 불러오기 + // 댓글 리스트 불러오기 api const getPostCommentList = async () => { setIsLoading(true); try { @@ -135,12 +155,16 @@ const LikeCommentBottomSheetContent: React.FC = ({ } }; + // 댓글 작성 Input const handleInputChange = useCallback((e: React.ChangeEvent) => { setInputValue(e.target.value); }, []); - // 댓글 작성 + // 댓글 작성 api const createComment = async () => { + if (isSubmitting) return; // 중복 요청 방지 + setIsSubmitting(true); + const content = inputValue.trim(); if (!content) return; // 내용이 없으면 함수 종료 @@ -149,17 +173,51 @@ const LikeCommentBottomSheetContent: React.FC = ({ setInputValue(''); // 입력창 초기화 await getPostCommentList(); // 댓글 목록 갱신 } catch (error) { - console.error('댓글 작성 중 에러 발생:', error); + const errorMessage = handleError(error, 'postComment'); + setModalContent(errorMessage); // 에러 메시지 설정 + setIsStatusModalOpen(true); // 상태 모달 열기 + } finally { + setIsSubmitting(false); // 상태 초기화 } }; - const sendBlock = async () => { + // 댓글 삭제 api + const deleteComment = async () => { + try { + if (!selectedComment) { + setModalContent('선택된 댓글이 없습니다.'); + return; + } + await deleteCommentApi(selectedComment.id); // 댓글 삭제 API 호출 + getPostCommentList(); // 댓글 목록 갱신 + } catch (error) { + const errorMessage = handleError(error, 'postComment'); + setModalContent(errorMessage); // 에러 메시지 설정 + setIsStatusModalOpen(true); // 상태 모달 열기 + } finally { + setIsCommentDeleteConfirmationModalOpen(false); // 모달 닫기 + setIsMenuVisible(false); + } + }; + + // 유저 차단 api + const postUserBlock = async () => { + const storedUserId = localStorage.getItem('my_id'); + + // 사용자 ID 또는 선택된 댓글이 없으면 함수 종료 + if (!storedUserId || !selectedComment) { + setModalContent('유저 정보를 찾을 수 없습니다.'); + setIsStatusModalOpen(true); + return; + } + try { const blockRequest: PostUserBlockRequest = { - fromUserId: userBlock?.userId || -1, - toUserId: userBlock?.friendId || -1, + fromUserId: Number(storedUserId), + toUserId: selectedComment.user.id, action: 'block', }; + const response = await postUserBlockApi(blockRequest); if (response.isSuccess) { @@ -169,21 +227,74 @@ const LikeCommentBottomSheetContent: React.FC = ({ const errorMessage = handleError(error, 'user'); setModalContent(errorMessage); } finally { - setIsBlockModalOpen(false); + setIsBlockConfirmationModalOpen(false); setIsStatusModalOpen(true); } }; + // 본인 댓글 메뉴 항목 + const MyCommentMenuItems = [ + { + text: '삭제', + action: () => { + setIsCommentDeleteConfirmationModalOpen(true); + setIsMenuVisible(false); + }, + icon: Delete, + color: 'red', + }, + { + text: '취소', + action: () => setIsMenuVisible(false), + icon: X, + }, + ]; + + // 타 사용자 댓글 메뉴 항목 + const OtherCommentMenuItems = [ + { + text: '신고하기', + action: () => { + setIsCommentReportModalOpen(true); + }, + icon: Report, + }, + { + text: '차단하기', + action: () => { + setIsBlockConfirmationModalOpen(true); + setIsMenuVisible(false); + }, + icon: Block, + }, + { + text: '취소', + action: () => setIsMenuVisible(false), + icon: X, + }, + ]; + // 차단하기 모달 - const blockModalProps: ModalProps = { + const blockConfirmationModalProps: ModalProps = { isCloseButtonVisible: true, onClose: () => { - setIsBlockModalOpen(false); + setIsBlockConfirmationModalOpen(false); }, - content: `${userBlock?.friendId || '알수없음'} 님을\n정말로 차단하시겠어요?`, + content: `${selectedComment?.user.nickname || '알수없음'} 님을\n정말로 차단하시겠어요?`, button: { content: '차단하기', - onClick: sendBlock, + onClick: postUserBlock, + }, + }; + + // 댓글 삭제 확인 모달 + const deleteConfirmationModalProps: ModalProps = { + isCloseButtonVisible: true, + onClose: () => setIsCommentDeleteConfirmationModalOpen(false), + content: '정말 댓글을 삭제하시겠습니까?', + button: { + content: '삭제', + onClick: deleteComment, }, }; @@ -195,6 +306,14 @@ const LikeCommentBottomSheetContent: React.FC = ({ }, }; + // 댓글 메뉴 클릭한 경우 + const handleMenuOpen = (comment: Comment, event: React.MouseEvent) => { + setSelectedComment(comment); + const rect = event.currentTarget.getBoundingClientRect(); + setMenuPosition({ top: rect.bottom + window.scrollY - 90, left: rect.left + window.scrollX - 100 }); + setIsMenuVisible(true); + }; + // 유저 클릭한 경우 const handleUserClick = (userId: number) => { // 로컬 스토리지에서 사용자 ID 가져오기 @@ -260,7 +379,7 @@ const LikeCommentBottomSheetContent: React.FC = ({ )) @@ -271,6 +390,13 @@ const LikeCommentBottomSheetContent: React.FC = ({ placeholder="댓글 추가..." value={inputValue} onChange={handleInputChange} + onKeyDown={(e) => { + // 엔터 키 감지 + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); // 기본 엔터 동작 방지 (줄바꿈) + createComment(); // 댓글 작성 함수 호출 + } + }} > + {isMenuVisible && ( + setIsMenuVisible(false)} + position={menuPosition} + /> + )} + {isCommentDeleteConfirmationModalOpen && } + {isStatusModalOpen && } )} {isLoading && }
- {isBlockModalOpen && } + {isBlockConfirmationModalOpen && } {isStatusModalOpen && } ); diff --git a/src/components/PostBase/LikeCommentBottomSheetContent/styles.tsx b/src/components/PostBase/LikeCommentBottomSheetContent/styles.tsx index f4fa36de..49de8dbf 100644 --- a/src/components/PostBase/LikeCommentBottomSheetContent/styles.tsx +++ b/src/components/PostBase/LikeCommentBottomSheetContent/styles.tsx @@ -33,7 +33,7 @@ export const ContentContainer = styled.div<{ $isCommentTab: boolean }>` display: flex; gap: 16px; flex-direction: column; - align-items: start; + justify-content: start; overflow-y: auto; /* Comment 탭일 때만 padding-bottom 추가 */ diff --git a/src/components/PostBase/index.tsx b/src/components/PostBase/index.tsx index d54ea54a..1ec0b8fa 100644 --- a/src/components/PostBase/index.tsx +++ b/src/components/PostBase/index.tsx @@ -171,11 +171,34 @@ const PostBase: React.FC = ({ onClickMenu }) => { + {!post ? : image.imageUrl)} />} + + + {post?.postClothings?.map((clothingObj, index) => ( + + ))} + + + + + + {post?.isPostLike ? like : like} + + handleLikeCommentOpen('likes')}>{post?.postLikesCount ?? 0} + + handleLikeCommentOpen('comments')}> + + message + + {post?.postCommentsCount ?? 0} + + + {!post ? ( ) : ( - <> +
= ({ onClickMenu }) => { {showFullText ? '간략히 보기' : '더 보기'} )} - +
)}
- - {!post ? : image.imageUrl)} />} - - - - - {post?.isPostLike ? like : like} - - handleLikeCommentOpen('likes')}>{post?.postLikesCount ?? 0} - - handleLikeCommentOpen('comments')}> - - message - - {post?.postCommentsCount ?? 0} - - - - - {post?.postClothings?.map((clothingObj, index) => ( - - ))} - diff --git a/src/components/PostBase/styles.tsx b/src/components/PostBase/styles.tsx index 688187ba..bf1d6dbe 100644 --- a/src/components/PostBase/styles.tsx +++ b/src/components/PostBase/styles.tsx @@ -28,6 +28,7 @@ export const PostLayout = styled.div` flex-direction: column; align-items: center; width: 100%; + height: 100%; height: calc(100vh - 2.75rem); overflow-y: scroll; @@ -39,23 +40,29 @@ export const PostLayout = styled.div` `; export const PostContainer = styled.div` + display: flex; + flex-direction: column; width: 100%; max-width: 450px; height: 100%; overflow-y: scroll; + gap: 16px; scrollbar-width: none; // Firefox -ms-overflow-style: none; // IE 10+ &::-webkit-scrollbar { display: none; // Safari & Chrome } + + &:last-child { + padding-bottom: 110px; + } `; export const PostInfoContainer = styled.div` display: flex; align-items: center; margin-top: 8px; - margin-bottom: 16px; padding: 0 20px; gap: 8px; align-self: stretch; @@ -90,7 +97,6 @@ export const MenuBtn = styled.button` export const PostContentContainer = styled.div` width: 100%; padding: 0 20px; - margin-bottom: 16px; `; export const ContentSkeleton = styled(LoadingSkeleton)` @@ -126,7 +132,6 @@ export const IconRow = styled.div` height: 20px; align-items: center; padding: 0 20px; - margin: 16px 0; `; export const IconWrapper = styled.div` @@ -153,10 +158,13 @@ export const Icon = styled.div` `; export const ClothingInfoList = styled.div` - margin-bottom: 88px; + padding: 0 20px; display: flex; + flex-shrink: 0; overflow-x: auto; white-space: nowrap; + gap: 16px; + scrollbar-width: none; /* Firefox에서 스크롤바 숨기기 */ -ms-overflow-style: none; /* Internet Explorer에서 스크롤바 숨기기 */ diff --git a/src/pages/PostUpload/styles.tsx b/src/pages/PostUpload/styles.tsx index c1c9b679..74292ba7 100644 --- a/src/pages/PostUpload/styles.tsx +++ b/src/pages/PostUpload/styles.tsx @@ -130,9 +130,10 @@ export const TagContainer = styled.div` export const ClothingInfoList = styled.ul` padding: 0 1.25rem; - padding-bottom: 0.3125rem; + padding-bottom: 20px; display: flex; flex-direction: column; + gap: 12px; `; export const StyletagList = styled.ul` diff --git a/src/recoil/Home/BlockBottomSheetAtom.ts b/src/recoil/Home/BlockBottomSheetAtom.ts deleted file mode 100644 index 1fc8a233..00000000 --- a/src/recoil/Home/BlockBottomSheetAtom.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { atom } from 'recoil'; -import { BlockInfoDto } from '../../components/BottomSheet/OptionsBottomSheet/dto'; - -export const IsBlockConfirmationModalOpenAtom = atom({ - key: 'isBlockConfirmationModalOpenAtom', - default: false, -}); - -export const IsBlockSuccessModalOpenAtom = atom({ - key: 'IsBlockSuccessModalOpenAtom', - default: false, -}); - -export const IsBlockFailModalOpenAtom = atom({ - key: 'IsBlockFailModalOpenAtom', - default: false, -}); - -export const UserBlockAtom = atom({ - key: 'UserBlockAtom', - default: null, -}); diff --git a/src/recoil/Post/PostCommentAtom.ts b/src/recoil/Post/PostCommentAtom.ts index 4298dabf..1762d1ce 100644 --- a/src/recoil/Post/PostCommentAtom.ts +++ b/src/recoil/Post/PostCommentAtom.ts @@ -1,4 +1,5 @@ import { atom } from 'recoil'; +import { Comment } from '../../apis/post-comment/dto'; export const IsCommentDeleteConfirmationModalOpenAtom = atom({ key: 'isCommentDeleteConfirmationModalOpenAtom', @@ -9,3 +10,8 @@ export const IsCommentReportModalOpenAtom = atom({ key: 'IsCommentReportModalOpenAtom', default: false, }); + +export const selectedCommentAtom = atom({ + key: 'selectedCommentAtom', // 고유 키 + default: null, // 초기값 +});