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.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, 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/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..fdf7070 100644 --- a/src/features/topics/components/TopicHeader.tsx +++ b/src/features/topics/components/TopicHeader.tsx @@ -64,13 +64,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() {