From 8d382598dfdbe0c4a07c81a7fea648921b2537f7 Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:08:57 +0900 Subject: [PATCH 01/12] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20=EC=A0=91=EA=B7=BC?= =?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/SnackBar.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/SnackBar.tsx b/components/SnackBar.tsx index ff298e3..cbdfd9e 100644 --- a/components/SnackBar.tsx +++ b/components/SnackBar.tsx @@ -66,10 +66,13 @@ export default function SnackBar({ return (
{icon && snackbar icon}

{children}

From 3d258be402487d29ce684b5594d2782e0012b9fc Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:41:30 +0900 Subject: [PATCH 02/12] =?UTF-8?q?=F0=9F=94=A5=20=EA=B8=B0=EC=A1=B4=20useSn?= =?UTF-8?q?ackBar=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 향후 처리까지 너무 많은 기능을 담고 있어서 삭제 후 Context로 사용 --- hooks/useSanckBar.tsx | 36 ------------------------------------ hooks/useSnackBar.tsx | 36 ------------------------------------ 2 files changed, 72 deletions(-) delete mode 100644 hooks/useSanckBar.tsx delete mode 100644 hooks/useSnackBar.tsx diff --git a/hooks/useSanckBar.tsx b/hooks/useSanckBar.tsx deleted file mode 100644 index 023207f..0000000 --- a/hooks/useSanckBar.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useState } from 'react'; -import type { SnackBarProps } from '@/components/SnackBar'; - -// 스낵바 타입 정의 -type severityType = 'fail' | 'success' | 'info'; - -export default function useSnackBar() { - const [snackBarValues, setSnackBarValues] = useState({ - severity: 'success', - children: '', - open: false, - onClose: () => {}, - autoHideDuration: 3000, - }); - - // 스낵바 오픈 함수 - const snackBarOpen = ( - severity: severityType, - children: string, - done: null | (() => void) = null, // 스넥바 사라지고 난 후 실행할 함수 - autoHideDuration: number = 3000 - ) => { - setSnackBarValues({ - open: true, - severity, - children, - onClose: () => { - setSnackBarValues({ ...snackBarValues, open: false }); - if (done) done(); - }, - autoHideDuration, - }); - }; - - return { snackBarValues, snackBarOpen }; -} diff --git a/hooks/useSnackBar.tsx b/hooks/useSnackBar.tsx deleted file mode 100644 index 023207f..0000000 --- a/hooks/useSnackBar.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useState } from 'react'; -import type { SnackBarProps } from '@/components/SnackBar'; - -// 스낵바 타입 정의 -type severityType = 'fail' | 'success' | 'info'; - -export default function useSnackBar() { - const [snackBarValues, setSnackBarValues] = useState({ - severity: 'success', - children: '', - open: false, - onClose: () => {}, - autoHideDuration: 3000, - }); - - // 스낵바 오픈 함수 - const snackBarOpen = ( - severity: severityType, - children: string, - done: null | (() => void) = null, // 스넥바 사라지고 난 후 실행할 함수 - autoHideDuration: number = 3000 - ) => { - setSnackBarValues({ - open: true, - severity, - children, - onClose: () => { - setSnackBarValues({ ...snackBarValues, open: false }); - if (done) done(); - }, - autoHideDuration, - }); - }; - - return { snackBarValues, snackBarOpen }; -} From 9236d2cdd0fe376eb47c4b064fbd7af1f0062e5e Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:42:36 +0900 Subject: [PATCH 03/12] =?UTF-8?q?=E2=9C=A8=20=EA=B3=B5=ED=86=B5=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=82=AC=EC=9A=A9=EB=90=A0=20SnackBar=20state=20Co?= =?UTF-8?q?ntext?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- context/SnackBarContext.tsx | 84 +++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 context/SnackBarContext.tsx diff --git a/context/SnackBarContext.tsx b/context/SnackBarContext.tsx new file mode 100644 index 0000000..5e127e5 --- /dev/null +++ b/context/SnackBarContext.tsx @@ -0,0 +1,84 @@ +import { + createContext, + useContext, + useState, + ReactNode, + useCallback, +} from 'react'; +import SnackBar, { SnackBarProps } from '@/components/SnackBar'; + +interface SnackbarContextProps { + showSnackbar: ( + message: string, + severity?: SnackBarProps['severity'], + autoHideDuration?: number + ) => void; +} + +const SnackbarContext = createContext( + undefined +); + +export const SnackbarProvider = ({ children }: { children: ReactNode }) => { + const [snackbarState, setSnackbarState] = useState<{ + open: boolean; + message: string; + severity: SnackBarProps['severity']; + autoHideDuration?: number; + }>({ + open: false, + message: '', + severity: 'info', + autoHideDuration: 2000, + }); + + /** + * 스낵바를 띄웁니다. + * @param message 메시지 + * @param severity 알림 종류 + * @param autoHideDuration 자동 숨김 시간 + */ + const showSnackbar = useCallback( + ( + message: string, + severity: SnackBarProps['severity'] = 'info', + autoHideDuration = 2000 + ) => { + setSnackbarState({ + open: true, + message, + severity, + autoHideDuration, + }); + }, + [] + ); + + const handleClose = () => { + setSnackbarState((prev) => ({ ...prev, open: false })); + }; + + return ( + + {children} + + {snackbarState.message} + + + ); +}; + +export const useSnackbar = (): SnackbarContextProps => { + const context = useContext(SnackbarContext); + if (!context) { + throw new Error( + 'useSnackbar는 SnackbarProvider 내부에서 사용되어야 합니다.' + ); + } + return context; +}; From 387947e592a807ea8047a534610c2b3c797f6702 Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:43:03 +0900 Subject: [PATCH 04/12] =?UTF-8?q?=E2=9C=A8=20=EC=A0=84=EC=97=AD=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20context=20snackbar=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/_app.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index 8ad8a02..b14ad0d 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -8,6 +8,7 @@ import Headers from '@/components/Headers/Headers'; import { ProfileProvider } from '../context/ProfileContext'; import '@/styles/globals.css'; +import { SnackbarProvider } from 'context/SnackBarContext'; const queryClient = new QueryClient(); export default function App({ Component, pageProps }: AppProps) { @@ -26,7 +27,9 @@ export default function App({ Component, pageProps }: AppProps) { - + + + {/* NOTE : 배포 시 false로 변경 */} From caa22e125571f0bc3d9712937a6a4bf4e5ac0d4e Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:43:35 +0900 Subject: [PATCH 05/12] =?UTF-8?q?=F0=9F=94=A8=20snackbar=20=EC=98=A4?= =?UTF-8?q?=ED=94=88=EC=8B=9C=EA=B0=84=203000->2000=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 너무 긴 시간동안 열려있는듯해서 수정 --- components/SnackBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/SnackBar.tsx b/components/SnackBar.tsx index cbdfd9e..ce70594 100644 --- a/components/SnackBar.tsx +++ b/components/SnackBar.tsx @@ -43,7 +43,7 @@ export default function SnackBar({ children, open, onClose, - autoHideDuration = 3000, + autoHideDuration = 2000, }: SnackBarProps) { const { style, icon, textStyle } = severityConfig[severity ? severity : 'info']; @@ -55,7 +55,7 @@ export default function SnackBar({ if (autoHideDuration) { const timer = setTimeout(() => { setVisible(false); - setTimeout(onClose, 300); + setTimeout(onClose, 200); }, autoHideDuration); return () => clearTimeout(timer); } From a77e8a5b981b25a1fcf9fcbe68e96d021ba0e575 Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:44:35 +0900 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=94=A8=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20snackbar=20=EC=A0=84=EC=97=AD=20c?= =?UTF-8?q?ontext=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Modal/ImageUploadModal.tsx | 17 +++------- components/boards.page/BoardDetailCard.tsx | 39 +++++++--------------- pages/addboard/index.tsx | 24 ++++--------- pages/boards/[articleId].tsx | 20 ++--------- pages/boards/index.tsx | 19 ++--------- pages/login/index.tsx | 29 +++------------- pages/mypage/index.tsx | 21 +++--------- pages/signup/index.tsx | 29 +++------------- pages/updateboard/[articleId].tsx | 25 ++++---------- pages/wikilist/index.tsx | 16 ++------- 10 files changed, 52 insertions(+), 187 deletions(-) diff --git a/components/Modal/ImageUploadModal.tsx b/components/Modal/ImageUploadModal.tsx index d7538c6..b81d0d8 100644 --- a/components/Modal/ImageUploadModal.tsx +++ b/components/Modal/ImageUploadModal.tsx @@ -3,8 +3,7 @@ import React, { useEffect, useRef, useState } from 'react'; import Button from '../Button'; import Modal from './Modal'; -import SnackBar from '../SnackBar'; -import useSnackBar from '@/hooks/useSanckBar'; +import { useSnackbar } from 'context/SnackBarContext'; interface ImageUploadModalProps { imageFile?: File | null; @@ -29,20 +28,20 @@ const ImageUploadModal = ({ const [imageFile, setImageFile] = useState(initialImageFile); const [isDragging, setIsDragging] = useState(false); const [isValidFile, setIsValidFile] = useState(true); - const { snackBarValues, snackBarOpen } = useSnackBar(); + const { showSnackbar } = useSnackbar(); const validateFile = (file: File): boolean => { setIsValidFile(true); const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; if (!allowedTypes.includes(file.type)) { - snackBarOpen('fail', NOT_SVG_MESSAGE); + showSnackbar(NOT_SVG_MESSAGE, 'fail'); setIsValidFile(false); return false; } if (file.size > MAX_FILE_SIZE) { - snackBarOpen('fail', SIZE_LIMIT_MESSAGE); + showSnackbar(SIZE_LIMIT_MESSAGE, 'fail'); setIsValidFile(false); return false; } @@ -210,14 +209,6 @@ const ImageUploadModal = ({
- - - {snackBarValues.children} - ); }; diff --git a/components/boards.page/BoardDetailCard.tsx b/components/boards.page/BoardDetailCard.tsx index 0733308..fe1e36e 100644 --- a/components/boards.page/BoardDetailCard.tsx +++ b/components/boards.page/BoardDetailCard.tsx @@ -14,6 +14,7 @@ import dateConversion from '@/utils/dateConversion'; import ButtonIcon from './ButtonIcon'; import { useProfileContext } from '@/hooks/useProfileContext'; import ModalDefault from '../Modal/ModalDefault'; +import { useSnackbar } from 'context/SnackBarContext'; interface BoardDetailCard extends BoardBase { title: string; @@ -46,16 +47,13 @@ export default function BoardDetailCard({ }: BoardDetailCard & Writer) { const [isLiked, setIsLiked] = useState(initialIsLiked); const [likeCountState, setLikeCountState] = useState(likeCount); - const [snackBarOpen, setSnackBarOpen] = useState(false); - const [snackBarMessage, setSnackBarMessage] = useState(''); - const [snackStyled, setSnackStyled] = - useState(undefined); const [isModal, setIsModal] = useState(false); const isMobile = useCheckMobile(); const router = useRouter(); const { articleId } = router.query; const id = articleId as string; const { isAuthenticated } = useProfileContext(); + const { showSnackbar } = useSnackbar(); const handleModalClose = () => { setIsModal(false); @@ -74,16 +72,15 @@ export default function BoardDetailCard({ try { await instance.delete(`/articles/${id}`); setIsModal(false); - setSnackBarMessage( - '게시글이 삭제되었습니다. 게시판 메인으로 이동합니다.' + showSnackbar( + '게시글이 삭제되었습니다. 게시판 메인으로 이동합니다.', + 'success' ); - setSnackStyled('success'); - setSnackBarOpen(true); setTimeout(() => { router.push('/boards'); - }, 2500); + }, 2300); } catch (error) { - console.error('게시글을 삭제하지 못했습니다.', error); + showSnackbar('게시글을 삭제하지 못했습니다.', 'fail'); } }; @@ -97,18 +94,15 @@ export default function BoardDetailCard({ setLikeCountState((prevCount) => isLiked ? prevCount - 1 : prevCount + 1 ); - setSnackBarMessage( - isLiked ? '좋아요가 취소되었습니다.' : '좋아요가 반영되었습니다.' + showSnackbar( + isLiked ? '좋아요가 취소되었습니다.' : '좋아요가 반영되었습니다.', + 'success' ); - setSnackBarOpen(true); - setSnackStyled('success'); } catch (error) { - console.error('--- handleHeartClick:error:', error); + showSnackbar('좋아요를 반영하지 못했습니다.', 'fail'); } } else { - setSnackBarMessage('로그인 후 이용해주세요.'); - setSnackBarOpen(true); - setSnackStyled('fail'); + showSnackbar('로그인 후 이용해주세요.', 'fail'); } }; @@ -165,15 +159,6 @@ export default function BoardDetailCard({ - setSnackBarOpen(false)} - autoHideDuration={2000} - > - {snackBarMessage} - - (null); + const { showSnackbar } = useSnackbar(); const router = useRouter(); - const { snackBarValues, snackBarOpen } = useSnackBar(); const formData = new FormData(); const submitDisabled = title.length === 0 || content.length === 0; // 등록 버튼 비활성화 @@ -54,14 +53,13 @@ export default function Addboard() { return res; }, onSuccess: (data) => { - snackBarOpen( - 'success', + showSnackbar( '게시물이 등록되었습니다. 작성된 게시물로 이동 됩니다.', - async () => { - await router.push('/boards/' + data.id); - }, - 1000 + 'success' ); + setTimeout(() => { + router.push('/boards/' + data.id); + }, 2300); }, }); @@ -179,14 +177,6 @@ export default function Addboard() { onClose={handleImageModalClose} onGetImageFile={getImageFile} /> - - snackBarValues.onClose()} - > - {snackBarValues.children} - ); } diff --git a/pages/boards/[articleId].tsx b/pages/boards/[articleId].tsx index 6077102..58d946e 100644 --- a/pages/boards/[articleId].tsx +++ b/pages/boards/[articleId].tsx @@ -26,24 +26,20 @@ import { deleteComment, } from '@/services/api/commentAPI'; import { CommentsData, CommentType } from 'types/board'; -import SnackBar, { SnackBarProps } from '@/components/SnackBar'; import ModalDefault from '@/components/Modal/ModalDefault'; +import { useSnackbar } from 'context/SnackBarContext'; export default function BoardsDetails() { const [boardData, setBoardData] = useState(null); const [value, setValue] = useState(''); const [userId, setUserId] = useState(null); - const [snackBarOpen, setSnackBarOpen] = useState(false); - const [snackBarMessage, setSnackBarMessage] = useState(''); - const [snackStyled, setSnackStyled] = - useState(undefined); - const [isModal, setIsModal] = useState(false); const [commentToDelete, setCommentToDelete] = useState(null); const router = useRouter(); const { articleId } = router.query; const { isAuthenticated } = useProfileContext(); + const { showSnackbar } = useSnackbar(); const queryClient = useQueryClient(); // 유저 정보 페칭 @@ -156,9 +152,7 @@ export default function BoardsDetails() { await addCommentMutation.mutateAsync(value); setValue(''); } else { - setSnackBarMessage('로그인이 필요한 서비스입니다.'); - setSnackStyled('fail'); - setSnackBarOpen(true); + showSnackbar('로그인이 필요한 서비스입니다.', 'fail'); } } catch (error) { console.error('댓글을 등록하지 못했습니다.', error); @@ -284,14 +278,6 @@ export default function BoardsDetails() { {isFetching && !isFetchingNextPage &&

로딩 중...

} - setSnackBarOpen(false)} - autoHideDuration={2000} - > - {snackBarMessage} - import('@/components/boards.page/BoardCardList.swiper'), @@ -76,10 +76,7 @@ export default function Boards({ const [value, setValue] = useState(''); const [searchValue, setSearchValue] = useState(''); const [selectedOption, setSelectedOption] = useState('최신순'); - const [snackBarOpen, setSnackBarOpen] = useState(false); - const [snackBarMessage, setSnackBarMessage] = useState(''); - const [snackStyled, setSnackStyled] = - useState(undefined); + const { showSnackbar } = useSnackbar(); const isMobile = useCheckMobile(); const PAGE_SIZE = 10; @@ -121,9 +118,7 @@ export default function Boards({ if (isAuthenticated) { Router.push('/addboard'); } else { - setSnackStyled('fail'); - setSnackBarMessage('로그인 후 이용해주세요'); - setSnackBarOpen(true); + showSnackbar('로그인 후 이용해주세요', 'fail'); return; } }; @@ -195,14 +190,6 @@ export default function Boards({ )} - setSnackBarOpen(false)} - autoHideDuration={2000} - > - {snackBarMessage} - ); } diff --git a/pages/login/index.tsx b/pages/login/index.tsx index 5eec889..f5daad1 100644 --- a/pages/login/index.tsx +++ b/pages/login/index.tsx @@ -4,22 +4,18 @@ import React, { useState } from 'react'; import Button from '@/components/Button'; import InputField from '@/components/Input'; -import SnackBar from '@/components/SnackBar'; import { AuthAPI } from '@/services/api/auth'; +import { useSnackbar } from 'context/SnackBarContext'; function Login(): React.ReactElement { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); - const [snackbarOpen, setSnackbarOpen] = useState(false); - const [snackbarMessage, setSnackbarMessage] = useState(''); - const [snackbarSeverity, setSnackbarSeverity] = useState<'success' | 'fail'>( - 'success' - ); const [validFields, setValidFields] = useState({ email: false, password: false, }); + const { showSnackbar } = useSnackbar(); const router = useRouter(); const handleEmailChange = (e: React.ChangeEvent) => { @@ -51,9 +47,7 @@ function Login(): React.ReactElement { })) as { accessToken: string }; localStorage.setItem('accessToken', response.accessToken); - setSnackbarMessage('로그인이 완료되었습니다'); - setSnackbarSeverity('success'); - setSnackbarOpen(true); + showSnackbar('로그인이 완료되었습니다', 'success'); // 3초 후 메인 페이지로 이동 setTimeout(() => { @@ -61,13 +55,9 @@ function Login(): React.ReactElement { }, 3000); } catch (error) { if (error instanceof Error) { - setSnackbarMessage(error.message); - setSnackbarSeverity('fail'); - setSnackbarOpen(true); + showSnackbar(error.message, 'fail'); } else { - setSnackbarMessage('로그인 중 오류가 발생했습니다'); - setSnackbarSeverity('fail'); - setSnackbarOpen(true); + showSnackbar('로그인 중 오류가 발생했습니다', 'fail'); } } finally { setIsSubmitting(false); @@ -117,15 +107,6 @@ function Login(): React.ReactElement { - {snackbarOpen && ( - setSnackbarOpen(false)} - > - {snackbarMessage} - - )} ); diff --git a/pages/mypage/index.tsx b/pages/mypage/index.tsx index 8dd3a6c..bcf0f63 100644 --- a/pages/mypage/index.tsx +++ b/pages/mypage/index.tsx @@ -3,10 +3,10 @@ import React, { FormEvent, useState } from 'react'; import Button from '@/components/Button'; import InputField from '@/components/Input'; -import SnackBar from '@/components/SnackBar'; import { useValidation } from '@/hooks/useValidation'; import { AuthAPI } from '@/services/api/auth'; import { ProfileAPI } from '@/services/api/profileAPI'; +import { useSnackbar } from 'context/SnackBarContext'; function MyPage(): React.ReactElement { const [currentPassword, setCurrentPassword] = useState(''); @@ -14,10 +14,9 @@ function MyPage(): React.ReactElement { const [newPasswordConfirm, setNewPasswordConfirm] = useState(''); const [question, setQuestion] = useState(''); const [answer, setAnswer] = useState(''); - const [error, setError] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); - const [snackbarOpen, setSnackbarOpen] = useState(false); const router = useRouter(); + const { showSnackbar } = useSnackbar(); const currentPasswordValidation = useValidation({ type: 'password' }); const newPasswordValidation = useValidation({ type: 'password' }); @@ -61,7 +60,6 @@ function MyPage(): React.ReactElement { if (isSubmitting) return; setIsSubmitting(true); - setError(''); try { await AuthAPI.changePassword({ @@ -74,8 +72,7 @@ function MyPage(): React.ReactElement { await router.push('/login'); } catch (error) { if (error instanceof Error) { - setError(error.message); - setSnackbarOpen(true); + showSnackbar(error.message, 'fail'); } } finally { setIsSubmitting(false); @@ -88,7 +85,6 @@ function MyPage(): React.ReactElement { if (isSubmitting) return; setIsSubmitting(true); - setError(''); try { // 프로필 생성 API 호출 @@ -101,7 +97,7 @@ function MyPage(): React.ReactElement { await router.push(`/wiki/${code}`); } catch (error) { if (error instanceof Error) { - setError(error.message); + showSnackbar(error.message, 'fail'); } } finally { setIsSubmitting(false); @@ -213,15 +209,6 @@ function MyPage(): React.ReactElement { - {snackbarOpen && ( - setSnackbarOpen(false)} - > - {error} - - )} ); diff --git a/pages/signup/index.tsx b/pages/signup/index.tsx index 9cfc62d..17cb91b 100644 --- a/pages/signup/index.tsx +++ b/pages/signup/index.tsx @@ -4,8 +4,8 @@ import React, { useState } from 'react'; import Button from '@/components/Button'; import InputField from '@/components/Input'; -import SnackBar from '@/components/SnackBar'; import { AuthAPI } from '@/services/api/auth'; +import { useSnackbar } from 'context/SnackBarContext'; function SignUp() { const [email, setEmail] = useState(''); @@ -14,11 +14,6 @@ function SignUp() { const [isSubmitting, setIsSubmitting] = useState(false); const [isCompleted, setIsCompleted] = useState(false); const [name, setName] = useState(''); - const [snackbarOpen, setSnackbarOpen] = useState(false); - const [snackbarMessage, setSnackbarMessage] = useState(''); - const [snackbarSeverity, setSnackbarSeverity] = useState<'success' | 'fail'>( - 'success' - ); const [validFields, setValidFields] = useState({ name: false, email: false, @@ -26,6 +21,7 @@ function SignUp() { passwordConfirm: false, }); const router = useRouter(); + const { showSnackbar } = useSnackbar(); const handleEmailChange = (e: React.ChangeEvent) => { setEmail(e.target.value); @@ -68,9 +64,7 @@ function SignUp() { setIsCompleted(true); // 회원가입 성공 시 스낵바 표시 - setSnackbarMessage('회원가입이 완료되었습니다'); - setSnackbarSeverity('success'); - setSnackbarOpen(true); + showSnackbar('회원가입이 완료되었습니다', 'success'); // 3초 후 로그인 페이지로 이동 setTimeout(() => { @@ -78,13 +72,9 @@ function SignUp() { }, 3000); } catch (error) { if (error instanceof Error) { - setSnackbarMessage(error.message); - setSnackbarSeverity('fail'); - setSnackbarOpen(true); + showSnackbar(error.message, 'fail'); } else { - setSnackbarMessage('회원가입 중 오류가 발생했습니다'); - setSnackbarSeverity('fail'); - setSnackbarOpen(true); + showSnackbar('회원가입 중 오류가 발생했습니다', 'fail'); } } finally { setIsSubmitting(false); @@ -161,15 +151,6 @@ function SignUp() { - {snackbarOpen && ( - setSnackbarOpen(false)} - > - {snackbarMessage} - - )} ); diff --git a/pages/updateboard/[articleId].tsx b/pages/updateboard/[articleId].tsx index 109adba..e19a602 100644 --- a/pages/updateboard/[articleId].tsx +++ b/pages/updateboard/[articleId].tsx @@ -7,9 +7,9 @@ import Button from '@/components/Button'; import TextEditor from '@/components/TextEditor'; import { getBoardDetail, patchBoard } from '@/services/api/boardsAPI'; import { extractContent, formatDate } from '@/utils/boardHelpers'; -import SnackBar, { SnackBarProps } from '@/components/SnackBar'; import ImageUploadModal from '@/components/Modal/ImageUploadModal'; import instance from '@/lib/axios-client'; +import { useSnackbar } from 'context/SnackBarContext'; // 제목 글자수 제한 const MAX_TITLE = 30; @@ -28,13 +28,10 @@ interface ImageResponse { export default function UpdateBoard() { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); - const [snackBarOpen, setSnackBarOpen] = useState(false); - const [snackBarMessage, setSnackBarMessage] = useState(''); - const [snackStyled, setSnackStyled] = - useState(undefined); const [isModalOpen, setIsModalOpen] = useState(false); const [imageFile, setImageFile] = useState(null); const [getImage, setGetImage] = useState(null); + const { showSnackbar } = useSnackbar(); const router = useRouter(); const formData = new FormData(); const { articleId } = router.query; @@ -116,14 +113,13 @@ export default function UpdateBoard() { await patchBoard(articleId as number | string, updateData); if (typeof articleId === 'string') { - setSnackStyled('success'); - setSnackBarMessage( - '게시글이 수정되었습니다. 수정된 게시판으로 이동합니다.' + showSnackbar( + '게시글이 수정되었습니다. 수정된 게시판으로 이동합니다.', + 'success' ); - setSnackBarOpen(true); setTimeout(() => { router.push(`/boards/${articleId}`); - }, 2500); + }, 2300); } } catch { throw new Error('게시글을 수정하지 못했습니다.'); @@ -218,15 +214,6 @@ export default function UpdateBoard() { onClose={handleImageModalClose} onGetImageFile={getImageFile} /> - - setSnackBarOpen(false)} - autoHideDuration={2000} - > - {snackBarMessage} - ); diff --git a/pages/wikilist/index.tsx b/pages/wikilist/index.tsx index d604781..2b6aa83 100644 --- a/pages/wikilist/index.tsx +++ b/pages/wikilist/index.tsx @@ -9,9 +9,8 @@ import EmptyList from '@/components/EmptyList'; import ListItem from '@/components/wikiList.page/ListItem'; import Pagination from '@/components/Pagination/Pagination'; import SearchInput from '@/components/SearchInput'; -import useSnackBar from '@/hooks/useSanckBar'; -import SnackBar from '@/components/SnackBar'; import FullCoverSpinner from '@/components/FullCoverSpinner'; +import { useSnackbar } from 'context/SnackBarContext'; // 위키 목록 페이지 프로필 데이터 타입 export interface ProfileProps { @@ -61,8 +60,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { export default function WikiList() { const [searchValue, setSearchValue] = useState(''); const router = useRouter(); - const { snackBarValues, snackBarOpen } = useSnackBar(); - + const { showSnackbar } = useSnackbar(); const { page, name } = router.query; const { isPending, error, data } = useQuery({ queryKey: ['profiles', page, name], @@ -102,7 +100,7 @@ export default function WikiList() { // 목록의 위키 링크 클릭 const handleSnackBarClick = (name: string) => { - snackBarOpen('success', `${name}님 위키 링크가 복사되었습니다.`); + showSnackbar(`${name}님 위키 링크가 복사되었습니다.`, 'success'); }; useEffect(() => { @@ -166,14 +164,6 @@ export default function WikiList() { )} - - - {snackBarValues.children} - ); } From 7a89a8b95006fdc52edd8b8127e19ad77e096bfc Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Fri, 27 Dec 2024 20:47:50 +0900 Subject: [PATCH 07/12] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=20=EB=B0=80=EB=A6=AC=EC=B4=88=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/SnackBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/SnackBar.tsx b/components/SnackBar.tsx index ce70594..603d35d 100644 --- a/components/SnackBar.tsx +++ b/components/SnackBar.tsx @@ -36,7 +36,7 @@ const severityConfig = { * @param children - Snackbar에 표시할 메시지 * @param open - Snackbar가 열려있는지 여부 * @param onClose - Snackbar를 닫기 위한 함수 - * @param autoHideDuration - 자동으로 닫히는 시간 (밀리초) 기본값은 3000 + * @param autoHideDuration - 자동으로 닫히는 시간 (밀리초) 기본값은 2000 */ export default function SnackBar({ severity, From d5c6a83a3e6ac1437dc3c2a8148835841b69c1d5 Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Fri, 27 Dec 2024 21:00:23 +0900 Subject: [PATCH 08/12] =?UTF-8?q?=F0=9F=94=A5=20=EC=95=88=EC=93=B0?= =?UTF-8?q?=EB=8A=94=20import=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/boards.page/BoardDetailCard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/components/boards.page/BoardDetailCard.tsx b/components/boards.page/BoardDetailCard.tsx index fe1e36e..ba31034 100644 --- a/components/boards.page/BoardDetailCard.tsx +++ b/components/boards.page/BoardDetailCard.tsx @@ -6,7 +6,6 @@ import { BoardBase, Writer } from 'types/board'; import Button from '@/components/Button'; import EditorViewer from '@/components/EditorViewer'; import Heart from '@/components/Heart/Heart'; -import SnackBar, { SnackBarProps } from '@/components/SnackBar'; import useCheckMobile from '@/hooks/useCheckMobile'; import instance from '@/lib/axios-client'; import dateConversion from '@/utils/dateConversion'; From 3f44d33743c41b5a03ee3d5d23865153fec829a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=98=88=EB=8F=84?= Date: Fri, 27 Dec 2024 22:31:02 +0900 Subject: [PATCH 09/12] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20=EC=9C=84=ED=82=A4?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=8A=A4=EB=82=B5=EB=B0=94=20?= =?UTF-8?q?=EC=BB=A8=ED=85=8D=EC=8A=A4=ED=8A=B8=EB=A1=9C=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4=20/=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83,=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=8A=A4=EB=82=B5=EB=B0=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Headers/Login.tsx | 3 ++ components/wiki.page/ContentHeader.tsx | 36 ++-------------------- components/wiki.page/Contents.tsx | 41 +++----------------------- pages/_app.tsx | 14 ++++----- 4 files changed, 17 insertions(+), 77 deletions(-) diff --git a/components/Headers/Login.tsx b/components/Headers/Login.tsx index 59ad2a0..c8cf2d2 100644 --- a/components/Headers/Login.tsx +++ b/components/Headers/Login.tsx @@ -5,6 +5,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Profile } from 'types/profile'; import Menu from '../Menu'; +import { useSnackbar } from 'context/SnackBarContext'; interface LoginProps { isMobile: boolean; @@ -23,6 +24,7 @@ interface LoginProps { export default function Login({ isMobile, isLoggedIn, profile }: LoginProps) { const [isOpen, setIsOpen] = useState(false); const [profileMenu, setProfileMenu] = useState([]); + const { showSnackbar } = useSnackbar(); const profileImage = profile?.image ?? '/icon/icon-profile.svg'; const router = useRouter(); @@ -56,6 +58,7 @@ export default function Login({ isMobile, isLoggedIn, profile }: LoginProps) { localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); await router.push('/'); + showSnackbar('로그아웃 되었습니다.', 'fail'); } }; diff --git a/components/wiki.page/ContentHeader.tsx b/components/wiki.page/ContentHeader.tsx index 4089b56..8fac4d3 100644 --- a/components/wiki.page/ContentHeader.tsx +++ b/components/wiki.page/ContentHeader.tsx @@ -4,6 +4,7 @@ import Button from '@/components/Button'; import LinkBar from '@/components/LinkBar'; import UnsavedChangesModal from '@/components/Modal/UnsavedChangesModal'; import SnackBar from '@/components/SnackBar'; +import { useSnackbar } from 'context/SnackBarContext'; interface ContentHeaderProps { name: string; @@ -41,17 +42,7 @@ export default function ContentHeader({ saveContent, }: ContentHeaderProps) { const [isUCOpen, setIsUCOpen] = useState(false); - const [linkSnackBarState, setLinkSnackBarState] = useState<{ - open: boolean; - severity: 'fail' | 'success' | 'info'; - message: string; - autoHideDuration?: number; - }>({ - open: false, - severity: 'success', - message: '', - }); - + const { showSnackbar } = useSnackbar(); const [infoSnackBarState, setInfoSnackBarState] = useState<{ open: boolean; severity: 'fail' | 'success' | 'info'; @@ -88,25 +79,13 @@ export default function ContentHeader({ navigator.clipboard .writeText(link) .then(() => { - setLinkSnackBarState({ - open: true, - severity: 'success', - message: '클립보드에 복사되었습니다.', - autoHideDuration: 1500, - }); + showSnackbar('클립보드에 복사되었습니다.', 'success'); }) .catch(() => { alert('링크 복사에 실패했습니다.'); }); }; - const handleCloseLinkSnackBar = () => { - setLinkSnackBarState({ - ...linkSnackBarState, - open: false, - }); - }; - const handleCloseInfoSnackBar = () => { setInfoSnackBarState({ ...infoSnackBarState, @@ -151,15 +130,6 @@ export default function ContentHeader({ {infoSnackBarState.message} )} - - - {linkSnackBarState.message} - ); } diff --git a/components/wiki.page/Contents.tsx b/components/wiki.page/Contents.tsx index 6d385e0..ead4303 100644 --- a/components/wiki.page/Contents.tsx +++ b/components/wiki.page/Contents.tsx @@ -12,7 +12,7 @@ import instance from '@/lib/axios-client'; import Blank from './Blank'; import ContentHeader from './ContentHeader'; import { useProfileContext } from '@/hooks/useProfileContext'; -import SnackBar from '../SnackBar'; +import { useSnackbar } from 'context/SnackBarContext'; interface ProfileProps { profile: ProfileAnswer; @@ -26,17 +26,7 @@ export default function Contents({ profile }: ProfileProps) { const [isDMOpen, setIsDMOpen] = useState(false); const [newContent, setNewContent] = useState(profile.content || ''); const [profileData, setProfileData] = useState(profile); - const [snackBarState, setSnackBarState] = useState<{ - open: boolean; - severity: 'fail' | 'success' | 'info'; - message: string; - autoHideDuration?: number; - }>({ - open: false, - severity: 'fail', - message: '', - autoHideDuration: 1000, - }); + const { showSnackbar } = useSnackbar(); const [diffTime, setDiffTime] = useState(0); const previousContent = useRef(newContent); @@ -45,11 +35,7 @@ export default function Contents({ profile }: ProfileProps) { const handleQuizOpen = async () => { if (!isAuthenticated) { - setSnackBarState({ - open: true, - severity: 'fail', - message: '로그인 후 이용해주세요.', - }); + showSnackbar('로그인 후 이용해주세요.', 'fail'); return; } const res = await instance.get(`/profiles/${profile.code}/ping`); @@ -67,15 +53,7 @@ export default function Contents({ profile }: ProfileProps) { //퀴즈 성공 후 위키 편집모드 const handleQuizSuccess = async () => { - setSnackBarState({ - open: true, - severity: 'success', - message: '정답입니다!', - }); - - setTimeout(() => { - setSnackBarState((prev) => ({ ...prev, open: false })); - }, 1500); + showSnackbar('정답입니다!', 'success'); setIsQuizOpen(false); const accessToken = localStorage.getItem('accessToken'); @@ -182,17 +160,6 @@ export default function Contents({ profile }: ProfileProps) { diffTime={diffTime} /> - -
- setSnackBarState({ ...snackBarState, open: false })} - autoHideDuration={snackBarState.autoHideDuration} - > - {snackBarState.message} - -
wikid - - - + + + - - {/* NOTE : 배포 시 false로 변경 */} - - + {/* NOTE : 배포 시 false로 변경 */} + + + ); From e651e45186056691bf029d95d1b5a3a8ee78517a Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Sat, 28 Dec 2024 00:54:02 +0900 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=92=84=20snackbar=20=EB=AA=A8?= =?UTF-8?q?=EB=B0=94=EC=9D=BC=20=EA=B3=A0=EC=A0=95=20=EA=B0=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/SnackBar.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/SnackBar.tsx b/components/SnackBar.tsx index 603d35d..831e547 100644 --- a/components/SnackBar.tsx +++ b/components/SnackBar.tsx @@ -13,15 +13,15 @@ export interface SnackBarProps { const severityConfig = { fail: { style: - 'bg-red-50 border-red-100 fixed left-1/2 transform -translate-x-1/2 top-[120px] mx-auto mo:bottom-[80px] mo:top-auto whitespace-nowrap shadow-custom', + 'bg-red-50 border-red-100 fixed left-1/2 transform -translate-x-1/2 top-[120px] mx-auto mo:bottom-[80px] mo:top-auto shadow-custom mo:min-w-[280px]', icon: '/icon/icon-fail.svg', - textStyle: 'text-red-100 text-14sb mo:text-12sb', + textStyle: 'text-red-100 text-14sb mo:text-12sb ', }, success: { style: - 'bg-green-100 border-green-200 fixed left-1/2 transform -translate-x-1/2 top-[120px] mx-auto mo:bottom-[80px] mo:top-auto whitespace-nowrap shadow-custom', + 'bg-green-100 border-green-200 fixed left-1/2 transform -translate-x-1/2 top-[120px] mx-auto mo:bottom-[80px] mo:top-auto shadow-custom mo:min-w-[280px]', icon: '/icon/icon-success.svg', - textStyle: 'text-green-300 text-14sb mo:text-12sb', + textStyle: 'text-green-300 text-14sb mo:text-12sb ', }, info: { style: 'bg-background border-background', @@ -69,13 +69,13 @@ export default function SnackBar({ role="alert" aria-live="assertive" aria-atomic="true" - className={`rounded-custom ${style} flex items-center gap-[15px] border px-5 py-[11px] transition-opacity duration-300 mo:px-[15px] mo:py-[11px] ${ + className={`rounded-custom ${style} flex items-start gap-[15px] border px-5 py-[11px] transition-opacity duration-300 mo:px-[15px] mo:py-[11px] ${ visible ? 'opacity-100' : 'opacity-0' }`} style={{ display: open || visible ? 'flex' : 'none', zIndex: 9999 }} > {icon && snackbar icon} -

{children}

+

{children}

); } From ea10416a0da187c47c2351b3999dc6ac62d38e52 Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Sat, 28 Dec 2024 01:16:55 +0900 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=92=84=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EC=BD=98=20=EA=B0=80=EC=9A=B4=EB=8C=80=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/SnackBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/SnackBar.tsx b/components/SnackBar.tsx index 831e547..9c24544 100644 --- a/components/SnackBar.tsx +++ b/components/SnackBar.tsx @@ -69,7 +69,7 @@ export default function SnackBar({ role="alert" aria-live="assertive" aria-atomic="true" - className={`rounded-custom ${style} flex items-start gap-[15px] border px-5 py-[11px] transition-opacity duration-300 mo:px-[15px] mo:py-[11px] ${ + className={`rounded-custom ${style} flex items-center gap-[15px] border px-5 py-[11px] transition-opacity duration-300 mo:px-[15px] mo:py-[11px] ${ visible ? 'opacity-100' : 'opacity-0' }`} style={{ display: open || visible ? 'flex' : 'none', zIndex: 9999 }} From 5371c634567d90542bf48198e74a8d9d8998247d Mon Sep 17 00:00:00 2001 From: tare <59001439+junghwa1996@users.noreply.github.com> Date: Sat, 28 Dec 2024 01:25:40 +0900 Subject: [PATCH 12/12] =?UTF-8?q?=F0=9F=94=A8=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=20=ED=9B=84=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=9D=B4=EB=8F=99=20=ED=9B=84=20=EC=8A=A4=EB=82=B5?= =?UTF-8?q?=EB=B0=94=20=EB=85=B8=EC=B6=9C,=20=EB=91=90=EC=A4=84=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=95=9C=EC=A4=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/boards.page/BoardDetailCard.tsx | 9 ++------- pages/addboard/index.tsx | 9 ++------- pages/updateboard/[articleId].tsx | 9 ++------- 3 files changed, 6 insertions(+), 21 deletions(-) diff --git a/components/boards.page/BoardDetailCard.tsx b/components/boards.page/BoardDetailCard.tsx index ba31034..e50373b 100644 --- a/components/boards.page/BoardDetailCard.tsx +++ b/components/boards.page/BoardDetailCard.tsx @@ -71,13 +71,8 @@ export default function BoardDetailCard({ try { await instance.delete(`/articles/${id}`); setIsModal(false); - showSnackbar( - '게시글이 삭제되었습니다. 게시판 메인으로 이동합니다.', - 'success' - ); - setTimeout(() => { - router.push('/boards'); - }, 2300); + router.push('/boards'); + showSnackbar('게시글이 삭제되었습니다.', 'success'); } catch (error) { showSnackbar('게시글을 삭제하지 못했습니다.', 'fail'); } diff --git a/pages/addboard/index.tsx b/pages/addboard/index.tsx index cddc39d..5c33b63 100644 --- a/pages/addboard/index.tsx +++ b/pages/addboard/index.tsx @@ -53,13 +53,8 @@ export default function Addboard() { return res; }, onSuccess: (data) => { - showSnackbar( - '게시물이 등록되었습니다. 작성된 게시물로 이동 됩니다.', - 'success' - ); - setTimeout(() => { - router.push('/boards/' + data.id); - }, 2300); + router.push('/boards/' + data.id); + showSnackbar('게시물이 등록되었습니다.', 'success'); }, }); diff --git a/pages/updateboard/[articleId].tsx b/pages/updateboard/[articleId].tsx index e19a602..d67cd2a 100644 --- a/pages/updateboard/[articleId].tsx +++ b/pages/updateboard/[articleId].tsx @@ -113,13 +113,8 @@ export default function UpdateBoard() { await patchBoard(articleId as number | string, updateData); if (typeof articleId === 'string') { - showSnackbar( - '게시글이 수정되었습니다. 수정된 게시판으로 이동합니다.', - 'success' - ); - setTimeout(() => { - router.push(`/boards/${articleId}`); - }, 2300); + router.push(`/boards/${articleId}`); + showSnackbar('게시글이 수정되었습니다.', 'success'); } } catch { throw new Error('게시글을 수정하지 못했습니다.');