diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index a17b8a08b..f3b6525f9 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -26,6 +26,9 @@ "plugin:storybook/recommended" ], "rules": { + "@typescript-eslint/consistent-type-imports": "error", + "jsx-a11y/click-events-have-key-events": "off", + "jsx-a11y/no-noninteractive-element-interactions": "off", "react-refresh/only-export-components": [ "warn", { diff --git a/frontend/.prettierrc b/frontend/.prettierrc index 28c31bf41..030d86cd6 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -13,10 +13,12 @@ "importOrder": [ "^@layouts/(.*)$", + "^@contexts/(.*)$", "^@pages/(.*)$", "^@components/(.*)$", "^@hooks/(.*)$", "^@apis/(.*)$", + "^@stores/(.*)$", "^@common/(.*)$", "^@utils/(.*)$", "^@assets/(.*)$", diff --git a/frontend/.vscode/setting.json b/frontend/.vscode/setting.json deleted file mode 100644 index 62b4a1c1f..000000000 --- a/frontend/.vscode/setting.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "stylelint.workingDirectories": ["./front"], - "eslint.workingDirectories": ["./front"], - "stylelint.configFile": ".stylelintrc.json", - "stylelint.packageManager": "npm", - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit", - "source.fixAll.stylelint": "explicit" - } -} diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json new file mode 100644 index 000000000..1ad429a9f --- /dev/null +++ b/frontend/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "eslint.workingDirectories": ["./frontend"], + "stylelint.configFile": ".stylelintrc.json", + "stylelint.packageManager": "npm", + "editor.codeActionsOnSave": { + "source.fixAll.stylelint": "explicit", + "source.fixAll.eslint": "explicit", + "source.fixAll.prettier": "explicit" + }, + "stylelint.validate": ["typescript", "typescriptreact"] +} diff --git a/frontend/src/apis/getMeeting.ts b/frontend/src/apis/getMeeting.ts index eb79395f0..43646cdac 100644 --- a/frontend/src/apis/getMeeting.ts +++ b/frontend/src/apis/getMeeting.ts @@ -5,7 +5,7 @@ export interface Schedules { times: string[]; } -export interface getMeetingResponse { +export interface GetMeetingResponse { meetingName: string; startTime: string; endTime: string; @@ -13,7 +13,8 @@ export interface getMeetingResponse { schedules: Schedules[]; } -const getMeeting = async (uuid = '550e8400') => { +// uuid 기본값은 임시 설정된 uuid +const getMeeting = async (uuid = '550e8400'): Promise => { const url = `${API_URL}/api/v1/meeting/${uuid}`; const response = await fetch(url, { @@ -29,7 +30,7 @@ const getMeeting = async (uuid = '550e8400') => { const data = await response.json(); - return data.data as getMeetingResponse; + return data.data; }; export default getMeeting; diff --git a/frontend/src/apis/getSchedule.ts b/frontend/src/apis/getSchedule.ts deleted file mode 100644 index ee9318dd4..000000000 --- a/frontend/src/apis/getSchedule.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { API_URL } from '@constants/api'; - -const getSchedule = async () => { - const url = `${API_URL}/api/v1/schedule}`; - - const response = await fetch(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const data = await response.json(); - return data; -}; - -export default getSchedule; diff --git a/frontend/src/apis/postSchedule.ts b/frontend/src/apis/postSchedule.ts deleted file mode 100644 index eb33186df..000000000 --- a/frontend/src/apis/postSchedule.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { API_URL } from '@constants/api'; - -import { Schedules } from './getMeeting'; - -const postSchedule = async (requestData: Schedules[]) => { - const url = `${API_URL}/api/v1/schedule`; - - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - meetingId: 1, - guestName: 'daon', - dateTimes: requestData, - }), - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - const data = await response.json(); - return data; -}; - -export default postSchedule; diff --git a/frontend/src/apis/schedule.ts b/frontend/src/apis/schedule.ts new file mode 100644 index 000000000..670aff223 --- /dev/null +++ b/frontend/src/apis/schedule.ts @@ -0,0 +1,42 @@ +import { API_URL } from '@constants/api'; + +import type { Schedules } from './getMeeting'; + +export const postSchedule = async (requestData: Schedules[]) => { + const url = `${API_URL}/api/v1/schedule`; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + meetingId: 1, + guestName: 'daon', + dateTimes: requestData, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } +}; + +// 현재 쓰지 않는 API (schedule 수정 시 사용하는 API) +export const getSchedule = async () => { + const url = `${API_URL}/api/v1/schedule}`; + + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const data = await response.json(); + return data; +}; diff --git a/frontend/src/components/TimePicker/TimePicker.utils.ts b/frontend/src/components/Time/Picker/TimePicker.util.ts similarity index 66% rename from frontend/src/components/TimePicker/TimePicker.utils.ts rename to frontend/src/components/Time/Picker/TimePicker.util.ts index 3f517fd01..0627f2c37 100644 --- a/frontend/src/components/TimePicker/TimePicker.utils.ts +++ b/frontend/src/components/Time/Picker/TimePicker.util.ts @@ -1,37 +1,42 @@ -import { TimePickerProps } from '.'; +import type { GetMeetingResponse } from '@apis/getMeeting'; -type Test = Record; +type TimeSlot = Record; const generateTimeSlots = (start: string, end: string) => { const startHour = Number(start.split(':')[0]); const endHour = Number(end.split(':')[0]); - const slots = []; + const slots: string[] = []; for (let i = startHour; i <= endHour; i++) { - slots.push(`${i.toString().padStart(2, '0')}:00`); + slots.push(`${i.toString().padStart(2, '0') + ':00'}`); } return slots; }; -export const generateScheduleMatrix = (data: TimePickerProps) => { - const { startTime, endTime, availableDates, schedules } = data; - +export const generateScheduleMatrix = ({ + startTime, + endTime, + availableDates, + schedules, +}: GetMeetingResponse) => { const timeSlots = generateTimeSlots(startTime, endTime); const timeSlotIndex = timeSlots.reduce((acc, slot, idx) => { acc[slot] = idx; return acc; - }, {} as Test); + }, {} as TimeSlot); - const scheduleMatrix = Array.from({ length: timeSlots.length }, () => + const scheduleMatrix: boolean[][] = Array.from({ length: timeSlots.length }, (): boolean[] => Array(availableDates.length).fill(false), ); schedules.forEach((schedule) => { const dateIndex = availableDates.indexOf(schedule.date); + if (dateIndex !== -1) { schedule.times.forEach((time) => { const timeIndex = timeSlotIndex[time]; + if (timeIndex !== undefined) { scheduleMatrix[timeIndex][dateIndex] = true; } @@ -52,16 +57,16 @@ export const convertToSchedule = ( const schedules = availableDates.map((date, colIndex) => { const times: string[] = []; + matrix.forEach((row, rowIndex) => { if (row[colIndex]) { - times.push(timeSlots[rowIndex] + ':00'); - times.push(timeSlots[rowIndex].slice(0, 2) + ':30:00'); + // 임시로 30분 단위도 추가되도록 설정 + times.push(timeSlots[rowIndex]); + times.push(timeSlots[rowIndex].slice(0, 2) + ':30'); } }); - return { - date, - times, - }; + + return { date, times }; }); return schedules.filter((schedule) => schedule.times.length > 0); diff --git a/frontend/src/components/Time/Picker/index.tsx b/frontend/src/components/Time/Picker/index.tsx new file mode 100644 index 000000000..940981e28 --- /dev/null +++ b/frontend/src/components/Time/Picker/index.tsx @@ -0,0 +1,74 @@ +import { useContext, useMemo } from 'react'; + +import { TimePickerUpdateStateContext } from '@contexts/TimePickerUpdateStateProvider'; + +import Button from '@components/_common/Button'; +import TimeSlot from '@components/_common/TimeSlot'; + +import useTimePick from '@hooks/useTimePick/useTimePick'; + +import type { GetMeetingResponse } from '@apis/getMeeting'; + +import { usePostScheduleMutation } from '@stores/servers/meeting/mutations'; + +import { s_buttonContainer, s_cell, s_table, s_th } from '../Time.styles'; +import { convertToSchedule, generateScheduleMatrix } from './TimePicker.util'; + +export interface TimePickerProps { + data: GetMeetingResponse; +} + +export default function TimePicker({ data }: TimePickerProps) { + const { isTimePickerUpdate, handleToggleIsTimePickerUpdate } = useContext( + TimePickerUpdateStateContext, + ); + + const initialValue = useMemo(() => generateScheduleMatrix(data), [data]); + const [ref, value] = useTimePick(isTimePickerUpdate, initialValue); + + const { mutate: postScheduleMutate } = usePostScheduleMutation(() => + handleToggleIsTimePickerUpdate(), + ); + + const handleOnToggle = () => { + const convert = convertToSchedule(value, data.availableDates, data.startTime, data.endTime); + + postScheduleMutate(convert); + }; + + const formattedAvailableDates = ['', ...data.availableDates]; + + return ( +
+ + + + {formattedAvailableDates.map((date) => ( + + ))} + + + + {value.map((row, rowIndex) => ( + + + {row.map((_, columnIndex) => ( + + ))} + + ))} + +
{date}
+ {String(rowIndex + Number(data.startTime.slice(0, 2)) + ':00')} +
+ +
+
+
+ ); +} diff --git a/frontend/src/components/TimePicker/TimePicker.styles.ts b/frontend/src/components/Time/Time.styles.tsx similarity index 53% rename from frontend/src/components/TimePicker/TimePicker.styles.ts rename to frontend/src/components/Time/Time.styles.tsx index b919a42de..eadd8b262 100644 --- a/frontend/src/components/TimePicker/TimePicker.styles.ts +++ b/frontend/src/components/Time/Time.styles.tsx @@ -2,63 +2,44 @@ import { css } from '@emotion/react'; import theme from '@styles/theme'; -export const tableStyle = css` - user-select: none; - border-collapse: collapse; -`; - -export const tdStyle = css` - cursor: pointer; - padding: 8px; - text-align: center; - border-radius: 0.4rem; -`; - -export const selectedStyle = css` - color: white; - background: ${theme.linear.selectedTime}; -`; - -export const table = css` +export const s_table = css` cursor: pointer; user-select: none; border-spacing: 0.4rem 0.4rem; + border-collapse: collapse; border-collapse: separate; width: 100%; `; -export const styledTd = (isSelected: boolean) => css` +export const s_td = css` cursor: pointer; - - padding: 0.4rem; - - color: ${isSelected ? '#121010' : '#fff'}; - - background: ${isSelected ? theme.linear.selectedTime : '#ececec'}; + padding: 8px; + text-align: center; border-radius: 0.4rem; +`; - &:hover { - opacity: 0.7; - } +export const s_selected = css` + color: white; + background: ${theme.linear.selectedTime}; `; -export const styledTh = css` +export const s_th = css` display: flex; align-items: center; justify-content: center; `; -export const buttonContainer = css` +export const s_cell = css` + cursor: default; + width: 4rem; + height: 4rem; +`; + +export const s_buttonContainer = css` display: flex; justify-content: flex-end; width: 100%; margin-top: 1rem; `; - -export const tableTexture = css` - cursor: default; - width: 4rem; - height: 4rem; -`; diff --git a/frontend/src/components/Time/Viewer/index.tsx b/frontend/src/components/Time/Viewer/index.tsx new file mode 100644 index 000000000..46461af5e --- /dev/null +++ b/frontend/src/components/Time/Viewer/index.tsx @@ -0,0 +1,59 @@ +import { useContext } from 'react'; + +import { TimePickerUpdateStateContext } from '@contexts/TimePickerUpdateStateProvider'; + +import Button from '@components/_common/Button'; +import TimeSlot from '@components/_common/TimeSlot'; + +import type { GetMeetingResponse } from '@apis/getMeeting'; + +import { generateScheduleMatrix } from '../Picker/TimePicker.util'; +import { s_buttonContainer, s_cell, s_table, s_th } from '../Time.styles'; + +interface TimeViewerProps { + data: GetMeetingResponse; +} + +export default function TimeViewer({ data }: TimeViewerProps) { + const { isTimePickerUpdate, handleToggleIsTimePickerUpdate } = useContext( + TimePickerUpdateStateContext, + ); + + const schedules = generateScheduleMatrix(data); + + const formattedAvailableDates = ['', ...data.availableDates]; + + return ( +
+ + + + {formattedAvailableDates.map((date) => ( + + ))} + + + + {schedules.map((row, rowIndex) => ( + + + {row.map((_, columnIndex) => ( + + ))} + + ))} + +
{date}
+ {String(rowIndex + Number(data.startTime.slice(0, 2)) + ':00')} +
+ +
+
+
+ ); +} diff --git a/frontend/src/components/TimePicker/index.tsx b/frontend/src/components/TimePicker/index.tsx deleted file mode 100644 index eef5f8e9d..000000000 --- a/frontend/src/components/TimePicker/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useState } from 'react'; - -import Button from '@components/_common/Button'; - -import useTimePick from '@hooks/useTimePick/useTimePick'; - -import postSchedule from '@apis/postSchedule'; - -import { buttonContainer, styledTd, styledTh, table, tableTexture } from './TimePicker.styles'; -import { convertToSchedule, generateScheduleMatrix } from './TimePicker.utils'; - -interface Schedules { - date: string; - times: string[]; -} - -export interface TimePickerProps { - startTime: string; - endTime: string; - availableDates: string[]; - schedules: Schedules[]; -} - -export default function TimePicker({ - startTime, - endTime, - availableDates, - schedules, -}: TimePickerProps) { - const mutation = useMutation({ - mutationFn: postSchedule, - }); - - const formattedAvailableDates = ['', ...availableDates]; - const [isUpdate, setIsUpdate] = useState(false); - - const initialValue = generateScheduleMatrix({ startTime, endTime, availableDates, schedules }); - - const [ref, value] = useTimePick(isUpdate, initialValue); - - const onToggle = () => { - setIsUpdate((prevIsUpdate) => !prevIsUpdate); - - if (isUpdate) { - const convert = convertToSchedule(value, availableDates, startTime, endTime); - mutation.mutate(convert); - } - }; - - return ( -
- - - - {formattedAvailableDates.map((date) => ( - - ))} - - - - {value.map((row, rowIndex) => ( - - - {row.map((_, columnIndex) => ( - - ))} - -
{date}
- {String(rowIndex + Number(startTime.slice(0, 2)) + ':00')} - - ))} -
- -
-
-
- ); -} diff --git a/frontend/src/components/_common/Button/Button.styles.ts b/frontend/src/components/_common/Button/Button.styles.ts index 21dc9ead9..0b6f77388 100644 --- a/frontend/src/components/_common/Button/Button.styles.ts +++ b/frontend/src/components/_common/Button/Button.styles.ts @@ -2,7 +2,7 @@ import { css } from '@emotion/react'; import theme from '@styles/theme'; -export const styledButton = css` +export const s_button = css` display: flex; align-items: center; justify-content: center; diff --git a/frontend/src/components/_common/Button/index.tsx b/frontend/src/components/_common/Button/index.tsx index a8d4bd81a..a86c43d3d 100644 --- a/frontend/src/components/_common/Button/index.tsx +++ b/frontend/src/components/_common/Button/index.tsx @@ -1,6 +1,6 @@ import type { ButtonHTMLAttributes } from 'react'; -import { styledButton } from './Button.styles'; +import { s_button } from './Button.styles'; interface ButtonProps extends ButtonHTMLAttributes { text: string; @@ -8,7 +8,7 @@ interface ButtonProps extends ButtonHTMLAttributes { export default function Button({ text, type = 'button', ...props }: ButtonProps) { return ( - ); diff --git a/frontend/src/components/_common/TimeSlot/TimeSlot.styles.ts b/frontend/src/components/_common/TimeSlot/TimeSlot.styles.ts new file mode 100644 index 000000000..a46a6a9ef --- /dev/null +++ b/frontend/src/components/_common/TimeSlot/TimeSlot.styles.ts @@ -0,0 +1,19 @@ +import { css } from '@emotion/react'; + +import theme from '@styles/theme'; + +export const s_td = (isSelected: boolean, isUpdate: boolean) => css` + cursor: pointer; + + width: 4rem; + height: 4rem; + + color: ${isSelected ? '#121010' : '#fff'}; + + background: ${isSelected ? theme.linear.selectedTime : '#ececec'}; + border-radius: 0.4rem; + + &:hover { + opacity: ${isUpdate ? 0.7 : 1.0}; + } +`; diff --git a/frontend/src/components/_common/TimeSlot/index.tsx b/frontend/src/components/_common/TimeSlot/index.tsx new file mode 100644 index 000000000..5c62d5484 --- /dev/null +++ b/frontend/src/components/_common/TimeSlot/index.tsx @@ -0,0 +1,10 @@ +import { s_td } from './TimeSlot.styles'; + +interface TimeSlotProps { + isSelected: boolean; + isUpdate: boolean; +} + +export default function TimeSlot({ isSelected, isUpdate }: TimeSlotProps) { + return ; +} diff --git a/frontend/src/constants/api.ts b/frontend/src/constants/api.ts index fc6248bcf..1929121ff 100644 --- a/frontend/src/constants/api.ts +++ b/frontend/src/constants/api.ts @@ -1,3 +1,3 @@ export const API_URL = process.env.API_URL; -export const END_POINTS = {}; +export const END_POINTS = {} as const; diff --git a/frontend/src/constants/queryKeys.ts b/frontend/src/constants/queryKeys.ts new file mode 100644 index 000000000..911ad1485 --- /dev/null +++ b/frontend/src/constants/queryKeys.ts @@ -0,0 +1,3 @@ +export const QUERY_KEY = { + meeting: 'meeting', +} as const; diff --git a/frontend/src/contexts/TimePickerUpdateStateProvider.tsx b/frontend/src/contexts/TimePickerUpdateStateProvider.tsx new file mode 100644 index 000000000..b2b74f356 --- /dev/null +++ b/frontend/src/contexts/TimePickerUpdateStateProvider.tsx @@ -0,0 +1,27 @@ +import type { PropsWithChildren } from 'react'; +import { createContext, useCallback, useState } from 'react'; + +interface TimePickerUpdateStateContextProps { + isTimePickerUpdate: boolean; + handleToggleIsTimePickerUpdate: () => void; +} + +export const TimePickerUpdateStateContext = createContext( + {} as TimePickerUpdateStateContextProps, +); + +export const TimePickerUpdateStateProvider = ({ children }: PropsWithChildren) => { + const [isTimePickerUpdate, setIsTimePickerUpdate] = useState(false); + + const handleToggleIsTimePickerUpdate = useCallback(() => { + setIsTimePickerUpdate((prevState) => !prevState); + }, []); + + return ( + + {children} + + ); +}; diff --git a/frontend/src/layouts/GlobalLayout.styles.ts b/frontend/src/layouts/GlobalLayout.styles.ts index 95216fbbe..e3b9576c1 100644 --- a/frontend/src/layouts/GlobalLayout.styles.ts +++ b/frontend/src/layouts/GlobalLayout.styles.ts @@ -1,6 +1,6 @@ import { css } from '@emotion/react'; -export const globalContainer = css` +export const s_globalContainer = css` min-width: 39.3rem; max-width: 60rem; height: 100%; diff --git a/frontend/src/layouts/GlobalLayout.tsx b/frontend/src/layouts/GlobalLayout.tsx index cc0a5d1a6..2f68f0cc0 100644 --- a/frontend/src/layouts/GlobalLayout.tsx +++ b/frontend/src/layouts/GlobalLayout.tsx @@ -1,10 +1,10 @@ import { Outlet } from 'react-router-dom'; -import { globalContainer } from './GlobalLayout.styles'; +import { s_globalContainer } from './GlobalLayout.styles'; export default function GlobalLayout() { return ( -
+
); diff --git a/frontend/src/mocks/.gitkeep b/frontend/src/mocks/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/mocks/data.json b/frontend/src/mocks/data.json new file mode 100644 index 000000000..5b6ef8445 --- /dev/null +++ b/frontend/src/mocks/data.json @@ -0,0 +1,30 @@ +{ + "data": { + "meetingName": "momo_1", + "startTime": "10:00:00", + "endTime": "18:00:00", + "availableDates": [ + "2024-07-15", + "2024-07-16", + "2024-07-17", + "2024-07-18", + "2024-07-19", + "2024-07-20", + "2024-07-21" + ], + "schedules": [ + { + "date": "2024-07-16", + "times": ["16:00"] + }, + { + "date": "2024-07-15", + "times": ["10:00", "12:00", "13:00"] + }, + { + "date": "2024-07-17", + "times": ["15:00"] + } + ] + } +} diff --git a/frontend/src/pages/.gitkeep b/frontend/src/pages/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/frontend/src/pages/MeetingTimePickPage/MeetingTimePickPage.styles.ts b/frontend/src/pages/MeetingTimePickPage/MeetingTimePickPage.styles.ts index e68817188..cc6427060 100644 --- a/frontend/src/pages/MeetingTimePickPage/MeetingTimePickPage.styles.ts +++ b/frontend/src/pages/MeetingTimePickPage/MeetingTimePickPage.styles.ts @@ -2,7 +2,7 @@ import { css } from '@emotion/react'; import theme from '@styles/theme'; -export const title = css` +export const s_title = css` width: 100%; margin-bottom: 1rem; diff --git a/frontend/src/pages/MeetingTimePickPage/index.tsx b/frontend/src/pages/MeetingTimePickPage/index.tsx index 2cc77492f..53ef8efb1 100644 --- a/frontend/src/pages/MeetingTimePickPage/index.tsx +++ b/frontend/src/pages/MeetingTimePickPage/index.tsx @@ -1,22 +1,24 @@ -import { useQuery } from '@tanstack/react-query'; +import { useContext } from 'react'; -import TimePicker from '@components/TimePicker'; +import { TimePickerUpdateStateContext } from '@contexts/TimePickerUpdateStateProvider'; -import getMeeting from '@apis/getMeeting'; +import TimePicker from '@components/Time/Picker'; +import TimeViewer from '@components/Time/Viewer'; -import { title } from './MeetingTimePickPage.styles'; +import { useGetMeetingQuery } from '@stores/servers/meeting/queries'; + +import { s_title } from './MeetingTimePickPage.styles'; export default function MeetingTimePickPage() { - const { data } = useQuery({ - queryKey: ['getMeeting'], - queryFn: () => getMeeting(), - retry: 1, - }); + const { data } = useGetMeetingQuery(); + const { isTimePickerUpdate } = useContext(TimePickerUpdateStateContext); + + if (!data) return null; return (
-

momo TimePicker

- {data && } +

momo TimePicker

+ {isTimePickerUpdate ? : }
); } diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 46b434ae5..07239ebaf 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -2,7 +2,9 @@ import { createBrowserRouter } from 'react-router-dom'; import GlobalLayout from '@layouts/GlobalLayout'; -import Join from '@pages/MeetingTimePickPage'; +import { TimePickerUpdateStateProvider } from '@contexts/TimePickerUpdateStateProvider'; + +import MeetingTimePickPage from '@pages/MeetingTimePickPage'; const router = createBrowserRouter( [ @@ -12,7 +14,11 @@ const router = createBrowserRouter( children: [ { index: true, - element: , + element: ( + + + + ), }, ], }, diff --git a/frontend/src/stores/servers/meeting/mutations.ts b/frontend/src/stores/servers/meeting/mutations.ts new file mode 100644 index 000000000..37d852b4d --- /dev/null +++ b/frontend/src/stores/servers/meeting/mutations.ts @@ -0,0 +1,34 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; + +import type { GetMeetingResponse } from '@apis/getMeeting'; +import { postSchedule } from '@apis/schedule'; + +import { QUERY_KEY } from '@constants/queryKeys'; + +export const usePostScheduleMutation = (onSettledCallback: () => void) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: postSchedule, + onMutate: async (newSchedules) => { + await queryClient.cancelQueries({ queryKey: [QUERY_KEY.meeting] }); + + const prevSchedules = queryClient.getQueryData([QUERY_KEY.meeting]); + + queryClient.setQueryData([QUERY_KEY.meeting], (prevData: GetMeetingResponse) => { + const nextMeetingSchedules = { + ...prevData, + schedules: newSchedules, + }; + + return nextMeetingSchedules; + }); + + return { prevSchedules }; + }, + onSettled: () => { + onSettledCallback(); + queryClient.invalidateQueries({ queryKey: [QUERY_KEY.meeting] }); + }, + }); +}; diff --git a/frontend/src/stores/servers/meeting/queries.ts b/frontend/src/stores/servers/meeting/queries.ts new file mode 100644 index 000000000..588b4659c --- /dev/null +++ b/frontend/src/stores/servers/meeting/queries.ts @@ -0,0 +1,12 @@ +import { useQuery } from '@tanstack/react-query'; + +import getMeeting from '@apis/getMeeting'; + +import { QUERY_KEY } from '@constants/queryKeys'; + +export const useGetMeetingQuery = () => + useQuery({ + queryKey: [QUERY_KEY.meeting], + queryFn: () => getMeeting(), + retry: 1, + }); diff --git a/frontend/src/styles/global.ts b/frontend/src/styles/global.ts index abd2cca3b..dac3f3d6c 100644 --- a/frontend/src/styles/global.ts +++ b/frontend/src/styles/global.ts @@ -162,7 +162,7 @@ const globalStyles = css` button { cursor: pointer; - background-color: red; + border: none; } `; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 263b377a0..04d0d0673 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -31,7 +31,9 @@ "@pages/*": ["pages/*"], "@styles/*": ["styles/*"], "@types/*": ["types/*"], - "@utils/*": ["utils/*"] + "@utils/*": ["utils/*"], + "@stores/*": ["stores/*"], + "@contexts/*": ["contexts/*"] }, // test "types": ["jest"] diff --git a/frontend/webpack.common.js b/frontend/webpack.common.js index 7543a9579..ad517ccb5 100644 --- a/frontend/webpack.common.js +++ b/frontend/webpack.common.js @@ -46,6 +46,8 @@ module.exports = () => ({ '@styles': path.resolve(__dirname, 'src/styles'), '@types': path.resolve(__dirname, 'src/types'), '@utils': path.resolve(__dirname, 'src/utils'), + '@contexts': path.resolve(__dirname, 'src/contexts'), + '@stores': path.resolve(__dirname, 'src/stores'), }, }, output: {