diff --git a/src/api/auth/index.ts b/src/api/auth/index.ts index 6474497..5b66f5b 100644 --- a/src/api/auth/index.ts +++ b/src/api/auth/index.ts @@ -1,5 +1,7 @@ import { accessClient, client } from 'api'; -import { CoopMeResponse, LoginParams, LoginResponse } from 'models/auth'; +import { + CoopMeResponse, LoginParams, LoginResponse, RefreshParams, RefreshResponse, +} from 'models/auth'; export const postLogin = async (param: LoginParams) => { const { data } = await client.post('/user/login', param); @@ -8,6 +10,11 @@ export const postLogin = async (param: LoginParams) => { export const postLogout = () => accessClient.post('/user/logout'); +export const postRefresh = async (params: RefreshParams) => { + const { data } = await client.post('/user/refresh', params); + return RefreshResponse.parse(data); +}; + export const getCoopMe = async () => { const { data } = await accessClient.get('/user/coop/me'); return CoopMeResponse.parse(data); diff --git a/src/api/coop/index.ts b/src/api/coop/index.ts index 31d8471..90afde2 100644 --- a/src/api/coop/index.ts +++ b/src/api/coop/index.ts @@ -1,7 +1,7 @@ import { accessClient } from 'api'; import { DiningImagesParams, SoldOutParams } from 'models/coop'; -export const updateSoldOut = async (data: SoldOutParams) => { +export const patchSoldOut = async (data: SoldOutParams) => { await accessClient.patch('/coop/dining/soldout', data); }; diff --git a/src/assets/svg/coop/soldout.svg b/src/assets/svg/coop/sold-out.svg similarity index 100% rename from src/assets/svg/coop/soldout.svg rename to src/assets/svg/coop/sold-out.svg diff --git a/src/models/auth.ts b/src/models/auth.ts index 49b9829..0be52fa 100644 --- a/src/models/auth.ts +++ b/src/models/auth.ts @@ -12,9 +12,9 @@ export interface LoginForm extends LoginParams { } export const LoginResponse = z.object({ - refresh_token: z.string(), token: z.string(), - user_type: z.string(), + refresh_token: z.string(), + user_type: z.string(), // 삭제 가능성 있음 }); export type LoginResponse = z.infer; diff --git a/src/models/dinings.ts b/src/models/dinings.ts index c367d7f..1a2fca6 100644 --- a/src/models/dinings.ts +++ b/src/models/dinings.ts @@ -18,7 +18,7 @@ export const DINING_TYPE_MAP = { DINNER: '저녁', } as const; -export type Place = 'A코너' | 'B코너' | 'C코너' | '능수관' | '2캠퍼스'; +export type DiningPlace = 'A코너' | 'B코너' | 'C코너' | '능수관' | '2캠퍼스'; export const PlaceSchema = z.union([ z.literal('A코너'), diff --git a/src/pages/Coop/components/MenuCard/MenuCard.module.scss b/src/pages/Coop/components/MenuCard/MenuCard.module.scss index e09d09e..5e78410 100644 --- a/src/pages/Coop/components/MenuCard/MenuCard.module.scss +++ b/src/pages/Coop/components/MenuCard/MenuCard.module.scss @@ -61,7 +61,7 @@ object-fit: cover; } - &--soldout { + &--sold-out { width: 150px; height: 100px; background: linear-gradient(0deg, rgb(0 0 0 / 50%) 0%, rgb(0 0 0 / 50%) 100%); @@ -110,7 +110,7 @@ } } - &__soldout { + &__sold-out { color: #8e8e8e; font-size: 13px; font-weight: 400; diff --git a/src/pages/Coop/components/MenuCard/index.tsx b/src/pages/Coop/components/MenuCard/index.tsx index 20b4d35..325af26 100644 --- a/src/pages/Coop/components/MenuCard/index.tsx +++ b/src/pages/Coop/components/MenuCard/index.tsx @@ -1,15 +1,14 @@ /* eslint-disable no-nested-ternary */ import { useEffect, useRef, useState } from 'react'; -import { getCoopUrl } from 'api/uploadFile/index'; import Photo from 'assets/svg/coop/photo.svg?react'; -import SoldOut from 'assets/svg/coop/soldout.svg?react'; -import { Dinings, Corner } from 'models/coop'; -import { DiningType, DINING_TYPE_MAP } from 'models/dinings'; -import SoldoutModal from 'pages/Coop/components/SoldoutModal'; -import SoldoutToggle from 'pages/Coop/components/SoldoutToggle'; +import SoldOut from 'assets/svg/coop/sold-out.svg?react'; +import { DiningPlace, Dinings, DiningType } from 'models/dinings'; +import SoldOutModal from 'pages/Coop/components/SoldOutModal'; +import SoldOutToggle from 'pages/Coop/components/SoldOutToggle'; import { getOpenMenuType, OperatingStatus, OPEN } from 'pages/Coop/hook/useGetCurrentMenuType'; -import { useGetDining, useUploadDiningImage, useUpdateSoldOut } from 'query/coop'; +import { useUploadDiningImage, useSoldOut } from 'query/coop'; +import { useGetDining } from 'query/dinings'; import axios from 'axios'; @@ -27,11 +26,11 @@ interface FileInfo { export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardProps) { const { uploadDiningImageMutation } = useUploadDiningImage(); - const { updateSoldOutMutation } = useUpdateSoldOut(); + const { updateSoldOut: updateSoldOutMutation } = useSoldOut(); const fileInputRefs = useRef<{ [key: number]: HTMLInputElement | null }>({}); - const [isSoldoutModalOpen, setIsSoldoutModalOpen] = useState(false); + const [isSoldOutModalOpen, setIsSoldOutModalOpen] = useState(false); const [selectedMenu, setSelectedMenu] = useState(null); - const [selectedCorner, setSelectedCorner] = useState(null); + const [selectedPlace, setSelectedPlace] = useState(null); const [formmatDate, setFormmatDate] = useState(''); const { data } = useGetDining(formmatDate); const [openMenu, setOpenMenu] = useState( @@ -70,45 +69,40 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro fileInputRefs.current[menuId]?.click(); }; - const getDiningType = (menuType: DiningType) => DINING_TYPE_MAP[menuType]; + const filteredData = data?.filter((menu: Dinings) => ['A코너', 'B코너', 'C코너'].includes(menu.place)); - const filteredData = data?.filter((menu:Dinings) => { - const diningType = getDiningType(selectedMenuType); - return menu.type === diningType && ['A코너', 'B코너', 'C코너'].includes(menu.place); - }); - - const getDiningDataForCorner = (corner: Corner, diningData: Dinings[]): + const getDiningDataForCorner = (place: DiningPlace, diningData: Dinings[]): Dinings | null => diningData.find( - (menu) => menu.place === corner, + (menu) => menu.place === place, ) || null; - const handleToggleSoldoutModal = (menu?: Dinings, corner?: Corner) => { + const handleToggleSoldOutModal = (menu: Dinings, type: DiningType) => { setSelectedMenu(menu || null); - setSelectedCorner(corner || null); + setSelectedPlace(type || null); if (menu?.soldout_at === null) { - setIsSoldoutModalOpen((prev) => !prev); + setIsSoldOutModalOpen((prev) => !prev); } else if (menu) { - setIsSoldoutModalOpen((prev) => !prev); + setIsSoldOutModalOpen((prev) => !prev); } }; - const handleSoldoutModalClose = () => { - setIsSoldoutModalOpen(false); + const handleSoldOutModalClose = () => { + setIsSoldOutModalOpen(false); }; - const handleSoldoutModalConfirm = () => { + const handleSoldOutModalConfirm = () => { if (selectedMenu && selectedMenu.soldout_at === null) { updateSoldOutMutation({ menu_id: selectedMenu.id, sold_out: true, }); - setIsSoldoutModalOpen(false); + setIsSoldOutModalOpen(false); } else if (selectedMenu && selectedMenu.soldout_at) { updateSoldOutMutation({ menu_id: selectedMenu.id, sold_out: false, }); - setIsSoldoutModalOpen(false); + setIsSoldOutModalOpen(false); } }; @@ -129,14 +123,14 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro return ( <>
- {(['A코너', 'B코너', 'C코너'] as Corner[]).map((corner) => { - const menu = getDiningDataForCorner(corner, filteredData); + {(['A코너', 'B코너', 'C코너'] as const).map((place) => { + const menu = getDiningDataForCorner(place, filteredData); return ( -
+
{menu && menu.changed_at !== null ? (
- {corner} + {place}
{menu.kcal} kcal @@ -147,7 +141,7 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro menu && (
- {corner} + {place}
{menu.kcal} kcal @@ -159,8 +153,8 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro
{menu && 품절} {menu && ( - handleToggleSoldoutModal(menu, corner)} + handleToggleSoldOutModal(menu)} menu={menu} /> )} @@ -189,7 +183,7 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro ))} {menu.soldout_at && ( -
+
품절된 메뉴입니다.
@@ -204,7 +198,7 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro ) : (
- {corner} + {place} 에서 제공하는 식단 정보가 없습니다.
)} @@ -224,19 +218,19 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro ); })}
- {openMenu === OPEN ? ( selectedMenu?.soldout_at === null ? (
- {selectedCorner} + {selectedPlace} 를 {' '} 품절 상태 @@ -244,10 +238,10 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro 알림이 발송되니 신중하게 설정해주세요.
- -
@@ -255,7 +249,7 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro ) : (
- {selectedCorner} + {selectedPlace} 를 {' '} 품절 취소 @@ -263,10 +257,10 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro 이미 발송된 알림은 취소되지 않습니다.
- -
@@ -281,14 +275,14 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro 인 식단이 아닙니다. - {selectedCorner} + {selectedPlace} 를 품절 상태로 설정할까요?
- -
@@ -302,20 +296,20 @@ export default function MenuCard({ selectedMenuType, selectedDate }: MenuCardPro 인 식단이 아닙니다. - {selectedCorner} + {selectedPlace} 를 품절 취소로 설정할까요?
- -
))} - + ); } diff --git a/src/pages/Coop/components/SoldoutToggle/index.tsx b/src/pages/Coop/components/SoldoutToggle/index.tsx index eebce5b..38c21b0 100644 --- a/src/pages/Coop/components/SoldoutToggle/index.tsx +++ b/src/pages/Coop/components/SoldoutToggle/index.tsx @@ -1,7 +1,7 @@ -import { Dinings } from 'models/coop'; +import { Dinings } from 'models/dinings'; import useToggleStore from 'store/useToggleStore'; -import styles from './SoldoutToggle.module.scss'; +import styles from './SoldOutToggle.module.scss'; interface SoldoutToggleProps { onClick: () => void; diff --git a/src/query/auth.ts b/src/query/auth.ts index 946e91e..b2036a9 100644 --- a/src/query/auth.ts +++ b/src/query/auth.ts @@ -1,27 +1,12 @@ import { useNavigate } from 'react-router-dom'; -import { postLogin, postLogout } from 'api/auth'; -import { LoginForm } from 'models/auth'; +import { postLogin, postRefresh, postLogout } from 'api/auth'; +import { LoginForm, RefreshParams } from 'models/auth'; import { useErrorMessageStore } from 'store/useErrorMessageStore'; import { isKoinError, sendClientError } from '@bcsdlab/koin'; import { useMutation } from '@tanstack/react-query'; -export interface ErrorResponse { - response: undefined | { - message: string; - data: { - code: number; - message: string; - violations: { - field: string; - message: string; - }[]; - } - } - message: string; -} - export const useLogin = () => { const { setLoginError, setLoginErrorStatus } = useErrorMessageStore(); const navigate = useNavigate(); @@ -29,13 +14,15 @@ export const useLogin = () => { const { mutate, error, isError, isSuccess, } = useMutation({ - mutationFn: (variables: LoginForm) => postLogin({ - email: variables.email, password: variables.password, + mutationFn: (form: LoginForm) => postLogin({ + email: form.email, password: form.password, }), - onSuccess: async (data, variables) => { - if (data.token) { sessionStorage.setItem('access_token', data.token); } + onSuccess: async (data, form) => { + if (data.token) { + sessionStorage.setItem('access_token', data.token); + } - if (variables.isAutoLogin) { + if (form.isAutoLogin) { localStorage.setItem('refresh_token', data.refresh_token); } navigate('/'); @@ -85,7 +72,6 @@ export const useLogout = () => { }, onSuccess: () => { sessionStorage.removeItem('access_token'); - sessionStorage.removeItem('user_type'); localStorage.removeItem('refresh_token'); }, onError: (err) => { diff --git a/src/query/coop.ts b/src/query/coop.ts index 094a74c..371b919 100644 --- a/src/query/coop.ts +++ b/src/query/coop.ts @@ -1,21 +1,21 @@ import { getCoopMe } from 'api/auth'; -import { updateSoldOut, uploadDiningImage } from 'api/coop'; +import { patchSoldOut, uploadDiningImage } from 'api/coop'; import { DiningImagesParams, SoldOutParams } from 'models/coop'; import { useMutation, useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; import { coopKeys } from './KeyFactory/coopKeys'; -export const useUpdateSoldOut = () => { +export const useSoldOut = () => { const queryClient = useQueryClient(); - const { mutate: updateSoldOutMutation } = useMutation({ + const { mutate: updateSoldOut } = useMutation({ mutationFn: (data: SoldOutParams) => updateSoldOut(data), onSuccess: () => { queryClient.invalidateQueries(); }, }); return { - updateSoldOutMutation, + updateSoldOut, }; };