From e324f46b36a2d761fd7916eda9fcf8db012baa11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoonkyoung=20=28=EB=B9=99=EB=B4=89=29?= Date: Tue, 15 Oct 2024 22:28:50 +0900 Subject: [PATCH] =?UTF-8?q?[FE]=20=EC=95=BD=EC=86=8D=20=EC=9E=85=EC=9E=A5?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B5=AC=ED=98=84=20=EB=B0=8F?= =?UTF-8?q?=20=EB=B0=94=EB=A1=9C=20=EC=95=BD=EC=86=8D=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=ED=95=98=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B0=94=EB=A1=9C=20=EB=93=9C=EB=9E=98=EA=B7=B8/?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=EC=9C=BC=EB=A1=9C=20=EC=95=BD=EC=86=8D=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=EC=9D=B4=20=EA=B0=80=EB=8A=A5=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=EA=B5=AC=ED=98=84=20(#403)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: MeetingInvitePage 구현 * fix: postSchedule 반환값 오류 수정 및 onSuccess로 콜백 처리 변경 - postSchedule 함수에서 반환값이 없는데 `return data.data`를 잘못 사용해 발생했던 오류 수정 - onSettled`를 제거하고 `onSuccess`로 콜백 처리 변경 * feat: MeetingRegisterPage 구현 * feat: SchedulePicker 컴포넌트에 register와 edit 모드에 따른 처리 로직 추가 * feat: MeetingConfirmCalendar.Picker 컴포넌트에 register와 edit 모드에 따른 처리 로직 추가 * feat: 날짜/시간을 등록할 때 아무것도 선택하지 않으면 버튼이 비활성화 되게 설정 * refactor: edit 모드에서 버튼 텍스트를 변경 (등록하기 → 수정하기) * rename: 파일명 수정 (meeting/mutation → meeting/mutations) * fix: navigate 경로 에러 수정 * rename: meeting 관련 api 호출 파일들을 meetings 디렉터리 내로 이동 * feat: MeetingInvitePage 버튼 상호작용 로직 구현 * feat: getMeetingInvitationDetails 함수 정의 및 적용 * rename: useGetMyScheduleQuery 훅을 schedule 관련 파일로 이동 (meeting → schedule) * rename: 디렉터리명 수정 (MeetingTimePickPage → MeetingViewerPage) * refactor: 일정 등록 및 수정 로직을 usePostScheduleByMode hook으로 통합 * rename: meetingInvite → meetingEntrance '약속 초대'라는 의미를 '약속 입장'으로 변경 * feat: MSW 핸들러에 약속 타입(DATETIME, DAYSONLY)에 따른 meetingEntranceDetails API 응답 분기 처리 추가 * refactor: GlobalLayout으로 Suspense 처리 로직 이동 --- .../confirms.ts} | 2 +- frontend/src/apis/{ => meetings}/meetings.ts | 17 +++- .../recommends.ts} | 2 +- frontend/src/apis/schedules.ts | 31 ++++--- .../MeetingConfirmCalendar/Picker/index.tsx | 74 ++++++++++++----- .../MeetingConfirmCalendar/Viewer/index.tsx | 12 ++- .../SchedulePickerContainer.tsx | 11 ++- .../Schedules/SchedulePicker/index.tsx | 81 +++++++++++++++---- .../ScheduleViewer/SchedulesViewer.tsx | 12 ++- frontend/src/constants/queryKeys.ts | 1 + .../hooks/useCalendarPick/useCalendarPick.tsx | 2 +- .../useCreateMeeting/useCreateMeeting.ts | 2 +- .../useMeetingTimeRecommendFilter.ts | 2 +- .../hooks/useMeetingType/useMeetingType.ts | 2 +- frontend/src/layouts/GlobalLayout.tsx | 6 +- frontend/src/mocks/data.json | 30 ------- .../data/meetingEntranceDateTimeInfo.json | 6 ++ .../data/meetingEntranceDaysOnlyInfo.json | 6 ++ .../mocks/meeting/data/meetingTableFrame.json | 2 +- frontend/src/mocks/meeting/handlers.ts | 29 ------- frontend/src/mocks/meeting/meetingHandlers.ts | 54 ++++++++----- .../components/ActionButtonGroup/index.tsx | 2 +- .../components/MeetingTicket/index.tsx | 2 +- .../components/MeetingTimeOptions/index.tsx | 4 +- .../src/pages/MeetingConfirmPage/index.tsx | 2 +- .../MeetingEntrancePage.styles.ts | 72 +++++++++++++++++ .../src/pages/MeetingEntrancePage/index.tsx | 71 ++++++++++++++++ .../src/pages/MeetingLinkSharePage/index.tsx | 2 +- .../src/pages/MeetingRecommendPage/index.tsx | 2 +- .../MeetingRegisterPage.styles.ts | 24 ++++++ .../src/pages/MeetingRegisterPage/index.tsx | 69 ++++++++++++++++ .../MeetingViewerPage.styles.ts} | 0 .../index.tsx | 14 ++-- frontend/src/router.tsx | 48 +++++------ .../src/stores/servers/confirm/mutations.ts | 2 +- .../src/stores/servers/confirm/queries.ts | 2 +- .../meeting/{mutation.ts => mutations.ts} | 2 +- .../src/stores/servers/meeting/queries.ts | 16 ++-- .../src/stores/servers/schedule/mutations.ts | 47 ++++++++--- .../src/stores/servers/schedule/queries.ts | 9 ++- frontend/src/types/schedule.ts | 2 + 41 files changed, 579 insertions(+), 197 deletions(-) rename frontend/src/apis/{meetingConfirm.ts => meetings/confirms.ts} (96%) rename frontend/src/apis/{ => meetings}/meetings.ts (89%) rename frontend/src/apis/{meetingRecommend.ts => meetings/recommends.ts} (96%) delete mode 100644 frontend/src/mocks/data.json create mode 100644 frontend/src/mocks/meeting/data/meetingEntranceDateTimeInfo.json create mode 100644 frontend/src/mocks/meeting/data/meetingEntranceDaysOnlyInfo.json delete mode 100644 frontend/src/mocks/meeting/handlers.ts create mode 100644 frontend/src/pages/MeetingEntrancePage/MeetingEntrancePage.styles.ts create mode 100644 frontend/src/pages/MeetingEntrancePage/index.tsx create mode 100644 frontend/src/pages/MeetingRegisterPage/MeetingRegisterPage.styles.ts create mode 100644 frontend/src/pages/MeetingRegisterPage/index.tsx rename frontend/src/pages/{MeetingTimePickPage/MeetingTimePickPage.styles.ts => MeetingViewerPage/MeetingViewerPage.styles.ts} (100%) rename frontend/src/pages/{MeetingTimePickPage => MeetingViewerPage}/index.tsx (92%) rename frontend/src/stores/servers/meeting/{mutation.ts => mutations.ts} (98%) diff --git a/frontend/src/apis/meetingConfirm.ts b/frontend/src/apis/meetings/confirms.ts similarity index 96% rename from frontend/src/apis/meetingConfirm.ts rename to frontend/src/apis/meetings/confirms.ts index 3026c0a4d..6015e8589 100644 --- a/frontend/src/apis/meetingConfirm.ts +++ b/frontend/src/apis/meetings/confirms.ts @@ -1,6 +1,6 @@ import { BASE_URL } from '@constants/api'; -import { fetchClient } from './_common/fetchClient'; +import { fetchClient } from '../_common/fetchClient'; import type { MeetingType } from './meetings'; export interface ConfirmDates { diff --git a/frontend/src/apis/meetings.ts b/frontend/src/apis/meetings/meetings.ts similarity index 89% rename from frontend/src/apis/meetings.ts rename to frontend/src/apis/meetings/meetings.ts index 4493d9bcb..6cb6088a6 100644 --- a/frontend/src/apis/meetings.ts +++ b/frontend/src/apis/meetings/meetings.ts @@ -4,9 +4,10 @@ import { ResponseError } from '@utils/responseError'; import { BASE_URL } from '@constants/api'; -import { fetchClient } from './_common/fetchClient'; +import { fetchClient } from '../_common/fetchClient'; export type MeetingType = 'DAYSONLY' | 'DATETIME'; + interface MeetingBaseResponse { meetingName: string; firstTime: string; @@ -136,3 +137,17 @@ export const unlockMeeting = async (uuid: string) => { throw new ResponseError(data); } }; + +interface MeetingEntranceDetails { + meetingName: string; + type: MeetingType; +} + +export const getMeetingEntranceDetails = async (uuid: string) => { + const data = await fetchClient({ + path: `/${uuid}/home`, + method: 'GET', + }); + + return data; +}; diff --git a/frontend/src/apis/meetingRecommend.ts b/frontend/src/apis/meetings/recommends.ts similarity index 96% rename from frontend/src/apis/meetingRecommend.ts rename to frontend/src/apis/meetings/recommends.ts index d4050e3b7..a80230c5a 100644 --- a/frontend/src/apis/meetingRecommend.ts +++ b/frontend/src/apis/meetings/recommends.ts @@ -1,4 +1,4 @@ -import { fetchClient } from './_common/fetchClient'; +import { fetchClient } from '../_common/fetchClient'; import type { MeetingType } from './meetings'; interface GetMeetingRecommendRequest { diff --git a/frontend/src/apis/schedules.ts b/frontend/src/apis/schedules.ts index 41c55a11f..59cfc1889 100644 --- a/frontend/src/apis/schedules.ts +++ b/frontend/src/apis/schedules.ts @@ -5,25 +5,34 @@ import type { MeetingSingleSchedule, } from 'types/schedule'; +import { ResponseError } from '@utils/responseError'; + +import { BASE_URL } from '@constants/api'; + import { fetchClient } from './_common/fetchClient'; -export const postSchedule = async ({ - uuid, - requestData, -}: { +export interface PostScheduleRequest { uuid: string; requestData: MeetingSingeScheduleItem[]; -}) => { - const path = `/${uuid}/schedules`; +} - await fetchClient({ - path, +export const postSchedule = async ({ uuid, requestData }: PostScheduleRequest) => { + const response = await fetch(`${BASE_URL}/${uuid}/schedules`, { method: 'POST', - body: { - dateTimes: requestData, + headers: { + 'Content-Type': 'application/json', }, - isAuthRequire: true, + body: JSON.stringify({ + dateTimes: requestData, + }), + credentials: 'include', }); + + if (!response.ok) { + const data = await response.json(); + + throw new ResponseError(data); + } }; export const createMeetingSchedulesRequestUrl = (uuid: string, attendeeName: string) => { diff --git a/frontend/src/components/MeetingConfirmCalendar/Picker/index.tsx b/frontend/src/components/MeetingConfirmCalendar/Picker/index.tsx index 86d18e2ce..3010d1f57 100644 --- a/frontend/src/components/MeetingConfirmCalendar/Picker/index.tsx +++ b/frontend/src/components/MeetingConfirmCalendar/Picker/index.tsx @@ -1,5 +1,6 @@ import { useContext } from 'react'; -import { useParams } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; +import type { Mode } from 'types/schedule'; import { AuthContext } from '@contexts/AuthProvider'; import { TimePickerUpdateStateContext } from '@contexts/TimePickerUpdateStateProvider'; @@ -13,7 +14,7 @@ import Calendar from '@components/_common/Calendar'; import useCalendarPick from '@hooks/useCalendarPick/useCalendarPick'; -import { usePostScheduleMutation } from '@stores/servers/schedule/mutations'; +import { usePostScheduleByMode } from '@stores/servers/schedule/mutations'; import { getFullDate } from '@utils/date'; @@ -23,35 +24,48 @@ import WeekDays from '../WeekDays'; interface PickerProps { availableDates: string[]; + mode: Mode; } -export default function Picker({ availableDates }: PickerProps) { +export default function Picker({ availableDates, mode }: PickerProps) { + const navigate = useNavigate(); + const params = useParams<{ uuid: string }>(); const uuid = params.uuid!; - const { userName } = useContext(AuthContext).state; + const { userName } = useContext(AuthContext).state; const { handleToggleIsTimePickerUpdate } = useContext(TimePickerUpdateStateContext); const { selectedDates, hasDate, handleSelectedDate } = useCalendarPick(uuid, userName); - const { mutate: postScheduleMutate, isPending } = usePostScheduleMutation(() => - handleToggleIsTimePickerUpdate(), - ); + const { submitSchedule, isEditModePending, isRegisterModePending } = usePostScheduleByMode(mode); - // 백엔드에 날짜 데이터 보내주기 위해 임시로 generate함수 선언(@낙타) - const generateScheduleTable = (dates: string[]) => { - return dates.map((date) => { + const convertSelectedDatesToRequest = (dates: string[]) => { + const convertedData = dates.map((date) => { return { date, times: ['00:00'], }; }); + + return { uuid, requestData: convertedData }; }; - const handleOnToggle = () => { - postScheduleMutate({ uuid, requestData: generateScheduleTable(selectedDates) }); + const handleMeetingViewerNavigate = () => { + navigate(`/meeting/${uuid}/viewer`); }; + const handleScheduleSave = () => { + if (mode === 'register' && selectedDates.length === 0) { + return; + } + + const scheduleRequestData = convertSelectedDatesToRequest(selectedDates); + submitSchedule(scheduleRequestData); + }; + + const isScheduleEmpty = selectedDates.length === 0; + return ( <> @@ -73,12 +87,36 @@ export default function Picker({ availableDates }: PickerProps) {
- - + {mode === 'register' ? ( + <> + + + + ) : ( + <> + + + + )}
diff --git a/frontend/src/components/MeetingConfirmCalendar/Viewer/index.tsx b/frontend/src/components/MeetingConfirmCalendar/Viewer/index.tsx index 2b7f68d2b..3652775a4 100644 --- a/frontend/src/components/MeetingConfirmCalendar/Viewer/index.tsx +++ b/frontend/src/components/MeetingConfirmCalendar/Viewer/index.tsx @@ -120,11 +120,19 @@ export default function Viewer({