From 9e6128bac789335c06f4f7cb3364c09034caa831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=82=98=ED=98=84?= <142777396+Nahyun-Kang@users.noreply.github.com> Date: Thu, 15 Feb 2024 17:21:28 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84(=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C,=20=EC=A1=B4=EC=9E=AC=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=B2=98=EB=A6=AC,?= =?UTF-8?q?=20=EC=9C=A0=EC=A0=80=20=EC=A0=95=EB=B3=B4=EC=97=90=20=EB=94=B0?= =?UTF-8?q?=EB=A5=B8=20ui=20=EC=B2=98=EB=A6=AC)=20=EB=B0=8F=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81=20(#31)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: 리스트 상세 조회 api 연결 * Feat: comment create 일부 구현 * Feat: 댓글 로직 구현 * Feat: 댓글 조회 데이터 바인딩 * Feat: useQuery 함수 일부 수정 * Feat: comment 타입 지정 * Design: (1차mvp) QA 댓글 wrapper gap 조정 * Design: (1차 mvp QA) collaborators default profile 아웃라인 깨짐 현상 수정 * Fix: 바뀐 url에 맞게 폴더 구조 변경 * Fix: 폴더구조 바꾸면서 생긴 경로 에러 처리 * Chore: 필요없는 파일 삭제 * Refactor: collaborators 매직 넘버 없애기 * Refactor: 이미지 태그 클로징 태그 삭제 * Refactor: 파일 이름 정리 * Feat: 리스트 상세 페이지 floating button 추가 * Refactor: 리스트 상세 페이지 페이지명 수정 * Design: 콜라보레이터 팝오버 그림자, 불투명도 수정 * Feat: 답글 없으면 답글 구분선 안 보이게 하기 * Feat: 댓글 생성 기능 구현 * Fix: 댓글 작성자 안 나오는 문제 해결 * Refactor: comment type 정리 * Fix: 답글 타입 정리 및 사용자 이름 안 보이는 문제 해결 * Feat: 댓글 삭제 기능 구현 * Feat: 리스트 수정하기 기능 구현 * Feat: 리스트 삭제 기능 구현 * Design: 댓글, 답글 간격 조정 * Design: 답글 간격 조정 * Fix: time-diff 함수 수정 * Feat: soft delete 된 댓글 처리 * Feat: 삭제된 댓글에 대해서 삭제 못하게 버튼 처리 * Feat: 무한스크롤 훅 구현 * Fix: getListdetail parameter 수정 * Feat: 댓글 무한스크롤 구현 * Fix: simple list rank로 수정 * Feat: 댓글 스켈레톤 ui 구현 * Style: 필요없는 import 문 삭제 * Fix: toast container 다시 복구 * Feat: 콜라보레이터들 데이터 연동 * Style: 목데이터 삭제 * Design: 콜라보레이터 ui 수정(콜라보레이터 태그 삭제) * Feat: 헤더 list의 오너일 때만 리스트 관리 보이게 수정 * Fix: 히스토리 아이콘 잘리는 문제 해결 * Feat: 댓글 및 답글 작성자만 삭제할 수 있도록 수정 * Feat: 리스트 삭제하기 모달 로직 구현 * Fix: 삭제된 댓글 문구 수정 * Style: _api 함수 내 호출 함수들 export 방식 수정 * Style: 바뀐 export 문에 맞게 import문 수정 * Feat: 탐색 페이지 리스트 추천 부분 더보기 버튼 로직 구현(리스트 상세조회에서 필요) * Feat: 탐색 페이지 트렌딩 리스트 리스트 상세 페이지로 이동하는 로직 구현(리스트 상세조회에서 필요) * Feat: 트렌딩 리스트 클릭시 리스트 상세조회 이동 로직 구현 * Feat: 댓글 폼 인풋 로직 임시 수정 * Feat: 코멘트 리액트 쿼리 enabled 조건 추가 * Feat: 로그인 안 한 사람 댓글, 답글 생성 기능 막기 * Feat: time-diff 함수 수정 * Feat: 답글 생성 시 화면 초기화 * Style: 댓글 생성 시 필요없는 코드 삭제 * Feat: 리스트가 존재하지 않을 때 모달 처리 * Feat: 리스트 없을 때 처리 수정 * Feat: retry 옵션 0으로 수정 * Feat: 공용 헤더 사용 및 로직 변경 * Feat: 콜라보레이터 리스트 자신을 제외한 목록만 조회되게 수정 * Feat: 콜라보레이터일때 수정할 수 있도록 로직 수정 * Feat: 바텀시트 버튼 disabled 됐을 때 처리 * Style: 필요없는 console.log 삭제 * Refactor: 전반적인 타입 수정 * Feat: 바텀시트 리스트 삭제 권한 로직 수정 * Refactor: collaborators.tsx / collaborators.css.ts 리팩토링 * Refactor: collaboratorspopover.tsx / collaboratorpopover.css.ts 리팩토링 * Refactor: comments.tsx, style 리팩토링 * Refactor: reply.tsx, style 리팩토링 * Refactor: comments.tsx, style 리팩토링 * Refactor: headerRight.tsx 와 style 리팩토링 * Refactor: listinformation.tsx와 style 리팩토링 * Chore: 지금 사용하지 않는 컴포넌트 삭제 * Design: 세부 여백, 디자인 수정 * Chore: 필요없는 파일 및 목데이터 폴더 삭제 * Style: 타입명 대문자로 수정 --- package.json | 1 + public/icons/default_profile_temporary.svg | 2 +- public/icons/history.svg | 2 +- .../BottomSheet/BottomSheet.css.tsx | 74 --- .../ListDetailOuter/Collaborators.tsx | 55 -- .../ListDetailOuter/CollaboratorsPopOver.tsx | 36 -- .../_components/ListDetailOuter/Comment.tsx | 57 -- .../_components/ListDetailOuter/Comments.tsx | 56 -- .../_components/ListDetailOuter/Header.tsx | 40 -- .../ListDetailOuter/ListInformation.tsx | 64 --- .../ListDetailOuter/OpenBottomSheetButton.tsx | 44 -- .../ListDetailOuter/PopOverMenu.css.ts | 17 - .../ListDetailOuter/PopOverMenu.tsx | 14 - .../_components/ListDetailOuter/Replies.tsx | 60 -- .../[listId]/mockData/mockdata.ts | 515 ------------------ .../[listId]/mockData/mockdataType.ts | 33 -- src/app/[userNickname]/[listId]/page.tsx | 15 - src/app/_api/comment/createComment.ts | 22 +- src/app/_api/comment/createReply.ts | 29 + src/app/_api/comment/deleteComment.ts | 14 + src/app/_api/comment/deleteReply.ts | 15 + src/app/_api/comment/getComments.ts | 24 + src/app/_api/explore/getRecommendedLists.ts | 5 +- src/app/_api/explore/getRecommendedUsers.ts | 5 +- src/app/_api/explore/getTrendingLists.ts | 5 +- src/app/_api/list/deleteList.ts | 9 + src/app/_api/user/updateProfile.ts | 2 +- .../[userId]/list}/[listId]/ListDetail.css.ts | 0 .../BottomSheet/BottomSheet.css.ts | 26 + .../_components/BottomSheet/BottomSheet.tsx | 17 +- .../_components/ListDetailInner/Footer.css.ts | 0 .../_components/ListDetailInner/Footer.tsx | 6 +- .../_components/ListDetailInner/Header.css.ts | 0 .../_components/ListDetailInner/Header.tsx | 0 .../ListDetailInner/RankList.css.ts | 0 .../_components/ListDetailInner/RankList.tsx | 2 +- .../_components/ListDetailInner/index.css.ts | 0 .../_components/ListDetailInner/index.tsx | 31 +- .../ListDetailOuter/Collaborators.css.ts | 26 +- .../ListDetailOuter/Collaborators.tsx | 54 ++ .../CollaboratorsPopOver.css.ts | 19 +- .../ListDetailOuter/CollaboratorsPopOver.tsx | 36 ++ .../ListDetailOuter/Comment.css.ts | 41 +- .../_components/ListDetailOuter/Comment.tsx | 96 ++++ .../ListDetailOuter/Comments.css.ts | 27 +- .../_components/ListDetailOuter/Comments.tsx | 195 +++++++ .../ListDetailOuter/CommentsSkeleton.tsx | 19 + .../ListDetailOuter/DeleteModalButton.tsx | 5 +- .../ListDetailOuter/HeaderRight.css.ts} | 16 - .../ListDetailOuter/HeaderRight.tsx | 42 ++ .../ListDetailOuter/ListInformation.css.ts | 42 +- .../ListDetailOuter/ListInformation.tsx | 114 ++++ .../ListDetailOuter/ModalButtonStyle.css.ts | 0 .../ListDetailOuter/OpenBottomSheetButton.tsx | 71 +++ .../ListDetailOuter/Replies.css.ts | 34 +- .../_components/ListDetailOuter/Replies.tsx | 97 ++++ src/app/user/[userId]/list/[listId]/page.tsx | 12 +- .../KakaotalkShare/kakaotalkShare.tsx | 6 +- src/components/Modal/Modal.tsx | 2 +- .../exploreComponents/ListsRecommendation.tsx | 12 +- .../exploreComponents/TrendingLists.css.ts | 1 + .../exploreComponents/TrendingLists.tsx | 10 +- .../exploreComponents/UsersRecommendation.tsx | 46 +- .../_mockdata/mockdataType.ts | 1 + .../{ModalPortal.ts => modal-portal.ts} | 0 src/hooks/useInfiniteScroll.ts | 27 + src/lib/types/commentType.ts | 33 ++ src/lib/types/exploreType.ts | 8 +- src/lib/utils/{timeDiff.ts => time-diff.ts} | 2 +- yarn.lock | 120 +++- 70 files changed, 1225 insertions(+), 1286 deletions(-) delete mode 100644 src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.tsx delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.tsx delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.tsx delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.tsx delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.tsx delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.tsx delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.css.ts delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.tsx delete mode 100644 src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.tsx delete mode 100644 src/app/[userNickname]/[listId]/mockData/mockdata.ts delete mode 100644 src/app/[userNickname]/[listId]/mockData/mockdataType.ts delete mode 100644 src/app/[userNickname]/[listId]/page.tsx rename src/app/{[userNickname] => user/[userId]/list}/[listId]/ListDetail.css.ts (100%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/BottomSheet/BottomSheet.css.ts (75%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/BottomSheet/BottomSheet.tsx (64%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailInner/Footer.css.ts (100%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailInner/Footer.tsx (96%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailInner/Header.css.ts (100%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailInner/Header.tsx (100%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailInner/RankList.css.ts (100%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailInner/RankList.tsx (98%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailInner/index.css.ts (100%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailInner/index.tsx (57%) rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailOuter/Collaborators.css.ts (64%) create mode 100644 src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Collaborators.tsx rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts (74%) create mode 100644 src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailOuter/Comment.css.ts (61%) create mode 100644 src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comment.tsx rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailOuter/Comments.css.ts (78%) create mode 100644 src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comments.tsx create mode 100644 src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CommentsSkeleton.tsx rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx (89%) rename src/app/{[userNickname]/[listId]/_components/ListDetailOuter/Header.css.ts => user/[userId]/list/[listId]/_components/ListDetailOuter/HeaderRight.css.ts} (54%) create mode 100644 src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/HeaderRight.tsx rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailOuter/ListInformation.css.ts (68%) create mode 100644 src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ListInformation.tsx rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailOuter/ModalButtonStyle.css.ts (100%) create mode 100644 src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx rename src/app/{[userNickname] => user/[userId]/list}/[listId]/_components/ListDetailOuter/Replies.css.ts (69%) create mode 100644 src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Replies.tsx rename src/components/{ModalPortal.ts => modal-portal.ts} (100%) create mode 100644 src/hooks/useInfiniteScroll.ts rename src/lib/utils/{timeDiff.ts => time-diff.ts} (94%) diff --git a/package.json b/package.json index 9030b352..94381adf 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "@egjs/react-grid": "^1.16.0", + "@mui/material": "^5.15.9", "@tanstack/react-query": "^5.17.12", "@tanstack/react-query-devtools": "^5.17.12", "@vanilla-extract/dynamic": "^2.1.0", diff --git a/public/icons/default_profile_temporary.svg b/public/icons/default_profile_temporary.svg index a99b8e97..2c00e1db 100644 --- a/public/icons/default_profile_temporary.svg +++ b/public/icons/default_profile_temporary.svg @@ -1,4 +1,4 @@ - + diff --git a/public/icons/history.svg b/public/icons/history.svg index 40ae8256..70d30f02 100644 --- a/public/icons/history.svg +++ b/public/icons/history.svg @@ -1,3 +1,3 @@ - + diff --git a/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.tsx b/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.tsx deleted file mode 100644 index e2734537..00000000 --- a/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { keyframes, style } from '@vanilla-extract/css'; - -export const backGround = style({ - position: 'fixed', - top: 0, - left: 0, - bottom: 0, - right: 0, - background: 'rgba(0,0,0,0.3)', - zIndex: 999, -}); - -export const wrapper = style({ - padding: '37px 0 43px', - - position: 'fixed', - bottom: 0, - left: 0, - right: 0, - - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - - backgroundColor: '#ffffff', - borderTopLeftRadius: '25px', - borderTopRightRadius: '25px', - - transitionProperty: 'all', - transitionDuration: '0.2s', -}); - -const slideIn = keyframes({ - from: { transform: 'translateY(100%)' }, - to: { transform: 'translateY(0)' }, -}); - -export const sheetActive = style({ - animation: `${slideIn} 0.2s ease-in-out`, -}); - -export const sheetItemWrapper = style({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - - ':hover': { - backgroundColor: '#EFEFF0', - }, -}); - -export const checkIcon = style({ - display: 'none', - marginRight: '28px', - - selectors: { - [`${sheetItemWrapper}:hover &`]: { - display: 'block', - }, - }, -}); - -export const sheetItem = style({ - width: '100%', - fontSize: '1.4rem', - cursor: 'pointer', - padding: '2.5rem 2.8rem 2.5rem', - - selectors: { - [`${sheetItemWrapper}:hover &`]: { - color: '#FF5454', - }, - }, -}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.tsx deleted file mode 100644 index a302edfb..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Image from 'next/image'; -import CollaboratorsPopOver from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver'; -import * as styles from './Collaborators.css'; -import DefaultProfile from '/public/icons/default_profile_temporary.svg'; -import PlusIcon from '/public/icons/collaborators_plus.svg'; -import { CollaboratorType } from '../../mockData/mockdataType'; - -interface CollaboratorsProps { - collaborators: CollaboratorType[] | null; -} - -function Collaborators({ collaborators }: CollaboratorsProps) { - const collaboratorsList = collaborators && collaborators?.length >= 3 ? collaborators?.slice(0, 3) : collaborators; - - return ( - <> - {collaborators && ( -
-
- -
- 콜라보레이터 -
-
- {`${collaborators && collaborators?.length - 3}`} - -
- {collaboratorsList?.map((item: CollaboratorType) => { - return ( -
- {item.profileImageUrl ? ( - 사용자 프로필 이미지 - ) : ( - - )} -
- ); - })} -
-
- )} - - ); -} - -export default Collaborators; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx deleted file mode 100644 index 03b8d1d5..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import Image from 'next/image'; -import * as styles from './CollaboratorsPopOver.css'; -import { CollaboratorType } from '../../mockData/mockdataType'; - -interface CollaboratorsProps { - collaborators: CollaboratorType[] | null; -} - -function CollaboratorsPopOver({ collaborators }: CollaboratorsProps) { - return ( -
- -
- ); -} - -export default CollaboratorsPopOver; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.tsx deleted file mode 100644 index 577b39a9..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.tsx +++ /dev/null @@ -1,57 +0,0 @@ -'use client'; -import Image from 'next/image'; -import Replies from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies'; -import DeleteModalButton from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton'; -import timeDiff from '@/lib/utils/timeDiff'; -import * as styles from './Comment.css'; -import DefaultProfile from '/public/icons/default_profile_temporary.svg'; -import { CommentType } from '../../mockData/mockdataType'; - -interface CommentProps { - comment: CommentType | undefined; - onUpdate: (userName: string | null) => void; - activeNickname?: string | null; -} - -function Comment({ comment, onUpdate }: CommentProps) { - const handleActiveNicknameUpdate = () => { - const currentUserName = comment?.userName; - if (currentUserName) { - onUpdate(currentUserName); - } - }; - - return ( - <> -
-
- {comment && comment.userProfileImageUrl ? ( - 프로필 이미지 - ) : ( - - )} -
-
- {comment?.userName} - {comment && timeDiff(comment?.createdDate)} -
-
{comment?.content}
-
-
- -
- - - - ); -} - -export default Comment; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.tsx deleted file mode 100644 index 8f66675f..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.tsx +++ /dev/null @@ -1,56 +0,0 @@ -'use client'; -import { useState } from 'react'; -import Image from 'next/image'; -import Comment from './Comment'; -import * as styles from './Comments.css'; -import { MOCKDATA_COMMENTS } from '../../mockData/mockdata'; -import CancelButton from '/public/icons/cancel_button.svg'; - -const COMMENTS = MOCKDATA_COMMENTS[1]; - -function Comments() { - const [activeNickname, setActiveNickname] = useState(null); - - const handleActiveNicknameDelete = () => { - if (activeNickname) { - setActiveNickname(null); - } - }; - - return ( -
-
- {/* {유저 정보 들어오면 이미지 src 추가할 예정} */} - 프로필 이미지 -
- {activeNickname && ( -
- {`@${activeNickname}님에게 남긴 답글`} - -
- )} -
- - -
-
-
-
{`${COMMENTS.totalCount}개의 댓글`}
- {COMMENTS.comments.map((item) => { - return ( -
- -
- ); - })} -
- ); -} - -export default Comments; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.tsx deleted file mode 100644 index 09b3b5b6..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.tsx +++ /dev/null @@ -1,40 +0,0 @@ -'use client'; -import { useRouter, useParams } from 'next/navigation'; -import OpenBottomSheetButton from './OpenBottomSheetButton'; -import * as styles from './Header.css'; -import BackButton from '/public/icons/back.svg'; -import HistoryButton from '/public/icons/history.svg'; - -function Header() { - const router = useRouter(); - const params = useParams<{ userNickname: string; listId: string }>(); - - const handleBackButtonClick = () => { - router.back(); - }; - - const handleHistoryButtonClick = () => { - router.push(`/${params?.userNickname}/${params?.listId}/history`); - }; - - return ( - <> -
- -
리스트
-
- -
- -
-
-
- - ); -} - -export default Header; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.tsx deleted file mode 100644 index a048864e..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import Image from 'next/image'; -import { useQuery } from '@tanstack/react-query'; -import { useParams } from 'next/navigation'; -import Label from '@/components/Label/Label'; -import Collaborators from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators'; -import { getListDetail } from '@/app/_api/list/getLists'; -import timeDiff from '@/lib/utils/timeDiff'; -import * as styles from './ListInformation.css'; -import { MOCKDATA_LIST } from '../../mockData/mockdata'; -import ListDetailInner from '../ListDetailInner'; -import { LabelType } from '@/lib/types/listType'; - -const LIST = MOCKDATA_LIST[0]; - -function ListInformation() { - const params = useParams<{ listId: string }>(); - const { data } = useQuery({ queryKey: ['getListDetail'], queryFn: () => getListDetail(params?.listId) }); - const list = data; - - return ( - <> -
-
-
- -
- {list?.labels.map((item: LabelType, idx: number) => { - return ( -
- -
- ); - })} -
-
{list?.title}
-
{list?.description}
-
- -
-
- 사용자 프로필 이미지 -
-
{list?.ownerNickname}
-
- {timeDiff(list?.createdDate)} - {list?.isPublic ? '공개' : '비공개'} -
-
-
-
- -
-
- - ); -} - -export default ListInformation; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx deleted file mode 100644 index 29f03497..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import useBooleanOutput from '@/hooks/useBooleanOutput'; -import BottomSheet from '@/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet'; -import KebabButton from '/public/icons/vertical_kebab_button.svg'; -import * as styles from './ModalButtonStyle.css'; - -export default function OpenBottomSheetButton({}) { - const { isOn, handleSetOff, handleSetOn } = useBooleanOutput(); //바텀시트 열림,닫힘 상태 관리 - const bottomSheetOptionList = [ - { - key: 'editList', - title: '리스트 수정하기', - onClick: () => { - handleEditClick(); - }, - }, - { - key: 'deleteList', - title: '리스트 삭제하기', - onClick: () => { - handleDeleteClick(); - }, - }, - ]; - - const handleEditClick = () => { - //확인버튼 클릭시 실행될 로직() - handleSetOff(); //닫기 - }; - - const handleDeleteClick = () => { - //확인버튼 클릭시 실행될 로직() - handleSetOff(); //닫기 - }; - - return ( - <> - - - {isOn && } - - ); -} diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.css.ts b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.css.ts deleted file mode 100644 index 7ea7c566..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.css.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { style } from '@vanilla-extract/css'; - -export const wrapper = style({ - padding: '10px', - - position: 'absolute', - top: '30px', - right: '3px', - - display: 'flex', - flexDirection: 'column', - gap: '20px', - - borderRadius: '8px', - backgroundColor: '#ffffff', - boxShadow: '0 0 10px 0.8px', -}); diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.tsx deleted file mode 100644 index ea06e826..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/PopOverMenu.tsx +++ /dev/null @@ -1,14 +0,0 @@ -'use client'; - -import * as styles from './PopOverMenu.css'; - -function PopOverMenu() { - return ( -
- - -
- ); -} - -export default PopOverMenu; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.tsx b/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.tsx deleted file mode 100644 index e2683188..00000000 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.tsx +++ /dev/null @@ -1,60 +0,0 @@ -'use client'; -import { useState } from 'react'; -import Image from 'next/image'; -import timeDiff from '@/lib/utils/timeDiff'; -import DeleteModalButton from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton'; -import * as styles from './Replies.css'; -import Line from '/public/icons/horizontal_line.svg'; -import { ReplyType } from '../../mockData/mockdataType'; - -interface RepliesProps { - replies: ReplyType[] | null | undefined; -} - -function Replies({ replies }: RepliesProps) { - const [showReplies, setShowReplies] = useState(false); - - const handleShowReplies = () => { - setShowReplies((prev) => !prev); - }; - - return ( - <> - {replies && !showReplies && ( -
- -
{`답글 ${replies?.length}개 더 보기`}
-
- )} - {showReplies && ( -
    - {replies?.map((item: ReplyType) => { - return ( -
  • -
    - 사용자 프로필 이미지 -
    -
    - {item.userNickName} - {timeDiff(item.createdDate)} -
    -
    {item.content}
    -
    -
    - -
  • - ); - })} -
- )} - - ); -} - -export default Replies; diff --git a/src/app/[userNickname]/[listId]/mockData/mockdata.ts b/src/app/[userNickname]/[listId]/mockData/mockdata.ts deleted file mode 100644 index ee7a4a4e..00000000 --- a/src/app/[userNickname]/[listId]/mockData/mockdata.ts +++ /dev/null @@ -1,515 +0,0 @@ -export const MOCKDATA_LIST = [ - { - category: '음악', - labels: [ - { - name: '세븐틴', - }, - { - name: '음악의 신', - }, - { - name: '최애 멤버', - }, - ], - title: '세븐틴 최애 멤버 Top 5', - description: - '내가 좋아하는 세븐틴에서 최애 멤버 top5 내가 좋아하는 세븐틴에서 최애 멤버 top5 내가 좋아하는 세븐틴에서 최애 멤버 top5 내가 좋아하는 세븐틴에서 최애 멤버 top5', - createdDate: '2022-01-15T11:00:05.817Z', - lastUpdatedDate: '2024-01-20T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 - ownerId: 'tarea202001@gmail.com', - ownerNickname: 'nabongee', - ownerProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', - collaborators: [ - // Nullable - { - id: 1, - nickname: '현지', - profileImageUrl: null, - }, - { - id: 2, - nickname: '도우너', - profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', - }, - { - id: 3, - nickname: '또치', - profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', - }, - { - id: 4, - nickname: 'Samuel', - profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', - }, - { - id: 5, - nickname: 'Clark', - profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', - }, - { - id: 6, - nickname: '나봉이', - profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', - }, - { - id: 7, - nickname: '미니언', - profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', - }, - ], - items: [ - { - id: 1, - rank: 1, - title: '전원우', - comment: '전원우 존잘임', // Nullable - link: null, // Nullable - imageUrl: - 'https://yt3.googleusercontent.com/AmdlIs_zKRbg1LUzyDC2aQu9UHklGlWibXNVolKlgseHaCKEOLDESXNwYX0hQp2lSGJoQDBN=s900-c-k-c0x00ffffff-no-rj', // Nullable - }, - { - id: 2, - rank: 2, - title: '호시', - comment: '작은 아기 호랑이', - link: null, - imageUrl: 'https://image.bugsm.co.kr/artist/images/1000/802323/80232314.jpg', - }, - { - id: 3, - rank: 3, - title: '부승관', - comment: 'boo', - link: null, - imageUrl: - 'https://talkimg.imbc.com/TVianUpload/tvian/TViews/image/2023/10/10/33d10ea7-d2c4-4260-8381-cbf644c9972e.jpg', - }, - { - id: 4, - rank: 4, - title: '정한', - comment: '남신', - link: null, - imageUrl: 'https://news.kbs.co.kr/data/news/2022/04/13/20220413_c85vo7.jpg', - }, - { - id: 5, - rank: 5, - title: '디에잇', - comment: '웃김', - link: null, - imageUrl: - 'data:image/jpeg;base64,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', - }, - ], - isCollected: true, - isPublic: false, - backgroundColor: null, - collectCount: 34, - viewCount: 24, - }, - { - category: '장소', - labels: [ - { - name: '서울', - }, - { - name: '카페', - }, - { - name: '디저트', - }, - ], - title: '맛있는 을지로 카페 Top5', - description: '커피가 맛있는 서울 카페들', - createdDate: '2024-01-16T13:00:05.817Z', - lastUpdatedDate: '2024-01-23T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 - ownerId: 'tarea202001@gmail.com', - ownerNickname: 'nabongee', - ownerProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', - collaborators: null, - items: [ - { - id: 1, - rank: 1, - title: '이프프커피', - comment: '을지로 크림카페, 티라미수 맛집', // Nullable - link: 'https://map.naver.com/p/search/%EC%9D%84%EC%A7%80%EB%A1%9C%20%EC%B9%B4%ED%8E%98/place/1113055605?c=15.00,0,0,0,dh&placePath=%3Fentry%3Dbmp', // Nullable - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240125_142%2F1706170547004iEWpa_JPEG%2F%25BF%25A1%25BD%25BA%25C7%25C1%25B7%25B9%25BC%25D2_%25BA%25B9%25BB%25E7%25BA%25BB.jpg', // Nullable - }, - { - id: 2, - rank: 2, - title: '을지로 문덕 커피', - comment: '촙촙 바로 옆 건물 골목길 초입', - link: 'https://map.naver.com/p/search/%EC%9D%84%EC%A7%80%EB%A1%9C%20%EC%B9%B4%ED%8E%98/place/1046777005?c=15.00,0,0,0,dh&placePath=%3Fentry%3Dbmp', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20220322_105%2F1647938541721kvaqf_JPEG%2FIMG_20220320_181609_849.jpg', - }, - { - id: 3, - rank: 3, - title: '보잉', - comment: '공항 컨셉 카페', - link: 'https://m.place.naver.com/share?id=1055469623&tabsPath=%2Fhome&appMode=detail', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240123_153%2F1705941421630Jj4f6_JPEG%2FIMG_3251.jpeg', - }, - { - id: 4, - rank: 4, - title: '공간갑', - comment: '공간이 갑이에요', - link: 'https://m.place.naver.com/share?id=1053282197&tabsPath=%2Fhome&appMode=detail', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20210820_184%2F1629432280312yxh2Q_JPEG%2FUuWYISVR47gu08_pqiflKOPd.jpg', - }, - { - id: 5, - rank: 5, - title: '커피한약방', - comment: '허준 선생님의 혜민서를 개조한 카페', - link: 'https://naver.me/IIqf14Bp', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fpup-review-phinf.pstatic.net%2FMjAyNDAxMjZfMTY0%2FMDAxNzA2MjM4NDU4NjU1.wUX3yX7JmXR1p9WgkkVfRHL4_KLEBbl25r3p3kLmKgkg.F8nRZinwmV_VxMXWbZ2MPcZPpcLzUxurYOTQrn6SFdgg.JPEG%2F763BC882-BB25-4788-809B-DBAED88B1652.jpeg', - }, - ], - isCollected: false, - isPublic: true, - backgroundColor: null, - collectCount: 10, - viewCount: 55, - }, - { - category: '음악', - labels: [ - { - name: '세븐틴', - }, - { - name: '음악의 신', - }, - { - name: '최애 멤버', - }, - ], - title: '세븐틴 최애 멤버 Top 5', - description: '세븐틴에서 최애 멤버 top5', - createdDate: '2024-01-15T13:00:05.817Z', - lastUpdatedDate: '2024-01-20T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 - ownerId: 'tarea202001@gmail.com', - ownerNickname: 'nabongee', - ownerProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', - collaborators: [ - // Nullable - { - id: 1, - nickname: '현지', - profileImageUrl: 'https://cdn.pixabay.com/photo/2023/07/16/12/32/cat-8130611_640.jpg', - }, - ], - items: [ - { - id: 1, - rank: 1, - title: '전원우', - comment: '전원우 존잘임', // Nullable - link: null, // Nullable - imageUrl: - 'https://yt3.googleusercontent.com/AmdlIs_zKRbg1LUzyDC2aQu9UHklGlWibXNVolKlgseHaCKEOLDESXNwYX0hQp2lSGJoQDBN=s900-c-k-c0x00ffffff-no-rj', // Nullable - }, - { - id: 2, - rank: 2, - title: '호시', - comment: '작은 아기 호랑이', - link: null, - imageUrl: 'https://image.bugsm.co.kr/artist/images/1000/802323/80232314.jpg', - }, - { - id: 3, - rank: 3, - title: '부승관', - comment: 'boo', - link: null, - imageUrl: - 'https://talkimg.imbc.com/TVianUpload/tvian/TViews/image/2023/10/10/33d10ea7-d2c4-4260-8381-cbf644c9972e.jpg', - }, - { - id: 4, - rank: 4, - title: '정한', - comment: '남신', - link: null, - imageUrl: 'https://news.kbs.co.kr/data/news/2022/04/13/20220413_c85vo7.jpg', - }, - { - id: 5, - rank: 5, - title: '디에잇', - comment: '웃김', - link: null, - imageUrl: - 'data:image/jpeg;base64,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', - }, - ], - isCollected: false, - }, - { - category: '장소', - labels: [ - { - name: '서울', - }, - { - name: '카페', - }, - { - name: '디저트', - }, - ], - title: '맛있는 을지로 카페 Top5', - description: '커피가 맛있는 서울 카페들', - createdDate: '2024-01-16T13:00:05.817Z', - lastUpdatedDate: '2024-01-23T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 - ownerId: 'tarea202001@gmail.com', - ownerNickname: 'nabongee', - ownerProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', - collaborators: null, - items: [ - { - id: 1, - rank: 1, - title: '이프프커피', - comment: '을지로 크림카페, 티라미수 맛집', // Nullable - link: 'https://map.naver.com/p/search/%EC%9D%84%EC%A7%80%EB%A1%9C%20%EC%B9%B4%ED%8E%98/place/1113055605?c=15.00,0,0,0,dh&placePath=%3Fentry%3Dbmp', // Nullable - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240125_142%2F1706170547004iEWpa_JPEG%2F%25BF%25A1%25BD%25BA%25C7%25C1%25B7%25B9%25BC%25D2_%25BA%25B9%25BB%25E7%25BA%25BB.jpg', // Nullable - }, - { - id: 2, - rank: 2, - title: '을지로 문덕 커피', - comment: '촙촙 바로 옆 건물 골목길 초입', - link: 'https://map.naver.com/p/search/%EC%9D%84%EC%A7%80%EB%A1%9C%20%EC%B9%B4%ED%8E%98/place/1046777005?c=15.00,0,0,0,dh&placePath=%3Fentry%3Dbmp', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20220322_105%2F1647938541721kvaqf_JPEG%2FIMG_20220320_181609_849.jpg', - }, - { - id: 3, - rank: 3, - title: '보잉', - comment: '공항 컨셉 카페', - link: 'https://m.place.naver.com/share?id=1055469623&tabsPath=%2Fhome&appMode=detail', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20240123_153%2F1705941421630Jj4f6_JPEG%2FIMG_3251.jpeg', - }, - { - id: 4, - rank: 4, - title: '공간갑', - comment: '공간이 갑이에요', - link: 'https://m.place.naver.com/share?id=1053282197&tabsPath=%2Fhome&appMode=detail', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20210820_184%2F1629432280312yxh2Q_JPEG%2FUuWYISVR47gu08_pqiflKOPd.jpg', - }, - { - id: 5, - rank: 5, - title: '커피한약방', - comment: '허준 선생님의 혜민서를 개조한 카페', - link: 'https://naver.me/IIqf14Bp', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fpup-review-phinf.pstatic.net%2FMjAyNDAxMjZfMTY0%2FMDAxNzA2MjM4NDU4NjU1.wUX3yX7JmXR1p9WgkkVfRHL4_KLEBbl25r3p3kLmKgkg.F8nRZinwmV_VxMXWbZ2MPcZPpcLzUxurYOTQrn6SFdgg.JPEG%2F763BC882-BB25-4788-809B-DBAED88B1652.jpeg', - }, - ], - isCollected: false, - }, - { - category: '장소', - labels: [ - { - name: '청라', - }, - { - name: '맛집', - }, - ], - title: '호주에서 갓 귀국한 내가 가고싶은 청라 맛집 Top 7', - description: '청라에서 제일 가고싶은 맛집 순위', - createdDate: '2024-01-15T13:00:05.817Z', - lastUpdatedDate: '2024-01-20T13:00:05.817Z', // 가장 마지막에 저장된 히스토리의 날짜 - ownerId: '', - ownerNickname: 'minchi', - ownerProfileImageUrl: 'https://www.ynow.co.kr/data/photos/20230728/art_16894886847033_93d9b7.png', - collaborators: [ - // Nullable - { - id: 1, - nickname: 'nabongee', - profileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', - }, - ], - items: [ - { - id: 1, - rank: 1, - title: '우리동네쭈꾸미', - comment: '쭈꾸미 존맛', // Nullable - link: 'https://naver.me/Gi9EzhJA', // Nullable - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20180326_180%2F1522025612368rCHNT_JPEG%2FtR5PurBnequRELzopz7GEM1Z.jpg', // Nullable - }, - { - id: 2, - rank: 2, - title: '명태어장', - comment: '명태 존맛집', - link: 'https://naver.me/GJrF376V', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20171107_168%2F1510060558275xNH3k_JPEG%2F_6kwBr6aasuwJKu7m1QGzdvO.jpg', - }, - { - id: 3, - rank: 3, - title: '한촌설렁탕', - comment: '설렁탕 맛집', - link: 'https://naver.me/FzH4YkvZ', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20211118_275%2F16372277273592SGLC_JPEG%2FKakaoTalk_20211118_182752353_01.jpg', - }, - { - id: 4, - rank: 4, - title: '양양입암막국수', - comment: '막국수 존맛집', - link: 'https://naver.me/FiLOFLrN', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20190617_242%2F1560762962654Ezyom_JPEG%2F6dB5oiAUzHvqsq3j5aDWiG4p.jpg', - }, - { - id: 5, - rank: 5, - title: '진천토종순대국', - comment: '순대국 존맛집', - link: 'https://naver.me/F9QppBBM', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20180904_125%2F1536063146640tTQ9B_JPEG%2Fopi3rrEu2yGDbmLTLAjE451c.jpg', - }, - { - id: 6, - rank: 6, - title: '우리할매떡볶이', - comment: '떡볶이는 여기서만 먹어', - link: 'https://naver.me/x50vcmB9', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20210517_241%2F16212167904568orwP_JPEG%2FAT_ddo48d0TcgCi1tyyp_sQb.jpg', - }, - { - id: 7, - rank: 7, - title: '신간짬뽕', - comment: '간짬뽕 존맛탱', - link: 'https://naver.me/GWozhL8h', - imageUrl: - 'https://search.pstatic.net/common/?src=https%3A%2F%2Fldb-phinf.pstatic.net%2F20220218_274%2F1645146029164uJlrY_JPEG%2FKakaoTalk_20220218_091621489.jpg', - }, - ], - isCollected: true, - }, -]; - -export const MOCKDATA_COMMENTS = [ - { - totalCount: 2, - comments: [ - { - id: 1, - userId: 24, - userName: '전원우 아내', - userProfileImageUrl: 'https://img.hankyung.com/photo/202006/03.22990548.1.jpg', - createdDate: '2024-01-25T06:50:12.962Z', - content: '전원우 고앵이!', - replies: [ - { - id: 3, - userId: 5, - userNickName: '나현', - userProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', - createdDate: '2024-01-25T18:50:12.962Z', - content: '애기 고양이', - }, - ], - }, - ], - }, - { - totalCount: 5, - comments: [ - { - id: 2, - userId: 36, - userName: '승화', - userProfileImageUrl: - 'https://yt3.googleusercontent.com/mFpxH8XLL_uupbtB1U489iAss84q-Phzdz9hZBZcxtQvlKH292PdCwtfd6Lf6YwCQHbtjvhSCXY=s900-c-k-c0x00ffffff-no-rj', - createdDate: '2024-01-24T06:50:12.962Z', - content: '여기 저도 너무 좋아하는 곳들이에요~~', - replies: [ - { - id: 3, - userId: 5, - userNickName: '나현', - userProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', - createdDate: '2024-01-25T18:50:12.962Z', - content: '매니저님~ 혹시 여기 말고 또 추천해주실 카페 있으신가요?~', - }, - { - id: 4, - userId: 36, - userNickName: '승화', - userProfileImageUrl: - 'https://yt3.googleusercontent.com/mFpxH8XLL_uupbtB1U489iAss84q-Phzdz9hZBZcxtQvlKH292PdCwtfd6Lf6YwCQHbtjvhSCXY=s900-c-k-c0x00ffffff-no-rj', - createdDate: '2024-01-26T18:50:12.962Z', - content: '물론이죠~ 00이라는 카페에 가보세요!!', - }, - ], - }, - { - id: 5, - userId: 37, - userName: '유진', - userProfileImageUrl: - 'https://yt3.googleusercontent.com/mFpxH8XLL_uupbtB1U489iAss84q-Phzdz9hZBZcxtQvlKH292PdCwtfd6Lf6YwCQHbtjvhSCXY=s900-c-k-c0x00ffffff-no-rj', - createdDate: '2024-01-25T06:50:12.962Z', - content: '저 더 맛있는 데 알고 있어요~~', - replies: [ - { - id: 3, - userId: 5, - userNickName: '나현', - userProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', - createdDate: '2024-01-25T18:50:12.962Z', - content: '어디에요 당장 공유해주세요', - }, - ], - }, - ], - }, - { - totalCount: 1, - comments: [ - { - id: 2, - userId: 36, - userName: '나현', - userProfileImageUrl: 'https://cdn.pixabay.com/photo/2020/05/17/20/21/cat-5183427_1280.jpg', - createdDate: '2024-01-24T06:50:12.962Z', - content: '털기 성공??', - replies: null, - }, - ], - }, -]; diff --git a/src/app/[userNickname]/[listId]/mockData/mockdataType.ts b/src/app/[userNickname]/[listId]/mockData/mockdataType.ts deleted file mode 100644 index 7c989b6d..00000000 --- a/src/app/[userNickname]/[listId]/mockData/mockdataType.ts +++ /dev/null @@ -1,33 +0,0 @@ -export interface RepliesType { - id: number; - userId: number; - userNickName: string; - userProfileImageUrl: string; - createdDate: string; - content: string; -} - -export interface ReplyType { - id: number; - userId: number; - userNickName: string; - userProfileImageUrl: string; - createdDate: string; - content: string; -} - -export interface CommentType { - id: number; - userId: number; - userName: string; - userProfileImageUrl: string; - createdDate: string; - content: string; - replies: RepliesType[] | null; -} - -export interface CollaboratorType { - id?: number; - nickname: string; - profileImageUrl: string | null; -} diff --git a/src/app/[userNickname]/[listId]/page.tsx b/src/app/[userNickname]/[listId]/page.tsx deleted file mode 100644 index e499e160..00000000 --- a/src/app/[userNickname]/[listId]/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -'use client'; -import * as styles from './ListDetail.css'; -import Comments from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments'; -import Header from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/Header'; -import ListInformation from '@/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation'; - -export default function ListDetail() { - return ( -
-
- - -
- ); -} diff --git a/src/app/_api/comment/createComment.ts b/src/app/_api/comment/createComment.ts index 5e900d00..9c1bdcb3 100644 --- a/src/app/_api/comment/createComment.ts +++ b/src/app/_api/comment/createComment.ts @@ -1,8 +1,20 @@ import axiosInstance from '@/lib/axios/axiosInstance'; -import { ListCreateType, ListIdType } from '@/lib/types/listType'; - -export const createList = async (listId: string, data: string) => { - const response = await axiosInstance.post(`/lists/${listId}/comment`, data); +import { CreateCommentType } from '@/lib/types/commentType'; +import { ListIdType } from '@/lib/types/listType'; +async function createComment({ listId, comment }: CreateCommentType) { + const response = await axiosInstance.post( + `/lists/${listId}/comments`, + { + content: comment, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + } + ); return response.data; -}; +} + +export default createComment; diff --git a/src/app/_api/comment/createReply.ts b/src/app/_api/comment/createReply.ts index e69de29b..ca0f7734 100644 --- a/src/app/_api/comment/createReply.ts +++ b/src/app/_api/comment/createReply.ts @@ -0,0 +1,29 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; + +interface CreateReplyType { + listId: number | undefined; + commentId: number | undefined | null; + data: string; +} + +async function createReply({ listId, commentId, data }: CreateReplyType) { + if (commentId === null) { + return; + } + + const response = await axiosInstance.post( + `/lists/${listId}/comments/${commentId}/replies`, + { + content: data, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + return response.data; +} + +export default createReply; diff --git a/src/app/_api/comment/deleteComment.ts b/src/app/_api/comment/deleteComment.ts index e69de29b..17d992c8 100644 --- a/src/app/_api/comment/deleteComment.ts +++ b/src/app/_api/comment/deleteComment.ts @@ -0,0 +1,14 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; + +interface DeleteCommentType { + listId: number | undefined; + commentId: number | undefined; +} + +//댓글 삭제 api +async function deleteComment({ listId, commentId }: DeleteCommentType) { + const response = await axiosInstance.delete(`/lists/${listId}/comments/${commentId}`); + return response.data; +} + +export default deleteComment; diff --git a/src/app/_api/comment/deleteReply.ts b/src/app/_api/comment/deleteReply.ts index e69de29b..ac1cf6b8 100644 --- a/src/app/_api/comment/deleteReply.ts +++ b/src/app/_api/comment/deleteReply.ts @@ -0,0 +1,15 @@ +import axiosInstance from '@/lib/axios/axiosInstance'; + +interface DeleteReplyType { + listId: number | undefined; + commentId: number | undefined | null; + replyId: number | undefined | null; +} + +//답글 삭제 api +async function deleteReply({ listId, commentId, replyId }: DeleteReplyType) { + const response = await axiosInstance.delete(`/lists/${listId}/comments/${commentId}/replies/${replyId}`); + return response.data; +} + +export default deleteReply; diff --git a/src/app/_api/comment/getComments.ts b/src/app/_api/comment/getComments.ts index e69de29b..2bfe4e2e 100644 --- a/src/app/_api/comment/getComments.ts +++ b/src/app/_api/comment/getComments.ts @@ -0,0 +1,24 @@ +// 리스트 조회 api +import axiosInstance from '@/lib/axios/axiosInstance'; + +interface GetCommentsType { + listId: number | undefined; + cursorId: number | undefined | null; +} + +//리스트 상세 페이지 리스트 조회 api +async function getComments({ listId, cursorId }: GetCommentsType) { + const params = new URLSearchParams({ + size: '5', + }); + + if (cursorId) { + params.append('cursorId', cursorId.toString()); + } + + const response = await axiosInstance.get(`/lists/${listId}/comments?${params.toString()}`); + + return response.data; +} + +export default getComments; diff --git a/src/app/_api/explore/getRecommendedLists.ts b/src/app/_api/explore/getRecommendedLists.ts index fe7b207a..bfcafd1c 100644 --- a/src/app/_api/explore/getRecommendedLists.ts +++ b/src/app/_api/explore/getRecommendedLists.ts @@ -1,8 +1,9 @@ // 리스트 조회 api import axiosInstance from '@/lib/axios/axiosInstance'; +import { ListRecommendationType } from '@/lib/types/exploreType'; //리스트 추천 상위 10개 -export async function getRecommendedLists() { - const response = await axiosInstance.get(`/lists`); +async function getRecommendedLists() { + const response = await axiosInstance.get(`/lists`); return response.data; } diff --git a/src/app/_api/explore/getRecommendedUsers.ts b/src/app/_api/explore/getRecommendedUsers.ts index b313b475..17fe578c 100644 --- a/src/app/_api/explore/getRecommendedUsers.ts +++ b/src/app/_api/explore/getRecommendedUsers.ts @@ -1,8 +1,9 @@ // 리스트 조회 api import axiosInstance from '@/lib/axios/axiosInstance'; +import { UsersRecommendationType } from '@/lib/types/exploreType'; //리스트 추천 상위 10개 -export async function getRecommendedUsers() { - const response = await axiosInstance.get(`/users/recommend`); +async function getRecommendedUsers() { + const response = await axiosInstance.get(`/users/recommend`); return response.data; } diff --git a/src/app/_api/explore/getTrendingLists.ts b/src/app/_api/explore/getTrendingLists.ts index 72c864f1..4e919461 100644 --- a/src/app/_api/explore/getTrendingLists.ts +++ b/src/app/_api/explore/getTrendingLists.ts @@ -1,8 +1,9 @@ // 리스트 조회 api import axiosInstance from '@/lib/axios/axiosInstance'; +import { TrendingListType } from '@/lib/types/exploreType'; //리스트 추천 상위 10개 -export async function getTrendingLists() { - const response = await axiosInstance.get(`/lists/explore`); +async function getTrendingLists() { + const response = await axiosInstance.get(`/lists/explore`); return response.data; } diff --git a/src/app/_api/list/deleteList.ts b/src/app/_api/list/deleteList.ts index d0064953..5a7743f0 100644 --- a/src/app/_api/list/deleteList.ts +++ b/src/app/_api/list/deleteList.ts @@ -1 +1,10 @@ // 리스트 삭제 api +import axiosInstance from '@/lib/axios/axiosInstance'; + +//댓글 삭제 api +async function deleteList(listId: string | undefined) { + const response = await axiosInstance.delete(`/lists/${listId}`); + return response.data; +} + +export default deleteList; diff --git a/src/app/_api/user/updateProfile.ts b/src/app/_api/user/updateProfile.ts index b4d4c6c2..6b72efc3 100644 --- a/src/app/_api/user/updateProfile.ts +++ b/src/app/_api/user/updateProfile.ts @@ -11,7 +11,7 @@ interface UserPresignedUrlsType { } interface UpdateProfileParams { - userId: Number; + userId: number; data: UserProfileEditType; } diff --git a/src/app/[userNickname]/[listId]/ListDetail.css.ts b/src/app/user/[userId]/list/[listId]/ListDetail.css.ts similarity index 100% rename from src/app/[userNickname]/[listId]/ListDetail.css.ts rename to src/app/user/[userId]/list/[listId]/ListDetail.css.ts diff --git a/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.ts b/src/app/user/[userId]/list/[listId]/_components/BottomSheet/BottomSheet.css.ts similarity index 75% rename from src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.ts rename to src/app/user/[userId]/list/[listId]/_components/BottomSheet/BottomSheet.css.ts index e2734537..bff33f60 100644 --- a/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.css.ts +++ b/src/app/user/[userId]/list/[listId]/_components/BottomSheet/BottomSheet.css.ts @@ -40,6 +40,9 @@ export const sheetActive = style({ }); export const sheetItemWrapper = style({ + width: '100%', + textAlign: 'left', + display: 'flex', justifyContent: 'space-between', alignItems: 'center', @@ -72,3 +75,26 @@ export const sheetItem = style({ }, }, }); + +export const disabledSheetItemWrapper = style({ + ':hover': { + backgroundColor: '#ffffff', + }, +}); + +export const disabledSheetItem = style({ + cursor: 'not-allowed', + selectors: { + [`${sheetItemWrapper}:hover &`]: { + color: '#e9e9e9', + }, + }, +}); + +export const disabledCheckIcon = style({ + selectors: { + [`${sheetItemWrapper}:hover &`]: { + display: 'none', + }, + }, +}); diff --git a/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.tsx b/src/app/user/[userId]/list/[listId]/_components/BottomSheet/BottomSheet.tsx similarity index 64% rename from src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.tsx rename to src/app/user/[userId]/list/[listId]/_components/BottomSheet/BottomSheet.tsx index d89d4769..3a040189 100644 --- a/src/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet.tsx +++ b/src/app/user/[userId]/list/[listId]/_components/BottomSheet/BottomSheet.tsx @@ -9,6 +9,7 @@ interface BottomSheetOptionsProps { key: string; title: string; onClick: () => void; + disabled?: boolean; } interface BottomSheetProps { @@ -26,12 +27,20 @@ function BottomSheet({ onClose, isActive, optionList }: BottomSheetProps) {
{optionList.map((option) => ( -
-
+
+ + ))}
diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailInner/Footer.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/Footer.css.ts similarity index 100% rename from src/app/[userNickname]/[listId]/_components/ListDetailInner/Footer.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailInner/Footer.css.ts diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailInner/Footer.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/Footer.tsx similarity index 96% rename from src/app/[userNickname]/[listId]/_components/ListDetailInner/Footer.tsx rename to src/app/user/[userId]/list/[listId]/_components/ListDetailInner/Footer.tsx index e3e20924..39cae209 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailInner/Footer.tsx +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/Footer.tsx @@ -2,8 +2,8 @@ import { useParams, useRouter } from 'next/navigation'; import { MouseEvent, useState } from 'react'; -import BottomSheet from '@/app/[userNickname]/[listId]/_components/BottomSheet/BottomSheet'; -import ModalPortal from '@/components/ModalPortal'; +import BottomSheet from '@/app/user/[userId]/list/[listId]/_components/BottomSheet/BottomSheet'; +import ModalPortal from '@/components/modal-portal'; import saveImageFromHtml from '@/lib/utils/saveImageFromHtml'; import copyUrl from '@/lib/utils/copyUrl'; import toasting from '@/lib/utils/toasting'; @@ -27,7 +27,7 @@ interface SheetTypeProps { interface FooterProps { category: string; - listId: string; + listId: string | null; title: string; description: string; items: ItemType[]; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailInner/Header.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/Header.css.ts similarity index 100% rename from src/app/[userNickname]/[listId]/_components/ListDetailInner/Header.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailInner/Header.css.ts diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailInner/Header.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/Header.tsx similarity index 100% rename from src/app/[userNickname]/[listId]/_components/ListDetailInner/Header.tsx rename to src/app/user/[userId]/list/[listId]/_components/ListDetailInner/Header.tsx diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailInner/RankList.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/RankList.css.ts similarity index 100% rename from src/app/[userNickname]/[listId]/_components/ListDetailInner/RankList.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailInner/RankList.css.ts diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailInner/RankList.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/RankList.tsx similarity index 98% rename from src/app/[userNickname]/[listId]/_components/ListDetailInner/RankList.tsx rename to src/app/user/[userId]/list/[listId]/_components/ListDetailInner/RankList.tsx index 77e03a5b..15088b12 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailInner/RankList.tsx +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/RankList.tsx @@ -26,7 +26,7 @@ export function SimpleList({ listData }: RankListProps) { } > {index === 0 && } -
{item.ranking}
+
{item.rank}
{item.title}
diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailInner/index.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/index.css.ts similarity index 100% rename from src/app/[userNickname]/[listId]/_components/ListDetailInner/index.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailInner/index.css.ts diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailInner/index.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/index.tsx similarity index 57% rename from src/app/[userNickname]/[listId]/_components/ListDetailInner/index.tsx rename to src/app/user/[userId]/list/[listId]/_components/ListDetailInner/index.tsx index 0b4f6b95..38d10f79 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailInner/index.tsx +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailInner/index.tsx @@ -1,11 +1,11 @@ 'use client'; import { useState } from 'react'; -import Header from '@/app/[userNickname]/[listId]/_components/ListDetailInner/Header'; -import RankList from '@/app/[userNickname]/[listId]/_components/ListDetailInner/RankList'; -import Footer from '@/app/[userNickname]/[listId]/_components/ListDetailInner/Footer'; +import Header from '@/app/user/[userId]/list/[listId]/_components/ListDetailInner/Header'; +import RankList from '@/app/user/[userId]/list/[listId]/_components/ListDetailInner/RankList'; +import Footer from '@/app/user/[userId]/list/[listId]/_components/ListDetailInner/Footer'; import * as styles from './index.css'; -import { CollaboratorType, ListItemsType } from '@/lib/types/listType'; +import { ListDetailType } from '@/lib/types/listType'; export interface ListItemProps { id?: number; @@ -23,26 +23,11 @@ interface OptionsProps { } interface ListDetailInnerProps { - listId: string; - category: string; - labels: []; - title: string; - description: string; - createdDate: string; - lastUpdatedDate: string; - ownerId: number; - ownerNickname: string; - ownerProfileImageUrl: string; - collaborators: CollaboratorType[]; - items: ListItemsType[]; - isCollected: boolean; - isPublic: boolean; - backgroundColor: string; - collectCount: number; - viewCount: number; + data: ListDetailType; + listId: string | null; } -function ListDetailInner({ data }: { data: ListDetailInnerProps }) { +function ListDetailInner({ data, listId }: ListDetailInnerProps) { const listData = data?.items; const [listType, setListType] = useState('simple'); const handleChangeListType = (target: OptionsProps) => { @@ -52,7 +37,7 @@ function ListDetailInner({ data }: { data: ListDetailInnerProps }) { const footerData = { category: data?.category, - listId: data?.listId, + listId: listId, title: data?.title, description: data?.description, items: listData, diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Collaborators.css.ts similarity index 64% rename from src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Collaborators.css.ts index bbbc1c23..63328e56 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Collaborators.css.ts +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Collaborators.css.ts @@ -1,4 +1,5 @@ import { style } from '@vanilla-extract/css'; +import { vars } from '@/styles/theme.css'; export const collaboratorWrapper = style({ position: 'relative', @@ -9,11 +10,11 @@ export const wrapper = style({ display: 'flex', flexDirection: 'row-reverse', justifyContent: 'center', - alignItems: 'cemter', + alignItems: 'center', transform: 'translateZ(0px)', }); -export const ProfileImg = style({ +export const profileImage = style({ marginRight: '-10px', width: '36px', height: '36px', @@ -22,26 +23,19 @@ export const ProfileImg = style({ justifyContent: 'center', alignItems: 'center', - outline: '3px solid #ffffff', + border: `3px solid ${vars.color.white}`, borderRadius: '9999px', }); export const profilePlus = style({ - backgroundColor: '#AEB0B6', + backgroundColor: vars.color.gray7, }); export const profileText = style({ - color: '#DEE7EE', + color: vars.color.lightblue, fontSize: '2rem', }); -export const collaboratorTitle = style({ - marginRight: '11px', - - fontSize: '1rem', - fontWeight: 600, -}); - export const collaboratorsPopOverWrapper = style({ display: 'none', @@ -51,11 +45,3 @@ export const collaboratorsPopOverWrapper = style({ }, }, }); - -export const defaultProfile = style({ - width: '36px', - height: '36px', - - outline: '3px solid #ffffff', - borderRadius: '9999px', -}); diff --git a/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Collaborators.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Collaborators.tsx new file mode 100644 index 00000000..4a116830 --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Collaborators.tsx @@ -0,0 +1,54 @@ +import Image from 'next/image'; +import CollaboratorsPopOver from '@/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CollaboratorsPopOver'; +import * as styles from './Collaborators.css'; +import PlusIcon from '/public/icons/collaborators_plus.svg'; +import { UserProfileType } from '@/lib/types/userProfileType'; + +interface CollaboratorsProps { + collaborators: UserProfileType[] | undefined; +} + +function Collaborators({ collaborators }: CollaboratorsProps) { + //콜라보레이터 목록이 3명 이상일 경우, 3명의 프로필 이미지만 보이고 그 이외에는 +로 처리하기 위한 로직 + const MAX_NUMBER = 3; + const collaboratorsList = + collaborators && collaborators?.length >= MAX_NUMBER ? collaborators?.slice(0, MAX_NUMBER) : collaborators; + + return ( + <> + {collaborators && ( +
+
+ +
+
+ {collaborators.length > MAX_NUMBER && ( +
+ {`${collaborators && collaborators?.length - 3}`} + +
+ )} + {collaboratorsList?.map((item: UserProfileType) => { + return ( +
+ 사용자 프로필 이미지 +
+ ); + })} +
+
+ )} + + ); +} + +export default Collaborators; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts similarity index 74% rename from src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts index fe8a5160..f37b3525 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.css.ts @@ -1,8 +1,13 @@ import { style } from '@vanilla-extract/css'; +/** + * @todo 공용 폰트 스타일 적용 + */ + export const wrapper = style({ padding: '12px', - height: '142px', + height: 'auto', + maxHeight: '142px', width: 'auto', position: 'absolute', @@ -11,8 +16,8 @@ export const wrapper = style({ overflow: 'scroll', borderRadius: '10px', - background: 'rgba(255, 255, 255, 0.8)', - boxShadow: '0px 4px 4px 0px rgba(0, 0, 0, 0.25)', + background: 'rgba(255, 255, 255, 1)', + boxShadow: 'rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px', zIndex: 20, '::-webkit-scrollbar': { @@ -47,11 +52,3 @@ export const nickname = style({ fontWeight: 600, letterSpacing: '-0.36px', }); - -export const defaultProfileImage = style({ - width: '25px', - height: '25px', - - borderRadius: '9999px', - backgroundColor: '#494949', -}); diff --git a/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx new file mode 100644 index 00000000..a1fb60ed --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CollaboratorsPopOver.tsx @@ -0,0 +1,36 @@ +import Image from 'next/image'; +import * as styles from './CollaboratorsPopOver.css'; +import { UserProfileType } from '@/lib/types/userProfileType'; + +interface CollaboratorsProps { + collaborators: UserProfileType[] | null; +} + +function CollaboratorsPopOver({ collaborators }: CollaboratorsProps) { + return ( +
+ 콜라보레이터 +
    + {collaborators?.map((item: UserProfileType) => { + return ( +
  • + 사용자 프로필 이미지 + {item.nickname} +
  • + ); + })} +
+
+ ); +} + +export default CollaboratorsPopOver; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comment.css.ts similarity index 61% rename from src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comment.css.ts index c5d190ac..68fe12c3 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comment.css.ts +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comment.css.ts @@ -1,12 +1,17 @@ import { style } from '@vanilla-extract/css'; +import { vars } from '@/styles/theme.css'; +import { caption1 } from '@/styles/font.css'; + +/**@todo 공용폰트 스타일 적용 */ export const commentOuterWrapper = style({ - marginBottom: '10px', + marginBottom: '9px', position: 'relative', display: 'flex', justifyContent: 'space-between', + gap: '16px', }); export const profileImage = style({ @@ -16,19 +21,20 @@ export const profileImage = style({ flex: '0 0 1', borderRadius: '16px', - backgroundColor: '#909090', + backgroundColor: vars.color.gray7, }); export const commentWrapper = style({ display: 'flex', - gap: '8px', + gap: '8.85px', alignItems: 'center', }); export const commentContainer = style({ display: 'flex', flexDirection: 'column', - gap: '5px', + justifyContent: 'space-between', + gap: '8px', }); export const commentInformationWrapper = style({ @@ -37,28 +43,33 @@ export const commentInformationWrapper = style({ gap: '8px', }); -export const commentWriter = style({ - fontSize: '1.2rem', - fontWeight: 600, -}); +export const commentWriter = style([ + caption1, + { + fontWeight: 600, + lineHeight: 'normal', + }, +]); export const commentCreatedTime = style({ fontSize: '1rem', fontWeight: 500, - color: '#494949', + color: vars.color.gray9, }); -export const commentContent = style({ - fontSize: '1.2rem', - fontWeight: 500, -}); +export const commentContent = style([ + caption1, + { + fontWeight: 500, + lineHeight: 'normal', + }, +]); export const createReplyButton = style({ padding: '0 0 0 36px', - marginBottom: '16px', background: 'none', fontSize: '1rem', - color: '#494949', + color: vars.color.gray9, fontWeight: 500, }); diff --git a/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comment.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comment.tsx new file mode 100644 index 00000000..cd5e76f9 --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comment.tsx @@ -0,0 +1,96 @@ +'use client'; +import Image from 'next/image'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import Replies from '@/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Replies'; +import deleteComment from '@/app/_api/comment/deleteComment'; +import DeleteModalButton from '@/app/user/[userId]/list/[listId]/_components/ListDetailOuter/DeleteModalButton'; + +import timeDiff from '@/lib/utils/time-diff'; +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; +import { CommentType } from '@/lib/types/commentType'; +import { UserType } from '@/lib/types/userProfileType'; + +import * as styles from './Comment.css'; +import DefaultProfile from '/public/icons/default_profile_temporary.svg'; + +/** + * @todo 타입 정리 필요 + */ +interface CommentProps { + comment: CommentType | undefined; + onUpdate: (userName: string | undefined) => void; + activeNickname?: string | null; + handleSetCommentId: (id: number | undefined) => void; + listId?: number | undefined; + commentId?: null | number | undefined; + currentUserInfo?: UserType; +} + +function Comment({ comment, onUpdate, handleSetCommentId, listId, commentId, currentUserInfo }: CommentProps) { + const queryClient = useQueryClient(); + + //현재 작성중인 답글의 원댓글 정보를 업데이트 하는 로직 + const handleActiveNicknameAndIdUpdate = () => { + const currentUserName = comment?.userNickname; + const currentCommentId = comment?.id; + if (!currentUserName && !currentCommentId) { + return null; + } + onUpdate(currentUserName); + handleSetCommentId(currentCommentId); + }; + + //댓글 삭제 리액트 쿼리 함수 + const deleteCommentMutation = useMutation({ + mutationFn: () => deleteComment({ listId: Number(listId), commentId: comment?.id }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getComments] }); + }, + }); + + //댓글 삭제 실행 함수 + const handleClickDeleteButton = () => { + deleteCommentMutation.mutate(); + }; + + return ( + <> +
+
+ {comment && !comment.isDeleted && ( + 프로필 이미지 + )} + {comment?.isDeleted && } +
+
+ {comment?.isDeleted ? '알 수 없음' : comment?.userNickname} + {comment && timeDiff(comment?.createdDate)} +
+
+ {comment?.isDeleted ? '작성자의 요청으로 삭제된 댓글이에요.' : comment?.content} +
+
+
+ {!comment?.isDeleted && currentUserInfo?.id === comment?.userId && ( + + )} +
+ + + + ); +} + +export default Comment; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comments.css.ts similarity index 78% rename from src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comments.css.ts index bc86fa97..f37b8d75 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Comments.css.ts +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comments.css.ts @@ -1,8 +1,11 @@ import { style } from '@vanilla-extract/css'; +import { vars } from '@/styles/theme.css'; + +/**@todo 공용폰트 스타일 적용 */ export const wrapper = style({ height: 'auto', - padding: '20px 24px 80px', + padding: '0 27.5px 80px', }); export const formWrapperOuter = style({ @@ -17,25 +20,25 @@ export const formWrapperOuter = style({ right: 0, display: 'flex', - alignItems: 'center', + alignItems: 'flex-end', gap: '4px', - border: '1px solid rgba(0, 0, 0, 0.10)', - background: '#fff', - zIndex: 30, + border: `1px solid ${vars.color.gray5}`, + background: vars.color.white, + zIndex: 1, }); export const formWrapperInner = style({ width: '100%', height: 'auto', - padding: '8px 12px', + padding: '7px 12px', display: 'flex', flexDirection: 'column', gap: '4px', borderRadius: '50px', - border: '1px solid #D9D9D9', + border: `1px solid ${vars.color.gray5}`, }); export const activeFormWrapper = style({ @@ -52,6 +55,8 @@ export const formInput = style({ height: 'auto', flex: '1 0 0', + + fontSize: '1.2rem', wordBreak: 'break-all', wordWrap: 'break-word', whiteSpace: 'pre-wrap', @@ -65,7 +70,7 @@ export const replyNickname = style({ fontSize: '1.2rem', fontWeight: 400, - color: '#AFB1B6', + color: vars.color.gray7, }); export const formButton = style({ @@ -91,7 +96,11 @@ export const profileImage = style({ flex: '0 0 1', borderRadius: '16px', - backgroundColor: '#909090', + backgroundColor: vars.color.gray9, +}); + +export const commentWrapper = style({ + marginBottom: '20px', }); export const activeReplyWrapper = style({ diff --git a/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comments.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comments.tsx new file mode 100644 index 00000000..25845a9e --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Comments.tsx @@ -0,0 +1,195 @@ +'use client'; +import { useParams } from 'next/navigation'; +import Image from 'next/image'; +import { ChangeEvent, useEffect, useMemo, useState } from 'react'; +import { useMutation, useQueryClient, useInfiniteQuery, useQuery } from '@tanstack/react-query'; + +import Comment from './Comment'; +import CommentsSkeleton from './CommentsSkeleton'; +import createComment from '@/app/_api/comment/createComment'; +import createReply from '@/app/_api/comment/createReply'; +import getComments from '@/app/_api/comment/getComments'; +import { getUserOne } from '@/app/_api/user/getUserOne'; +import useIntersectionObserver from '@/hooks/useIntersectionObserver'; +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; +import { CommentType } from '@/lib/types/commentType'; +import { UserType } from '@/lib/types/userProfileType'; +import { useUser } from '@/store/useUser'; + +import * as styles from './Comments.css'; +import CancelButton from '/public/icons/cancel_button.svg'; + +function Comments() { + const [activeNickname, setActiveNickname] = useState(null); + const [commentId, setCommentId] = useState(null); + const [comment, setComment] = useState(''); + const params = useParams<{ listId: string }>(); + const queryClient = useQueryClient(); + + //zustand로 관리하는 user정보 불러오기 + const { user } = useUser(); + const userId = user?.id; + + //user정보 불러오는 리액트 쿼리 함수 + const { data: userInformation } = useQuery({ + queryKey: [QUERY_KEYS.userOne, userId], + queryFn: () => getUserOne(userId), + enabled: userId !== 0, + }); + + //댓글 무한스크롤 리액트 쿼리 함수 + const { + data: commentsData, + hasNextPage, + fetchNextPage, + isFetching, + } = useInfiniteQuery({ + queryKey: [QUERY_KEYS.getComments, params?.listId], + queryFn: ({ pageParam: cursorId }) => { + return getComments({ listId: Number(params?.listId), cursorId: cursorId }); + }, + initialPageParam: null, + getNextPageParam: (lastPage) => (lastPage.hasNext ? lastPage.cursorId : null), + }); + + //댓글 주요 정보 변수화 + const comments = useMemo(() => { + const totalCount = commentsData ? commentsData.pages[commentsData.pages.length - 1].totalCount : 0; + const commentsList = commentsData ? commentsData.pages.flatMap(({ comments }) => comments) : []; + return { commentsList, totalCount }; + }, [commentsData]); + + //옵저버 훅 사용 + const ref = useIntersectionObserver(() => { + if (hasNextPage) { + fetchNextPage(); + } + }); + + //작성중이던 답글의 원댓글에 관련된 정보를 리셋하는 함수 + const handleReplyInformationDelete = () => { + if (activeNickname) { + setActiveNickname(null); + setCommentId(null); + } + }; + + //답글 생성중인 댓글에 대한 id를 받아오는 함수 + const handleSetCommentId = (id: number | undefined) => { + setCommentId(id); + }; + + //댓글 폼 사용(추후 리액트 훅폼으로 수정해 볼 예정) + const handleInputChange = (e: ChangeEvent) => { + setComment(e.target.value); + }; + + //댓글 생성 리액트 쿼리 함수 + const createCommentMutation = useMutation({ + mutationFn: () => createComment({ listId: Number(params?.listId), comment: comment }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getComments] }); + }, + onSettled: () => { + setComment(''); + }, + }); + + //답글 생성 리액트 쿼리 함수 + const createReplyMutation = useMutation({ + mutationFn: () => createReply({ listId: Number(params?.listId), commentId: commentId, data: comment }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getComments] }); + }, + onSettled: () => { + setComment(''); + setCommentId(null); + setActiveNickname(null); + }, + }); + + //댓글/답글 폼 submit 함수 + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!userId) { + return; + } + if (commentId && activeNickname) { + createReplyMutation.mutate(); + return; + } + createCommentMutation.mutate(); + }; + + //무한 스크롤시 필요한 쿼리 리셋함수 + useEffect(() => { + return () => { + queryClient.removeQueries({ + queryKey: [QUERY_KEYS.getComments], + exact: true, + }); + }; + }, [queryClient]); + + return ( +
+
+ 프로필 이미지 +
+ {activeNickname && ( +
+ {`@${activeNickname}님에게 남긴 답글`} + +
+ )} +
+ + {comment && userId !== 0 && ( + + )} +
+
+
+
{`${comments?.totalCount}개의 댓글`}
+ {comments?.commentsList?.map((item: CommentType) => { + return ( +
+ {isFetching ? ( + + ) : ( + + )} +
+ ); + })} + {/* {옵저버를 위한 요소} */} +
+
+ ); +} + +export default Comments; diff --git a/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CommentsSkeleton.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CommentsSkeleton.tsx new file mode 100644 index 00000000..fe222420 --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/CommentsSkeleton.tsx @@ -0,0 +1,19 @@ +import Skeleton from '@mui/material/Skeleton'; +import * as styles from './Comment.css'; + +function CommentsSkeleton() { + return ( +
+ +
+
+ + +
+ +
+
+ ); +} + +export default CommentsSkeleton; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx similarity index 89% rename from src/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx rename to src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx index 009d0038..564c3977 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/DeleteModalButton.tsx @@ -6,12 +6,13 @@ import DeleteButton from '/public/icons/trash_can.svg'; interface DeleteModalProps { children?: ReactNode; + onDelete: () => void; } -export default function DeleteModal({ children }: DeleteModalProps) { +export default function DeleteModal({ children, onDelete }: DeleteModalProps) { const { isOn, handleSetOff, handleSetOn } = useBooleanOutput(); //모달 열림,닫힘 상태 관리 const handleConfirmButtonClick = () => { - //확인버튼 클릭시 실행될 로직() + onDelete(); handleSetOff(); //닫기 }; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/HeaderRight.css.ts similarity index 54% rename from src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/HeaderRight.css.ts index ef6a60de..3e2c6bc2 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Header.css.ts +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/HeaderRight.css.ts @@ -1,21 +1,5 @@ import { style } from '@vanilla-extract/css'; -export const wrapper = style({ - height: '90px', - padding: '0 16px', - - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - - borderBottom: '1px solid rgba(0, 0, 0, 0.10)', -}); - -export const title = style({ - fontWeight: 600, - fontSize: '2rem', -}); - export const buttonResetStyle = style({ width: '24px', height: '24px', diff --git a/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/HeaderRight.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/HeaderRight.tsx new file mode 100644 index 00000000..e2feb9a0 --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/HeaderRight.tsx @@ -0,0 +1,42 @@ +'use client'; +import { useRouter, useParams } from 'next/navigation'; + +import { useUser } from '@/store/useUser'; +import OpenBottomSheetButton from './OpenBottomSheetButton'; +import * as styles from './HeaderRight.css'; +import HistoryButton from '/public/icons/history.svg'; + +interface HeaderRightProps { + isCollaborator: boolean | undefined; +} + +function HeaderRight({ isCollaborator }: HeaderRightProps) { + const router = useRouter(); + const params = useParams<{ userId: string; listId: string }>(); + + //zustand로 관리하는 user정보 불러오기 + const { user } = useUser(); + const userId = user?.id; + + const handleHistoryButtonClick = () => { + router.push(`/${params?.userId}/${params?.listId}/history`); + }; + + return ( + <> +
+ + {/* {리스트 관리 버튼은 리스트 오너일 때만 보이게 하기} */} + {(Number(params?.userId) === userId || isCollaborator) && ( +
+ +
+ )} +
+ + ); +} + +export default HeaderRight; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ListInformation.css.ts similarity index 68% rename from src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ListInformation.css.ts index 54f91d15..06103729 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ListInformation.css.ts +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ListInformation.css.ts @@ -1,7 +1,10 @@ import { style } from '@vanilla-extract/css'; +import { vars } from '@/styles/theme.css'; +import { body2, title3 } from '@/styles/font.css'; +/**@todo 공용폰트 스타일 적용 */ export const wrapper = style({ - padding: '48px 38px', + padding: '25px 32px 15px', }); export const categoryWrapper = style({ @@ -10,27 +13,25 @@ export const categoryWrapper = style({ display: 'flex', justifyContent: 'flex-start', alignItems: 'center', - - fontSize: '.75rem', }); export const labelWrapper = style({ marginRight: '8px', }); -export const listTitle = style({ - marginBottom: '1.6rem', +export const listTitle = style([ + title3, + { + marginBottom: '1.6rem', + }, +]); - fontSize: '2rem', - fontWeight: 600, -}); - -export const listDescription = style({ - fontSize: '1.5rem', - fontWeight: 500, - lineHeight: '25px', - color: '#909090', -}); +export const listDescription = style([ + body2, + { + color: vars.color.black, + }, +]); export const listComponentTemporary = style({ padding: '0 38px', @@ -39,7 +40,7 @@ export const listComponentTemporary = style({ }); export const bottomWrapper = style({ - padding: '21px 24px', + padding: '21px 24px 25px', display: 'flex', justifyContent: 'space-between', @@ -54,7 +55,7 @@ export const bottomLeftWrapper = style({ export const informationWrapper = style({ display: 'flex', flexDirection: 'column', - gap: '2px', + gap: '8px', }); export const profileImage = style({ @@ -62,12 +63,13 @@ export const profileImage = style({ height: '36px', borderRadius: '9999px', - backgroundColor: '#909090', + backgroundColor: vars.color.gray7, }); export const listOwnerNickname = style({ fontSize: '1.2rem', fontWeight: 600, + lineHeight: 'normal', }); export const infoDetailWrapper = style({ @@ -75,7 +77,9 @@ export const infoDetailWrapper = style({ gap: '7.5px', fontSize: '1rem', - color: '#909090', + fontWeight: 500, + lineHeight: 'normal', + color: vars.color.black, }); export const collaboratorWrapper = style({ diff --git a/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ListInformation.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ListInformation.tsx new file mode 100644 index 00000000..dd647182 --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ListInformation.tsx @@ -0,0 +1,114 @@ +'use client'; +import Image from 'next/image'; +import { useQuery } from '@tanstack/react-query'; +import { useParams, useRouter } from 'next/navigation'; + +import Collaborators from '@/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Collaborators'; +import getListDetail from '@/app/_api/list/getListDetail'; +import Label from '@/components/Label/Label'; +import Modal from '@/components/Modal/Modal'; +import Header from '@/components/Header/Header'; +import HeaderRight from './HeaderRight'; +import Comments from './Comments'; +import useBooleanOutput from '@/hooks/useBooleanOutput'; +import { useUser } from '@/store/useUser'; +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; +import timeDiff from '@/lib/utils/time-diff'; +import { UserProfileType } from '@/lib/types/userProfileType'; +import { LabelType, ListDetailType } from '@/lib/types/listType'; +import ListDetailInner from '@/app/user/[userId]/list/[listId]/_components/ListDetailInner'; +import * as styles from './ListInformation.css'; + +function ListInformation() { + const params = useParams<{ listId: string; userId: string }>(); + const router = useRouter(); + const { handleSetOff } = useBooleanOutput(); + + //zustand로 관리하는 user정보 불러오기 + const { user } = useUser(); + const userId = user?.id; + + const { data: list, error } = useQuery({ + queryKey: [QUERY_KEYS.getListDetail], + queryFn: () => getListDetail(Number(params?.listId)), + enabled: !!params?.listId, + retry: 0, + }); + + //리스트 생성자 제외한 사람들만 콜라보레이터들로 설정 + const filteredCollaboratorsList = list?.collaborators.filter((item: UserProfileType) => item?.id !== list.ownerId); + //리스트 오너가 아니고 콜라보레이터인 경우에 권한을 설정하기 위한 변수 + const isCollaborator: boolean | undefined = + list?.collaborators.some((item: UserProfileType) => item?.id === userId) && userId !== Number(params?.userId); + + const handleConfirmButtonClick = () => { + router.push('/'); + }; + + if (error && error?.message.includes('404')) { + return ( + + 이 리스트는 삭제 또는 비공개 처리 되었어요. + + 확인 + + + ); + } + + if (!list) { + return null; + } + + return ( + <> +
} + leftClick={() => router.back()} + /> +
+
+
+ +
+ {list?.labels.map((item: LabelType) => { + return ( +
+ +
+ ); + })} +
+
{list?.title}
+
{list?.description}
+
+ +
+
+ 사용자 프로필 이미지 +
+
{list?.ownerNickname}
+
+ {timeDiff(String(list?.createdDate))} + {list?.isPublic ? '공개' : '비공개'} +
+
+
+
+ +
+
+ + + ); +} + +export default ListInformation; diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/ModalButtonStyle.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ModalButtonStyle.css.ts similarity index 100% rename from src/app/[userNickname]/[listId]/_components/ListDetailOuter/ModalButtonStyle.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ModalButtonStyle.css.ts diff --git a/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx new file mode 100644 index 00000000..4a719d10 --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/OpenBottomSheetButton.tsx @@ -0,0 +1,71 @@ +'use client'; +import { useRouter } from 'next/navigation'; +import deleteList from '@/app/_api/list/deleteList'; +import useBooleanOutput from '@/hooks/useBooleanOutput'; +import BottomSheet from '@/app/user/[userId]/list/[listId]/_components/BottomSheet/BottomSheet'; +import Modal from '@/components/Modal/Modal'; +import KebabButton from '/public/icons/vertical_kebab_button.svg'; +import * as styles from './ModalButtonStyle.css'; + +interface OpenBottomSheetButtonProps { + listId: string | undefined; + isCollaborator: boolean | undefined; +} + +export default function OpenBottomSheetButton({ listId, isCollaborator }: OpenBottomSheetButtonProps) { + const router = useRouter(); + const { isOn, handleSetOff, handleSetOn } = useBooleanOutput(); //바텀시트 열림,닫힘 상태 관리 + const { isOn: isModalOn, handleSetOff: handleSetModalOff, handleSetOn: handleSetModalOn } = useBooleanOutput(); //모달 상태 관리 + const bottomSheetOptionList = [ + { + key: 'editList', + title: '리스트 수정하기', + onClick: () => { + handleEditClick(); + }, + }, + { + key: 'deleteList', + title: '리스트 삭제하기', + onClick: () => { + handleSetModalOn(); + }, + disabled: isCollaborator, + }, + ]; + + /** + * @todo 유저 정보 받아서 유저 ID로 바꿔줘야 함 + */ + const handleEditClick = () => { + router.push(`/user/${1}/list/${listId}/edit`); + handleSetOff(); //닫기 + }; + + /** + * @todo 삭제 시 어느 경로로 이동되는지 확인해야 함 + */ + const handleDeleteClick = () => { + deleteList(listId); + router.push('/'); + }; + + return ( + <> + + + {isModalOn && ( + + 정말 리스트를 삭제하시나요? + + 확인 + + + )} + + {isOn && } + + ); +} diff --git a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.css.ts b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Replies.css.ts similarity index 69% rename from src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.css.ts rename to src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Replies.css.ts index f54b5d6d..4f2139aa 100644 --- a/src/app/[userNickname]/[listId]/_components/ListDetailOuter/Replies.css.ts +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Replies.css.ts @@ -1,5 +1,8 @@ import { style } from '@vanilla-extract/css'; -import './Comment.css'; +import { vars } from '@/styles/theme.css'; +import { caption1 } from '@/styles/font.css'; + +/**@todo 공용폰트 스타일 적용 */ export const repliesOuterWrapper = style({ display: 'flex', @@ -7,7 +10,9 @@ export const repliesOuterWrapper = style({ marginBottom: '20px', }); -export const repliesWrapper = style({}); +export const repliesWrapper = style({ + marginTop: '16px', +}); export const replyWrapper = style({ marginLeft: '30px', @@ -17,8 +22,7 @@ export const replyWrapper = style({ }); export const showMoreRepliesWrapper = style({ - marginLeft: '30px', - marginBottom: '25px', + margin: '14px 0 23px 38px', display: 'flex', alignItems: 'center', @@ -27,6 +31,9 @@ export const showMoreRepliesWrapper = style({ export const showMoreReplies = style({ fontSize: '1rem', + color: vars.color.gray9, + fontWeight: 500, + cursor: 'pointer', }); export const deleteButton = style({ @@ -40,19 +47,19 @@ export const profileImage = style({ flex: '0 0 1', borderRadius: '16px', - backgroundColor: '#909090', + backgroundColor: vars.color.gray7, }); export const replyContainer = style({ display: 'flex', flexDirection: 'column', - gap: '5px', + gap: '8px', }); export const replyInformationWrapper = style({ display: 'flex', alignItems: 'baseline', - gap: '8px', + gap: '8.85px', }); export const replyWriter = style({ @@ -63,10 +70,13 @@ export const replyWriter = style({ export const replyCreatedTime = style({ fontSize: '1rem', fontWeight: 500, - color: '#494949', + color: vars.color.gray9, }); -export const replyContent = style({ - fontSize: '1.2rem', - fontWeight: 500, -}); +export const replyContent = style([ + caption1, + { + fontWeight: 500, + lineHeight: 'normal', + }, +]); diff --git a/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Replies.tsx b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Replies.tsx new file mode 100644 index 00000000..08d4df02 --- /dev/null +++ b/src/app/user/[userId]/list/[listId]/_components/ListDetailOuter/Replies.tsx @@ -0,0 +1,97 @@ +'use client'; +import { useState } from 'react'; +import Image from 'next/image'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import DeleteModalButton from '@/app/user/[userId]/list/[listId]/_components/ListDetailOuter/DeleteModalButton'; +import deleteReply from '@/app/_api/comment/deleteReply'; +import { QUERY_KEYS } from '@/lib/constants/queryKeys'; +import timeDiff from '@/lib/utils/time-diff'; +import { ReplyType } from '@/lib/types/commentType'; +import { UserType } from '@/lib/types/userProfileType'; + +import * as styles from './Replies.css'; +import Line from '/public/icons/horizontal_line.svg'; + +interface RepliesProps { + replies: ReplyType[] | null | undefined; + listId?: number | undefined; + commentId?: null | number | undefined; + currentUserInfo?: UserType | undefined; +} + +function Replies({ replies, listId, currentUserInfo }: RepliesProps) { + const [showReplies, setShowReplies] = useState(false); + + const handleShowReplies = () => { + setShowReplies((prev) => !prev); + }; + + return ( + <> + {replies?.length !== 0 && !showReplies && ( +
+ +
{`답글 ${replies?.length}개 더 보기`}
+
+ )} + {showReplies && ( +
    + {replies?.map((item: ReplyType) => { + return ( +
  • + +
  • + ); + })} +
+ )} + + ); +} + +export default Replies; + +interface ReplyProps { + reply: ReplyType; + listId?: number | undefined; + currentUserInfo: UserType | undefined; +} + +function Reply({ reply, listId, currentUserInfo }: ReplyProps) { + const queryClient = useQueryClient(); + const deleteReplyMutation = useMutation({ + mutationFn: () => deleteReply({ listId: listId, commentId: reply?.commentId, replyId: reply?.id }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.getComments] }); + }, + }); + + const handleDeleteButtonClick = () => { + deleteReplyMutation.mutate(); + }; + return ( + <> +
+ 사용자 프로필 이미지 +
+
+ {reply.userNickname} + {timeDiff(reply.createdDate)} +
+

{reply.content}

+
+
+ {currentUserInfo?.id === reply.userId && } + + ); +} diff --git a/src/app/user/[userId]/list/[listId]/page.tsx b/src/app/user/[userId]/list/[listId]/page.tsx index 0b2b5630..e03d9401 100644 --- a/src/app/user/[userId]/list/[listId]/page.tsx +++ b/src/app/user/[userId]/list/[listId]/page.tsx @@ -1,3 +1,11 @@ -export default function ListPage() { - return
리스트페이지
; +import * as styles from './ListDetail.css'; + +import ListInformation from '@/app/user/[userId]/list/[listId]/_components/ListDetailOuter/ListInformation'; + +export default function ListDetailPage() { + return ( +
+ +
+ ); } diff --git a/src/components/KakaotalkShare/kakaotalkShare.tsx b/src/components/KakaotalkShare/kakaotalkShare.tsx index 28bb1fac..4a3b93dd 100644 --- a/src/components/KakaotalkShare/kakaotalkShare.tsx +++ b/src/components/KakaotalkShare/kakaotalkShare.tsx @@ -1,15 +1,15 @@ 'use client'; -import { CollaboratorType } from '@/lib/types/listType'; +import { UserProfileType } from '@/lib/types/userProfileType'; interface kakaotalkShareProps { title: string; description: string; image?: string; listItem?: { title: string }[]; - collaborators: CollaboratorType[]; + collaborators: UserProfileType[]; userNickname: string; - listId: string; + listId: string | null; } function kakaotalkShare({ title, diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index 29040b02..55b83028 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react'; -import ModalPortal from '@/components/ModalPortal'; +import ModalPortal from '@/components/modal-portal'; import * as styles from './Modal.css'; import ModalTitle from './ModalTitle'; import ModalButton from './ModalButton'; diff --git a/src/components/exploreComponents/ListsRecommendation.tsx b/src/components/exploreComponents/ListsRecommendation.tsx index 8a7b98ba..813abfe6 100644 --- a/src/components/exploreComponents/ListsRecommendation.tsx +++ b/src/components/exploreComponents/ListsRecommendation.tsx @@ -3,9 +3,10 @@ import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; -import { SimpleList } from '@/app/[userNickname]/[listId]/_components/ListDetailInner/RankList'; +import { SimpleList } from '@/app/user/[userId]/list/[listId]/_components/ListDetailInner/RankList'; import getRecommendedLists from '@/app/_api/explore/getRecommendedLists'; import { QUERY_KEYS } from '@/lib/constants/queryKeys'; +import { useUser } from '@/store/useUser'; import * as styles from './ListsRecommendation.css'; import { ListRecommendationType } from './_mockdata/mockdataType'; import Label from '@/components/Label/Label'; @@ -22,6 +23,10 @@ function ListRecommendation() { const recommendLists = result?.lists; + //zustand로 관리하는 user정보 불러오기 + const { user } = useUser(); + const userId = user?.id; + const handleShowMoreButtonClick = (url: string) => { router.push(`${url}`); }; @@ -69,7 +74,10 @@ function ListRecommendation() { >
-
diff --git a/src/components/exploreComponents/TrendingLists.css.ts b/src/components/exploreComponents/TrendingLists.css.ts index 78735c94..7614b14b 100644 --- a/src/components/exploreComponents/TrendingLists.css.ts +++ b/src/components/exploreComponents/TrendingLists.css.ts @@ -41,6 +41,7 @@ export const swiperContainer = style({ height: '100%', background: blackLayer, + cursor: 'pointer', }); export const trendingListTitle = style({ diff --git a/src/components/exploreComponents/TrendingLists.tsx b/src/components/exploreComponents/TrendingLists.tsx index a756f9d7..60551426 100644 --- a/src/components/exploreComponents/TrendingLists.tsx +++ b/src/components/exploreComponents/TrendingLists.tsx @@ -1,5 +1,6 @@ 'use client'; import Image from 'next/image'; +import { useRouter } from 'next/navigation'; import { Swiper, SwiperSlide } from 'swiper/react'; import { useQuery } from '@tanstack/react-query'; import { QUERY_KEYS } from '@/lib/constants/queryKeys'; @@ -12,6 +13,7 @@ import 'swiper/css'; import * as styles from './TrendingLists.css'; function TrendingList() { + const router = useRouter(); const { data: trendingLists, isPending } = useQuery({ queryKey: [QUERY_KEYS.getTrendingLists], queryFn: () => getTrendingLists(), @@ -32,7 +34,13 @@ function TrendingList() { > {trendingLists?.map((item: TrendingListType) => { return ( - + { + router.push(`/user/${item.ownerId}/list/${item.id}`); + }} + >
(recommendationUsersMockdata); const wrapperRef = useRef(null); - - const { data: usersList, isPending } = useQuery({ + const { data: usersList } = useQuery({ queryKey: [QUERY_KEYS.getRecommendedUsers], queryFn: () => getRecommendedUsers(), }); - const handleRemoveUser = (id: number) => { - if (!recommendUsersList) { - return null; - } - const list = recommendUsersList.filter((listItem) => listItem?.id !== id); - setRecommendUserList([...list]); - }; - const handleScrollToRight = () => { if (wrapperRef.current) { wrapperRef.current.scrollTo({ @@ -38,21 +27,18 @@ function UsersRecommendation() { return (
- {recommendUsersList?.length !== 0 && ( + {usersList?.length !== 0 && ( <>
사용자 추천
    - {usersList?.map((item: UsersRecommendationType) => { - return ( -
  • - -
  • - ); - })} + {usersList && + usersList?.map((item: UsersRecommendationItemType) => { + return ( +
  • + +
  • + ); + })}
)} @@ -63,12 +49,11 @@ function UsersRecommendation() { export default UsersRecommendation; interface UserRecommendListItemProps { - data: UsersRecommendationType; - handleRemoveUser: (id: number) => null | undefined; + data: UsersRecommendationItemType; handleScrollToRight: () => void; } -function UserRecommendListItem({ data, handleRemoveUser, handleScrollToRight }: UserRecommendListItemProps) { +function UserRecommendListItem({ data, handleScrollToRight }: UserRecommendListItemProps) { const [isFollowing, setIsFollowing] = useState(false); const handleFollowingState = () => { @@ -83,12 +68,9 @@ function UserRecommendListItem({ data, handleRemoveUser, handleScrollToRight }: return ( <>
-
추천 사용자 프로필 이미지 void) => { + const ref = useRef(null); + + //scroll로 한 번 도전해봤는데 ref wrapper 자체가 clientHeight보다 작으면 문제가 생겨서 document로 걸어주었습니다 ㅠ + useEffect(() => { + const handleScrollToBottom = () => { + const element = document.documentElement; + if (!element) return; + + const { scrollTop, scrollHeight, clientHeight } = element; + if (scrollHeight - scrollTop <= clientHeight + 0.5) { + handler(); + } + }; + + document.addEventListener('scroll', handleScrollToBottom); + return () => { + document.removeEventListener('scroll', handleScrollToBottom); + }; + }, [ref, handler]); + + return { ref }; +}; + +export default useInfiniteScroll; diff --git a/src/lib/types/commentType.ts b/src/lib/types/commentType.ts index c90ca7ab..efd6b3f6 100644 --- a/src/lib/types/commentType.ts +++ b/src/lib/types/commentType.ts @@ -1 +1,34 @@ // 댓글 관련 타입 +export interface ReplyType { + commentId: number; + content: string; + id: number; + userId: number; + userNickname: string; + userProfileImageUrl: string; + createdDate: string; + updatedDate: string; +} + +export interface CommentType { + id: number; + userId: number; + userNickname: string; + userProfileImageUrl: string; + createdDate: string; + updatedDate: string; + content: string; + replies: ReplyType[] | null; + isDeleted: boolean; +} + +export interface CollaboratorType { + id?: number; + nickname: string; + profileImageUrl: string | null; +} + +export interface CreateCommentType { + listId: number; + comment: string; +} diff --git a/src/lib/types/exploreType.ts b/src/lib/types/exploreType.ts index 8e7a04ca..8367bf08 100644 --- a/src/lib/types/exploreType.ts +++ b/src/lib/types/exploreType.ts @@ -7,13 +7,7 @@ export interface TrendingListType { itemImageUrl: string; } -export interface UsersRecommendationType { - id: number; - nickname: string; - profileImageUrl: string; -} - -export interface UsersRecommendationType { +export interface UsersRecommendationItemType { id: number; nickname: string; profileImageUrl: string; diff --git a/src/lib/utils/timeDiff.ts b/src/lib/utils/time-diff.ts similarity index 94% rename from src/lib/utils/timeDiff.ts rename to src/lib/utils/time-diff.ts index 94ec223d..9ee2d593 100644 --- a/src/lib/utils/timeDiff.ts +++ b/src/lib/utils/time-diff.ts @@ -14,7 +14,7 @@ export default function timeDiff(dateString: string) { const dateObject = new Date(dateString); - const now = new Date(new Date().getTime() + 9 * 60 * 60 * 1000); + const now = new Date(new Date().getTime() - 9 * 60 * 60 * 1000); const diff = (now.getTime() - dateObject.getTime()) / 1000; const year = dateObject.getFullYear().toString(); diff --git a/yarn.lock b/yarn.lock index e5fa1f06..28f2b46f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1128,7 +1128,7 @@ dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.12.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.12.0", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== @@ -1768,7 +1768,7 @@ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== -"@floating-ui/core@^1.6.0": +"@floating-ui/core@^1.0.0", "@floating-ui/core@^1.6.0": version "1.6.0" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.0.tgz#fa41b87812a16bf123122bf945946bae3fdf7fc1" integrity sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g== @@ -1783,7 +1783,22 @@ "@floating-ui/core" "^1.6.0" "@floating-ui/utils" "^0.2.1" -"@floating-ui/utils@^0.2.1": +"@floating-ui/dom@^1.6.1": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.2.tgz#08eb5dd8da69582eeff26144f366b96f131fbd18" + integrity sha512-xymkSSowKdGqo0SRr2Mp4czH5A8o2Pum35PAD0ftb3gCcPacWzwhvtUeUqmVXm9EVtm2hThD/lRrFNcahMOaSQ== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/react-dom@^2.0.8": + version "2.0.8" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.8.tgz#afc24f9756d1b433e1fe0d047c24bd4d9cefaa5d" + integrity sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw== + dependencies: + "@floating-ui/dom" "^1.6.1" + +"@floating-ui/utils@^0.2.0", "@floating-ui/utils@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== @@ -2296,6 +2311,90 @@ resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== +"@mui/base@5.0.0-beta.36": + version "5.0.0-beta.36" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.36.tgz#29ca2de9d387f6d3943b6f18a84415c43e5f206c" + integrity sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@floating-ui/react-dom" "^2.0.8" + "@mui/types" "^7.2.13" + "@mui/utils" "^5.15.9" + "@popperjs/core" "^2.11.8" + clsx "^2.1.0" + prop-types "^15.8.1" + +"@mui/core-downloads-tracker@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.9.tgz#c29138c70cc0fb49cd909c29beef3fb0647e5af7" + integrity sha512-CSDpVevGaxsvMkiYBZ8ztki1z/eT0mM2MqUT21eCRiMz3DU4zQw5rXG5ML/yTuJF9Z2Wv9SliIeaRAuSR/9Nig== + +"@mui/material@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.9.tgz#4d6a4aee002c6a2d0e174e08c6d23245c18dd828" + integrity sha512-kbHTZDcFmN8GHKzRpImUEl9AJfFWI/0Kl+DsYVT3kHzQWUuHiKm3uHXR1RCOqr7H8IgHFPdbxItmCSQ/mj7zgg== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/base" "5.0.0-beta.36" + "@mui/core-downloads-tracker" "^5.15.9" + "@mui/system" "^5.15.9" + "@mui/types" "^7.2.13" + "@mui/utils" "^5.15.9" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + react-is "^18.2.0" + react-transition-group "^4.4.5" + +"@mui/private-theming@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.9.tgz#3ea3514ed2f6bf68541dbe9206665a82cd89cb01" + integrity sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.15.9" + prop-types "^15.8.1" + +"@mui/styled-engine@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.9.tgz#444605039ec3fe456bdd5d5cb94330183be62b91" + integrity sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og== + dependencies: + "@babel/runtime" "^7.23.9" + "@emotion/cache" "^11.11.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/system@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.9.tgz#8a34ac0ab133af2550cc7ab980a35174142fd265" + integrity sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.15.9" + "@mui/styled-engine" "^5.15.9" + "@mui/types" "^7.2.13" + "@mui/utils" "^5.15.9" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/types@^7.2.13": + version "7.2.13" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.13.tgz#d1584912942f9dc042441ecc2d1452be39c666b8" + integrity sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g== + +"@mui/utils@^5.15.9": + version "5.15.9" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.9.tgz#2bdf925e274d87cbe90c14eb52d0835318205e86" + integrity sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg== + dependencies: + "@babel/runtime" "^7.23.9" + "@types/prop-types" "^15.7.11" + prop-types "^15.8.1" + react-is "^18.2.0" + "@next/env@14.0.4": version "14.0.4" resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.4.tgz#d5cda0c4a862d70ae760e58c0cd96a8899a2e49a" @@ -2406,6 +2505,11 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -3119,7 +3223,7 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== -"@types/prop-types@*": +"@types/prop-types@*", "@types/prop-types@^15.7.11": version "15.7.11" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== @@ -3163,7 +3267,7 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" -"@types/react-transition-group@^4.4.0": +"@types/react-transition-group@^4.4.0", "@types/react-transition-group@^4.4.10": version "4.4.10" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q== @@ -5169,7 +5273,7 @@ cssstyle@^2.3.0: dependencies: cssom "~0.3.6" -csstype@^3.0.2, csstype@^3.0.7: +csstype@^3.0.2, csstype@^3.0.7, csstype@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== @@ -10589,7 +10693,7 @@ react-is@^17.0.1, react-is@^17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^18.0.0: +react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== @@ -10693,7 +10797,7 @@ react-toastify@^10.0.4: dependencies: clsx "^2.1.0" -react-transition-group@^4.3.0: +react-transition-group@^4.3.0, react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==