From dc6b691de3f85b7dfaf5b9699cbb1a7623faa3b8 Mon Sep 17 00:00:00 2001 From: Haeun Date: Sat, 14 Feb 2026 16:30:28 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20=EC=95=BD=EC=86=8D/=EC=A3=BC?= =?UTF-8?q?=EC=A0=9C=20=ED=99=95=EC=A0=95=20=EA=B4=80=EB=A0=A8=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EC=97=B0=EA=B2=B0=20(#78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - confirmedTopicExpand 필드명을 confirmedTopic으로 수정 - 약속 승인 후 모임 약속 리스트 캐시 무효화 추가 - 주제 확정 후 약속 상세 쿼리 캐시 무효화 추가 - 약속 수정 완료 후 약속 상세 페이지로 이동하도록 수정 - 약속 수정 버튼(CAN_EDIT) 클릭 시 수정 페이지로 이동 연결 - 수정 모드에서 도서 선택 유효성 검사 예외 처리 - 주제 확정 시 좋아요/삭제 버튼 비활성화 처리 - 주제 확정 API 메서드 POST → PATCH로 수정 --- .../meetings/components/MeetingApprovalItem.tsx | 2 +- .../meetings/components/MeetingDetailButton.tsx | 10 ++++++++-- .../meetings/components/MeetingDetailInfo.tsx | 7 +++++-- src/features/meetings/hooks/useConfirmMeeting.ts | 11 +++++++---- src/features/meetings/hooks/useMeetingForm.ts | 11 ++++++----- src/features/meetings/meetings.types.ts | 2 +- .../topics/components/ConfirmTopicModal.tsx | 1 + .../topics/components/ProposedTopicList.tsx | 7 +++++-- src/features/topics/components/TopicHeader.tsx | 14 +++++++------- src/features/topics/hooks/useConfirmTopics.ts | 5 +++++ src/features/topics/topics.api.ts | 4 ++-- src/pages/Meetings/MeetingCreatePage.tsx | 2 +- src/pages/Meetings/MeetingDetailPage.tsx | 6 ++++-- 13 files changed, 53 insertions(+), 29 deletions(-) diff --git a/src/features/meetings/components/MeetingApprovalItem.tsx b/src/features/meetings/components/MeetingApprovalItem.tsx index c9fea42..210edd9 100644 --- a/src/features/meetings/components/MeetingApprovalItem.tsx +++ b/src/features/meetings/components/MeetingApprovalItem.tsx @@ -30,7 +30,7 @@ export default function MeetingApprovalItem({ item, gatheringId }: MeetingApprov const { meetingName, bookName, nickname, startDateTime, endDateTime, meetingStatus, meetingId } = item - const confirmMutation = useConfirmMeeting() + const confirmMutation = useConfirmMeeting(gatheringId) const rejectMutation = useRejectMeeting(gatheringId) const deleteMutation = useDeleteMeeting(gatheringId) const isPending = diff --git a/src/features/meetings/components/MeetingDetailButton.tsx b/src/features/meetings/components/MeetingDetailButton.tsx index 6625064..28bd4ce 100644 --- a/src/features/meetings/components/MeetingDetailButton.tsx +++ b/src/features/meetings/components/MeetingDetailButton.tsx @@ -1,5 +1,8 @@ +import { useNavigate } from 'react-router-dom' + import { useCancelJoinMeeting, useJoinMeeting } from '@/features/meetings/hooks' import type { MeetingDetailActionStateType } from '@/features/meetings/meetings.types' +import { ROUTES } from '@/shared/constants/routes' import { Button } from '@/shared/ui' import { useGlobalModalStore } from '@/store' @@ -7,6 +10,7 @@ interface MeetingDetailButtonProps { buttonLabel: string isEnabled: boolean type: MeetingDetailActionStateType + gatheringId: number meetingId: number } @@ -14,8 +18,10 @@ export default function MeetingDetailButton({ buttonLabel, isEnabled, type, + gatheringId, meetingId, }: MeetingDetailButtonProps) { + const navigate = useNavigate() const joinMutation = useJoinMeeting() const cancelJoinMutation = useCancelJoinMeeting() const { openError, openConfirm } = useGlobalModalStore() @@ -25,9 +31,9 @@ export default function MeetingDetailButton({ const handleClick = async () => { if (!isEnabled || isPending) return - // 약속 수정 - 페이지 이동 예정 (TODO) + // 약속 수정 if (type === 'CAN_EDIT') { - // 페이지 이동 로직 추가 예정 + navigate(ROUTES.MEETING_UPDATE(gatheringId, meetingId)) return } diff --git a/src/features/meetings/components/MeetingDetailInfo.tsx b/src/features/meetings/components/MeetingDetailInfo.tsx index 3710ea6..1dbc594 100644 --- a/src/features/meetings/components/MeetingDetailInfo.tsx +++ b/src/features/meetings/components/MeetingDetailInfo.tsx @@ -36,8 +36,11 @@ export function MeetingDetailInfo({ meeting }: MeetingDetailInfoProps) { {/* 도서 */}
도서
-
-

{meeting.book.bookName}

+
+
+

{meeting.book.bookName}

+

{meeting.book.authors}

+
{ +export const useConfirmMeeting = (gatheringId: number) => { const queryClient = useQueryClient() return useMutation, ApiError, number>({ @@ -33,6 +34,8 @@ export const useConfirmMeeting = () => { queryClient.invalidateQueries({ queryKey: meetingQueryKeys.approvals(), }) + // 모임 약속 리스트 캐시 무효화 + queryClient.invalidateQueries({ queryKey: gatheringQueryKeys.meetings(gatheringId) }) }, }) } diff --git a/src/features/meetings/hooks/useMeetingForm.ts b/src/features/meetings/hooks/useMeetingForm.ts index 060dda7..a1ec808 100644 --- a/src/features/meetings/hooks/useMeetingForm.ts +++ b/src/features/meetings/hooks/useMeetingForm.ts @@ -123,11 +123,12 @@ export const useMeetingForm = ({ gatheringMaxCount, initialData }: UseMeetingFor const newError: ValidationErrors = {} if ( - !formData.bookId || - !formData.bookName || - !formData.bookThumbnail || - !formData.bookAuthors || - !formData.bookPublisher + !isEditMode && + (!formData.bookId || + !formData.bookName || + !formData.bookThumbnail || + !formData.bookAuthors || + !formData.bookPublisher) ) { newError.bookId = '* 도서를 선택해주세요.' } diff --git a/src/features/meetings/meetings.types.ts b/src/features/meetings/meetings.types.ts index e3d380c..8d64d6b 100644 --- a/src/features/meetings/meetings.types.ts +++ b/src/features/meetings/meetings.types.ts @@ -215,7 +215,7 @@ export type GetMeetingDetailResponse = { /** 약속 진행 상태 */ progressStatus: MeetingProgressStatus /** 주제 확정 여부 */ - confirmedTopicExpand: boolean + confirmedTopic: boolean /** 주제 확정 일시 */ confirmedTopicDate: string | null /** 모임 정보 */ diff --git a/src/features/topics/components/ConfirmTopicModal.tsx b/src/features/topics/components/ConfirmTopicModal.tsx index f12b01c..4276fc4 100644 --- a/src/features/topics/components/ConfirmTopicModal.tsx +++ b/src/features/topics/components/ConfirmTopicModal.tsx @@ -73,6 +73,7 @@ export default function ConfirmTopicModal({ handleClose() }, onError: (error) => { + console.log(error) openError('확정 실패', error.userMessage) }, } diff --git a/src/features/topics/components/ProposedTopicList.tsx b/src/features/topics/components/ProposedTopicList.tsx index 08c1135..d3ba223 100644 --- a/src/features/topics/components/ProposedTopicList.tsx +++ b/src/features/topics/components/ProposedTopicList.tsx @@ -12,6 +12,7 @@ type ProposedTopicListProps = { onLoadMore: () => void gatheringId: number meetingId: number + confirmedTopic: boolean } export default function ProposedTopicList({ @@ -21,6 +22,7 @@ export default function ProposedTopicList({ onLoadMore, gatheringId, meetingId, + confirmedTopic, }: ProposedTopicListProps) { // 무한 스크롤: IntersectionObserver로 다음 페이지 로드 const observerRef = useInfiniteScroll(onLoadMore, { @@ -44,11 +46,12 @@ export default function ProposedTopicList({ description={topic.description} createdByNickname={topic.createdByInfo.nickname} likeCount={topic.likeCount} - isLiked={topic.isLiked} - canDelete={topic.canDelete} + isLiked={confirmedTopic ? false : topic.isLiked} + canDelete={confirmedTopic ? false : topic.canDelete} gatheringId={gatheringId} meetingId={meetingId} topicId={topic.topicId} + isLikeDisabled={confirmedTopic} /> ))} diff --git a/src/features/topics/components/TopicHeader.tsx b/src/features/topics/components/TopicHeader.tsx index 7d9c698..59fa467 100644 --- a/src/features/topics/components/TopicHeader.tsx +++ b/src/features/topics/components/TopicHeader.tsx @@ -28,6 +28,7 @@ type TopicHeaderProps = ProposedHeaderProps | ConfirmedHeaderProps export default function TopicHeader(props: TopicHeaderProps) { const navigate = useNavigate() + console.log(props.confirmedTopic, props.confirmedTopicDate) return ( <> {/* 제안탭 */} @@ -64,13 +65,12 @@ export default function TopicHeader(props: TopicHeaderProps) { )} - {props.actions.canSuggest && ( - - )} +
)} diff --git a/src/features/topics/hooks/useConfirmTopics.ts b/src/features/topics/hooks/useConfirmTopics.ts index ca2a2c7..ea8e036 100644 --- a/src/features/topics/hooks/useConfirmTopics.ts +++ b/src/features/topics/hooks/useConfirmTopics.ts @@ -6,6 +6,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query' import { ApiError } from '@/api/errors' +import { meetingQueryKeys } from '@/features/meetings/hooks/meetingQueryKeys' import { confirmTopics } from '../topics.api' import type { ConfirmTopicsParams, ConfirmTopicsResponse } from '../topics.types' @@ -55,6 +56,10 @@ export const useConfirmTopics = () => { meetingId: variables.meetingId, }), }) + // 약속 상세 무효화 + queryClient.invalidateQueries({ + queryKey: meetingQueryKeys.detail(variables.meetingId), + }) }, }) } diff --git a/src/features/topics/topics.api.ts b/src/features/topics/topics.api.ts index 926758d..6f1abf6 100644 --- a/src/features/topics/topics.api.ts +++ b/src/features/topics/topics.api.ts @@ -230,14 +230,14 @@ export const confirmTopics = async ( // 🚧 임시: 로그인 기능 개발 전까지 목데이터 사용 // TODO: 로그인 완료 후 아래 주석을 해제하고 목데이터 로직 제거 - if (USE_MOCK_DATA) { + if (USE_MOCK) { // 실제 API 호출을 시뮬레이션하기 위한 지연 await new Promise((resolve) => setTimeout(resolve, 500)) return getMockConfirmTopics(meetingId, topicIds) } // 실제 API 호출 (로그인 완료 후 사용) - return api.post(TOPICS_ENDPOINTS.CONFIRM(gatheringId, meetingId), { + return api.patch(TOPICS_ENDPOINTS.CONFIRM(gatheringId, meetingId), { topicIds, }) } diff --git a/src/pages/Meetings/MeetingCreatePage.tsx b/src/pages/Meetings/MeetingCreatePage.tsx index 77dbb03..8b28f84 100644 --- a/src/pages/Meetings/MeetingCreatePage.tsx +++ b/src/pages/Meetings/MeetingCreatePage.tsx @@ -154,7 +154,7 @@ export default function MeetingCreatePage() { { onSuccess: () => { openAlert('약속 수정 완료', '약속이 성공적으로 수정되었습니다.', () => { - navigate(ROUTES.GATHERING_DETAIL(gatheringId), { replace: true }) + navigate(ROUTES.MEETING_DETAIL(gatheringId, id), { replace: true }) }) }, onError: (error) => { diff --git a/src/pages/Meetings/MeetingDetailPage.tsx b/src/pages/Meetings/MeetingDetailPage.tsx index aec2a2d..09e3b7d 100644 --- a/src/pages/Meetings/MeetingDetailPage.tsx +++ b/src/pages/Meetings/MeetingDetailPage.tsx @@ -105,6 +105,7 @@ export default function MeetingDetailPage() { buttonLabel={meeting.actionState.buttonLabel} isEnabled={meeting.actionState.enabled} type={meeting.actionState.type} + gatheringId={Number(gatheringId)} meetingId={meeting.meetingId} /> @@ -145,7 +146,7 @@ export default function MeetingDetailPage() {
page.items )} + confirmedTopic={meeting?.confirmedTopic ?? false} hasNextPage={hasNextProposedPage} isFetchingNextPage={isFetchingNextProposedPage} onLoadMore={fetchNextProposedPage} @@ -174,7 +176,7 @@ export default function MeetingDetailPage() {
From 136806162ce7c0ffbe4d668a710a0c6b71f42736 Mon Sep 17 00:00:00 2001 From: Haeun Date: Sat, 14 Feb 2026 16:37:14 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20confirmedTopic=ED=95=84=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD(#78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/meetings/meetings.mock.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/meetings/meetings.mock.ts b/src/features/meetings/meetings.mock.ts index 65191b2..c6f7be4 100644 --- a/src/features/meetings/meetings.mock.ts +++ b/src/features/meetings/meetings.mock.ts @@ -261,7 +261,7 @@ const mockMeetingDetails: Record = { buttonLabel: '약속이 끝났어요', enabled: false, }, - confirmedTopicExpand: true, + confirmedTopic: true, confirmedTopicDate: '2026-01-20T14:00:00', }, 11: { @@ -269,7 +269,7 @@ const mockMeetingDetails: Record = { progressStatus: 'PRE', meetingName: '킥오프 모임', meetingStatus: 'CONFIRMED', - confirmedTopicExpand: false, + confirmedTopic: false, confirmedTopicDate: null, gathering: { gatheringId: 102, From 8c2b87552b35038783694dd7f2226c8ee59fc11a Mon Sep 17 00:00:00 2001 From: Haeun Date: Sat, 14 Feb 2026 16:39:36 +0900 Subject: [PATCH 3/3] =?UTF-8?q?style:=20=EB=94=94=EB=B2=84=EA=B9=85?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0(#78)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/topics/components/ConfirmTopicModal.tsx | 1 - src/features/topics/components/TopicHeader.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/features/topics/components/ConfirmTopicModal.tsx b/src/features/topics/components/ConfirmTopicModal.tsx index 4276fc4..f12b01c 100644 --- a/src/features/topics/components/ConfirmTopicModal.tsx +++ b/src/features/topics/components/ConfirmTopicModal.tsx @@ -73,7 +73,6 @@ export default function ConfirmTopicModal({ handleClose() }, onError: (error) => { - console.log(error) openError('확정 실패', error.userMessage) }, } diff --git a/src/features/topics/components/TopicHeader.tsx b/src/features/topics/components/TopicHeader.tsx index 59fa467..fdf7070 100644 --- a/src/features/topics/components/TopicHeader.tsx +++ b/src/features/topics/components/TopicHeader.tsx @@ -28,7 +28,6 @@ type TopicHeaderProps = ProposedHeaderProps | ConfirmedHeaderProps export default function TopicHeader(props: TopicHeaderProps) { const navigate = useNavigate() - console.log(props.confirmedTopic, props.confirmedTopicDate) return ( <> {/* 제안탭 */}