From b2ceeadcef3ed86bfbda79528fd472c7efc7e813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=98=81=EA=B8=B8/KIM=20YOUNG=20GIL?= <80146176+Gilpop8663@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:52:40 +0900 Subject: [PATCH] =?UTF-8?q?=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=EC=97=90?= =?UTF-8?q?=20=ED=86=A0=EC=8A=A4=ED=8A=B8=EA=B0=80=20=EA=B0=80=EB=A0=A4?= =?UTF-8?q?=EC=A7=80=EB=8D=98=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20?= =?UTF-8?q?(feat.createPortal)=20=20(#841)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: (#738) createPortal을 이용하여 Drawer가 켜졌을 때와 닫힐 때 토스트가 생성될 위치를 변경 Drawer가 켜진다면 dialog 태그 내에서 생성되고, 닫히거나 열리지 않았다면 toast-content라는 별개의 div에서 생성되도록 구현 * style: 모바일에서 토스트가 중앙으로 오도록 스타일 변경 * feat: (#738) 토스트를 보여주는 엘리먼트 아이디가 중복되어서 각각 설정하도록 useDialog, Dialog 코드 변경 * refactor: (#738) 없어도 무방한 async/await 코드 삭제 및 주석되어 있던 코드 복구 * chore: (#738) 모킹 데이터로 실험하던 코드 복구 * refactor: (#738) Drawer와 Toast의 연관 관계가 없도록 변경 --- frontend/public/index.html | 1 + .../src/components/ToastContainer/style.ts | 7 +++-- .../Drawer/DrawerToastWrapper/index.tsx | 14 +++++++++ .../common/Drawer/DrawerToastWrapper/style.ts | 8 +++++ .../src/components/common/Drawer/index.tsx | 8 +---- frontend/src/hooks/context/toast.tsx | 31 +++++++++++++------ .../src/hooks/query/alarm/useReadAlarm.ts | 2 +- .../src/hooks/query/report/useReportAction.ts | 3 +- .../hooks/query/report/useReportContent.ts | 2 +- .../hooks/query/useReportApproveResult.tsx | 10 +++--- .../hooks/query/user/useReadLatestAlarm.ts | 2 +- .../src/hooks/query/user/useUpdateUserInfo.ts | 2 +- .../query/user/useWithdrawalMembership.ts | 2 +- frontend/src/hooks/useDrawer.tsx | 2 +- frontend/src/pages/HomePage/index.tsx | 28 ++++++++++++++--- frontend/src/types/toast.ts | 6 ++++ 16 files changed, 92 insertions(+), 36 deletions(-) create mode 100644 frontend/src/components/common/Drawer/DrawerToastWrapper/index.tsx create mode 100644 frontend/src/components/common/Drawer/DrawerToastWrapper/style.ts create mode 100644 frontend/src/types/toast.ts diff --git a/frontend/public/index.html b/frontend/public/index.html index 52646e723..b469e19bc 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -54,5 +54,6 @@
+
diff --git a/frontend/src/components/ToastContainer/style.ts b/frontend/src/components/ToastContainer/style.ts index b891f6fee..b742fec60 100644 --- a/frontend/src/components/ToastContainer/style.ts +++ b/frontend/src/components/ToastContainer/style.ts @@ -4,11 +4,14 @@ import { theme } from '@styles/theme'; // 컨테이너를 가로중앙에 위치시키기 위해 left = width * 1/2로 설정 export const Container = styled.div` - width: 80%; + width: 100vw; position: fixed; bottom: 20vh; - left: 10%; + left: auto; + right: auto; + + padding: 0 10%; z-index: ${theme.zIndex.toast}; diff --git a/frontend/src/components/common/Drawer/DrawerToastWrapper/index.tsx b/frontend/src/components/common/Drawer/DrawerToastWrapper/index.tsx new file mode 100644 index 000000000..f33436803 --- /dev/null +++ b/frontend/src/components/common/Drawer/DrawerToastWrapper/index.tsx @@ -0,0 +1,14 @@ +import { HTMLAttributes } from 'react'; + +import { ToastContentId } from '@type/toast'; + +import * as S from './style'; + +interface DrawerToastWrapperProps extends HTMLAttributes { + id: ToastContentId; + placement: 'left' | 'right'; +} + +export default function DrawerToastWrapper({ placement, ...rest }: DrawerToastWrapperProps) { + return ; +} diff --git a/frontend/src/components/common/Drawer/DrawerToastWrapper/style.ts b/frontend/src/components/common/Drawer/DrawerToastWrapper/style.ts new file mode 100644 index 000000000..2053a2763 --- /dev/null +++ b/frontend/src/components/common/Drawer/DrawerToastWrapper/style.ts @@ -0,0 +1,8 @@ +import { styled } from 'styled-components'; + +export const ToastWrapper = styled.div<{ $placement: 'left' | 'right' }>` + position: absolute; + width: 100vw; + left: ${({ $placement }) => ($placement === 'left' ? '0' : 'auto')}; + right: ${({ $placement }) => ($placement === 'right' ? '0' : 'auto')}; +`; diff --git a/frontend/src/components/common/Drawer/index.tsx b/frontend/src/components/common/Drawer/index.tsx index a5f54d8d3..b960b7909 100644 --- a/frontend/src/components/common/Drawer/index.tsx +++ b/frontend/src/components/common/Drawer/index.tsx @@ -1,10 +1,4 @@ -import React, { - ForwardedRef, - KeyboardEvent, - MouseEvent, - PropsWithChildren, - forwardRef, -} from 'react'; +import { ForwardedRef, KeyboardEvent, MouseEvent, PropsWithChildren, forwardRef } from 'react'; import * as S from './style'; diff --git a/frontend/src/hooks/context/toast.tsx b/frontend/src/hooks/context/toast.tsx index 4dc3ebdd4..6c5fd1b15 100644 --- a/frontend/src/hooks/context/toast.tsx +++ b/frontend/src/hooks/context/toast.tsx @@ -1,4 +1,7 @@ -import { PropsWithChildren, createContext, useEffect, useRef, useState } from 'react'; +import { PropsWithChildren, createContext, useCallback, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; + +import { ToastContentId } from '@type/toast'; import ToastContainer from '@components/ToastContainer'; @@ -11,17 +14,32 @@ export interface ToastInfo { interface ToastContextProps { addMessage: (message: string) => void; + setElementId: (id: ToastContentId) => void; } export const ToastContext = createContext({ addMessage: (message: string) => {}, + setElementId: () => {}, }); export default function ToastProvider({ children }: PropsWithChildren) { const [toastList, setToastList] = useState([]); + const [toastElementId, setToastElementId] = useState('toast-content'); + const toastContentEl = document.getElementById(toastElementId); const timeId = useRef(null); + const addMessage = (message: string) => { + if (toastList.find(toast => toast.text === message)) return; + + const id = Date.now(); + setToastList(toastList => [...toastList, { id, text: message }]); + }; + + const setElementId = useCallback((id: ToastContentId) => { + setToastElementId(id); + }, []); + useEffect(() => { if (timeId.current) window.clearTimeout(timeId.current); @@ -34,16 +52,9 @@ export default function ToastProvider({ children }: PropsWithChildren) { } }, [toastList]); - const addMessage = (message: string) => { - if (toastList.find(toast => toast.text === message)) return; - - const id = Date.now(); - setToastList(toastList => [...toastList, { id, text: message }]); - }; - return ( - - + + {toastContentEl && createPortal(, toastContentEl)} {children} ); diff --git a/frontend/src/hooks/query/alarm/useReadAlarm.ts b/frontend/src/hooks/query/alarm/useReadAlarm.ts index 7abbac228..a187080e3 100644 --- a/frontend/src/hooks/query/alarm/useReadAlarm.ts +++ b/frontend/src/hooks/query/alarm/useReadAlarm.ts @@ -11,7 +11,7 @@ export const useReadAlarm = (type: AlarmType) => { const alarmQueryKey = type === 'CONTENT' ? QUERY_KEY.ALARM_CONTENT : QUERY_KEY.ALARM_REPORT; const { mutate } = useMutation({ - mutationFn: async (alarmId: number) => await readAlarm(alarmId, type), + mutationFn: (alarmId: number) => readAlarm(alarmId, type), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [alarmQueryKey] }); }, diff --git a/frontend/src/hooks/query/report/useReportAction.ts b/frontend/src/hooks/query/report/useReportAction.ts index 8537cf518..8cd311e48 100644 --- a/frontend/src/hooks/query/report/useReportAction.ts +++ b/frontend/src/hooks/query/report/useReportAction.ts @@ -15,8 +15,7 @@ export const useReportAction = () => { const { addMessage } = useContext(ToastContext); const { mutate, isLoading, isSuccess, isError, error } = useMutation({ - mutationFn: async (reportActionData: ReportActionRequest) => - await reportAction(reportActionData), + mutationFn: (reportActionData: ReportActionRequest) => reportAction(reportActionData), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEY.REPORT] }); }, diff --git a/frontend/src/hooks/query/report/useReportContent.ts b/frontend/src/hooks/query/report/useReportContent.ts index 1d17cd164..2d9fb7245 100644 --- a/frontend/src/hooks/query/report/useReportContent.ts +++ b/frontend/src/hooks/query/report/useReportContent.ts @@ -15,7 +15,7 @@ export const useReportContent = () => { const { addMessage } = useContext(ToastContext); const { mutate, isLoading } = useMutation({ - mutationFn: async (reportData: ReportRequest) => await reportContent(reportData), + mutationFn: (reportData: ReportRequest) => reportContent(reportData), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEY.REPORT] }); addMessage('신고를 완료하였습니다.'); diff --git a/frontend/src/hooks/query/useReportApproveResult.tsx b/frontend/src/hooks/query/useReportApproveResult.tsx index b56e6c097..7b809a187 100644 --- a/frontend/src/hooks/query/useReportApproveResult.tsx +++ b/frontend/src/hooks/query/useReportApproveResult.tsx @@ -14,11 +14,11 @@ export const useReportApproveResult = (reportId: number) => { suspense: true, retry: (failCount, error) => { - // const fetchError = error as Error; - // const status = JSON.parse(fetchError.message).status; - // if (status === 404) { - // return false; - // } + const fetchError = error as Error; + const status = JSON.parse(fetchError.message).status; + if (status === 404) { + return false; + } return failCount <= 3; }, } diff --git a/frontend/src/hooks/query/user/useReadLatestAlarm.ts b/frontend/src/hooks/query/user/useReadLatestAlarm.ts index 0e06dceb7..2b890ef5c 100644 --- a/frontend/src/hooks/query/user/useReadLatestAlarm.ts +++ b/frontend/src/hooks/query/user/useReadLatestAlarm.ts @@ -8,7 +8,7 @@ export const useReadLatestAlarm = () => { const queryClient = useQueryClient(); const { mutate } = useMutation({ - mutationFn: async () => await readLatestAlarm(), + mutationFn: readLatestAlarm, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEY.USER_INFO] }); }, diff --git a/frontend/src/hooks/query/user/useUpdateUserInfo.ts b/frontend/src/hooks/query/user/useUpdateUserInfo.ts index 9ad57b448..91b092c4f 100644 --- a/frontend/src/hooks/query/user/useUpdateUserInfo.ts +++ b/frontend/src/hooks/query/user/useUpdateUserInfo.ts @@ -14,7 +14,7 @@ export const useUpdateUserInfo = () => { const LOGGED_IN = true; const { mutate, isLoading, isSuccess, isError, error } = useMutation({ - mutationFn: async (userInfo: UpdateUserInfoRequest) => await updateUserInfo(userInfo), + mutationFn: (userInfo: UpdateUserInfoRequest) => updateUserInfo(userInfo), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEY.USER_INFO, LOGGED_IN] }); diff --git a/frontend/src/hooks/query/user/useWithdrawalMembership.ts b/frontend/src/hooks/query/user/useWithdrawalMembership.ts index 2d78ef99d..136f75070 100644 --- a/frontend/src/hooks/query/user/useWithdrawalMembership.ts +++ b/frontend/src/hooks/query/user/useWithdrawalMembership.ts @@ -14,7 +14,7 @@ export const useWithdrawalMembership = () => { const LOGGED_IN = true; const { mutate, isLoading, isSuccess, isError, error } = useMutation({ - mutationFn: async () => await withdrawalMembership(), + mutationFn: withdrawalMembership, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [QUERY_KEY.USER_INFO, LOGGED_IN] }); addMessage('회원 탈퇴를 완료했습니다.'); diff --git a/frontend/src/hooks/useDrawer.tsx b/frontend/src/hooks/useDrawer.tsx index ad40fc2f2..cdf4e6a24 100644 --- a/frontend/src/hooks/useDrawer.tsx +++ b/frontend/src/hooks/useDrawer.tsx @@ -28,7 +28,7 @@ export const useDrawer = (placement: 'left' | 'right') => { drawerRef.current.style.transform = placement === 'left' ? 'translateX(-100%)' : 'translateX(100%)'; - }, []); + }, [placement]); return { drawerRef, openDrawer, closeDrawer }; }; diff --git a/frontend/src/pages/HomePage/index.tsx b/frontend/src/pages/HomePage/index.tsx index b4c2f69dc..57bcfb40d 100644 --- a/frontend/src/pages/HomePage/index.tsx +++ b/frontend/src/pages/HomePage/index.tsx @@ -13,6 +13,7 @@ import AddButton from '@components/common/AddButton'; import AppInstallPrompt from '@components/common/AppInstallPrompt'; import Dashboard from '@components/common/Dashboard'; import Drawer from '@components/common/Drawer'; +import DrawerToastWrapper from '@components/common/Drawer/DrawerToastWrapper'; import Layout from '@components/common/Layout'; import NarrowMainHeader from '@components/common/NarrowMainHeader'; import UpButton from '@components/common/UpButton'; @@ -43,6 +44,7 @@ export default function HomePage() { closeDrawer: closeAlarmDrawer, } = useDrawer('right'); + const { setElementId } = useContext(ToastContext); const { isBannerOpen, closeBanner } = useBannerToggle(); const { addMessage } = useContext(ToastContext); const loggedInfo = useContext(AuthContext).loggedInfo; @@ -53,15 +55,31 @@ export default function HomePage() { if (!loggedInfo.isLoggedIn) return addMessage('알림은 로그인 후 이용할 수 있습니다.'); openAlarmDrawer(); + setElementId('drawer-alarm-toast-content'); mutate(); }; + const handleAlarmDrawerClose = () => { + closeAlarmDrawer(); + setElementId('toast-content'); + }; + + const handleCategoryDrawerOpen = () => { + openCategoryDrawer(); + setElementId('drawer-category-toast-content'); + }; + + const handleCategoryDrawerClose = () => { + closeCategoryDrawer(); + setElementId('toast-content'); + }; + return ( @@ -77,21 +95,23 @@ export default function HomePage() { )} + + {loggedInfo.isLoggedIn && ( - + )} diff --git a/frontend/src/types/toast.ts b/frontend/src/types/toast.ts new file mode 100644 index 000000000..355a26266 --- /dev/null +++ b/frontend/src/types/toast.ts @@ -0,0 +1,6 @@ +export type ToastContentId = + | 'toast-content' + | 'drawer-category-toast-content' + | 'drawer-alarm-toast-content'; + +export type DrawerToastContentId = Exclude;