Skip to content

Commit

Permalink
[Feat][Fix]소셜 로그인 완벽 구현
Browse files Browse the repository at this point in the history
소셜 로그인 성공 시 로컬 스토리지에 엑세스토큰, 리프레시 토큰 저장
다시 로그인 버튼을 누를 시 로그인 상태 변화 및 원래 페이지로 렌더링
로그인,로그아웃 상태를 전역변수에서만 관리하도록 구현
Issues #99
  • Loading branch information
김병현 authored and 김병현 committed Sep 15, 2023
1 parent 211d957 commit c250994
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 94 deletions.
18 changes: 12 additions & 6 deletions client/src/components/Headers/LoginHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ import { useNavigate } from "react-router-dom";
import AlarmImage from "../../asset/images/alarm.png";
import ProfileModal from "../Profile/profileModal";
import StockSearchComponent from './stockSearchComponent';
import { setLogoutState } from '../../reducer/member/loginSlice';
import { useDispatch } from 'react-redux';


// 로그인 상태일 때의 헤더 컴포넌트
const LoginHeader: React.FC<LoginHeaderProps> = ({ onLogoutClick }) => {
const LoginHeader: React.FC<LoginHeaderProps> = () => {
const [isProfileModalOpen, setProfileModalOpen] = useState(false); // 프로필 모달 상태
const navigate = useNavigate(); // 페이지 이동 함수



const logoutText = "로그아웃";
const dispatch = useDispatch(); // 👈 useDispatch hook 추가

// 프로필 모달 열기 함수
const handleProfileOpen = () => {
setProfileModalOpen(true);
Expand All @@ -32,6 +33,12 @@ const LoginHeader: React.FC<LoginHeaderProps> = ({ onLogoutClick }) => {
navigate("/"); // 메인 페이지로 이동
};

const handleLogout = () => {
dispatch(setLogoutState()); // 전역변수에서 로그아웃 상태로 설정
localStorage.removeItem("Authorization"); // 엑세스 토큰 제거
localStorage.removeItem("Refresh-token"); // 리프레시 토큰 제거
};

return (
<HeaderContainer>
<LogoButton onClick={handleLogoClick}>
Expand All @@ -47,7 +54,7 @@ const LoginHeader: React.FC<LoginHeaderProps> = ({ onLogoutClick }) => {
<ProfileImage src={SampleProfile} />
</ProfileButton>
{isProfileModalOpen && <ProfileModal onClose={handleProfileClose} />}
<LogoutButton onClick={onLogoutClick}>{logoutText}</LogoutButton>
<LogoutButton onClick={handleLogout}>{logoutText}</LogoutButton>
</UserActions>
</HeaderContainer>
);
Expand All @@ -57,7 +64,6 @@ export default LoginHeader;

// 로그아웃 클릭 이벤트 타입 정의
interface LoginHeaderProps {
onLogoutClick: () => void;
onProfileClick: () => void;
}

Expand Down
4 changes: 0 additions & 4 deletions client/src/components/Logins/LoginConfirmatationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,10 @@ const LoginConfirmationModal: React.FC<LoginConfirmationProps> = ({ onClose }) =
const messageText = "로그인이 성공적으로 완료되었습니다!";
const confirmText = "확인"



return (
<ModalBackground>
<ModalContainer>

<Message>{messageText}</Message>

<ConfirmButton onClick={onClose}>{confirmText}</ConfirmButton>
</ModalContainer>
</ModalBackground>
Expand Down
83 changes: 15 additions & 68 deletions client/src/components/Logins/OAuthLogin.tsx
Original file line number Diff line number Diff line change
@@ -1,71 +1,40 @@
import React from 'react';
import React, { useEffect } from 'react';
import styled from 'styled-components';
// import kakaoLogo from '../../asset/images/KakaoLogo.svg';
// import axios from 'axios';
import GoogleLoginButton from './GoogleLoginButton'
import GoogleLoginButton from './GoogleLoginButton';
import KakaoLoginButton from './KakaoLoginButton';



import { useSelector } from 'react-redux';
import { RootState } from '../../store/config';
import TokenHandler from './TokenHandler';

const OAuthLoginModal: React.FC<LoginModalProps> = ({ onClose, onEmailLoginClick, onEmailSignupClick }) => {
const titleText = "로그인";

// const kakaoLoginText = "카카오로 로그인";
const orText = "또는";
const emailLoginText = "이메일로 로그인";
const emailSignupText = "이메일로 회원가입";

// URL에서 엑세스 토큰과 리프레시 토큰을 추출합니다.
const urlParams = new URLSearchParams(window.location.search);
const accessToken = urlParams.get("access_token");
const refreshToken = urlParams.get("refresh_token");

if (accessToken && refreshToken) {
// 엑세스 토큰을 로컬 스토리지에 저장합니다.
localStorage.setItem("Authorization", `Bearer ${accessToken}`);

// 리프레시 토큰을 로컬 스토리지에 저장합니다.
localStorage.setItem("Refresh-token", refreshToken);
}

const loginState = useSelector((state: RootState) => state.login);
console.log("Login State:", loginState);



// const handleGoogleLogout = async () => {
// googleLogout();
// }

//임시로 비활성화

// const handleKakaoLogin = async () => {
// try {
// const response = await axios.get('/oauth2/authorization/kakao');
// if (response.status === 200) {
// const redirectUri = response.data.uri;
// window.location.href = redirectUri;
// } else {
// console.error("Error logging in with Kakao, unexpected status code:", response.status);
// }
// } catch (error) {
// console.error("Error logging in with Kakao:", error);
// }
// };
useEffect(() => {
if (loginState === 1) {
onClose();
}
}, [loginState, onClose]);

return (
<ModalBackground>
<ModalContainer>
<TokenHandler />
<CloseButton onClick={onClose}>&times;</CloseButton>
<Title>{titleText}</Title>
<GoogleLoginButton backendURL="http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/oauth2/authorization/google" />
<KakaoLoginButton backendURL="http://ec2-13-125-246-160.ap-northeast-2.compute.amazonaws.com:8080/oauth2/authorization/kakao" />
{/*임시로 비활성화 <KakaoButton onClick={handleKakaoLogin}>
<LogoImage src={kakaoLogo} alt="Kakao Logo" />
{kakaoLoginText}
</KakaoButton> */}
<OrText>{orText}</OrText>
<EmailButtonsContainer>
<EmailButton onClick={onEmailLoginClick}>{emailLoginText}</EmailButton>
<EmailButton onClick={onEmailSignupClick}>{emailSignupText}</EmailButton>
<EmailButton onClick={onEmailSignupClick}>{emailSignupText}</EmailButton>
</EmailButtonsContainer>
</ModalContainer>
</ModalBackground>
Expand Down Expand Up @@ -124,28 +93,6 @@ const OrText = styled.span`
color: grey;
`;



// 임시로 비활성화
// const KakaoButton = styled.button`
// margin: 10px 0;
// padding: 10px 20px;
// background-color: #FFFFFF;
// border: 1px solid lightgray;
// border-radius: 5px;
// cursor: pointer;
// width: 300px;
// display: flex;
// align-items: center;
// justify-content: center;
// `;

// const LogoImage = styled.img`
// margin-right: 30px;
// width: 60px;
// height: auto;
// `;

const EmailButtonsContainer = styled.div`
display: flex;
justify-content: space-around;
Expand Down
28 changes: 28 additions & 0 deletions client/src/components/Logins/TokenHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// TokenHandler.tsx

import React, { useEffect } from 'react';
import { useDispatch } from "react-redux";
import { useNavigate } from 'react-router-dom';
import { setLoginState } from "../../reducer/member/loginSlice";

const TokenHandler: React.FC = () => {
const dispatch = useDispatch();
const navigate = useNavigate();

useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const accessToken = urlParams.get("access_token");
const refreshToken = urlParams.get("refresh_token");

if (accessToken && refreshToken) {
localStorage.setItem("Authorization", `Bearer ${accessToken}`);
localStorage.setItem("Refresh-token", refreshToken);
dispatch(setLoginState());
navigate('/');
}
}, [dispatch, navigate]);

return null; // 이 컴포넌트는 UI를 렌더링하지 않습니다.
};

export default TokenHandler;
36 changes: 20 additions & 16 deletions client/src/page/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ import EmailSignupModal from "../components/Signups/EmailSignup";
import EmailVerificationModal from "../components/Signups/EmailCertify";
import PasswordSettingModal from "../components/Signups/Password";
import CentralChart from "../components/CentralChart/Index";
import WatchList from "../components/watchlist/WatchList";
import Holdings from "../components/watchlist/Holdings"; // Assuming you have a Holdings component
import WatchList from "../components/WatchList/WatchList";
import Holdings from "../components/WatchList/Holdings";// Assuming you have a Holdings component
import CompareChartSection from "../components/CompareChartSection/Index";
import StockOrderSection from "../components/StockOrderSection/Index";
import Welcome from "../components/Signups/Welcome";
import ProfileModal from "../components/Profile/profileModal";
import { StateProps } from "../models/stateProps";
import { TabContainerPage } from "./TabPages/TabContainerPage";
import { RootState } from "../store/config";

// 🔴 로그아웃 관련 action 함수
import { setLogoutState } from "../reducer/member/loginSlice";
import { setLoginState } from "../reducer/member/loginSlice";
import { setLoginState } from "../reducer/member/loginSlice"

const MainPage = () => {
const expandScreen = useSelector((state: StateProps) => state.expandScreen);
Expand All @@ -33,6 +33,8 @@ const MainPage = () => {
const [isWelcomeModalOpen, setWelcomeModalOpen] = useState(false);
const [isProfileModalOpen, setProfileModalOpen] = useState(false); //프로필 모달 보이기/숨기기



const openOAuthModal = useCallback(() => {
setOAuthModalOpen(true);
}, []);
Expand Down Expand Up @@ -92,23 +94,16 @@ const MainPage = () => {
setWelcomeModalOpen(false);
}, []);

// 🔴 로그인 지역 상태 제거 → 전역 상태로 대체 (지역 상태 관련된 코드 싹 다 지워야함... -> 전역 상태 만들었으니 전역 상태로 활용)
const dispatch = useDispatch();
const isLogin = useSelector((state: StateProps) => state.login);
const isLogin = useSelector((state: RootState) => state.login);

// 🔴 페이지 로드 시 로컬 스토리지의 토큰을 기반으로 로그인 상태를 확인합니다.
useEffect(() => {
const authToken = localStorage.getItem("authToken");
const authToken = localStorage.getItem("Authorization");
if (authToken !== null) {
dispatch(setLoginState());
}
});

// 🔴 로그아웃 시 로컬스토리지에 있는 Auth 토큰 제거
const handleLogout = () => {
dispatch(setLogoutState());
localStorage.removeItem("authToken");
};
}, [dispatch]);

//프로필 모달 열고닫는 매커니즘
const openProfileModal = useCallback(() => {
Expand All @@ -123,6 +118,12 @@ const MainPage = () => {
dispatch(setLoginState());
};

// // 🔴 로그아웃 시 로컬스토리지에 있는 Auth 토큰 제거
// const handleLogout = () => {
// dispatch(setLogoutState());
// localStorage.removeItem("Authorization");
// };

const handleLoginConfirmationClose = () => {
setLoginConfirmationModalOpen(false);
};
Expand All @@ -135,8 +136,11 @@ const MainPage = () => {

return (
<Container>
{isLogin === 1 ? <LoginHeader onLogoutClick={handleLogout} onProfileClick={openProfileModal} /> : <LogoutHeader onLoginClick={openOAuthModal} />}

{isLogin === 1 ? (
<LoginHeader onProfileClick={openProfileModal} />
) : (
<LogoutHeader onLoginClick={openOAuthModal} />
)}
<Main>
<CompareChartSection />
{!expandScreen.left && (
Expand Down

0 comments on commit c250994

Please sign in to comment.