diff --git a/src/app/routes/components/auth.routes.tsx b/src/app/routes/components/auth.routes.tsx index 0817f87..1712695 100644 --- a/src/app/routes/components/auth.routes.tsx +++ b/src/app/routes/components/auth.routes.tsx @@ -1,4 +1,4 @@ -import { LoginPage, RealtorCertificationPage, RealtorLoginPage } from '@/pages'; +import { LoginPage, OAuthRedirectPage, RealtorCertificationPage, RealtorLoginPage } from '@/pages'; import { ROUTER_PATH } from '@/shared'; import { Layout } from '@/widgets'; @@ -14,6 +14,11 @@ export const authRoutes = [ element: , handle: { layout: ROUTE_CONFIG.LOGIN.layout }, }, + { + path: ROUTE_CONFIG.OAUTH_REDIRECT.path, + element: , + handle: { layout: ROUTE_CONFIG.OAUTH_REDIRECT.layout }, + }, ], }, { diff --git a/src/app/routes/config/route-config.ts b/src/app/routes/config/route-config.ts index 30c7a56..1c679b3 100644 --- a/src/app/routes/config/route-config.ts +++ b/src/app/routes/config/route-config.ts @@ -31,6 +31,10 @@ export const ROUTE_CONFIG: Record = { path: ROUTER_PATH.REALTOR_CERTIFICATION, layout: 'Auth', }, + OAUTH_REDIRECT: { + path: ROUTER_PATH.OAUTH_REDIRECT, + layout: 'Auth', + }, REALTOR: { path: ROUTER_PATH.REALTOR, requiresRealtor: true, // 공인중개사만 접근 가능 diff --git a/src/entities/auth/apis/get-ticket.api.ts b/src/entities/auth/apis/get-ticket.api.ts new file mode 100644 index 0000000..5e7d8e7 --- /dev/null +++ b/src/entities/auth/apis/get-ticket.api.ts @@ -0,0 +1,16 @@ +import { fetchInstance } from '@/shared'; + +export const GET_TICKET_API_PATH = '/api/auth/ticket'; + +interface GetTicketApiResponse { + refreshToken: string; +} + +export const getTicketApi = async (ticket: string): Promise => { + const response = await fetchInstance.get(GET_TICKET_API_PATH, { + params: { + ticket, + }, + }); + return response.data; +}; diff --git a/src/entities/auth/apis/index.ts b/src/entities/auth/apis/index.ts new file mode 100644 index 0000000..5cbea31 --- /dev/null +++ b/src/entities/auth/apis/index.ts @@ -0,0 +1 @@ +export * from './get-ticket.api'; diff --git a/src/entities/auth/hooks/getAuthTicket.ts b/src/entities/auth/hooks/getAuthTicket.ts new file mode 100644 index 0000000..0dbd3f2 --- /dev/null +++ b/src/entities/auth/hooks/getAuthTicket.ts @@ -0,0 +1,14 @@ +import { useQuery } from '@tanstack/react-query'; + +import { getTicketApi } from '../apis'; + +export const GetAuthTicketQueryKey = { + ticket: (ticket: string) => ['ticket', ticket], +}; + +export const useGetAuthTicket = (ticket: string) => { + return useQuery({ + queryKey: GetAuthTicketQueryKey.ticket(ticket), + queryFn: () => getTicketApi(ticket), + }); +}; diff --git a/src/entities/auth/hooks/index.ts b/src/entities/auth/hooks/index.ts new file mode 100644 index 0000000..df60f1b --- /dev/null +++ b/src/entities/auth/hooks/index.ts @@ -0,0 +1 @@ +export * from './getAuthTicket'; diff --git a/src/entities/auth/index.ts b/src/entities/auth/index.ts index e69de29..fa88d83 100644 --- a/src/entities/auth/index.ts +++ b/src/entities/auth/index.ts @@ -0,0 +1,2 @@ +export * from './apis'; +export * from './hooks'; diff --git a/src/features/login/components/common/buttons/KakaoLoginButton.tsx b/src/features/login/components/common/buttons/KakaoLoginButton.tsx index 4dc0621..67aefd7 100644 --- a/src/features/login/components/common/buttons/KakaoLoginButton.tsx +++ b/src/features/login/components/common/buttons/KakaoLoginButton.tsx @@ -1,10 +1,19 @@ -import { Button } from '@/shared'; +import { BASE_URL, Button } from '@/shared'; import KakaoSymbol from '../../../_assets/kakao-symbol.webp'; export const KakaoLoginButton = () => { + const kakaoLogin = () => { + // OAuth 서버로 리다이렉트하되, 리다이렉트 URL을 명시적으로 지정 + const redirectUri = encodeURIComponent(`${window.location.origin}/oauth/redirect`); + window.location.href = `${BASE_URL}/oauth2/authorization/kakao?redirect_uri=${redirectUri}`; + }; + return ( - diff --git a/src/pages/index.ts b/src/pages/index.ts index 5d0aa3c..ab03dd7 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -2,3 +2,4 @@ export * from './main'; export * from './realtor'; export * from './login'; export * from './diagnostics'; +export * from './oauth'; diff --git a/src/pages/oauth/OAuthRedirectPage.tsx b/src/pages/oauth/OAuthRedirectPage.tsx new file mode 100644 index 0000000..0569a70 --- /dev/null +++ b/src/pages/oauth/OAuthRedirectPage.tsx @@ -0,0 +1,104 @@ +import { useEffect, useState } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; + +import { useGetAuthTicket } from '@/entities/auth'; + +import { ROUTER_PATH } from '@/shared'; + +export default function OAuthRedirectPage() { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const [error, setError] = useState(null); + + // URL에서 ticket 파라미터 추출 + const ticket = searchParams.get('ticket'); + const oauthError = searchParams.get('error'); + const errorDescription = searchParams.get('error_description'); + + useEffect(() => { + if (oauthError) { + setError(errorDescription || `OAuth 인증 오류: ${oauthError}`); + } else if (!ticket) { + setError('인증 토큰이 없습니다. 다시 로그인해주세요.'); + } + }, [oauthError, errorDescription, ticket]); + + // ticket이 있고 OAuth 에러가 없을 때만 API 호출 + const shouldCallApi = !!ticket && !oauthError; + const { + data: authData, + error: apiError, + isLoading, + } = useGetAuthTicket(shouldCallApi ? ticket : ''); + + // API 에러 처리 + useEffect(() => { + if (apiError) { + console.error('API 호출 중 오류:', apiError); + setError('인증 검증에 실패했습니다. 다시 시도해주세요.'); + } + }, [apiError]); + + // 성공 시 처리 + useEffect(() => { + if (authData && !isLoading && !apiError && shouldCallApi) { + console.log('OAuth 인증 성공!', authData); + + // refreshToken을 localStorage에 저장 + localStorage.setItem('refreshToken', authData.refreshToken); + + // 메인 페이지로 이동 + navigate(ROUTER_PATH.MAIN, { replace: true }); + } + }, [authData, isLoading, apiError, navigate, shouldCallApi]); + + if (isLoading && shouldCallApi) { + return ( +
+
+
+
+
+

인증 처리 중...

+

잠시만 기다려주세요.

+
+
+ ); + } + + if (error) { + return ( +
+
+
+
+ + + +
+
+

인증 실패

+

{error}

+ +
+
+ ); + } + + return null; +} diff --git a/src/pages/oauth/index.ts b/src/pages/oauth/index.ts new file mode 100644 index 0000000..2127646 --- /dev/null +++ b/src/pages/oauth/index.ts @@ -0,0 +1 @@ +export { default as OAuthRedirectPage } from './OAuthRedirectPage'; diff --git a/src/shared/constants/router-path.ts b/src/shared/constants/router-path.ts index bceeeeb..3b0f1af 100644 --- a/src/shared/constants/router-path.ts +++ b/src/shared/constants/router-path.ts @@ -3,6 +3,7 @@ export const ROUTER_PATH = { MAIN: '/', REALTOR: '/realtor', LOGIN: '/login', + OAUTH_REDIRECT: '/oauth/redirect', RISK_DIAGNOSTICS: '/diagnostics/risk', RELIABILITY_DIAGNOSTICS: '/diagnostics/reliability', REALTOR_LOGIN: '/realtor/login', diff --git a/src/shared/libs/fetch-instance.ts b/src/shared/libs/fetch-instance.ts index eed8f57..74c88ae 100644 --- a/src/shared/libs/fetch-instance.ts +++ b/src/shared/libs/fetch-instance.ts @@ -1,10 +1,9 @@ import { initInstance } from './axios-instance'; -export const BASE_URL = 'http://localhost:8080'; +export const BASE_URL = 'http://43.200.101.253'; export const fetchInstance = initInstance({ baseURL: BASE_URL, - withCredentials: true, headers: { 'Content-Type': 'application/json', },