From c2e3be491d8189df95f3cb25885b5b13a1966721 Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Wed, 11 Mar 2026 23:56:06 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20SSAFY=20OAuth=202.0=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SSAFY 로그인 버튼 연동, 콜백 페이지 추가, AuthContext에 setUserFromSSAFY 메서드 추가. Co-Authored-By: Claude Opus 4.6 --- src/App.jsx | 2 ++ src/api/auth.js | 11 ++++++++ src/context/AuthContext.jsx | 11 +++++++- src/pages/LoginPage.jsx | 10 ++++++- src/pages/SSAFYCallbackPage.jsx | 49 +++++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 src/pages/SSAFYCallbackPage.jsx diff --git a/src/App.jsx b/src/App.jsx index 1a47a60..bc8e88c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,6 +6,7 @@ import LoginPage from './pages/LoginPage'; import RegisterPage from './pages/RegisterPage'; import HomePage from './pages/HomePage'; import AdminPage from './pages/AdminPage'; +import SSAFYCallbackPage from './pages/SSAFYCallbackPage'; function AuthRedirect() { const { user } = useAuth(); @@ -42,6 +43,7 @@ function AppRoutes() { : } /> : } /> + } /> } /> { + localStorage.setItem('token', data.token); + localStorage.setItem('refreshToken', data.refreshToken); + localStorage.setItem('username', data.username); + localStorage.setItem('role', data.role); + setUser({ token: data.token, username: data.username, role: data.role }); + }, []); + // 로컬 세션만 정리 (토큰 만료·강제 로그아웃 시) const clearSession = useCallback(() => { localStorage.removeItem('token'); @@ -43,7 +52,7 @@ export function AuthProvider({ children }) { }, [clearSession]); return ( - + {children} ); diff --git a/src/pages/LoginPage.jsx b/src/pages/LoginPage.jsx index 0823138..6ff3482 100644 --- a/src/pages/LoginPage.jsx +++ b/src/pages/LoginPage.jsx @@ -1,6 +1,7 @@ import { useState } from 'react'; import { useNavigate, Link, useLocation } from 'react-router-dom'; import { useAuth } from '../context/useAuth'; +import { getSSAFYLoginURL } from '../api/auth'; import './LoginPage.css'; export default function LoginPage() { @@ -75,7 +76,14 @@ export default function LoginPage() { diff --git a/src/pages/SSAFYCallbackPage.jsx b/src/pages/SSAFYCallbackPage.jsx new file mode 100644 index 0000000..c167a47 --- /dev/null +++ b/src/pages/SSAFYCallbackPage.jsx @@ -0,0 +1,49 @@ +import { useEffect, useState, useRef } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { useAuth } from '../context/useAuth'; +import { ssafyCallback } from '../api/auth'; + +export default function SSAFYCallbackPage() { + const [error, setError] = useState(''); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const { setUserFromSSAFY } = useAuth(); + const called = useRef(false); + + useEffect(() => { + if (called.current) return; + called.current = true; + + const code = searchParams.get('code'); + if (!code) { + setError('인가 코드가 없습니다.'); + return; + } + + ssafyCallback(code) + .then((data) => { + setUserFromSSAFY(data); + navigate('/', { replace: true }); + }) + .catch((err) => { + setError(err.message || 'SSAFY 로그인에 실패했습니다.'); + }); + }, [searchParams, setUserFromSSAFY, navigate]); + + if (error) { + return ( +
+

{error}

+ +
+ ); + } + + return ( +
+

SSAFY 로그인 처리 중...

+
+ ); +}