bug fix
parent
dc42d26e6b
commit
61892a5d93
41
index.css
41
index.css
|
|
@ -7577,6 +7577,47 @@
|
||||||
background-color: rgba(255, 255, 255, 0.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.social-connect-spinner {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
margin: 0 auto 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-spinner-svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
animation: social-spinner-rotate 1.5s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-spinner-svg circle {
|
||||||
|
stroke: #2dd4bf;
|
||||||
|
stroke-linecap: round;
|
||||||
|
stroke-dasharray: 90, 150;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
animation: social-spinner-dash 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes social-spinner-rotate {
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes social-spinner-dash {
|
||||||
|
0% {
|
||||||
|
stroke-dasharray: 1, 150;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke-dasharray: 90, 150;
|
||||||
|
stroke-dashoffset: -35;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dasharray: 90, 150;
|
||||||
|
stroke-dashoffset: -124;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
My Info Page (내 정보)
|
My Info Page (내 정보)
|
||||||
============================================ */
|
============================================ */
|
||||||
|
|
|
||||||
15
src/App.tsx
15
src/App.tsx
|
|
@ -10,6 +10,7 @@ import LoginSection from './pages/Login/LoginSection';
|
||||||
import GenerationFlow from './pages/Dashboard/GenerationFlow';
|
import GenerationFlow from './pages/Dashboard/GenerationFlow';
|
||||||
import SocialConnectSuccess from './pages/Social/SocialConnectSuccess';
|
import SocialConnectSuccess from './pages/Social/SocialConnectSuccess';
|
||||||
import SocialConnectError from './pages/Social/SocialConnectError';
|
import SocialConnectError from './pages/Social/SocialConnectError';
|
||||||
|
import YouTubeOAuthCallback from './pages/Social/YouTubeOAuthCallback';
|
||||||
import { crawlUrl, autocomplete, kakaoCallback, isLoggedIn, saveTokens, AutocompleteRequest } from './utils/api';
|
import { crawlUrl, autocomplete, kakaoCallback, isLoggedIn, saveTokens, AutocompleteRequest } from './utils/api';
|
||||||
import { CrawlingResponse } from './types/api';
|
import { CrawlingResponse } from './types/api';
|
||||||
|
|
||||||
|
|
@ -102,14 +103,19 @@ const App: React.FC = () => {
|
||||||
const accessToken = urlParams.get('access_token');
|
const accessToken = urlParams.get('access_token');
|
||||||
const refreshToken = urlParams.get('refresh_token');
|
const refreshToken = urlParams.get('refresh_token');
|
||||||
const code = urlParams.get('code');
|
const code = urlParams.get('code');
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
|
||||||
|
// Social OAuth 콜백 경로는 여기서 처리하지 않음 (백엔드에서 처리)
|
||||||
|
const isSocialOAuthCallback = currentPath.includes('/social/oauth/') ||
|
||||||
|
currentPath.includes('/social/connect/');
|
||||||
|
|
||||||
// 백엔드에서 토큰을 URL 파라미터로 전달한 경우
|
// 백엔드에서 토큰을 URL 파라미터로 전달한 경우
|
||||||
if (accessToken && refreshToken && !isProcessingCallback) {
|
if (accessToken && refreshToken && !isProcessingCallback) {
|
||||||
setIsProcessingCallback(true);
|
setIsProcessingCallback(true);
|
||||||
handleTokenCallback(accessToken, refreshToken);
|
handleTokenCallback(accessToken, refreshToken);
|
||||||
}
|
}
|
||||||
// 기존 code 방식 (Redirect URI가 프론트엔드인 경우)
|
// 기존 code 방식 (Redirect URI가 프론트엔드인 경우) - Social OAuth는 제외
|
||||||
else if (code && !isProcessingCallback) {
|
else if (code && !isProcessingCallback && !isSocialOAuthCallback) {
|
||||||
setIsProcessingCallback(true);
|
setIsProcessingCallback(true);
|
||||||
handleKakaoCallback(code);
|
handleKakaoCallback(code);
|
||||||
}
|
}
|
||||||
|
|
@ -333,6 +339,11 @@ const App: React.FC = () => {
|
||||||
// Social OAuth 콜백 페이지 처리
|
// Social OAuth 콜백 페이지 처리
|
||||||
const pathname = window.location.pathname;
|
const pathname = window.location.pathname;
|
||||||
|
|
||||||
|
// YouTube OAuth 콜백 처리 (Google에서 리다이렉트)
|
||||||
|
if (pathname === '/social/oauth/youtube/callback') {
|
||||||
|
return <YouTubeOAuthCallback />;
|
||||||
|
}
|
||||||
|
|
||||||
if (pathname === '/social/connect/success') {
|
if (pathname === '/social/connect/success') {
|
||||||
return <SocialConnectSuccess />;
|
return <SocialConnectSuccess />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const API_URL = import.meta.env.VITE_API_URL || 'http://40.82.133.44';
|
||||||
|
|
||||||
|
const YouTubeOAuthCallback: React.FC = () => {
|
||||||
|
const [status, setStatus] = useState<'processing' | 'error'>('processing');
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleCallback = async () => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const code = urlParams.get('code');
|
||||||
|
const state = urlParams.get('state');
|
||||||
|
const error = urlParams.get('error');
|
||||||
|
|
||||||
|
// OAuth 에러 체크
|
||||||
|
if (error) {
|
||||||
|
console.error('[YouTube OAuth] Error from Google:', error);
|
||||||
|
setStatus('error');
|
||||||
|
setErrorMessage('Google 인증이 취소되었거나 실패했습니다.');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/social/connect/error?platform=youtube&error=' + encodeURIComponent(error);
|
||||||
|
}, 1500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
console.error('[YouTube OAuth] No code received');
|
||||||
|
setStatus('error');
|
||||||
|
setErrorMessage('인증 코드를 받지 못했습니다.');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/social/connect/error?platform=youtube&error=no_code';
|
||||||
|
}, 1500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 백엔드의 YouTube OAuth 콜백 엔드포인트로 code 전달
|
||||||
|
const callbackUrl = `${API_URL}/social/oauth/youtube/callback?code=${encodeURIComponent(code)}${state ? `&state=${encodeURIComponent(state)}` : ''}`;
|
||||||
|
|
||||||
|
console.log('[YouTube OAuth] Forwarding to backend:', callbackUrl);
|
||||||
|
|
||||||
|
// 백엔드로 리다이렉트 (백엔드가 처리 후 /social/connect/success 또는 /social/connect/error로 리다이렉트)
|
||||||
|
window.location.href = callbackUrl;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[YouTube OAuth] Failed to process callback:', err);
|
||||||
|
setStatus('error');
|
||||||
|
setErrorMessage('YouTube 연결 처리 중 오류가 발생했습니다.');
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = '/social/connect/error?platform=youtube&error=processing_failed';
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleCallback();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="social-connect-page">
|
||||||
|
<div className="social-connect-card">
|
||||||
|
{status === 'processing' ? (
|
||||||
|
<>
|
||||||
|
<div className="social-connect-spinner">
|
||||||
|
<svg viewBox="0 0 50 50" className="social-spinner-svg">
|
||||||
|
<circle cx="25" cy="25" r="20" fill="none" strokeWidth="4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 className="social-connect-title">YouTube 연결 중...</h1>
|
||||||
|
<p className="social-connect-message">잠시만 기다려주세요.</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="social-connect-icon error">
|
||||||
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<line x1="15" y1="9" x2="9" y2="15" />
|
||||||
|
<line x1="9" y1="9" x2="15" y2="15" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 className="social-connect-title">연결 실패</h1>
|
||||||
|
<p className="social-connect-message">{errorMessage}</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default YouTubeOAuthCallback;
|
||||||
Loading…
Reference in New Issue