From 408b7fc7ff6cabb3185d7a5cbbaba5f012971510 Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Wed, 6 Aug 2025 02:04:24 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9A=B0=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=B0=B8=EC=97=AC=20=ED=9B=84?= =?UTF-8?q?=20=EB=AA=A8=EC=9E=84=EB=B0=A9=20=EC=83=81=EC=84=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ParticipatedGroupDetail.styled.ts | 184 ++++++++++++++++++ .../groupDetail/ParticipatedGroupDetail.tsx | 182 +++++++++++++++++ src/pages/index.tsx | 2 + 3 files changed, 368 insertions(+) create mode 100644 src/pages/groupDetail/ParticipatedGroupDetail.styled.ts create mode 100644 src/pages/groupDetail/ParticipatedGroupDetail.tsx diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts b/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts new file mode 100644 index 00000000..c10aaa8b --- /dev/null +++ b/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts @@ -0,0 +1,184 @@ +import styled from '@emotion/styled'; +import { colors, typography } from '@/styles/global/global'; + +// 기록장 섹션 +export const RecordSection = styled.section` + display: flex; + flex-direction: column; + width: 94%; + gap: 24px; + background: ${colors.darkgrey.dark}; + margin: 10px 20px 0 20px; + padding: 20px; + border-radius: 16px; +`; + +export const RecordSectionHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +`; + +export const RecordSectionTitle = styled.h3` + color: ${colors.white}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.medium}; + margin: 0; +`; + +export const RecordSectionChevron = styled.img` + width: 24px; + height: 24px; +`; + +export const RecordSectionContent = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const BookTitle = styled.div` + color: ${colors.white}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; +`; + +export const CurrentPage = styled.div` + color: ${colors.white}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; +`; + +export const ProgressSection = styled.div` + display: flex; + align-items: center; + gap: 12px; +`; + +export const ProgressBar = styled.div` + flex: 1; + height: 8px; + background-color: ${colors.grey[400]}; + border-radius: 4px; + overflow: hidden; +`; + +export const ProgressBarFill = styled.div<{ progress: number }>` + width: ${({ progress }) => progress}%; + height: 100%; + background-color: ${colors.purple.main}; + border-radius: 4px; + transition: width 0.3s ease; +`; + +export const ProgressText = styled.span` + color: ${colors.purple.main}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; + min-width: 32px; +`; + +// 오늘의 한마디 섹션 +export const CommentSection = styled.section` + display: flex; + flex-direction: column; + width: 94%; + gap: 24px; + background: ${colors.darkgrey.dark}; + margin: 20px 20px 0 20px; + padding: 20px; + border-radius: 16px; +`; + +export const CommentSectionHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +`; + +export const CommentContent = styled.div` + display: flex; + flex-direction: column; +`; + +export const CommentText = styled.p` + color: ${colors.grey[200]}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.regular}; + margin: 0; +`; + +// 모임방의 뜨거운 감자 섹션 +export const HotTopicSection = styled.section` + display: flex; + flex-direction: column; + width: 94%; + gap: 24px; + background: ${colors.darkgrey.dark}; + margin: 20px 20px 80px 20px; + padding: 20px; + border-radius: 16px; +`; + +export const HotTopicSectionHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +`; + +export const HotTopicContent = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const HotTopicText = styled.p` + color: ${colors.grey[200]}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.regular}; + margin: 0; +`; + +export const VoteOptionsList = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const VoteOption = styled.div` + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + background: ${colors.grey[400]}; + border-radius: 40px; +`; + +export const VoteOptionNumber = styled.span` + color: ${colors.white}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; +`; + +export const VoteOptionText = styled.span` + color: ${colors.white}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; +`; + +export const Pagination = styled.div` + display: flex; + justify-content: center; + gap: 8px; + margin-top: 8px; +`; + +export const PaginationDot = styled.div<{ active?: boolean }>` + width: 4px; + height: 4px; + border-radius: 50%; + background-color: ${({ active }) => (active ? colors.white : colors.grey[300])}; +`; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx new file mode 100644 index 00000000..0631b3db --- /dev/null +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -0,0 +1,182 @@ +import { + Wrapper, + TopBackground, + Header, + BannerSection, + GroupTitle, + SubTitle, + Intro, + MetaInfo, + Meta, + MetaDate, + MetaMember, + MetaTotalMember, + TagRow, + Tag, + TagGenre, +} from './GroupDetail.styled'; +import { + RecordSection, + RecordSectionHeader, + RecordSectionTitle, + RecordSectionChevron, + RecordSectionContent, + BookTitle, + CurrentPage, + ProgressSection, + ProgressBar, + ProgressBarFill, + ProgressText, + CommentSection, + CommentSectionHeader, + CommentContent, + CommentText, + HotTopicSection, + HotTopicSectionHeader, + HotTopicContent, + HotTopicText, + VoteOptionsList, + VoteOption, + VoteOptionNumber, + VoteOptionText, + Pagination, + PaginationDot, +} from './ParticipatedGroupDetail.styled'; + +import leftArrow from '../../assets/common/leftArrow.svg'; +import moreIcon from '../../assets/common/more.svg'; +import { useNavigate } from 'react-router-dom'; +import { IconButton } from '@/components/common/IconButton'; +import { mockGroupDetail } from '../../mocks/groupDetail.mock'; +import lockIcon from '../../assets/group/lock.svg'; +import calendarIcon from '../../assets/group/calendar.svg'; +import peopleIcon from '../../assets/common/darkPeople.svg'; +import rightChevron from '../../assets/common/right-Chevron.svg'; + +const ParticipatedGroupDetail = () => { + const { title, isPrivate, introduction, activityPeriod, members, ddayText, genre } = + mockGroupDetail; + + const navigate = useNavigate(); + + const handleBackButton = () => { + navigate(-1); + }; + + const handleMoreButton = () => {}; + + const handleRecordSectionClick = () => { + navigate('/memory'); + }; + + const handleCommentSectionClick = () => { + // 한마디 작성 페이지로 이동 + }; + + const handleHotTopicSectionClick = () => { + // 뜨거운 감자 페이지로 이동 + }; + + return ( + + +
+ + +
+ + + {title} {isPrivate && 자물쇠 아이콘} + + +
소개글
+
+ {introduction} +
+ + + + 모임 활동기간 + + + {activityPeriod.start} ~ {activityPeriod.end} + + + + + 독서메이트 + + + {members.current} + 명 참여 중 + + + + + + 장르 {genre} + + +
+
+ + + + 기록장 + + + + 최정화 저 + 현재 페이지 1 + + + + + 30% + + + + + + + 오늘의 한마디 + + + + 모임방 멤버들과 간단한 인사를 나눠보세요! + + + + + + 모임방의 뜨거운 감자 + + + + 투표 1개 내용입니다... + + + 1. + 김땡땡 + + + 1. + 김땡땡 + + + 1. + 김땡땡 + + + + + + + + + +
+ ); +}; + +export default ParticipatedGroupDetail; diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 770e887b..e992c02b 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -35,6 +35,7 @@ import WithdrawPage from './mypage/WithdrawPage'; import WithdrawDonePage from './mypage/WithdrawDonePage'; import EditPage from './mypage/EditPage'; import Notice from './notice/Notice'; +import ParticipatedGroupDetail from './groupDetail/ParticipatedGroupDetail'; const Router = () => { const router = createBrowserRouter( @@ -51,6 +52,7 @@ const Router = () => { } /> } /> } /> + } /> } /> } /> } /> From 23ff99bf1d506179316519a7bb6210ef5eba3a83 Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 01:21:41 +0900 Subject: [PATCH 02/16] =?UTF-8?q?refactor:=20=EC=B0=B8=EC=97=AC=20?= =?UTF-8?q?=ED=9B=84=20=EB=AA=A8=EC=9E=84=EB=B0=A9=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9D=84=20=EC=9E=AC=EC=82=AC=EC=9A=A9=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EC=9D=BC=EB=B6=80=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=EB=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/group/CommentSection.styled.ts | 44 +++++ src/components/group/CommentSection.tsx | 30 +++ .../group/HotTopicSection.styled.ts | 86 ++++++++ src/components/group/HotTopicSection.tsx | 65 ++++++ src/components/group/RecordSection.styled.ts | 79 ++++++++ src/components/group/RecordSection.tsx | 44 +++++ src/pages/groupDetail/GroupDetail.styled.ts | 1 + .../ParticipatedGroupDetail.styled.ts | 186 +----------------- .../groupDetail/ParticipatedGroupDetail.tsx | 125 ++++-------- 9 files changed, 400 insertions(+), 260 deletions(-) create mode 100644 src/components/group/CommentSection.styled.ts create mode 100644 src/components/group/CommentSection.tsx create mode 100644 src/components/group/HotTopicSection.styled.ts create mode 100644 src/components/group/HotTopicSection.tsx create mode 100644 src/components/group/RecordSection.styled.ts create mode 100644 src/components/group/RecordSection.tsx diff --git a/src/components/group/CommentSection.styled.ts b/src/components/group/CommentSection.styled.ts new file mode 100644 index 00000000..4d9a89ad --- /dev/null +++ b/src/components/group/CommentSection.styled.ts @@ -0,0 +1,44 @@ +import styled from '@emotion/styled'; +import { colors, typography } from '@/styles/global/global'; + +export const CommentSection = styled.section` + display: flex; + flex-direction: column; + width: 94%; + gap: 24px; + background: ${colors.darkgrey.dark}; + margin: 20px 20px 0 20px; + padding: 20px; + border-radius: 16px; +`; + +export const CommentSectionHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +`; + +export const CommentSectionTitle = styled.h3` + color: ${colors.white}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.medium}; + margin: 0; +`; + +export const CommentSectionChevron = styled.img` + width: 24px; + height: 24px; +`; + +export const CommentContent = styled.div` + display: flex; + flex-direction: column; +`; + +export const CommentText = styled.p` + color: ${colors.grey[200]}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.regular}; + margin: 0; +`; diff --git a/src/components/group/CommentSection.tsx b/src/components/group/CommentSection.tsx new file mode 100644 index 00000000..7bf68a01 --- /dev/null +++ b/src/components/group/CommentSection.tsx @@ -0,0 +1,30 @@ +import { + CommentSection as StyledCommentSection, + CommentSectionHeader, + CommentSectionTitle, + CommentSectionChevron, + CommentContent, + CommentText, +} from './CommentSection.styled'; +import rightChevron from '../../assets/common/right-Chevron.svg'; + +interface CommentSectionProps { + message: string; + onClick: () => void; +} + +const CommentSection = ({ message, onClick }: CommentSectionProps) => { + return ( + + + 오늘의 한마디 + + + + {message} + + + ); +}; + +export default CommentSection; diff --git a/src/components/group/HotTopicSection.styled.ts b/src/components/group/HotTopicSection.styled.ts new file mode 100644 index 00000000..281ad74e --- /dev/null +++ b/src/components/group/HotTopicSection.styled.ts @@ -0,0 +1,86 @@ +import styled from '@emotion/styled'; +import { colors, typography } from '@/styles/global/global'; + +export const HotTopicSection = styled.section` + display: flex; + flex-direction: column; + width: 94%; + gap: 24px; + background: ${colors.darkgrey.dark}; + margin: 20px 20px 80px 20px; + padding: 20px; + border-radius: 16px; +`; + +export const HotTopicSectionHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +`; + +export const HotTopicSectionTitle = styled.h3` + color: ${colors.white}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.medium}; + margin: 0; +`; + +export const HotTopicSectionChevron = styled.img` + width: 24px; + height: 24px; +`; + +export const HotTopicContent = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const HotTopicText = styled.p` + color: ${colors.grey[200]}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.regular}; + margin: 0; +`; + +export const VoteOptionsList = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; + +export const VoteOption = styled.div` + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + background: ${colors.grey[400]}; + border-radius: 40px; +`; + +export const VoteOptionNumber = styled.span` + color: ${colors.white}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; +`; + +export const VoteOptionText = styled.span` + color: ${colors.white}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; +`; + +export const Pagination = styled.div` + display: flex; + justify-content: center; + gap: 8px; + margin-top: 8px; +`; + +export const PaginationDot = styled.div<{ active?: boolean }>` + width: 4px; + height: 4px; + border-radius: 50%; + background-color: ${({ active }) => (active ? colors.white : colors.grey[300])}; +`; diff --git a/src/components/group/HotTopicSection.tsx b/src/components/group/HotTopicSection.tsx new file mode 100644 index 00000000..576ea228 --- /dev/null +++ b/src/components/group/HotTopicSection.tsx @@ -0,0 +1,65 @@ +import { + HotTopicSection as StyledHotTopicSection, + HotTopicSectionHeader, + HotTopicSectionTitle, + HotTopicSectionChevron, + HotTopicContent, + HotTopicText, + VoteOptionsList, + VoteOption, + VoteOptionNumber, + VoteOptionText, + Pagination, + PaginationDot, +} from './HotTopicSection.styled'; +import rightChevron from '../../assets/common/right-Chevron.svg'; + +export interface VoteOption { + id: string; + text: string; +} + +interface HotTopicSectionProps { + topicText: string; + voteOptions: VoteOption[]; + currentPage: number; + totalPages: number; + onClick: () => void; +} + +const HotTopicSection = ({ + topicText, + voteOptions, + currentPage, + totalPages, + onClick, +}: HotTopicSectionProps) => { + return ( + + + 모임방의 뜨거운 감자 + + + + {topicText} + + {voteOptions.map((option, index) => ( + + {index + 1}. + {option.text} + + ))} + + {totalPages > 1 && ( + + {Array.from({ length: totalPages }, (_, index) => ( + + ))} + + )} + + + ); +}; + +export default HotTopicSection; diff --git a/src/components/group/RecordSection.styled.ts b/src/components/group/RecordSection.styled.ts new file mode 100644 index 00000000..29c24259 --- /dev/null +++ b/src/components/group/RecordSection.styled.ts @@ -0,0 +1,79 @@ +import styled from '@emotion/styled'; +import { colors, typography } from '@/styles/global/global'; + +export const RecordSection = styled.section` + display: flex; + flex-direction: column; + width: 94%; + gap: 24px; + background: ${colors.darkgrey.dark}; + margin: 10px 20px 0 20px; + padding: 20px; + border-radius: 16px; +`; + +export const RecordSectionHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +`; + +export const RecordSectionTitle = styled.h3` + color: ${colors.white}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.medium}; + margin: 0; +`; + +export const RecordSectionChevron = styled.img` + width: 24px; + height: 24px; +`; + +export const RecordSectionContent = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const BookTitle = styled.div` + color: ${colors.white}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; +`; + +export const CurrentPage = styled.div` + color: ${colors.white}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; +`; + +export const ProgressSection = styled.div` + display: flex; + align-items: center; + gap: 12px; +`; + +export const ProgressBar = styled.div` + flex: 1; + height: 8px; + background-color: ${colors.grey[400]}; + border-radius: 4px; + overflow: hidden; +`; + +export const ProgressBarFill = styled.div<{ progress: number }>` + width: ${({ progress }) => progress}%; + height: 100%; + background-color: ${colors.purple.main}; + border-radius: 4px; + transition: width 0.3s ease; +`; + +export const ProgressText = styled.span` + color: ${colors.purple.main}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; + min-width: 32px; +`; diff --git a/src/components/group/RecordSection.tsx b/src/components/group/RecordSection.tsx new file mode 100644 index 00000000..e7ab6d80 --- /dev/null +++ b/src/components/group/RecordSection.tsx @@ -0,0 +1,44 @@ +import { + RecordSection as StyledRecordSection, + RecordSectionHeader, + RecordSectionTitle, + RecordSectionChevron, + RecordSectionContent, + BookTitle, + CurrentPage, + ProgressSection, + ProgressBar, + ProgressBarFill, + ProgressText, +} from './RecordSection.styled'; +import rightChevron from '../../assets/common/right-Chevron.svg'; + +interface RecordSectionProps { + bookAuthor: string; + currentPage: number; + progress: number; + onClick: () => void; +} + +const RecordSection = ({ bookAuthor, currentPage, progress, onClick }: RecordSectionProps) => { + return ( + + + 기록장 + + + + {bookAuthor} + 현재 페이지 {currentPage} + + + + + {progress}% + + + + ); +}; + +export default RecordSection; diff --git a/src/pages/groupDetail/GroupDetail.styled.ts b/src/pages/groupDetail/GroupDetail.styled.ts index d8a44c9a..c34757ea 100644 --- a/src/pages/groupDetail/GroupDetail.styled.ts +++ b/src/pages/groupDetail/GroupDetail.styled.ts @@ -76,6 +76,7 @@ export const Intro = styled.p` font-size: ${typography.fontSize['xs']}; font-weight: ${typography.fontWeight.regular}; color: ${colors.grey[100]}; + line-height: 20px; `; export const MetaInfo = styled.div` diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts b/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts index c10aaa8b..fb6c2ba2 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts +++ b/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts @@ -1,184 +1,16 @@ import styled from '@emotion/styled'; -import { colors, typography } from '@/styles/global/global'; +import { colors } from '@/styles/global/global'; -// 기록장 섹션 -export const RecordSection = styled.section` +export const ParticipatedWrapper = styled.div` display: flex; + position: relative; flex-direction: column; - width: 94%; - gap: 24px; - background: ${colors.darkgrey.dark}; - margin: 10px 20px 0 20px; - padding: 20px; - border-radius: 16px; -`; - -export const RecordSectionHeader = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; -`; - -export const RecordSectionTitle = styled.h3` - color: ${colors.white}; - font-size: ${typography.fontSize.base}; - font-weight: ${typography.fontWeight.medium}; - margin: 0; -`; - -export const RecordSectionChevron = styled.img` - width: 24px; - height: 24px; -`; - -export const RecordSectionContent = styled.div` - display: flex; - flex-direction: column; - gap: 16px; -`; - -export const BookTitle = styled.div` - color: ${colors.white}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; -`; - -export const CurrentPage = styled.div` - color: ${colors.white}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; -`; - -export const ProgressSection = styled.div` - display: flex; - align-items: center; - gap: 12px; -`; - -export const ProgressBar = styled.div` - flex: 1; - height: 8px; - background-color: ${colors.grey[400]}; - border-radius: 4px; - overflow: hidden; -`; - -export const ProgressBarFill = styled.div<{ progress: number }>` - width: ${({ progress }) => progress}%; - height: 100%; - background-color: ${colors.purple.main}; - border-radius: 4px; - transition: width 0.3s ease; -`; - -export const ProgressText = styled.span` - color: ${colors.purple.main}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; - min-width: 32px; -`; - -// 오늘의 한마디 섹션 -export const CommentSection = styled.section` - display: flex; - flex-direction: column; - width: 94%; - gap: 24px; - background: ${colors.darkgrey.dark}; - margin: 20px 20px 0 20px; - padding: 20px; - border-radius: 16px; -`; - -export const CommentSectionHeader = styled.div` - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; -`; - -export const CommentContent = styled.div` - display: flex; - flex-direction: column; -`; - -export const CommentText = styled.p` - color: ${colors.grey[200]}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.regular}; - margin: 0; -`; - -// 모임방의 뜨거운 감자 섹션 -export const HotTopicSection = styled.section` - display: flex; - flex-direction: column; - width: 94%; - gap: 24px; - background: ${colors.darkgrey.dark}; - margin: 20px 20px 80px 20px; - padding: 20px; - border-radius: 16px; -`; - -export const HotTopicSectionHeader = styled.div` - display: flex; - justify-content: space-between; align-items: center; - cursor: pointer; -`; - -export const HotTopicContent = styled.div` - display: flex; - flex-direction: column; - gap: 16px; -`; - -export const HotTopicText = styled.p` - color: ${colors.grey[200]}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.regular}; - margin: 0; -`; - -export const VoteOptionsList = styled.div` - display: flex; - flex-direction: column; - gap: 12px; -`; - -export const VoteOption = styled.div` - display: flex; - align-items: center; - gap: 8px; - padding: 12px 16px; - background: ${colors.grey[400]}; - border-radius: 40px; -`; - -export const VoteOptionNumber = styled.span` - color: ${colors.white}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; -`; - -export const VoteOptionText = styled.span` - color: ${colors.white}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; -`; - -export const Pagination = styled.div` - display: flex; justify-content: center; - gap: 8px; - margin-top: 8px; -`; - -export const PaginationDot = styled.div<{ active?: boolean }>` - width: 4px; - height: 4px; - border-radius: 50%; - background-color: ${({ active }) => (active ? colors.white : colors.grey[300])}; + min-width: 320px; + max-width: 767px; + height: 100%; + margin: 0 auto; + background-color: ${colors.black.main}; + overflow: hidden; `; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 0631b3db..65a9450d 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -15,33 +15,10 @@ import { Tag, TagGenre, } from './GroupDetail.styled'; -import { - RecordSection, - RecordSectionHeader, - RecordSectionTitle, - RecordSectionChevron, - RecordSectionContent, - BookTitle, - CurrentPage, - ProgressSection, - ProgressBar, - ProgressBarFill, - ProgressText, - CommentSection, - CommentSectionHeader, - CommentContent, - CommentText, - HotTopicSection, - HotTopicSectionHeader, - HotTopicContent, - HotTopicText, - VoteOptionsList, - VoteOption, - VoteOptionNumber, - VoteOptionText, - Pagination, - PaginationDot, -} from './ParticipatedGroupDetail.styled'; +import RecordSection from '../../components/group/RecordSection'; +import CommentSection from '../../components/group/CommentSection'; +import HotTopicSection from '../../components/group/HotTopicSection'; +import type { VoteOption } from '../../components/group/HotTopicSection'; import leftArrow from '../../assets/common/leftArrow.svg'; import moreIcon from '../../assets/common/more.svg'; @@ -51,7 +28,6 @@ import { mockGroupDetail } from '../../mocks/groupDetail.mock'; import lockIcon from '../../assets/group/lock.svg'; import calendarIcon from '../../assets/group/calendar.svg'; import peopleIcon from '../../assets/common/darkPeople.svg'; -import rightChevron from '../../assets/common/right-Chevron.svg'; const ParticipatedGroupDetail = () => { const { title, isPrivate, introduction, activityPeriod, members, ddayText, genre } = @@ -77,6 +53,28 @@ const ParticipatedGroupDetail = () => { // 뜨거운 감자 페이지로 이동 }; + // 모킹 데이터 + const recordData = { + bookAuthor: '최정화 저', + currentPage: 1, + progress: 30, + }; + + const commentData = { + message: '모임방 멤버들과 간단한 인사를 나눠보세요!', + }; + + const hotTopicData = { + topicText: '투표 1개 내용입니다...', + voteOptions: [ + { id: '1', text: '김땡땡' }, + { id: '2', text: '김땡땡' }, + { id: '3', text: '김땡땡' }, + ] as VoteOption[], + currentPage: 0, + totalPages: 3, + }; + return ( @@ -120,61 +118,22 @@ const ParticipatedGroupDetail = () => { - - - 기록장 - - - - 최정화 저 - 현재 페이지 1 - - - - - 30% - - - - - - - 오늘의 한마디 - - - - 모임방 멤버들과 간단한 인사를 나눠보세요! - - - - - - 모임방의 뜨거운 감자 - - - - 투표 1개 내용입니다... - - - 1. - 김땡땡 - - - 1. - 김땡땡 - - - 1. - 김땡땡 - - - - - - - - - + + + + + ); }; From 1a33c83ba7b6e737dba1640f5c284bcdb60358ce Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 01:35:20 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat:=20=EC=B0=B8=EC=97=AC=20=ED=9B=84=20?= =?UTF-8?q?=EB=AA=A8=EC=9E=84=EB=B0=A9=EC=97=90=20=EC=B1=85=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=84=B9=EC=85=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/GroupBookSection.styled.ts | 83 +++++++++++++++++++ src/components/group/GroupBookSection.tsx | 53 ++++++++++++ .../groupDetail/ParticipatedGroupDetail.tsx | 23 +++-- 3 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 src/components/group/GroupBookSection.styled.ts create mode 100644 src/components/group/GroupBookSection.tsx diff --git a/src/components/group/GroupBookSection.styled.ts b/src/components/group/GroupBookSection.styled.ts new file mode 100644 index 00000000..41924553 --- /dev/null +++ b/src/components/group/GroupBookSection.styled.ts @@ -0,0 +1,83 @@ +import styled from '@emotion/styled'; +import { colors, typography } from '@/styles/global/global'; + +export const GroupBookSection = styled.section` + display: flex; + flex-direction: column; + width: 94%; + gap: 24px; + background: ${colors.darkgrey.dark}; + margin: 10px 20px 0 20px; + padding: 20px; + border-radius: 16px; +`; + +export const GroupBookSectionHeader = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; +`; + +export const GroupBookSectionTitle = styled.h3` + color: ${colors.white}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.medium}; + margin: 0; +`; + +export const GroupBookSectionChevron = styled.img` + width: 24px; + height: 24px; +`; + +export const GroupBookSectionContent = styled.div` + display: flex; + flex-direction: column; +`; + +export const BookInfo = styled.div` + display: flex; + gap: 16px; +`; + +export const BookCover = styled.img` + width: 80px; + height: 107px; + object-fit: cover; +`; + +export const BookDetails = styled.div` + display: flex; + flex-direction: column; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; + gap: 20px; + color: ${colors.white}; + margin: auto 0; +`; + +export const BookTitle = styled.h4` + color: ${colors.white}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.medium}; + margin: 0; +`; + +export const BookAuthor = styled.div` + color: ${colors.white}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; +`; + +export const BookDescription = styled.div` + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; + color: ${colors.white}; + + > p { + margin-top: 4px; + color: ${colors.grey[200]}; + font-weight: ${typography.fontWeight.regular}; + } +`; diff --git a/src/components/group/GroupBookSection.tsx b/src/components/group/GroupBookSection.tsx new file mode 100644 index 00000000..e3810fa9 --- /dev/null +++ b/src/components/group/GroupBookSection.tsx @@ -0,0 +1,53 @@ +import { + GroupBookSection as StyledGroupBookSection, + GroupBookSectionHeader, + GroupBookSectionTitle, + GroupBookSectionChevron, + GroupBookSectionContent, + BookInfo, + BookCover, + BookDetails, + BookAuthor, + BookDescription, +} from './GroupBookSection.styled'; +import rightChevron from '../../assets/common/right-Chevron.svg'; + +interface GroupBookSectionProps { + title: string; + author: string; + coverUrl: string; + description: string; + onClick: () => void; +} + +const GroupBookSection = ({ + title, + author, + coverUrl, + description, + onClick, +}: GroupBookSectionProps) => { + return ( + + + {title} + + + + + + + {author} + + 도서 소개 +
+

