From 0a9341cea31e6290e52ef1dce698c877372c8fad Mon Sep 17 00:00:00 2001 From: novice1993 Date: Wed, 13 Sep 2023 02:11:52 +0900 Subject: [PATCH] =?UTF-8?q?[Fix]=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B4=80=EB=A0=A8=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이메일 로그인 시 로그인 상태 제대로 갱신되지 않는 문제 수정 (로그인 관련 전역상태 코드 수정) - 로그인 상태로 새로고침 했을 때 로그인 해제되는 부분 수정 - 로그아웃 시 로컬 스토리지에 액세스 토큰 계속 남아있던 문제 해결 Issues #94 --- client/src/components/Logins/EmailLogin.tsx | 22 ++--- client/src/components/Logins/GoogleSignin.tsx | 77 +++++++--------- .../components/StockOrderSection/Index.tsx | 15 ++- client/src/models/stateProps.ts | 1 + client/src/page/MainPage.tsx | 91 +++++++------------ client/src/reducer/member/loginSlice.ts | 28 +++--- client/src/reducer/member/rootReducer.ts | 11 --- client/src/store/config.ts | 8 +- 8 files changed, 101 insertions(+), 152 deletions(-) delete mode 100644 client/src/reducer/member/rootReducer.ts diff --git a/client/src/components/Logins/EmailLogin.tsx b/client/src/components/Logins/EmailLogin.tsx index b7f8c4fa..630a7ea3 100644 --- a/client/src/components/Logins/EmailLogin.tsx +++ b/client/src/components/Logins/EmailLogin.tsx @@ -1,8 +1,8 @@ import axios from "axios"; import styled from "styled-components"; import React, { useState } from "react"; -import { setLoginState } from '../../reducer/member/loginSlice'; -import { useDispatch } from 'react-redux'; +import { setLoginState } from "../../reducer/member/loginSlice"; +import { useDispatch } from "react-redux"; // 이메일 로그인 모달 컴포넌트 const EmailLoginModal: React.FC = ({ onClose, onLogin }) => { @@ -40,18 +40,18 @@ const EmailLoginModal: React.FC = ({ onClose, onLogin }) = password, }); if (response.status === 200) { - const authToken = response.headers['authorization']; + const authToken = response.headers["authorization"]; console.log(authToken); - - const refreshToken = response.headers['refresh']; - + + const refreshToken = response.headers["refresh"]; + // 로그인 상태로 만들기 - dispatch(setLoginState(true)); - + dispatch(setLoginState()); + // 토큰들을 로컬 스토리지에 저장 - if(authToken) localStorage.setItem('authToken', authToken); - if(refreshToken) localStorage.setItem('refreshToken', refreshToken); - + if (authToken) localStorage.setItem("authToken", authToken); + if (refreshToken) localStorage.setItem("refreshToken", refreshToken); + onLogin(); onClose(); } else { diff --git a/client/src/components/Logins/GoogleSignin.tsx b/client/src/components/Logins/GoogleSignin.tsx index 88c9e51a..b3a89c25 100644 --- a/client/src/components/Logins/GoogleSignin.tsx +++ b/client/src/components/Logins/GoogleSignin.tsx @@ -1,47 +1,40 @@ -import React from 'react'; -import { GoogleOAuthProvider, GoogleLogin, useGoogleOneTapLogin } from '@react-oauth/google'; -import { useDispatch } from 'react-redux'; -import { setLoginState } from '../../reducer/member/loginSlice'; +import React from "react"; +import { GoogleOAuthProvider, GoogleLogin, useGoogleOneTapLogin } from "@react-oauth/google"; +import { useDispatch } from "react-redux"; +import { setLoginState } from "../../reducer/member/loginSlice"; const GoogleSignInComponent: React.FC = () => { - - const dispatch = useDispatch(); // Redux의 dispatch 함수를 사용하기 위해 가져옵니다. - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const handleSuccess = (credentialResponse: any) => { - console.log(credentialResponse); - - const token = credentialResponse.token; // 실제 응답에서 토큰의 경로가 어떤지 확인하고 수정해야 합니다. - localStorage.setItem('authToken', token); // 토큰을 localStorage에 저장 - - // 로그인 성공 시 전역 상태를 업데이트합니다. - dispatch(setLoginState({ - memberId: credentialResponse.memberId, // memberId는 예시입니다. 실제 값에 맞게 수정해야 합니다. - isLoggedIn: 1, - })); - }; - - const handleError = () => { - console.log('Login Failed'); - }; - - // One-tap 로그인 (선택적) - useGoogleOneTapLogin({ - onSuccess: handleSuccess, - onError: handleError, - }); - - return ( - - - - ); + const dispatch = useDispatch(); // Redux의 dispatch 함수를 사용하기 위해 가져옵니다. + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const handleSuccess = (credentialResponse: any) => { + console.log(credentialResponse); + + const token = credentialResponse.token; // 실제 응답에서 토큰의 경로가 어떤지 확인하고 수정해야 합니다. + localStorage.setItem("authToken", token); // 토큰을 localStorage에 저장 + + // 로그인 성공 시 전역 상태를 업데이트합니다. + dispatch(setLoginState()); + }; + + const handleError = () => { + console.log("Login Failed"); + }; + + // One-tap 로그인 (선택적) + useGoogleOneTapLogin({ + onSuccess: handleSuccess, + onError: handleError, + }); + + return ( + + + + ); }; export default GoogleSignInComponent; - diff --git a/client/src/components/StockOrderSection/Index.tsx b/client/src/components/StockOrderSection/Index.tsx index 8f8f7176..eb8273dc 100644 --- a/client/src/components/StockOrderSection/Index.tsx +++ b/client/src/components/StockOrderSection/Index.tsx @@ -18,23 +18,20 @@ const marketType: string = "코스피"; // dummyData import dummyLogo from "../../asset/CentralSectionMenu-dummyImg.png"; -import { useState } from "react"; const StockOrderSection = () => { const dispatch = useDispatch(); + const isLogin = useSelector((state: StateProps) => state.login); const companyId = useSelector((state: StateProps) => state.companyId); const stockOrderSet = useSelector((state: StateProps) => state.stockOrderSet); - // 🔴 로그인 구현될 때까지 임시 - const [login, setLogin] = useState(true); - if (companyId === 10000000) { - setLogin(true); - } - // - const { stockInfo, stockInfoLoading, stockInfoError } = useGetStockInfo(companyId); const { stockPrice, stockPriceLoading, stockPriceError } = useGetStockData(companyId); + console.log(isLogin); + const localData = localStorage.getItem("authToken"); + console.log(localData); + // 주식주문 창 닫기 const handleStockOrderClose = () => { dispatch(stockOrderClose()); @@ -71,7 +68,7 @@ const StockOrderSection = () => { ✕ - {login ? ( + {isLogin === 1 ? ( <> diff --git a/client/src/models/stateProps.ts b/client/src/models/stateProps.ts index f9f72121..c3fcc3ea 100644 --- a/client/src/models/stateProps.ts +++ b/client/src/models/stateProps.ts @@ -7,4 +7,5 @@ export interface StateProps { companyId: number; stockOrderVolume: number; decisionWindow: boolean; + login: number; } diff --git a/client/src/page/MainPage.tsx b/client/src/page/MainPage.tsx index 5d6b34c8..6c21b5e6 100644 --- a/client/src/page/MainPage.tsx +++ b/client/src/page/MainPage.tsx @@ -1,6 +1,5 @@ -// /client/src/pages/MainPage.tsx -import { useState, useCallback } from "react"; -import { useSelector } from "react-redux"; +import { useState, useEffect, useCallback } from "react"; +import { useSelector, useDispatch } from "react-redux"; import styled from "styled-components"; import LogoutHeader from "../components/Headers/LogoutHeader"; import LoginHeader from "../components/Headers/LoginHeader"; @@ -20,6 +19,10 @@ import ProfileModal from "../components/Profile/profileModal"; import { StateProps } from "../models/stateProps"; import { TabContainerPage } from "./TabPages/TabContainerPage"; +// 🔴 로그아웃 관련 action 함수 +import { setLogoutState } from "../reducer/member/loginSlice"; +import { setLoginState } from "../reducer/member/loginSlice"; + const MainPage = () => { const expandScreen = useSelector((state: StateProps) => state.expandScreen); @@ -56,8 +59,7 @@ const MainPage = () => { setEmailSignupModalOpen(false); }, []); - const [isEmailVerificationModalOpen, setEmailVerificationModalOpen] = - useState(false); + const [isEmailVerificationModalOpen, setEmailVerificationModalOpen] = useState(false); // 이메일 인증 모달을 열 때 사용자가 입력한 이메일을 저장하도록 변경 const openEmailVerificationModal = useCallback((enteredEmail: string) => { @@ -70,8 +72,7 @@ const MainPage = () => { setEmailVerificationModalOpen(false); }, []); - const [isPasswordSettingModalOpen, setPasswordSettingModalOpen] = - useState(false); + const [isPasswordSettingModalOpen, setPasswordSettingModalOpen] = useState(false); const openPasswordSettingModal = useCallback(() => { setEmailVerificationModalOpen(false); // 이메일 인증 모달 닫기 @@ -91,12 +92,23 @@ const MainPage = () => { setWelcomeModalOpen(false); }, []); + // 🔴 로그인 지역 상태 제거 → 전역 상태로 대체 (지역 상태 관련된 코드 싹 다 지워야함... -> 전역 상태 만들었으니 전역 상태로 활용) + const dispatch = useDispatch(); + const isLogin = useSelector((state: StateProps) => state.login); const [isLoggedIn, setIsLoggedIn] = useState(false); // 로그인 상태 관리 + useEffect(() => { + const authToken = localStorage.getItem("authToken"); + + if (authToken !== null) { + dispatch(setLoginState()); + } + }, []); + //프로필 모달 열고닫는 매커니즘 const openProfileModal = useCallback(() => { setProfileModalOpen(true); - }, []); + }, []); const [isLoginConfirmationModalOpen, setLoginConfirmationModalOpen] = useState(false); @@ -110,78 +122,40 @@ const MainPage = () => { setIsLoggedIn(true); }; - const [selectedMenu, setSelectedMenu] = useState<"관심목록" | "투자목록">( - "투자목록" - ); // Default menu is 관심목록 + const [selectedMenu, setSelectedMenu] = useState<"관심목록" | "투자목록">("투자목록"); // Default menu is 관심목록 const handleMenuChange = (menu: "관심목록" | "투자목록") => { setSelectedMenu(menu); }; + // 🔴 로그 아웃 시 로컬데이터 토큰 제거 const handleLogout = () => { - setIsLoggedIn(false); + dispatch(setLogoutState()); + localStorage.removeItem("authToken"); }; return ( - - {isLoggedIn ? ( - - ) : ( - - )} + {isLogin === 1 ? : }
{!expandScreen.left && ( - - {selectedMenu === "관심목록" ? ( - - ) : ( - - )} - + {selectedMenu === "관심목록" ? : } )} {!expandScreen.right && }
{isOAuthModalOpen && ( - handleMenuChange("관심목록")} - onHoldingsClick={() => handleMenuChange("투자목록")} - /> + handleMenuChange("관심목록")} onHoldingsClick={() => handleMenuChange("투자목록")} /> )} {isEmailLoginModalOpen && } - {isLoginConfirmationModalOpen && ( - + {isLoginConfirmationModalOpen && } - )} - - {isEmailSignupModalOpen && ( - - )} - {isEmailVerificationModalOpen && ( - - )} + {isEmailSignupModalOpen && } + {isEmailVerificationModalOpen && } {isPasswordSettingModalOpen && ( { closeWelcomeModal(); }} /> - )} - {isProfileModalOpen && setProfileModalOpen(false)} />} - + )} + {isProfileModalOpen && setProfileModalOpen(false)} />}
); }; diff --git a/client/src/reducer/member/loginSlice.ts b/client/src/reducer/member/loginSlice.ts index 36890e59..7216c336 100644 --- a/client/src/reducer/member/loginSlice.ts +++ b/client/src/reducer/member/loginSlice.ts @@ -1,23 +1,21 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { createSlice } from "@reduxjs/toolkit"; + +const initialState: number = 0; const loginSlice = createSlice({ - name: 'login', - initialState: { - memberId: 0, - isLoggedIn: 1 - }, + name: "login", + initialState: initialState, reducers: { - setLoginState: (state, action) => { - state.memberId = action.payload; - state.isLoggedIn = 1; + setLoginState: (state) => { + state = 1; + return state; }, setLogoutState: (state) => { - state.memberId = 0; - state.isLoggedIn = 0; + state = 0; + return state; }, - - } + }, }); -export const { setLoginState, setLogoutState} = loginSlice.actions; -export default loginSlice.reducer; \ No newline at end of file +export const { setLoginState, setLogoutState } = loginSlice.actions; +export const loginReducer = loginSlice.reducer; diff --git a/client/src/reducer/member/rootReducer.ts b/client/src/reducer/member/rootReducer.ts deleted file mode 100644 index 11ff01a0..00000000 --- a/client/src/reducer/member/rootReducer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { combineReducers } from '@reduxjs/toolkit'; -import loginReducer from './loginSlice'; -import memberInfoReducer from './memberInfoSlice'; - -const rootReducer = combineReducers({ - login: loginReducer, - memberInfo: memberInfoReducer, - -}); - -export type RootState = ReturnType; \ No newline at end of file diff --git a/client/src/store/config.ts b/client/src/store/config.ts index 137baa8c..fa8f28a9 100644 --- a/client/src/store/config.ts +++ b/client/src/store/config.ts @@ -4,13 +4,12 @@ import { stockOrderPriceReducer } from "../reducer/StockOrderPrice-Reducer"; import { expandScreenReducer } from "../reducer/ExpandScreen-Reducer"; import { stockOrderSetReducer } from "../reducer/StockOrderSet-Reducer"; import { companyIdReducer } from "../reducer/CompanyId-Reducer"; -import loginReducer from '../reducer/member/loginSlice'; -import cashSlice from '../reducer/cash/cashSlice'; +import { loginReducer } from "../reducer/member/loginSlice"; +import cashSlice from "../reducer/cash/cashSlice"; import memberInfoReducer from "../reducer/member/memberInfoSlice"; import { stockOrderVolumeReducer } from "../reducer/StockOrderVolume-Reducer"; import { setDecisionWindowReducer } from "../reducer/SetDecisionWindow-Reducer"; - const store = configureStore({ reducer: { stockOrderType: stockOrderTypeReducer, @@ -23,10 +22,9 @@ const store = configureStore({ cash: cashSlice, stockOrderVolume: stockOrderVolumeReducer, decisionWindow: setDecisionWindowReducer, - }, }); export default store; -export type RootState = ReturnType; \ No newline at end of file +export type RootState = ReturnType;