diff --git a/components/Headers/Login.tsx b/components/Headers/Login.tsx index 59ad2a0b..c8cf2d2d 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/Modal/ImageUploadModal.tsx b/components/Modal/ImageUploadModal.tsx index d7538c6e..b81d0d80 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/SnackBar.tsx b/components/SnackBar.tsx index ff298e3e..9c24544c 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', @@ -36,14 +36,14 @@ const severityConfig = { * @param children - Snackbar에 표시할 메시지 * @param open - Snackbar가 열려있는지 여부 * @param onClose - Snackbar를 닫기 위한 함수 - * @param autoHideDuration - 자동으로 닫히는 시간 (밀리초) 기본값은 3000 + * @param autoHideDuration - 자동으로 닫히는 시간 (밀리초) 기본값은 2000 */ export default function SnackBar({ severity, 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); } @@ -66,13 +66,16 @@ export default function SnackBar({ return (
{icon && snackbar icon} -

{children}

+

{children}

); } diff --git a/components/boards.page/BoardDetailCard.tsx b/components/boards.page/BoardDetailCard.tsx index 85a19d26..479178fa 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'; @@ -14,6 +13,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,10 +46,6 @@ 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 [isUpdate, setIsUpdate] = useState(false); const isMobile = useCheckMobile(); @@ -57,6 +53,7 @@ export default function BoardDetailCard({ const { articleId } = router.query; const id = articleId as string; const { isAuthenticated } = useProfileContext(); + const { showSnackbar } = useSnackbar(); useEffect(() => { if (createdAt !== updatedAt) { @@ -81,16 +78,10 @@ export default function BoardDetailCard({ try { await instance.delete(`/articles/${id}`); setIsModal(false); - setSnackBarMessage( - '게시글이 삭제되었습니다. 게시판 메인으로 이동합니다.' - ); - setSnackStyled('success'); - setSnackBarOpen(true); - setTimeout(() => { - router.push('/boards'); - }, 2500); + router.push('/boards'); + showSnackbar('게시글이 삭제되었습니다.', 'success'); } catch (error) { - console.error('게시글을 삭제하지 못했습니다.', error); + showSnackbar('게시글을 삭제하지 못했습니다.', 'fail'); } }; @@ -104,18 +95,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'); } }; @@ -172,15 +160,6 @@ export default function BoardDetailCard({ - setSnackBarOpen(false)} - autoHideDuration={2000} - > - {snackBarMessage} - - ({ - 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 6d385e0c..ead43039 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} - -
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; +}; diff --git a/hooks/useSanckBar.tsx b/hooks/useSanckBar.tsx deleted file mode 100644 index 023207fb..00000000 --- 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 023207fb..00000000 --- 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 }; -} diff --git a/pages/_app.tsx b/pages/_app.tsx index 8ad8a021..1e2e3468 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) { @@ -24,12 +25,14 @@ export default function App({ Component, pageProps }: AppProps) { wikid - - - - {/* NOTE : 배포 시 false로 변경 */} - - + + + + + {/* NOTE : 배포 시 false로 변경 */} + + + ); diff --git a/pages/addboard/index.tsx b/pages/addboard/index.tsx index 201ce116..06a0a171 100644 --- a/pages/addboard/index.tsx +++ b/pages/addboard/index.tsx @@ -3,15 +3,14 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; import { extractContent, formatDate } from '@/utils/boardHelpers'; -import useSnackBar from '@/hooks/useSnackBar'; import { createArticle } from '@/services/api/boardsAPI'; import { createImageUpload } from '@/services/api/imageAPI'; import Button from '@/components/Button'; import ImageUploadModal from '@/components/Modal/ImageUploadModal'; -import SnackBar from '@/components/SnackBar'; import TextEditor from '@/components/TextEditor'; import { useMutation } from '@tanstack/react-query'; +import { useSnackbar } from 'context/SnackBarContext'; // 제목 글자수 제한 const MAX_TITLE = 30; @@ -25,9 +24,9 @@ export default function Addboard() { const [isModalOpen, setIsModalOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const [imageFile, setImageFile] = useState(null); + const { showSnackbar } = useSnackbar(); const router = useRouter(); - const { snackBarValues, snackBarOpen } = useSnackBar(); const formData = new FormData(); const submitDisabled = title.length === 0 || content.length === 0; // 등록 버튼 비활성화 @@ -55,15 +54,8 @@ export default function Addboard() { return res; }, onSuccess: (data) => { - // console.log('--- 게시물 등록 성공:', data); - snackBarOpen( - 'success', - '게시물이 등록되었습니다. 작성된 게시물로 이동 됩니다.', - async () => { - await router.push('/boards/' + data.id); - }, - 500 - ); + router.push('/boards/' + data.id); + showSnackbar('게시물이 등록되었습니다.', 'success'); }, onError: (err) => { console.error('--- 게시물 등록 에러:', err); @@ -186,14 +178,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 ac85c057..e78a8cb7 100644 --- a/pages/boards/[articleId].tsx +++ b/pages/boards/[articleId].tsx @@ -26,8 +26,8 @@ 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'; import CommentEmpty from '@/components/boards.page/CommentEmpty'; export default function BoardsDetails() { @@ -35,16 +35,12 @@ export default function BoardsDetails() { 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(); // 유저 정보 페칭 @@ -157,9 +153,7 @@ export default function BoardsDetails() { await addCommentMutation.mutateAsync(value); setValue(''); } else { - setSnackBarMessage('로그인이 필요한 서비스입니다.'); - setSnackStyled('fail'); - setSnackBarOpen(true); + showSnackbar('로그인이 필요한 서비스입니다.', 'fail'); } } catch (error) { console.error('댓글을 등록하지 못했습니다.', error); @@ -285,14 +279,6 @@ export default function BoardsDetails() { {isFetching && !isFetchingNextPage &&

로딩 중...

} - setSnackBarOpen(false)} - autoHideDuration={2000} - > - {snackBarMessage} - (undefined); + const { showSnackbar } = useSnackbar(); const [isLoading, setIsLoading] = useState(false); const isMobile = useCheckMobile(); @@ -126,9 +123,7 @@ export default function Boards({ if (isAuthenticated) { Router.push('/addboard'); } else { - setSnackStyled('fail'); - setSnackBarMessage('로그인 후 이용해주세요'); - setSnackBarOpen(true); + showSnackbar('로그인 후 이용해주세요', 'fail'); return; } }; @@ -208,14 +203,6 @@ export default function Boards({ )} - setSnackBarOpen(false)} - autoHideDuration={2000} - > - {snackBarMessage} - ); } diff --git a/pages/login/index.tsx b/pages/login/index.tsx index 5eec8892..f5daad15 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 7f2d2eba..ddaf4f27 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'; import { AxiosError } from 'axios'; function MyPage(): React.ReactElement { @@ -15,11 +15,10 @@ function MyPage(): React.ReactElement { const [newPasswordConfirm, setNewPasswordConfirm] = useState(''); const [question, setQuestion] = useState(''); const [answer, setAnswer] = useState(''); - const [error, setError] = useState(null); const [isPasswordSubmitting, setIsPasswordSubmitting] = useState(false); const [isWikiSubmitting, setIsWikiSubmitting] = useState(false); - const [snackbarOpen, setSnackbarOpen] = useState(false); const router = useRouter(); + const { showSnackbar } = useSnackbar(); const currentPasswordValidation = useValidation({ type: 'password' }); const newPasswordValidation = useValidation({ type: 'password' }); @@ -63,7 +62,6 @@ function MyPage(): React.ReactElement { if (isPasswordSubmitting) return; setIsPasswordSubmitting(true); - setError(null); try { await AuthAPI.changePassword({ @@ -72,7 +70,7 @@ function MyPage(): React.ReactElement { newPasswordConfirm, }); - setSnackbarOpen(true); + showSnackbar('비밀번호가 성공적으로 변경되었습니다.', 'success'); setCurrentPassword(''); setNewPassword(''); setNewPasswordConfirm(''); @@ -82,11 +80,9 @@ function MyPage(): React.ReactElement { }, 2000); } catch (error) { if (error instanceof AxiosError) { - setError(error); - setSnackbarOpen(true); + showSnackbar(error.message, 'fail'); } else if (error instanceof Error) { - setError(error as AxiosError); - setSnackbarOpen(true); + showSnackbar(error.message, 'fail'); } } finally { setIsPasswordSubmitting(false); @@ -100,7 +96,6 @@ function MyPage(): React.ReactElement { if (isWikiSubmitting) return; setIsWikiSubmitting(true); - setError(null); try { // 프로필 생성 API 호출 @@ -114,8 +109,7 @@ function MyPage(): React.ReactElement { } catch (error) { if (error instanceof AxiosError) { const errorResponse = error.response?.data; - setError(error); - setSnackbarOpen(true); + showSnackbar(error.message, 'fail'); // 이미 프로필이 존재하는 경우 if (errorResponse?.code) { @@ -127,8 +121,7 @@ function MyPage(): React.ReactElement { } } // 기타 에러 처리 - setError(error as AxiosError); - setSnackbarOpen(true); + showSnackbar('알 수 없는 에러가 발생했습니다.', 'fail'); } finally { // 제출 상태 해제 setIsWikiSubmitting(false); @@ -249,14 +242,6 @@ function MyPage(): React.ReactElement { - setSnackbarOpen(false)} - severity={error ? 'fail' : 'success'} - autoHideDuration={2000} - > - {error?.message || '비밀번호가 성공적으로 변경되었습니다.'} - ); } diff --git a/pages/signup/index.tsx b/pages/signup/index.tsx index 9cfc62df..17cb91bf 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 109adbaf..d67cd2a1 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,8 @@ export default function UpdateBoard() { await patchBoard(articleId as number | string, updateData); if (typeof articleId === 'string') { - setSnackStyled('success'); - setSnackBarMessage( - '게시글이 수정되었습니다. 수정된 게시판으로 이동합니다.' - ); - setSnackBarOpen(true); - setTimeout(() => { - router.push(`/boards/${articleId}`); - }, 2500); + router.push(`/boards/${articleId}`); + showSnackbar('게시글이 수정되었습니다.', 'success'); } } catch { throw new Error('게시글을 수정하지 못했습니다.'); @@ -218,15 +209,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 d604781e..2b6aa837 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} - ); } diff --git a/services/api/auth.ts b/services/api/auth.ts index 4bd7ca47..38e0b37e 100644 --- a/services/api/auth.ts +++ b/services/api/auth.ts @@ -48,7 +48,9 @@ export const AuthAPI = { } // 500 에러 (서버 내부 오류) if (error.response?.status === 500) { - throw new Error('서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'); + throw new Error( + '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.' + ); } } // 기타 예상치 못한 에러