{description}

+
+
+
+
+
+ ); +}; + +export default GroupBookSection; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 65a9450d..7a33abf3 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -1,5 +1,4 @@ import { - Wrapper, TopBackground, Header, BannerSection, @@ -15,9 +14,11 @@ import { Tag, TagGenre, } from './GroupDetail.styled'; +import { ParticipatedWrapper } from './ParticipatedGroupDetail.styled'; import RecordSection from '../../components/group/RecordSection'; import CommentSection from '../../components/group/CommentSection'; import HotTopicSection from '../../components/group/HotTopicSection'; +import GroupBookSection from '../../components/group/GroupBookSection'; import type { VoteOption } from '../../components/group/HotTopicSection'; import leftArrow from '../../assets/common/leftArrow.svg'; @@ -30,8 +31,7 @@ import calendarIcon from '../../assets/group/calendar.svg'; import peopleIcon from '../../assets/common/darkPeople.svg'; const ParticipatedGroupDetail = () => { - const { title, isPrivate, introduction, activityPeriod, members, ddayText, genre } = - mockGroupDetail; + const { title, isPrivate, introduction, activityPeriod, members, genre, book } = mockGroupDetail; const navigate = useNavigate(); @@ -53,6 +53,11 @@ const ParticipatedGroupDetail = () => { // 뜨거운 감자 페이지로 이동 }; + const handleBookSectionClick = () => { + // 책 상세정보 페이지로 이동 (예: /book/:isbn) + navigate(`/book/${book.isbn || '123'}`); + }; + // 모킹 데이터 const recordData = { bookAuthor: '최정화 저', @@ -76,7 +81,7 @@ const ParticipatedGroupDetail = () => { }; return ( - +
@@ -118,6 +123,14 @@ const ParticipatedGroupDetail = () => { + + { totalPages={hotTopicData.totalPages} onClick={handleHotTopicSectionClick} /> - + ); }; From fdd6eb6fbbf77ed9b13130b29123678d97aa24dc Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 01:50:40 +0900 Subject: [PATCH 04/16] =?UTF-8?q?feat:=20=EC=B1=85=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/group/right-chevron.svg | 3 + .../group/GroupBookSection.styled.ts | 81 +++++-------------- src/components/group/GroupBookSection.tsx | 47 +++-------- .../groupDetail/ParticipatedGroupDetail.tsx | 8 +- 4 files changed, 34 insertions(+), 105 deletions(-) create mode 100644 src/assets/group/right-chevron.svg diff --git a/src/assets/group/right-chevron.svg b/src/assets/group/right-chevron.svg new file mode 100644 index 00000000..6c21a492 --- /dev/null +++ b/src/assets/group/right-chevron.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/group/GroupBookSection.styled.ts b/src/components/group/GroupBookSection.styled.ts index 41924553..83fb2684 100644 --- a/src/components/group/GroupBookSection.styled.ts +++ b/src/components/group/GroupBookSection.styled.ts @@ -1,83 +1,40 @@ import styled from '@emotion/styled'; -import { colors, typography } from '@/styles/global/global'; +import { colors, semanticColors, typography } from '@/styles/global/global'; export const GroupBookSection = styled.section` display: flex; - flex-direction: column; + align-items: center; + justify-content: space-between; width: 94%; - gap: 24px; background: ${colors.darkgrey.dark}; margin: 10px 20px 0 20px; - padding: 20px; - border-radius: 16px; -`; - -export const GroupBookSectionHeader = styled.div` - display: flex; - justify-content: space-between; - align-items: center; + padding: 10px 12px; + border-radius: 12px; cursor: pointer; `; -export const GroupBookSectionTitle = styled.h3` +export const BookTitle = styled.h3` color: ${colors.white}; font-size: ${typography.fontSize.base}; - font-weight: ${typography.fontWeight.medium}; + font-weight: ${typography.fontWeight.semibold}; margin: 0; + margin-right: 12px; `; -export const GroupBookSectionChevron = styled.img` - width: 24px; - height: 24px; -`; - -export const GroupBookSectionContent = styled.div` - display: flex; - flex-direction: column; -`; - -export const BookInfo = styled.div` - display: flex; - gap: 16px; -`; - -export const BookCover = styled.img` - width: 80px; - height: 107px; - object-fit: cover; -`; - -export const BookDetails = styled.div` - display: flex; - flex-direction: column; +export const BookAuthor = styled.span` + color: ${semanticColors.text.secondary}; font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; - gap: 20px; - color: ${colors.white}; - margin: auto 0; + font-weight: ${typography.fontWeight.regular}; `; -export const BookTitle = styled.h4` - color: ${colors.white}; - font-size: ${typography.fontSize.base}; - font-weight: ${typography.fontWeight.medium}; - margin: 0; -`; - -export const BookAuthor = styled.div` - color: ${colors.white}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; +export const ChevronIcon = styled.img` + width: 24px; + height: 24px; + flex-shrink: 0; `; -export const BookDescription = styled.div` - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; - color: ${colors.white}; - - > p { - margin-top: 4px; - color: ${colors.grey[200]}; - font-weight: ${typography.fontWeight.regular}; - } +export const RightSection = styled.div` + display: flex; + align-items: center; + margin-right: -8px; `; diff --git a/src/components/group/GroupBookSection.tsx b/src/components/group/GroupBookSection.tsx index e3810fa9..f3b9ce1e 100644 --- a/src/components/group/GroupBookSection.tsx +++ b/src/components/group/GroupBookSection.tsx @@ -1,51 +1,26 @@ import { GroupBookSection as StyledGroupBookSection, - GroupBookSectionHeader, - GroupBookSectionTitle, - GroupBookSectionChevron, - GroupBookSectionContent, - BookInfo, - BookCover, - BookDetails, + BookTitle, BookAuthor, - BookDescription, + ChevronIcon, + RightSection, } from './GroupBookSection.styled'; -import rightChevron from '../../assets/common/right-Chevron.svg'; +import rightChevron from '../../assets/group/right-chevron.svg'; interface GroupBookSectionProps { title: string; author: string; - coverUrl: string; - description: string; onClick: () => void; } -const GroupBookSection = ({ - title, - author, - coverUrl, - description, - onClick, -}: GroupBookSectionProps) => { +const GroupBookSection = ({ title, author, onClick }: GroupBookSectionProps) => { return ( - - - {title} - - - - - - - {author} - - 도서 소개 -
-

{description}

-
-
-
-
+ + {title} + + {author} + + ); }; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 7a33abf3..9b5b5e59 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -123,13 +123,7 @@ const ParticipatedGroupDetail = () => { - + Date: Thu, 7 Aug 2025 02:06:40 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat:=20=EA=B8=B0=EB=A1=9D=EC=9E=A5=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/GroupBookSection.styled.ts | 4 +- src/components/group/RecordSection.styled.ts | 48 +++++++++---------- src/components/group/RecordSection.tsx | 21 ++++---- 3 files changed, 34 insertions(+), 39 deletions(-) diff --git a/src/components/group/GroupBookSection.styled.ts b/src/components/group/GroupBookSection.styled.ts index 83fb2684..9663dd57 100644 --- a/src/components/group/GroupBookSection.styled.ts +++ b/src/components/group/GroupBookSection.styled.ts @@ -5,9 +5,9 @@ export const GroupBookSection = styled.section` display: flex; align-items: center; justify-content: space-between; - width: 94%; + width: 90%; background: ${colors.darkgrey.dark}; - margin: 10px 20px 0 20px; + margin: 0 20px 0 20px; padding: 10px 12px; border-radius: 12px; cursor: pointer; diff --git a/src/components/group/RecordSection.styled.ts b/src/components/group/RecordSection.styled.ts index 29c24259..34054029 100644 --- a/src/components/group/RecordSection.styled.ts +++ b/src/components/group/RecordSection.styled.ts @@ -1,15 +1,15 @@ import styled from '@emotion/styled'; -import { colors, typography } from '@/styles/global/global'; +import { colors, semanticColors, typography } from '@/styles/global/global'; export const RecordSection = styled.section` display: flex; flex-direction: column; - width: 94%; - gap: 24px; + width: 90%; + gap: 12px; background: ${colors.darkgrey.dark}; - margin: 10px 20px 0 20px; - padding: 20px; - border-radius: 16px; + margin: 20px 20px 0 20px; + padding: 16px 12px; + border-radius: 12px; `; export const RecordSectionHeader = styled.div` @@ -22,13 +22,14 @@ export const RecordSectionHeader = styled.div` export const RecordSectionTitle = styled.h3` color: ${colors.white}; font-size: ${typography.fontSize.base}; - font-weight: ${typography.fontWeight.medium}; + font-weight: ${typography.fontWeight.semibold}; margin: 0; `; export const RecordSectionChevron = styled.img` width: 24px; height: 24px; + margin-right: -8px; `; export const RecordSectionContent = styled.div` @@ -37,30 +38,20 @@ export const RecordSectionContent = styled.div` gap: 16px; `; -export const BookTitle = styled.div` - color: ${colors.white}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; -`; - export const CurrentPage = styled.div` - color: ${colors.white}; + color: ${semanticColors.text.secondary}; font-size: ${typography.fontSize.xs}; font-weight: ${typography.fontWeight.medium}; `; -export const ProgressSection = styled.div` - display: flex; - align-items: center; - gap: 12px; -`; - export const ProgressBar = styled.div` - flex: 1; - height: 8px; - background-color: ${colors.grey[400]}; + width: 100%; + height: 7px; + background-color: ${colors.grey[300]}; border-radius: 4px; overflow: hidden; + margin-top: -4px; + margin-bottom: 20px; `; export const ProgressBarFill = styled.div<{ progress: number }>` @@ -71,9 +62,14 @@ export const ProgressBarFill = styled.div<{ progress: number }>` transition: width 0.3s ease; `; -export const ProgressText = styled.span` +export const PercentText = styled.span` color: ${colors.purple.main}; font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; - min-width: 32px; + font-weight: ${typography.fontWeight.semibold}; +`; + +export const ProgressText = styled.span` + color: ${colors.purple.main}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.semibold}; `; diff --git a/src/components/group/RecordSection.tsx b/src/components/group/RecordSection.tsx index e7ab6d80..351b7f29 100644 --- a/src/components/group/RecordSection.tsx +++ b/src/components/group/RecordSection.tsx @@ -4,14 +4,13 @@ import { RecordSectionTitle, RecordSectionChevron, RecordSectionContent, - BookTitle, CurrentPage, - ProgressSection, ProgressBar, + PercentText, ProgressBarFill, ProgressText, } from './RecordSection.styled'; -import rightChevron from '../../assets/common/right-Chevron.svg'; +import rightChevron from '../../assets/group/right-chevron.svg'; interface RecordSectionProps { bookAuthor: string; @@ -20,7 +19,7 @@ interface RecordSectionProps { onClick: () => void; } -const RecordSection = ({ bookAuthor, currentPage, progress, onClick }: RecordSectionProps) => { +const RecordSection = ({ currentPage, progress, onClick }: RecordSectionProps) => { return ( @@ -28,14 +27,14 @@ const RecordSection = ({ bookAuthor, currentPage, progress, onClick }: RecordSec - {bookAuthor} 현재 페이지 {currentPage} - - - - - {progress}% - +
+ {progress} + % +
+ + +
); From 159ac49e55f03673ba11de2522182ff65727f4bc Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 02:10:55 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat:=20=EC=98=A4=EB=8A=98=EC=9D=98=20?= =?UTF-8?q?=ED=95=9C=EB=A7=88=EB=94=94=20=EA=B5=AC=ED=98=84=20=EB=B0=8F=20?= =?UTF-8?q?=EB=9D=BC=EC=9A=B0=ED=8A=B8=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/group/CommentSection.styled.ts | 15 ++++++++------- src/components/group/CommentSection.tsx | 2 +- src/pages/groupDetail/ParticipatedGroupDetail.tsx | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/group/CommentSection.styled.ts b/src/components/group/CommentSection.styled.ts index 4d9a89ad..8cf2f2b7 100644 --- a/src/components/group/CommentSection.styled.ts +++ b/src/components/group/CommentSection.styled.ts @@ -4,12 +4,12 @@ import { colors, typography } from '@/styles/global/global'; export const CommentSection = styled.section` display: flex; flex-direction: column; - width: 94%; - gap: 24px; + width: 90%; + gap: 12px; background: ${colors.darkgrey.dark}; margin: 20px 20px 0 20px; - padding: 20px; - border-radius: 16px; + padding: 16px 12px; + border-radius: 12px; `; export const CommentSectionHeader = styled.div` @@ -22,13 +22,14 @@ export const CommentSectionHeader = styled.div` export const CommentSectionTitle = styled.h3` color: ${colors.white}; font-size: ${typography.fontSize.base}; - font-weight: ${typography.fontWeight.medium}; + font-weight: ${typography.fontWeight.semibold}; margin: 0; `; export const CommentSectionChevron = styled.img` width: 24px; height: 24px; + margin-right: -8px; `; export const CommentContent = styled.div` @@ -37,8 +38,8 @@ export const CommentContent = styled.div` `; export const CommentText = styled.p` - color: ${colors.grey[200]}; + color: ${colors.grey[100]}; font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.regular}; + font-weight: ${typography.fontWeight.medium}; margin: 0; `; diff --git a/src/components/group/CommentSection.tsx b/src/components/group/CommentSection.tsx index 7bf68a01..6b38671c 100644 --- a/src/components/group/CommentSection.tsx +++ b/src/components/group/CommentSection.tsx @@ -6,7 +6,7 @@ import { CommentContent, CommentText, } from './CommentSection.styled'; -import rightChevron from '../../assets/common/right-Chevron.svg'; +import rightChevron from '../../assets/group/right-chevron.svg'; interface CommentSectionProps { message: string; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 9b5b5e59..0d9b9e56 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -46,7 +46,7 @@ const ParticipatedGroupDetail = () => { }; const handleCommentSectionClick = () => { - // 한마디 작성 페이지로 이동 + navigate('/today-words'); }; const handleHotTopicSectionClick = () => { From 7e3405960dc3f64081a9425b73f4ab0ed43ef4a4 Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 02:20:53 +0900 Subject: [PATCH 07/16] =?UTF-8?q?feat:=20=EB=9C=A8=EA=B1=B0=EC=9A=B4=20?= =?UTF-8?q?=EA=B0=90=EC=9E=90=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/HotTopicSection.styled.ts | 43 ++++++++----------- src/components/group/HotTopicSection.tsx | 3 -- .../groupDetail/ParticipatedGroupDetail.tsx | 2 +- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/src/components/group/HotTopicSection.styled.ts b/src/components/group/HotTopicSection.styled.ts index 281ad74e..19a45b35 100644 --- a/src/components/group/HotTopicSection.styled.ts +++ b/src/components/group/HotTopicSection.styled.ts @@ -4,12 +4,12 @@ import { colors, typography } from '@/styles/global/global'; export const HotTopicSection = styled.section` display: flex; flex-direction: column; - width: 94%; - gap: 24px; + width: 90%; + gap: 12px; background: ${colors.darkgrey.dark}; margin: 20px 20px 80px 20px; - padding: 20px; - border-radius: 16px; + padding: 16px 12px; + border-radius: 12px; `; export const HotTopicSectionHeader = styled.div` @@ -22,60 +22,55 @@ export const HotTopicSectionHeader = styled.div` export const HotTopicSectionTitle = styled.h3` color: ${colors.white}; font-size: ${typography.fontSize.base}; - font-weight: ${typography.fontWeight.medium}; + font-weight: ${typography.fontWeight.semibold}; margin: 0; `; -export const HotTopicSectionChevron = styled.img` - width: 24px; - height: 24px; -`; - export const HotTopicContent = styled.div` display: flex; flex-direction: column; - gap: 16px; + gap: 10px; `; export const HotTopicText = styled.p` - color: ${colors.grey[200]}; + color: ${colors.grey[100]}; font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.regular}; + font-weight: ${typography.fontWeight.medium}; margin: 0; `; export const VoteOptionsList = styled.div` display: flex; flex-direction: column; - gap: 12px; + gap: 10px; `; export const VoteOption = styled.div` display: flex; align-items: center; - gap: 8px; - padding: 12px 16px; - background: ${colors.grey[400]}; - border-radius: 40px; + gap: 2px; + padding: 12px; + background: ${colors.darkgrey.main}; + border-radius: 12px; `; export const VoteOptionNumber = styled.span` color: ${colors.white}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.regular}; `; export const VoteOptionText = styled.span` color: ${colors.white}; - font-size: ${typography.fontSize.xs}; - font-weight: ${typography.fontWeight.medium}; + font-size: ${typography.fontSize.sm}; + font-weight: ${typography.fontWeight.regular}; `; export const Pagination = styled.div` display: flex; justify-content: center; - gap: 8px; - margin-top: 8px; + gap: 12px; + margin-top: 2px; `; export const PaginationDot = styled.div<{ active?: boolean }>` diff --git a/src/components/group/HotTopicSection.tsx b/src/components/group/HotTopicSection.tsx index 576ea228..0167968e 100644 --- a/src/components/group/HotTopicSection.tsx +++ b/src/components/group/HotTopicSection.tsx @@ -2,7 +2,6 @@ import { HotTopicSection as StyledHotTopicSection, HotTopicSectionHeader, HotTopicSectionTitle, - HotTopicSectionChevron, HotTopicContent, HotTopicText, VoteOptionsList, @@ -12,7 +11,6 @@ import { Pagination, PaginationDot, } from './HotTopicSection.styled'; -import rightChevron from '../../assets/common/right-Chevron.svg'; export interface VoteOption { id: string; @@ -38,7 +36,6 @@ const HotTopicSection = ({ 모임방의 뜨거운 감자 - {topicText} diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 0d9b9e56..10ba67eb 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -70,7 +70,7 @@ const ParticipatedGroupDetail = () => { }; const hotTopicData = { - topicText: '투표 1개 내용입니다...', + topicText: '투표 1번 내용입니다...', voteOptions: [ { id: '1', text: '김땡땡' }, { id: '2', text: '김땡땡' }, From 7ec72220574e236dc8f868cc11e0e4ff249975cf Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 02:30:49 +0900 Subject: [PATCH 08/16] =?UTF-8?q?refactor:=20=ED=88=AC=ED=91=9C=EB=A5=BC?= =?UTF-8?q?=20=EC=9D=BD=EA=B8=B0=EC=A0=84=EC=9A=A9=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=ED=95=98=EA=B3=A0=20=EC=8A=AC=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20=EB=B0=8F=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/HotTopicSection.styled.ts | 39 ++++- src/components/group/HotTopicSection.tsx | 140 ++++++++++++++---- .../groupDetail/ParticipatedGroupDetail.tsx | 67 ++++++--- 3 files changed, 199 insertions(+), 47 deletions(-) diff --git a/src/components/group/HotTopicSection.styled.ts b/src/components/group/HotTopicSection.styled.ts index 19a45b35..cde75861 100644 --- a/src/components/group/HotTopicSection.styled.ts +++ b/src/components/group/HotTopicSection.styled.ts @@ -30,13 +30,20 @@ export const HotTopicContent = styled.div` display: flex; flex-direction: column; gap: 10px; + overflow: hidden; +`; + +export const SlideContainer = styled.div` + display: flex; + transition: transform 0.3s ease-in-out; + touch-action: pan-x; `; export const HotTopicText = styled.p` color: ${colors.grey[100]}; font-size: ${typography.fontSize.xs}; font-weight: ${typography.fontWeight.medium}; - margin: 0; + margin: 0 0 10px 0; `; export const VoteOptionsList = styled.div` @@ -52,6 +59,12 @@ export const VoteOption = styled.div` padding: 12px; background: ${colors.darkgrey.main}; border-radius: 12px; + transition: all 0.2s ease; + cursor: pointer; + + &:hover { + background: ${colors.grey[400]}; + } `; export const VoteOptionNumber = styled.span` @@ -70,7 +83,7 @@ export const Pagination = styled.div` display: flex; justify-content: center; gap: 12px; - margin-top: 2px; + margin-top: 10px; `; export const PaginationDot = styled.div<{ active?: boolean }>` @@ -78,4 +91,26 @@ export const PaginationDot = styled.div<{ active?: boolean }>` height: 4px; border-radius: 50%; background-color: ${({ active }) => (active ? colors.white : colors.grey[300])}; + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: ${colors.grey[100]}; + } +`; + +export const EmptyVoteContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 40px 20px; + text-align: center; +`; + +export const EmptyVoteTitle = styled.h4` + color: ${colors.grey[100]}; + font-size: ${typography.fontSize.xs}; + font-weight: ${typography.fontWeight.medium}; + margin: 0; `; diff --git a/src/components/group/HotTopicSection.tsx b/src/components/group/HotTopicSection.tsx index 0167968e..dbed4d12 100644 --- a/src/components/group/HotTopicSection.tsx +++ b/src/components/group/HotTopicSection.tsx @@ -10,51 +10,137 @@ import { VoteOptionText, Pagination, PaginationDot, + EmptyVoteContainer, + EmptyVoteTitle, + SlideContainer, } from './HotTopicSection.styled'; +import { useState, useRef } from 'react'; export interface VoteOption { id: string; text: string; } +export interface Poll { + id: string; + question: string; + options: VoteOption[]; + pageNumber: number; // 해당 투표가 위치한 페이지 +} + interface HotTopicSectionProps { - topicText: string; - voteOptions: VoteOption[]; - currentPage: number; - totalPages: number; + polls: Poll[]; + hasPolls: boolean; onClick: () => void; + onPollClick: (pageNumber: number) => void; // 투표 클릭 시 해당 페이지로 이동 } -const HotTopicSection = ({ - topicText, - voteOptions, - currentPage, - totalPages, - onClick, -}: HotTopicSectionProps) => { - return ( - - - 모임방의 뜨거운 감자 - +const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSectionProps) => { + const [currentPollIndex, setCurrentPollIndex] = useState(0); + const slideRef = useRef(null); + + // 투표 옵션 클릭 시 해당 페이지로 이동 + const handleVoteClick = (poll: Poll) => { + onPollClick(poll.pageNumber); + }; + + // 슬라이드 터치/드래그 처리 + const handleTouchStart = (e: React.TouchEvent) => { + const touch = e.touches[0]; + slideRef.current?.setAttribute('data-start-x', touch.clientX.toString()); + }; + + const handleTouchEnd = (e: React.TouchEvent) => { + const touch = e.changedTouches[0]; + const startX = parseFloat(slideRef.current?.getAttribute('data-start-x') || '0'); + const diffX = startX - touch.clientX; + + if (Math.abs(diffX) > 50) { + // 최소 50px 이상 드래그해야 슬라이드 + if (diffX > 0 && currentPollIndex < polls.length - 1) { + // 오른쪽으로 슬라이드 (다음 투표) + setCurrentPollIndex(currentPollIndex + 1); + } else if (diffX < 0 && currentPollIndex > 0) { + // 왼쪽으로 슬라이드 (이전 투표) + setCurrentPollIndex(currentPollIndex - 1); + } + } + }; + + const renderVoteOptions = (poll: Poll) => { + return poll.options.map((option, index) => ( + handleVoteClick(poll)} + style={{ cursor: 'pointer' }} + > + {index + 1}. + {option.text} + + )); + }; + + const renderEmptyState = () => ( + + + 모임방에 생성된 투표가 없어요 + + + ); + + const renderPollContent = () => { + if (!hasPolls || polls.length === 0) { + return renderEmptyState(); + } + + return ( - {topicText} - - {voteOptions.map((option, index) => ( - - {index + 1}. - {option.text} - + + {polls.map(poll => ( +
+ {poll.question} + {renderVoteOptions(poll)} +
))} -
- {totalPages > 1 && ( + + + {polls.length > 1 && ( - {Array.from({ length: totalPages }, (_, index) => ( - + {polls.map((_, index) => ( + setCurrentPollIndex(index)} + style={{ cursor: 'pointer' }} + /> ))} )}
+ ); + }; + + return ( + + + 모임방의 뜨거운 감자 + + {renderPollContent()} ); }; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 10ba67eb..0057e012 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -19,7 +19,7 @@ import RecordSection from '../../components/group/RecordSection'; import CommentSection from '../../components/group/CommentSection'; import HotTopicSection from '../../components/group/HotTopicSection'; import GroupBookSection from '../../components/group/GroupBookSection'; -import type { VoteOption } from '../../components/group/HotTopicSection'; +import type { Poll } from '../../components/group/HotTopicSection'; import leftArrow from '../../assets/common/leftArrow.svg'; import moreIcon from '../../assets/common/more.svg'; @@ -50,12 +50,18 @@ const ParticipatedGroupDetail = () => { }; const handleHotTopicSectionClick = () => { - // 뜨거운 감자 페이지로 이동 + // 뜨거운 감자 전체 페이지로 이동 + navigate('/memory'); // 또는 투표 전체 리스트 페이지 }; const handleBookSectionClick = () => { - // 책 상세정보 페이지로 이동 (예: /book/:isbn) - navigate(`/book/${book.isbn || '123'}`); + navigate(`/book/123`); + }; + + // 투표 클릭 시 해당 페이지의 기록장으로 이동 + const handlePollClick = (pageNumber: number) => { + // 해당 투표가 위치한 페이지 번호로 필터를 씌운 기록장 화면으로 이동 + navigate(`/memory?page=${pageNumber}&filter=poll`); }; // 모킹 데이터 @@ -69,16 +75,42 @@ const ParticipatedGroupDetail = () => { message: '모임방 멤버들과 간단한 인사를 나눠보세요!', }; - const hotTopicData = { - topicText: '투표 1번 내용입니다...', - voteOptions: [ - { id: '1', text: '김땡땡' }, - { id: '2', text: '김땡땡' }, - { id: '3', text: '김땡땡' }, - ] as VoteOption[], - currentPage: 0, - totalPages: 3, - }; + // 투표 데이터 (투표 결과 없이 질문과 선택지만) + const mockPolls: Poll[] = [ + { + id: '1', + question: '3연에 나오는 심장은 무엇을 의미하는 걸까요?', + options: [ + { id: '1', text: '김땡땡' }, + { id: '2', text: '김땡땡' }, + ], + pageNumber: 456, // 해당 투표가 위치한 페이지 + }, + { + id: '2', + question: '또 다른 투표 질문입니다', + options: [ + { id: '1', text: '선택지 1' }, + { id: '2', text: '선택지 2' }, + { id: '3', text: '선택지 3' }, + ], + pageNumber: 123, + }, + { + id: '3', + question: '세 번째 투표입니다', + options: [ + { id: '1', text: 'A 답변' }, + { id: '2', text: 'B 답변' }, + ], + pageNumber: 789, + }, + ]; + + // 투표가 없을 때 테스트하려면 이걸 사용 + // const mockPolls: Poll[] = []; + + const hasPolls = mockPolls.length > 0; return ( @@ -135,11 +167,10 @@ const ParticipatedGroupDetail = () => { ); From b4efd6a6c08deebbda67313c2e6bff46ca95a209 Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 02:34:39 +0900 Subject: [PATCH 09/16] =?UTF-8?q?feat:=20=EC=9E=90=EC=97=B0=EC=8A=A4?= =?UTF-8?q?=EB=9F=AC=EC=9A=B4=20=EB=93=9C=EB=9E=98=EA=B7=B8=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=20=EB=B0=8F=20=EC=8A=A4=EB=83=85=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/HotTopicSection.styled.ts | 11 +- src/components/group/HotTopicSection.tsx | 149 ++++++++++++++---- 2 files changed, 126 insertions(+), 34 deletions(-) diff --git a/src/components/group/HotTopicSection.styled.ts b/src/components/group/HotTopicSection.styled.ts index cde75861..1c0c553e 100644 --- a/src/components/group/HotTopicSection.styled.ts +++ b/src/components/group/HotTopicSection.styled.ts @@ -35,8 +35,17 @@ export const HotTopicContent = styled.div` export const SlideContainer = styled.div` display: flex; - transition: transform 0.3s ease-in-out; touch-action: pan-x; + user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; +`; + +export const SlideItem = styled.div` + width: 100%; + flex-shrink: 0; + padding-right: 0; `; export const HotTopicText = styled.p` diff --git a/src/components/group/HotTopicSection.tsx b/src/components/group/HotTopicSection.tsx index dbed4d12..a3934c1c 100644 --- a/src/components/group/HotTopicSection.tsx +++ b/src/components/group/HotTopicSection.tsx @@ -13,8 +13,9 @@ import { EmptyVoteContainer, EmptyVoteTitle, SlideContainer, + SlideItem, } from './HotTopicSection.styled'; -import { useState, useRef } from 'react'; +import { useState, useRef, useCallback, useEffect } from 'react'; export interface VoteOption { id: string; @@ -25,54 +26,140 @@ export interface Poll { id: string; question: string; options: VoteOption[]; - pageNumber: number; // 해당 투표가 위치한 페이지 + pageNumber: number; } interface HotTopicSectionProps { polls: Poll[]; hasPolls: boolean; onClick: () => void; - onPollClick: (pageNumber: number) => void; // 투표 클릭 시 해당 페이지로 이동 + onPollClick: (pageNumber: number) => void; } const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSectionProps) => { const [currentPollIndex, setCurrentPollIndex] = useState(0); const slideRef = useRef(null); + const [isDragging, setIsDragging] = useState(false); + const [startX, setStartX] = useState(0); + const [translateX, setTranslateX] = useState(0); + const [startTranslateX, setStartTranslateX] = useState(0); + + const containerWidth = 100; // 각 슬라이드의 width % // 투표 옵션 클릭 시 해당 페이지로 이동 const handleVoteClick = (poll: Poll) => { - onPollClick(poll.pageNumber); + if (!isDragging) { + onPollClick(poll.pageNumber); + } + }; + + // 슬라이드 위치 계산 + const getTargetTranslateX = (index: number) => { + return -index * containerWidth; }; - // 슬라이드 터치/드래그 처리 + // 가장 가까운 슬라이드로 스냅 + const snapToClosest = useCallback(() => { + const currentTranslate = translateX; + let closestIndex = Math.round(Math.abs(currentTranslate) / containerWidth); + + // 범위 제한 + closestIndex = Math.max(0, Math.min(closestIndex, polls.length - 1)); + + const targetTranslate = getTargetTranslateX(closestIndex); + setTranslateX(targetTranslate); + setCurrentPollIndex(closestIndex); + }, [translateX, polls.length]); + + // 드래그 시작 (마우스/터치 공통) + const handleDragStart = (clientX: number) => { + setIsDragging(true); + setStartX(clientX); + setStartTranslateX(translateX); + }; + + // 드래그 중 (마우스/터치 공통) + const handleDragMove = useCallback( + (clientX: number) => { + if (!isDragging) return; + + const deltaX = clientX - startX; + const newTranslateX = startTranslateX + (deltaX / window.innerWidth) * 100; + + // 드래그 범위 제한 (약간의 오버스크롤 허용) + const minTranslate = getTargetTranslateX(polls.length - 1) - 20; + const maxTranslate = getTargetTranslateX(0) + 20; + + const limitedTranslateX = Math.max(minTranslate, Math.min(maxTranslate, newTranslateX)); + setTranslateX(limitedTranslateX); + }, + [isDragging, startX, startTranslateX, polls.length], + ); + + // 드래그 끝 (마우스/터치 공통) + const handleDragEnd = () => { + if (isDragging) { + setIsDragging(false); + snapToClosest(); + } + }; + + // 터치 이벤트 const handleTouchStart = (e: React.TouchEvent) => { - const touch = e.touches[0]; - slideRef.current?.setAttribute('data-start-x', touch.clientX.toString()); + e.preventDefault(); + handleDragStart(e.touches[0].clientX); + }; + + const handleTouchMove = (e: React.TouchEvent) => { + e.preventDefault(); + handleDragMove(e.touches[0].clientX); }; const handleTouchEnd = (e: React.TouchEvent) => { - const touch = e.changedTouches[0]; - const startX = parseFloat(slideRef.current?.getAttribute('data-start-x') || '0'); - const diffX = startX - touch.clientX; - - if (Math.abs(diffX) > 50) { - // 최소 50px 이상 드래그해야 슬라이드 - if (diffX > 0 && currentPollIndex < polls.length - 1) { - // 오른쪽으로 슬라이드 (다음 투표) - setCurrentPollIndex(currentPollIndex + 1); - } else if (diffX < 0 && currentPollIndex > 0) { - // 왼쪽으로 슬라이드 (이전 투표) - setCurrentPollIndex(currentPollIndex - 1); - } - } + e.preventDefault(); + handleDragEnd(); + }; + + // 마우스 이벤트 + const handleMouseDown = (e: React.MouseEvent) => { + e.preventDefault(); + handleDragStart(e.clientX); }; + // 전역 마우스 이벤트 리스너 + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + handleDragMove(e.clientX); + }; + + const handleMouseUp = () => { + handleDragEnd(); + }; + + if (isDragging) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + } + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + }; + }, [isDragging, handleDragMove]); + + // 인덱스 변경 시 translateX 업데이트 + useEffect(() => { + if (!isDragging) { + setTranslateX(getTargetTranslateX(currentPollIndex)); + } + }, [currentPollIndex, isDragging]); + const renderVoteOptions = (poll: Poll) => { return poll.options.map((option, index) => ( handleVoteClick(poll)} - style={{ cursor: 'pointer' }} + style={{ cursor: isDragging ? 'grabbing' : 'pointer' }} > {index + 1}. {option.text} @@ -98,24 +185,20 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect {polls.map(poll => ( -
+ {poll.question} {renderVoteOptions(poll)} -
+ ))}
From cf512d48c54a9e6d408235ac46f11fe5dc640abb Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 16:35:27 +0900 Subject: [PATCH 10/16] =?UTF-8?q?fix:=20VoteOption=20=ED=83=80=EC=9E=85=20?= =?UTF-8?q?=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0=20-=20styled=20componen?= =?UTF-8?q?t=20alias=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/group/HotTopicSection.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/group/HotTopicSection.tsx b/src/components/group/HotTopicSection.tsx index a3934c1c..d4603bfb 100644 --- a/src/components/group/HotTopicSection.tsx +++ b/src/components/group/HotTopicSection.tsx @@ -5,7 +5,7 @@ import { HotTopicContent, HotTopicText, VoteOptionsList, - VoteOption, + VoteOption as StyledVoteOption, VoteOptionNumber, VoteOptionText, Pagination, @@ -156,14 +156,14 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect const renderVoteOptions = (poll: Poll) => { return poll.options.map((option, index) => ( - handleVoteClick(poll)} style={{ cursor: isDragging ? 'grabbing' : 'pointer' }} > {index + 1}. {option.text} - + )); }; From 990bcdb3e2c60ae36cc8c7f396b45708449739aa Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 16:40:04 +0900 Subject: [PATCH 11/16] =?UTF-8?q?refactor:=20RecordSection=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EB=8A=94=20bookAuthor=20prop=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/group/RecordSection.tsx | 1 - src/pages/groupDetail/ParticipatedGroupDetail.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/components/group/RecordSection.tsx b/src/components/group/RecordSection.tsx index 351b7f29..2ad44cd6 100644 --- a/src/components/group/RecordSection.tsx +++ b/src/components/group/RecordSection.tsx @@ -13,7 +13,6 @@ import { import rightChevron from '../../assets/group/right-chevron.svg'; interface RecordSectionProps { - bookAuthor: string; currentPage: number; progress: number; onClick: () => void; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 0057e012..22da793e 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -66,7 +66,6 @@ const ParticipatedGroupDetail = () => { // 모킹 데이터 const recordData = { - bookAuthor: '최정화 저', currentPage: 1, progress: 30, }; @@ -158,7 +157,6 @@ const ParticipatedGroupDetail = () => { Date: Thu, 7 Aug 2025 17:03:36 +0900 Subject: [PATCH 12/16] =?UTF-8?q?feat:=20=EB=AA=A8=EC=9E=84=EB=B0=A9=20?= =?UTF-8?q?=EB=8D=94=EB=B3=B4=EA=B8=B0=20=EC=95=A1=EC=85=98=20=EB=B0=94?= =?UTF-8?q?=ED=85=80=EC=8B=9C=ED=8A=B8=20=EA=B5=AC=ED=98=84=20-=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=EB=B3=84=20=EC=95=A1=EC=85=98=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../group/GroupActionBottomSheet.styled.ts | 93 +++++++++++++++++++ .../group/GroupActionBottomSheet.tsx | 70 ++++++++++++++ .../groupDetail/ParticipatedGroupDetail.tsx | 43 ++++++++- 3 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 src/components/group/GroupActionBottomSheet.styled.ts create mode 100644 src/components/group/GroupActionBottomSheet.tsx diff --git a/src/components/group/GroupActionBottomSheet.styled.ts b/src/components/group/GroupActionBottomSheet.styled.ts new file mode 100644 index 00000000..d4ad402a --- /dev/null +++ b/src/components/group/GroupActionBottomSheet.styled.ts @@ -0,0 +1,93 @@ +import styled from '@emotion/styled'; +import { colors, typography } from '@/styles/global/global'; + +export const Overlay = styled.div<{ isOpen: boolean }>` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + backdrop-filter: blur(2px); + z-index: 1000; + opacity: ${({ isOpen }) => (isOpen ? 1 : 0)}; + visibility: ${({ isOpen }) => (isOpen ? 'visible' : 'hidden')}; + transition: + opacity 0.3s ease, + visibility 0.3s ease; + display: flex; + align-items: flex-end; + justify-content: center; +`; + +export const BottomSheet = styled.div<{ isOpen: boolean }>` + background-color: ${colors.darkgrey.main}; + border-radius: 20px 20px 0 0; + width: 100%; + max-width: 767px; + min-width: 320px; + padding: 20px; + transform: ${({ isOpen }) => (isOpen ? 'translateY(0)' : 'translateY(100%)')}; + transition: transform 0.3s ease-in-out; +`; + +export const DeleteGroupActionItem = styled.button` + width: 100%; + background: none; + border: none; + padding: 13px 12px; + color: ${colors.red}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.medium}; + text-align: left; + cursor: pointer; + border-radius: 8px; + + &:hover { + background-color: ${colors.darkgrey.main}; + } +`; + +export const LeaveGroupActionItem = styled.button` + width: 100%; + background: none; + border: none; + padding: 13px 12px; + color: ${colors.white}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.medium}; + text-align: left; + cursor: pointer; + border-radius: 8px; + + &:hover { + background-color: ${colors.darkgrey.main}; + } +`; + +export const ReportGroupActionItem = styled.button` + width: 100%; + background: none; + border: none; + padding: 13px 12px; + color: ${colors.red}; + font-size: ${typography.fontSize.base}; + font-weight: ${typography.fontWeight.medium}; + text-align: left; + cursor: pointer; + border-radius: 8px; + + &:hover { + background-color: ${colors.darkgrey.main}; + } +`; + +export const ActionItemsContainer = styled.div` + display: flex; + flex-direction: column; + + > button:not(:last-child) { + border-bottom: 1px solid ${colors.grey[400]}; + margin-bottom: 8px; + padding-bottom: 20px; + } +`; diff --git a/src/components/group/GroupActionBottomSheet.tsx b/src/components/group/GroupActionBottomSheet.tsx new file mode 100644 index 00000000..8491c71b --- /dev/null +++ b/src/components/group/GroupActionBottomSheet.tsx @@ -0,0 +1,70 @@ +import { + Overlay, + BottomSheet, + DeleteGroupActionItem, + LeaveGroupActionItem, + ReportGroupActionItem, + ActionItemsContainer, +} from './GroupActionBottomSheet.styled'; + +interface GroupActionBottomSheetProps { + isOpen: boolean; + isGroupOwner: boolean; // 모임방 생성자인지 여부 + onClose: () => void; + onDeleteGroup?: () => void; // 방 삭제하기 + onLeaveGroup?: () => void; // 방 나가기 + onReportGroup?: () => void; // 방 신고하기 +} + +const GroupActionBottomSheet = ({ + isOpen, + isGroupOwner, + onClose, + onDeleteGroup, + onLeaveGroup, + onReportGroup, +}: GroupActionBottomSheetProps) => { + const handleOverlayClick = (e: React.MouseEvent) => { + if (e.target === e.currentTarget) { + onClose(); + } + }; + + const handleDeleteGroup = () => { + onDeleteGroup?.(); + onClose(); + }; + + const handleLeaveGroup = () => { + onLeaveGroup?.(); + onClose(); + }; + + const handleReportGroup = () => { + onReportGroup?.(); + onClose(); + }; + + if (!isOpen) return null; + + return ( + + + + {isGroupOwner ? ( + // 모임방 생성자인 경우 - 방 삭제하기만 표시 + 방 삭제하기 + ) : ( + // 참여자인 경우 - 방 나가기, 방 신고하기 표시 + <> + 방 나가기 + 방 신고하기 + + )} + + + + ); +}; + +export default GroupActionBottomSheet; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 22da793e..8929b314 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -19,6 +19,7 @@ import RecordSection from '../../components/group/RecordSection'; import CommentSection from '../../components/group/CommentSection'; import HotTopicSection from '../../components/group/HotTopicSection'; import GroupBookSection from '../../components/group/GroupBookSection'; +import GroupActionBottomSheet from '../../components/group/GroupActionBottomSheet'; import type { Poll } from '../../components/group/HotTopicSection'; import leftArrow from '../../assets/common/leftArrow.svg'; @@ -29,17 +30,48 @@ import { mockGroupDetail } from '../../mocks/groupDetail.mock'; import lockIcon from '../../assets/group/lock.svg'; import calendarIcon from '../../assets/group/calendar.svg'; import peopleIcon from '../../assets/common/darkPeople.svg'; +import { useState } from 'react'; const ParticipatedGroupDetail = () => { const { title, isPrivate, introduction, activityPeriod, members, genre, book } = mockGroupDetail; const navigate = useNavigate(); + const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false); + + // 모임방 생성자 여부 (실제로는 API에서 받아와야 함) + const [isGroupOwner] = useState(false); // true면 생성자, false면 참여자 const handleBackButton = () => { navigate(-1); }; - const handleMoreButton = () => {}; + const handleMoreButton = () => { + setIsBottomSheetOpen(true); + }; + + const handleCloseBottomSheet = () => { + setIsBottomSheetOpen(false); + }; + + const handleDeleteGroup = () => { + // 방 삭제하기 로직 + console.log('방 삭제하기'); + // 실제로는 API 호출 후 성공하면 홈으로 이동 + navigate('/group'); + }; + + const handleLeaveGroup = () => { + // 방 나가기 로직 + console.log('방 나가기'); + // 실제로는 API 호출 후 성공하면 홈으로 이동 + navigate('/group'); + }; + + const handleReportGroup = () => { + // 방 신고하기 로직 + console.log('방 신고하기'); + // 실제로는 신고 모달이나 페이지로 이동 + }; const handleRecordSectionClick = () => { navigate('/memory'); @@ -170,6 +202,15 @@ const ParticipatedGroupDetail = () => { onClick={handleHotTopicSectionClick} onPollClick={handlePollClick} /> + + ); }; From 9efe7e855c10ee4d7806f0516339ad9023d9f690 Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 17:17:11 +0900 Subject: [PATCH 13/16] =?UTF-8?q?feat:=20=EB=AA=A8=EC=9E=84=EB=B0=A9=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C/=EB=82=98=EA=B0=80=EA=B8=B0=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EB=AA=A8=EB=8B=AC=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../groupDetail/ParticipatedGroupDetail.tsx | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 8929b314..7a0470f9 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -21,6 +21,7 @@ import HotTopicSection from '../../components/group/HotTopicSection'; import GroupBookSection from '../../components/group/GroupBookSection'; import GroupActionBottomSheet from '../../components/group/GroupActionBottomSheet'; import type { Poll } from '../../components/group/HotTopicSection'; +import { usePopupActions } from '@/hooks/usePopupActions'; import leftArrow from '../../assets/common/leftArrow.svg'; import moreIcon from '../../assets/common/more.svg'; @@ -34,6 +35,7 @@ import { useState } from 'react'; const ParticipatedGroupDetail = () => { const { title, isPrivate, introduction, activityPeriod, members, genre, book } = mockGroupDetail; + const { openConfirm } = usePopupActions(); const navigate = useNavigate(); const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false); @@ -54,17 +56,27 @@ const ParticipatedGroupDetail = () => { }; const handleDeleteGroup = () => { - // 방 삭제하기 로직 - console.log('방 삭제하기'); - // 실제로는 API 호출 후 성공하면 홈으로 이동 - navigate('/group'); + openConfirm({ + title: '모임방을 삭제하시겠어요?', + disc: '방을 삭제하게 되면\n독서메이트들과의 추억이 사라집니다.', + onConfirm: () => { + console.log('방 삭제 확정'); + // 실제 삭제 API 호출 후 홈으로 이동 + navigate('/group'); + }, + }); }; const handleLeaveGroup = () => { - // 방 나가기 로직 - console.log('방 나가기'); - // 실제로는 API 호출 후 성공하면 홈으로 이동 - navigate('/group'); + openConfirm({ + title: '모임방을 나가시겠어요?', + disc: '방을 나가시게 되면\n독서메이트들과의 추억이 사라집니다.', + onConfirm: () => { + console.log('방 나가기 확정'); + // 실제 나가기 API 호출 후 홈으로 이동 + navigate('/group'); + }, + }); }; const handleReportGroup = () => { From 86a8664c7079edea136a78927e251c87bfdb976e Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 17:38:00 +0900 Subject: [PATCH 14/16] =?UTF-8?q?feat:=20=EB=8F=85=EC=84=9C=EB=A9=94?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EC=84=B9=EC=85=98=EC=97=90=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=20=EA=B0=80=EB=8A=A5=ED=95=9C=20=ED=99=94=EC=82=B4?= =?UTF-8?q?=ED=91=9C=20UI=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ParticipatedGroupDetail.styled.ts | 23 +++++++++++++ .../groupDetail/ParticipatedGroupDetail.tsx | 34 ++++++++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts b/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts index fb6c2ba2..40400ace 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts +++ b/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts @@ -14,3 +14,26 @@ export const ParticipatedWrapper = styled.div` background-color: ${colors.black.main}; overflow: hidden; `; + +export const ClickableMeta = styled.div` + display: flex; + flex-direction: column; + width: 100%; + cursor: pointer; + + &:hover { + opacity: 0.8; + } +`; + +export const MetaChevron = styled.img` + width: 24px; + height: 24px; +`; + +export const MetaTopRow = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +`; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 7a0470f9..31257fe1 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -14,7 +14,12 @@ import { Tag, TagGenre, } from './GroupDetail.styled'; -import { ParticipatedWrapper } from './ParticipatedGroupDetail.styled'; +import { + ParticipatedWrapper, + ClickableMeta, + MetaChevron, + MetaTopRow, +} from './ParticipatedGroupDetail.styled'; import RecordSection from '../../components/group/RecordSection'; import CommentSection from '../../components/group/CommentSection'; import HotTopicSection from '../../components/group/HotTopicSection'; @@ -22,6 +27,7 @@ import GroupBookSection from '../../components/group/GroupBookSection'; import GroupActionBottomSheet from '../../components/group/GroupActionBottomSheet'; import type { Poll } from '../../components/group/HotTopicSection'; import { usePopupActions } from '@/hooks/usePopupActions'; +import rightChevron from '../../assets/group/right-chevron.svg'; import leftArrow from '../../assets/common/leftArrow.svg'; import moreIcon from '../../assets/common/more.svg'; @@ -108,6 +114,10 @@ const ParticipatedGroupDetail = () => { navigate(`/memory?page=${pageNumber}&filter=poll`); }; + const handleMembersClick = () => { + navigate('/group/members'); // 또는 실제 독서메이트 페이지 경로 + }; + // 모킹 데이터 const recordData = { currentPage: 1, @@ -181,13 +191,21 @@ const ParticipatedGroupDetail = () => { - - 독서메이트 - - - {members.current} - 명 참여 중 - + +
+ +
+ + 독서메이트 +
+ +
+ + {members.current} + 명 참여 중 + +
+
From afe25b0a77f5f667ec7df81e7354fb64d678110d Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 22:24:10 +0900 Subject: [PATCH 15/16] =?UTF-8?q?style:=20=EC=B0=B8=EC=97=AC=20=EB=AA=A8?= =?UTF-8?q?=EC=9E=84=20=EC=83=81=EC=84=B8=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=EC=9D=98=20=EB=A9=94=ED=83=80=20=EC=A0=95=EB=B3=B4=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=EC=9D=84=201:1=20=EB=B9=84=EC=9C=A8?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ParticipatedGroupDetail.styled.ts | 24 ++++++++++++++++++- .../groupDetail/ParticipatedGroupDetail.tsx | 4 ++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts b/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts index 40400ace..055303d8 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts +++ b/src/pages/groupDetail/ParticipatedGroupDetail.styled.ts @@ -1,5 +1,5 @@ import styled from '@emotion/styled'; -import { colors } from '@/styles/global/global'; +import { colors, typography } from '@/styles/global/global'; export const ParticipatedWrapper = styled.div` display: flex; @@ -37,3 +37,25 @@ export const MetaTopRow = styled.div` justify-content: space-between; width: 100%; `; + +export const MetaInfo = styled.div` + display: flex; + margin-top: 16px; + gap: 20px; + width: 100%; +`; + +export const Meta = styled.div` + display: flex; + flex-direction: column; + font-size: ${typography.fontSize['xs']}; + font-weight: ${typography.fontWeight.medium}; + color: ${colors.white}; + gap: 12px; + flex: 1; + span { + display: flex; + align-items: center; + gap: 2px; + } +`; diff --git a/src/pages/groupDetail/ParticipatedGroupDetail.tsx b/src/pages/groupDetail/ParticipatedGroupDetail.tsx index 31257fe1..0b949ae0 100644 --- a/src/pages/groupDetail/ParticipatedGroupDetail.tsx +++ b/src/pages/groupDetail/ParticipatedGroupDetail.tsx @@ -5,8 +5,6 @@ import { GroupTitle, SubTitle, Intro, - MetaInfo, - Meta, MetaDate, MetaMember, MetaTotalMember, @@ -19,6 +17,8 @@ import { ClickableMeta, MetaChevron, MetaTopRow, + MetaInfo, + Meta, } from './ParticipatedGroupDetail.styled'; import RecordSection from '../../components/group/RecordSection'; import CommentSection from '../../components/group/CommentSection'; From a87e2f666b976edb7bb9ddd2bf412ba85060435c Mon Sep 17 00:00:00 2001 From: fr0gydev Date: Thu, 7 Aug 2025 22:32:38 +0900 Subject: [PATCH 16/16] =?UTF-8?q?refactor:=20HotTopicSection=20=EB=93=9C?= =?UTF-8?q?=EB=9E=98=EA=B7=B8=20=EC=83=81=ED=83=9C=EB=A5=BC=20useRef?= =?UTF-8?q?=EB=A1=9C=20=EC=B5=9C=EC=A0=81=ED=99=94=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=EC=84=B1=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/group/HotTopicSection.tsx | 36 +++++++++++++++--------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/components/group/HotTopicSection.tsx b/src/components/group/HotTopicSection.tsx index d4603bfb..c09066f8 100644 --- a/src/components/group/HotTopicSection.tsx +++ b/src/components/group/HotTopicSection.tsx @@ -40,9 +40,13 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect const [currentPollIndex, setCurrentPollIndex] = useState(0); const slideRef = useRef(null); const [isDragging, setIsDragging] = useState(false); - const [startX, setStartX] = useState(0); const [translateX, setTranslateX] = useState(0); - const [startTranslateX, setStartTranslateX] = useState(0); + + // useRef로 드래그 상태 관리 + const dragStateRef = useRef({ + startX: 0, + startTranslateX: 0, + }); const containerWidth = 100; // 각 슬라이드의 width % @@ -72,37 +76,41 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect }, [translateX, polls.length]); // 드래그 시작 (마우스/터치 공통) - const handleDragStart = (clientX: number) => { - setIsDragging(true); - setStartX(clientX); - setStartTranslateX(translateX); - }; + const handleDragStart = useCallback( + (clientX: number) => { + setIsDragging(true); + dragStateRef.current.startX = clientX; + dragStateRef.current.startTranslateX = translateX; + }, + [translateX], + ); // 드래그 중 (마우스/터치 공통) const handleDragMove = useCallback( (clientX: number) => { if (!isDragging) return; - const deltaX = clientX - startX; - const newTranslateX = startTranslateX + (deltaX / window.innerWidth) * 100; + const deltaX = clientX - dragStateRef.current.startX; + const newTranslateX = + dragStateRef.current.startTranslateX + (deltaX / window.innerWidth) * 100; - // 드래그 범위 제한 (약간의 오버스크롤 허용) + // 드래그 범위 제한 const minTranslate = getTargetTranslateX(polls.length - 1) - 20; const maxTranslate = getTargetTranslateX(0) + 20; const limitedTranslateX = Math.max(minTranslate, Math.min(maxTranslate, newTranslateX)); setTranslateX(limitedTranslateX); }, - [isDragging, startX, startTranslateX, polls.length], + [isDragging, polls.length], ); // 드래그 끝 (마우스/터치 공통) - const handleDragEnd = () => { + const handleDragEnd = useCallback(() => { if (isDragging) { setIsDragging(false); snapToClosest(); } - }; + }, [isDragging, snapToClosest]); // 터치 이벤트 const handleTouchStart = (e: React.TouchEvent) => { @@ -145,7 +153,7 @@ const HotTopicSection = ({ polls, hasPolls, onClick, onPollClick }: HotTopicSect document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; - }, [isDragging, handleDragMove]); + }, [isDragging, handleDragMove, handleDragEnd]); // 인덱스 변경 시 translateX 업데이트 useEffect(() => {