diff --git a/src/App.tsx b/src/App.tsx index f5034479..0fc93f11 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,7 @@ import React, { useEffect } from 'react'; import { LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import 'dayjs/locale/ko'; -import { Route, Routes } from 'react-router-dom'; +import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'; import MyPage from '@/pages/MyPage/MyPage'; import SearchGamePage from '@/pages/SearchResultsPage/SearchGamePage'; import SearchTalkPickPage from '@/pages/SearchResultsPage/SearchTalkPickPage'; @@ -23,6 +23,9 @@ import SignUpPage from './pages/SignUpPage/SignUpPage'; import BalanceGamePage from './pages/BalanceGamePage/BalanceGamePage'; import BalanceGameMobilePage from './pages/mobile/BalanceGameMobilePage/BalanceGameMobilePage'; import BalanceGameCreationPage from './pages/BalanceGameCreationPage/BalanceGameCreationPage'; +import { useNewSelector } from './store'; +import { selectAccessToken } from './store/auth'; +import useIsMobile from './hooks/common/useIsMobile'; // import NotAuthRoutes from './components/Routes/NotAuthRoutes'; // import { useMemberQuery } from './hooks/api/member/useMemberQuery'; // import { useParseJwt } from './hooks/common/useParseJwt'; @@ -40,30 +43,22 @@ import BalanceGameCreationPage from './pages/BalanceGameCreationPage/BalanceGame // import PostPage from './pages/PostPage/PostPage'; // import SearchResultPage from './pages/SearchResultPage/SearchResultPage'; // import SignUpPage from './pages/SignUpPage/SignUpPage'; -import { getCookie } from './utils/cookie'; -import { axiosInstance } from './api/interceptor'; -import { useNewDispatch } from './store'; -import { tokenActions } from './store/auth'; -import useIsMobile from './hooks/common/useIsMobile'; const App: React.FC = () => { - const dispatch = useNewDispatch(); + const location = useLocation(); + const navigate = useNavigate(); + const isMobile = useIsMobile(); + const isLoggedIn = !!useNewSelector(selectAccessToken); useEffect(() => { - const token = getCookie('accessToken'); - if (token) { - localStorage.setItem('accessToken', token); - localStorage.setItem('refreshToken', 'refreshToken'); + const searchParams = new URLSearchParams(location.search); + const status = searchParams.get('status'); - dispatch(tokenActions.setToken(token)); - axiosInstance.defaults.headers.Authorization = `Bearer ${token}`; + if (status === 'already_registered') { + navigate(`/${PATH.LOGIN}`, { state: { status } }); } - }, [dispatch]); - - // const accessToken = useNewSelector(selectAccessToken); - // const { member } = useMemberQuery(useParseJwt(accessToken).memberId); - const isLoggedIn = !!localStorage.getItem('accessToken'); + }, [location.search, navigate]); useTokenRefresh(); return ( diff --git a/src/api/interceptor.ts b/src/api/interceptor.ts index 685bdb29..6291a9f3 100644 --- a/src/api/interceptor.ts +++ b/src/api/interceptor.ts @@ -1,6 +1,11 @@ -import store from '@/store'; +/* eslint-disable no-alert */ import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios'; -import { AXIOS, END_POINT } from '../constants/api'; +import { PATH } from '@/constants/path'; +import { NOTICE } from '@/constants/message'; +import store from '@/store'; +import { ServerResponse } from '@/types/api'; +import { tokenActions } from '@/store/auth'; +import { AXIOS, END_POINT, HTTP_STATUS_CODE } from '../constants/api'; import { HTTPError } from './HttpError'; export interface AxiosErrorResponse { @@ -13,8 +18,6 @@ const baseURL = process.env.NODE_ENV === 'production' ? process.env.API_URL : '/api'; export const axiosInstance = axios.create({ - // baseURL: process.env.API_URL, - // baseURL: '/api', baseURL, headers: { 'Content-Type': 'application/json', @@ -23,27 +26,36 @@ export const axiosInstance = axios.create({ timeout: AXIOS.TIMEOUT, }); +export const axiosRefreshInstance = axios.create({ + baseURL, + headers: { + 'Content-Type': 'application/json', + }, + withCredentials: true, + timeout: AXIOS.TIMEOUT, +}); + +export const getRefreshToken = async () => { + const { data } = await axiosRefreshInstance.get( + `${END_POINT.REFRESH}`, + ); + return data; +}; + // request interceptor (before request) axiosInstance.interceptors.request.use( (config: InternalAxiosRequestConfig) => { if (config.headers.Authorization) return config; - const { accessToken } = store.getState().token; const newConfig = { ...config }; + const { accessToken } = store.getState().token; - if (newConfig.url === END_POINT.FILE_UPLOAD) { - newConfig.headers['Content-Type'] = 'multipart/form-data'; - } if (accessToken) { newConfig.headers.Authorization = `Bearer ${accessToken}`; } - - // console.log('요청 전 config', newConfig); return newConfig; }, (error: AxiosError) => { - // console.log('요청 전 config 에러'); - return Promise.reject(error); }, ); @@ -51,33 +63,33 @@ axiosInstance.interceptors.request.use( // response interceptor (after request) axiosInstance.interceptors.response.use( (response) => { - // console.log('요청 후 response'); return response; }, - (error: AxiosError) => { - // console.log('요청 후 response 에러'); + async (error: AxiosError) => { const originalRequest = error.config; if (!error.response || !originalRequest) throw error; const { data, status } = error.response; - // const refreshToken = localStorage.getItem('rtk'); - - // if (refreshToken) { - // if (status === HTTP_STATUS_CODE.UNAUTHORIZED) { - // const accessToken = getRefreshToken(); - // console.log('new accessToken: ', accessToken); - // localStorage.setItem('accessToken', accessToken); - // store.dispatch({ type: 'token/setAccessToken', payload: accessToken }); - // originalRequest.headers.Authorization = `Bearer ${accessToken}`; - // return axiosInstance(originalRequest); - // console.log('토큰 재발급'); - // } - // if (status === HTTP_STATUS_CODE.BAD_REQUEST) { - // localStorage.removeItem('accessToken'); - // localStorage.removeItem('rtk'); - // window.location.href = '/'; - // } - // } + + if (status === HTTP_STATUS_CODE.UNAUTHORIZED) { + try { + const newAccessToken = await getRefreshToken(); + + store.dispatch(tokenActions.setToken(newAccessToken)); + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + + return await axiosInstance(originalRequest); + } catch (err) { + delete axiosInstance.defaults.headers.Authorization; + store.dispatch(tokenActions.deleteToken()); + + alert(NOTICE.LOGIN.EXPIRED); + window.location.href = `/${PATH.LOGIN}`; + + return Promise.reject(err); + } + } + throw new HTTPError(status, data.httpStatus, data.message); }, ); diff --git a/src/api/member.ts b/src/api/member.ts index 82e0769e..bb9e58f8 100644 --- a/src/api/member.ts +++ b/src/api/member.ts @@ -9,10 +9,8 @@ import { } from '@/types/member'; import { axiosInstance } from './interceptor'; -export const getMember = async (memberId: number) => { - const { data } = await axiosInstance.get( - `${END_POINT.MEMBER(memberId)}`, - ); +export const getMember = async () => { + const { data } = await axiosInstance.get(`${END_POINT.MEMBER}`); return data; }; diff --git a/src/components/molecules/CommentItem/CommentItem.tsx b/src/components/molecules/CommentItem/CommentItem.tsx index 3b2f3871..5fe8d4b3 100644 --- a/src/components/molecules/CommentItem/CommentItem.tsx +++ b/src/components/molecules/CommentItem/CommentItem.tsx @@ -2,9 +2,6 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Comment } from '@/types/comment'; import { ArrowDown, ArrowUp } from '@/assets'; -import { useNewSelector } from '@/store'; -import { selectAccessToken } from '@/store/auth'; -import { useParseJwt } from '@/hooks/common/useParseJwt'; import { useMemberQuery } from '@/hooks/api/member/useMemberQuery'; import { formatDateFromISOWithTime } from '@/utils/formatData'; import { useCommentActions } from '@/hooks/comment/useCommentActions'; @@ -35,8 +32,7 @@ const CommentItem = ({ selectedPage, talkPickWriter, }: CommentItemProps) => { - const accessToken = useNewSelector(selectAccessToken); - const { member } = useMemberQuery(useParseJwt(accessToken).memberId); + const { member } = useMemberQuery(); const isMyComment = useMemo(() => { return comment?.nickname === member?.nickname; diff --git a/src/components/molecules/LoginForm/LoginForm.style.ts b/src/components/molecules/LoginForm/LoginForm.style.ts index a044152b..a83f9837 100644 --- a/src/components/molecules/LoginForm/LoginForm.style.ts +++ b/src/components/molecules/LoginForm/LoginForm.style.ts @@ -67,11 +67,3 @@ export const loginButtonStyling = css({ outline: `1px solid ${color.GY[2]}`, cursor: 'pointer', }); - -export const toastModalStyling = css({ - position: 'fixed', - top: '110px', - left: '50%', - transform: 'translate(-50%)', - zIndex: '1000', -}); diff --git a/src/components/molecules/LoginForm/LoginForm.tsx b/src/components/molecules/LoginForm/LoginForm.tsx index e7bb1041..9af120b4 100644 --- a/src/components/molecules/LoginForm/LoginForm.tsx +++ b/src/components/molecules/LoginForm/LoginForm.tsx @@ -6,31 +6,29 @@ import { PATH } from '@/constants/path'; import Button from '@/components/atoms/Button/Button'; import Input from '@/components/atoms/Input/Input'; import Divider from '@/components/atoms/Divider/Divider'; -import ToastModal from '@/components/atoms/ToastModal/ToastModal'; import SocialLoginButton from '@/components/atoms/SocialLoginButton/SocialLoginButton'; import { useLoginForm } from '@/hooks/login/useLoginForm'; +import type { State } from '@/pages/LoginPage/LoginPage'; import * as S from './LoginForm.style'; export interface LoginFormProps { + showToastModal?: (message: string, callback?: () => void) => void; withSignInText?: boolean; - pathTalkPickId?: number; + loginState?: State; onModalLoginSuccess?: () => void; } const LoginForm = ({ + showToastModal, withSignInText, - pathTalkPickId, + loginState, onModalLoginSuccess, }: LoginFormProps) => { - const { - form, - onChange, - isError, - errorMessage, - handleSubmit, - isVisible, - modalText, - } = useLoginForm(pathTalkPickId, onModalLoginSuccess); + const { form, onChange, isError, errorMessage, handleSubmit } = useLoginForm( + showToastModal, + loginState?.talkPickId, + onModalLoginSuccess, + ); const handleSocialLogin = (social: string) => { window.location.href = `${process.env.API_URL}/oauth2/authorization/${social}`; @@ -38,11 +36,6 @@ const LoginForm = ({ return (
- {isVisible && ( -
- {modalText} -
- )}
LOGIN
{ - const accessToken = useNewSelector(selectAccessToken); - const { member } = useMemberQuery(useParseJwt(accessToken).memberId); + const { member } = useMemberQuery(); const isMyReply = useMemo(() => { return reply?.nickname === member?.nickname; diff --git a/src/components/organisms/BalanceGameSection/BalanceGameSection.tsx b/src/components/organisms/BalanceGameSection/BalanceGameSection.tsx index 45b19ada..2964518e 100644 --- a/src/components/organisms/BalanceGameSection/BalanceGameSection.tsx +++ b/src/components/organisms/BalanceGameSection/BalanceGameSection.tsx @@ -4,6 +4,8 @@ import { BookmarkDF, BookmarkPR, NextArrow, PrevArrow, Share } from '@/assets'; import { VoteRecord } from '@/types/vote'; import { SUCCESS } from '@/constants/message'; import { GameDetail, GameSet } from '@/types/game'; +import { useNewSelector } from '@/store'; +import { selectAccessToken } from '@/store/auth'; import { formatDateFromISO } from '@/utils/formatData'; import Chips from '@/components/atoms/Chips/Chips'; import Divider from '@/components/atoms/Divider/Divider'; @@ -53,7 +55,7 @@ const BalanceGameSection = ({ const gameStages: GameDetail[] = game?.gameDetailResponses ?? gameDefaultDetail; - const isGuest = !localStorage.getItem('accessToken'); + const isGuest = !useNewSelector(selectAccessToken); const [guestVotedList, setGuestVotedList] = useState([]); diff --git a/src/components/organisms/CommentsSection/CommentsSection.tsx b/src/components/organisms/CommentsSection/CommentsSection.tsx index 1f3b1ff1..fed2fd52 100644 --- a/src/components/organisms/CommentsSection/CommentsSection.tsx +++ b/src/components/organisms/CommentsSection/CommentsSection.tsx @@ -1,7 +1,4 @@ import React, { useState } from 'react'; -import { useNewSelector } from '@/store'; -import { selectAccessToken } from '@/store/auth'; -import { useParseJwt } from '@/hooks/common/useParseJwt'; import { useMemberQuery } from '@/hooks/api/member/useMemberQuery'; import { CommentsPagination } from '@/types/comment'; import { generatePageNumbers } from '@/utils/pagination'; @@ -39,8 +36,7 @@ const CommentsSection = ({ handlePageChange, voted, }: CommentsSectionProps) => { - const accessToken = useNewSelector(selectAccessToken); - const { member } = useMemberQuery(useParseJwt(accessToken).memberId); + const { member } = useMemberQuery(); const isMyTalkPick: boolean = talkPickWriter === member?.nickname; const totalPages = commentList?.totalPages ?? 0; diff --git a/src/components/organisms/Header/Header.tsx b/src/components/organisms/Header/Header.tsx index b4e63d28..3bdd8177 100644 --- a/src/components/organisms/Header/Header.tsx +++ b/src/components/organisms/Header/Header.tsx @@ -7,7 +7,6 @@ import React, { useState } from 'react'; import { useNavigate, Link } from 'react-router-dom'; import { useLogoutMutation } from '@/hooks/api/member/useLogoutMutation'; import { useNewSelector } from '@/store'; -import { useParseJwt } from '@/hooks/common/useParseJwt'; import { useMemberQuery } from '@/hooks/api/member/useMemberQuery'; import { selectAccessToken } from '@/store/auth'; import { Logo, DefaultProfile, ListIcon, LogoSmall } from '@/assets'; @@ -30,7 +29,7 @@ const Header = () => { const [isMenuOpen, setIsMenuOpen] = useState(false); const accessToken = useNewSelector(selectAccessToken) ?? ''; const logout = useLogoutMutation(); - const { member } = useMemberQuery(useParseJwt(accessToken)?.memberId); + const { member } = useMemberQuery(); const handleMenuToggle = () => { setIsMenuOpen((prev) => !prev); diff --git a/src/constants/api.ts b/src/constants/api.ts index d98a6bba..7af036d2 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -22,7 +22,7 @@ export const END_POINT = { REFRESH: '/members/reissue', ALL_MEMBERS: '/members', EDIT_MEMBERS: '/members', - MEMBER: (id: number) => `/members/${id}`, + MEMBER: '/members/info', MEMBER_PROFILE: (id: number) => `/members/${id}/profile`, MEMBER_IMAGE: '/members/image', MEMBER_NICKNAME: '/members/nickname', diff --git a/src/constants/message.ts b/src/constants/message.ts index 0c5a044b..08904e21 100644 --- a/src/constants/message.ts +++ b/src/constants/message.ts @@ -77,6 +77,7 @@ export const SUCCESS = { }, SIGN_UP: '회원가입 완료!', LOGIN: '로그인 완료!', + LOGOUT: '로그아웃되었습니다.', COPY: { LINK: '복사 완료!', }, @@ -110,6 +111,9 @@ export const NOTICE = { STATUS: { NOT_READY: '아직 준비 중인 서비스입니다!', }, + LOGIN: { + EXPIRED: '로그인 시간이 만료되었습니다. 다시 로그인해주세요.', + }, } as const; export const NULL = { diff --git a/src/hooks/api/member/useChangeUserInfoMutation.ts b/src/hooks/api/member/useChangeUserInfoMutation.ts index 9a4376c4..c4a85477 100644 --- a/src/hooks/api/member/useChangeUserInfoMutation.ts +++ b/src/hooks/api/member/useChangeUserInfoMutation.ts @@ -26,7 +26,7 @@ export const useChangeUserInfoMutation = ( if (memberId) { await queryClient.invalidateQueries({ - queryKey: ['members', memberId], + queryKey: ['members'], }); } }, diff --git a/src/hooks/api/member/useLogoutMutation.ts b/src/hooks/api/member/useLogoutMutation.ts index 195a9f89..aaaadbe0 100644 --- a/src/hooks/api/member/useLogoutMutation.ts +++ b/src/hooks/api/member/useLogoutMutation.ts @@ -5,7 +5,6 @@ import { postLogout } from '@/api/member'; import { PATH } from '@/constants/path'; import { useNewDispatch } from '@/store'; import { tokenActions } from '@/store/auth'; -import { deleteCookie } from '@/utils/cookie'; import { useMutation } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; @@ -15,17 +14,10 @@ export const useLogoutMutation = () => { return useMutation({ mutationFn: postLogout, onSuccess: () => { - // TODO: 백엔드에서 리프레쉬 토큰 쿠키에 저장시키면, 해당 코드 제거 - localStorage.removeItem('accessToken'); - localStorage.removeItem('refreshToken'); - deleteCookie('accessToken'); - deleteCookie('refreshToken'); - delete axiosInstance.defaults.headers.Authorization; dispatch(tokenActions.deleteToken()); - navigate(`/${PATH.LOGIN}`); - alert('로그아웃되었습니다🙂'); + navigate(`/${PATH.LOGIN}`, { state: { status: 'logout' } }); }, onError: (err: AxiosErrorResponse) => { console.log('로그아웃 에러: ', err); diff --git a/src/hooks/api/member/useMemberQuery.ts b/src/hooks/api/member/useMemberQuery.ts index f3b56395..1cd21a1d 100644 --- a/src/hooks/api/member/useMemberQuery.ts +++ b/src/hooks/api/member/useMemberQuery.ts @@ -2,11 +2,10 @@ import { getMember } from '@/api/member'; import { Member } from '@/types/member'; import { useQuery } from '@tanstack/react-query'; -export const useMemberQuery = (memberId: number) => { +export const useMemberQuery = () => { const { data: member } = useQuery({ - queryKey: ['members', memberId], - queryFn: () => getMember(memberId), - enabled: !!memberId, + queryKey: ['members'], + queryFn: () => getMember(), }); return { member }; }; diff --git a/src/hooks/common/useTokenRefresh.ts b/src/hooks/common/useTokenRefresh.ts index 50dfbbd2..c9d173ee 100644 --- a/src/hooks/common/useTokenRefresh.ts +++ b/src/hooks/common/useTokenRefresh.ts @@ -1,46 +1,35 @@ -import { axiosInstance } from '@/api/interceptor'; +/* eslint-disable no-console */ +import { useQueryClient } from '@tanstack/react-query'; +import { getRefreshToken } from '@/api/interceptor'; import { useNewDispatch, useNewSelector } from '@/store'; import { selectAccessToken, tokenActions } from '@/store/auth'; -import { isTimeLimit } from '@/utils/validator'; import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useParseJwt } from './useParseJwt'; export const useTokenRefresh = () => { - const accessToken = useNewSelector(selectAccessToken); - // TODO - const refreshToken = localStorage.getItem('refreshToken'); - const localstorageAccessToken = localStorage.getItem('accessToken'); const dispatch = useNewDispatch(); const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const accessToken = useNewSelector(selectAccessToken); useEffect(() => { - const tokenRefresh = () => { - if (!accessToken && refreshToken) { - if (isTimeLimit(useParseJwt(localstorageAccessToken).exp)) { - localStorage.removeItem('accessToken'); - localStorage.removeItem('refreshToken'); - delete axiosInstance.defaults.headers.Authorization; - dispatch(tokenActions.deleteToken()); - alert('로그인 시간이 만료되었습니다. 다시 로그인해주세요.'); - navigate('/login'); - // getRefreshToken() - // .then((res) => { - // console.log('재발급 성공'); - // console.log(res); - // dispatch(tokenActions.setToken(res)); - // axiosInstance.defaults.headers.Authorization = `Bearer ${res}`; - // localStorage.setItem('accessToken', res); - // }) - // .catch((err) => { - // console.error(err); - // }); - } else { - dispatch(tokenActions.setToken(localstorageAccessToken)); - axiosInstance.defaults.headers.Authorization = `Bearer ${localstorageAccessToken}`; - } + const tokenRefresh = async () => { + if (accessToken) return; + + try { + const newAccessToken = await getRefreshToken(); + dispatch(tokenActions.setToken(newAccessToken)); + + await queryClient.invalidateQueries({ + queryKey: ['members'], + }); + } catch (error) { + dispatch(tokenActions.deleteToken()); } }; - tokenRefresh(); - }, [accessToken, refreshToken, dispatch]); + tokenRefresh().catch((error) => { + console.error('토큰 에러: ', error); + }); + }, [accessToken, dispatch, navigate, queryClient]); }; diff --git a/src/hooks/login/useLoginForm.ts b/src/hooks/login/useLoginForm.ts index d5c25272..f8b0bc41 100644 --- a/src/hooks/login/useLoginForm.ts +++ b/src/hooks/login/useLoginForm.ts @@ -4,19 +4,20 @@ import { HTTP_STATUS_CODE } from '@/constants/api'; import { useNewDispatch } from '@/store'; import { tokenActions } from '@/store/auth'; import { MemberForm } from '@/types/member'; -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { ChangeEvent, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { PATH } from '@/constants/path'; import { ERROR, SUCCESS } from '../../constants/message'; import useInputs from '../common/useInputs'; -import useToastModal from '../modal/useToastModal'; import { validateLoginForm } from './validateLoginForm'; export const useLoginForm = ( + showToastModal?: (message: string, callback?: () => void) => void, pathTalkPickId?: number, onModalLoginSuccess?: () => void, ) => { + const queryClient = useQueryClient(); const initialState: Pick = { email: localStorage.getItem('savedEmail') ?? '', password: '', @@ -25,7 +26,6 @@ export const useLoginForm = ( const { form, onChange } = useInputs>(initialState); - const { isVisible, modalText, showToastModal } = useToastModal(); const [isError, setIsError] = useState(false); const [errorMessage, setErrorMessage] = useState( undefined, @@ -42,7 +42,7 @@ export const useLoginForm = ( const login = useMutation({ mutationFn: postLogin, - onSuccess: (res: string) => { + onSuccess: async (res: string) => { setIsError(false); setErrorMessage(undefined); onModalLoginSuccess?.(); @@ -50,11 +50,13 @@ export const useLoginForm = ( dispatch(tokenActions.setToken(res)); axiosInstance.defaults.headers.Authorization = `Bearer ${res}`; - localStorage.setItem('accessToken', res); - localStorage.setItem('refreshToken', 'refreshToken'); localStorage.setItem('savedEmail', form.email); - showToastModal(SUCCESS.LOGIN, () => { + await queryClient.invalidateQueries({ + queryKey: ['members'], + }); + + showToastModal?.(SUCCESS.LOGIN, () => { if (pathTalkPickId) { navigate(`/${PATH.TALKPICK(pathTalkPickId)}`); } else { @@ -88,7 +90,5 @@ export const useLoginForm = ( isError, errorMessage, handleSubmit, - isVisible, - modalText, }; }; diff --git a/src/pages/BalanceGamePage/BalanceGamePage.tsx b/src/pages/BalanceGamePage/BalanceGamePage.tsx index c2a0276f..f8844b77 100644 --- a/src/pages/BalanceGamePage/BalanceGamePage.tsx +++ b/src/pages/BalanceGamePage/BalanceGamePage.tsx @@ -1,8 +1,5 @@ import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; -import { useNewSelector } from '@/store'; -import { selectAccessToken } from '@/store/auth'; -import { useParseJwt } from '@/hooks/common/useParseJwt'; import { useMemberQuery } from '@/hooks/api/member/useMemberQuery'; import { useGameBySetId } from '@/hooks/api/game/useGameBySetIdQuery'; import Divider from '@/components/atoms/Divider/Divider'; @@ -17,8 +14,7 @@ const BalanceGamePage = () => { const { gameSet } = useGameBySetId(gameSetId); const [currentStage, setCurrentStage] = useState(0); - const accessToken = useNewSelector(selectAccessToken); - const { member } = useMemberQuery(useParseJwt(accessToken).memberId); + const { member } = useMemberQuery(); const isMyGame: boolean = member?.nickname === gameSet?.member; diff --git a/src/pages/ChangeUserInfoPage/ChangeUserInfoPage.style.ts b/src/pages/ChangeUserInfoPage/ChangeUserInfoPage.style.ts index 978d436c..d80b6e99 100644 --- a/src/pages/ChangeUserInfoPage/ChangeUserInfoPage.style.ts +++ b/src/pages/ChangeUserInfoPage/ChangeUserInfoPage.style.ts @@ -7,6 +7,7 @@ export const changeUserInfoPageContainer = css({ flexDirection: 'column', alignItems: 'center', width: '100%', + minHeight: '720px', padding: '100px 0', background: color.GY[3], }); diff --git a/src/pages/ChangeUserInfoPage/ChangeUserInfoPage.tsx b/src/pages/ChangeUserInfoPage/ChangeUserInfoPage.tsx index 55481bf0..8f298fb9 100644 --- a/src/pages/ChangeUserInfoPage/ChangeUserInfoPage.tsx +++ b/src/pages/ChangeUserInfoPage/ChangeUserInfoPage.tsx @@ -1,7 +1,4 @@ -import React, { useState } from 'react'; -import { useNewSelector } from '@/store'; -import { selectAccessToken } from '@/store/auth'; -import { useParseJwt } from '@/hooks/common/useParseJwt'; +import React, { useState, useEffect } from 'react'; import { useMemberQuery } from '@/hooks/api/member/useMemberQuery'; import Button from '@/components/atoms/Button/Button'; import ToastModal from '@/components/atoms/ToastModal/ToastModal'; @@ -14,8 +11,7 @@ import { useChangeUserInfoForm } from '@/hooks/changeUserInfo/useChangeUserInfoF import * as S from './ChangeUserInfoPage.style'; const ChangeUserInfoPage = () => { - const accessToken = useNewSelector(selectAccessToken); - const { member } = useMemberQuery(useParseJwt(accessToken).memberId); + const { member } = useMemberQuery(); const [passwordInput, setPasswordInput] = useState(''); const [verifySuccess, setVerifySuccess] = useState(false); @@ -29,6 +25,12 @@ const ChangeUserInfoPage = () => { } }; + useEffect(() => { + if (member?.signupType === 'SOCIAL') { + setVerifySuccess(true); + } + }, [member]); + const { form, onChange, @@ -48,58 +50,59 @@ const ChangeUserInfoPage = () => { {modalText}
)} - {verifySuccess ? ( - <> - 회원정보 수정 -
-
- - + {member && + (verifySuccess ? ( + <> + 회원정보 수정 +
+
+ + +
+
- -
- - ) : ( - <> - 비밀번호 확인 - - 회원정보 보호를 위해, 비밀번호를 다시 한번 입력해 주세요. - -
-
- - ) => - setPasswordInput(e.target.value) - } - onKeyDown={handlePasswordKeyDown} - /> + + ) : ( + <> + 비밀번호 확인 + + 회원정보 보호를 위해, 비밀번호를 다시 한번 입력해 주세요. + +
+
+ + ) => + setPasswordInput(e.target.value) + } + onKeyDown={handlePasswordKeyDown} + /> +
+
- -
- - )} + + ))} ); }; diff --git a/src/pages/LoginPage/LoginPage.style.ts b/src/pages/LoginPage/LoginPage.style.ts index f6195993..e8141ad7 100644 --- a/src/pages/LoginPage/LoginPage.style.ts +++ b/src/pages/LoginPage/LoginPage.style.ts @@ -14,3 +14,11 @@ export const loginContainer = css({ export const logoStyle = css({ marginBottom: '24px', }); + +export const toastModalStyling = css({ + position: 'fixed', + top: '110px', + left: '50%', + transform: 'translate(-50%)', + zIndex: '1000', +}); diff --git a/src/pages/LoginPage/LoginPage.tsx b/src/pages/LoginPage/LoginPage.tsx index 43344589..9a78d131 100644 --- a/src/pages/LoginPage/LoginPage.tsx +++ b/src/pages/LoginPage/LoginPage.tsx @@ -1,21 +1,49 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { ERROR, SUCCESS } from '@/constants/message'; import { LogoLarge } from '@/assets'; import LoginForm from '@/components/molecules/LoginForm/LoginForm'; import { useLocation } from 'react-router-dom'; +import useToastModal from '@/hooks/modal/useToastModal'; +import ToastModal from '@/components/atoms/ToastModal/ToastModal'; import * as S from './LoginPage.style'; -interface State { - talkPickId: number; +export interface State { + talkPickId?: number; + status?: string; } const LoginPage = () => { const location = useLocation(); const state = location.state as State; + const { isVisible, modalText, showToastModal } = useToastModal(); + const [modalVisible, setModalVisible] = useState(true); + + useEffect(() => { + if (state?.status === 'already_registered' && modalVisible) { + showToastModal(ERROR.EMAIL.EXIST); + setModalVisible(false); + } + + if (state?.status === 'logout' && modalVisible) { + showToastModal(SUCCESS.LOGOUT); + setModalVisible(false); + } + }, [modalVisible, showToastModal, state?.status]); + return (
+ {isVisible && ( +
+ {modalText} +
+ )} - +
); }; diff --git a/src/pages/TalkPickPage/TalkPickPage.tsx b/src/pages/TalkPickPage/TalkPickPage.tsx index 1d09e2ac..11f552b8 100644 --- a/src/pages/TalkPickPage/TalkPickPage.tsx +++ b/src/pages/TalkPickPage/TalkPickPage.tsx @@ -1,8 +1,5 @@ import React, { useState } from 'react'; -import { useNewSelector } from '@/store'; -import { selectAccessToken } from '@/store/auth'; import { useLocation, useParams } from 'react-router-dom'; -import { useParseJwt } from '@/hooks/common/useParseJwt'; import { useMemberQuery } from '@/hooks/api/member/useMemberQuery'; import { useCommentsQuery } from '@/hooks/api/comment/useCommentsQuery'; import { useBestCommentsQuery } from '@/hooks/api/comment/useBestCommentsQuery'; @@ -24,8 +21,7 @@ const TalkPickPage = () => { order: 'desc', }); - const accessToken = useNewSelector(selectAccessToken); - const { member } = useMemberQuery(useParseJwt(accessToken).memberId); + const { member } = useMemberQuery(); const { talkPickId } = useParams(); const location = useLocation(); diff --git a/src/types/member.ts b/src/types/member.ts index e3ebe34f..7fda60fd 100644 --- a/src/types/member.ts +++ b/src/types/member.ts @@ -6,6 +6,7 @@ export type Member = { createdAt: string; postsCount: number; bookmarkedPostsCount: number; + signupType: 'SOCIAL' | 'STANDARD'; }; export interface MemberForm {