'use client'; import { useEffect, useRef, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { fetchApi } from '@/lib/utils/client'; import { SnsConnectResponse } from '@/types/sns'; import './style.scss'; const STATE_STORAGE_KEY = 'antooza-sns-google-state'; /** * Google OAuth Authorization Code 콜백. * * 1. URL ?code= ?state= ?error= 수신 * 2. state 검증 (sessionStorage 저장값과 비교) * 3. POST /api/mypage/sns/google/connect (code, redirectUri) * 4. 성공 → /sns-link?connected=google * 5. 실패 → /sns-link?error={message} */ export default function GoogleSnsCallbackPage() { const router = useRouter(); const searchParams = useSearchParams(); const [phase, setPhase] = useState<'verifying' | 'success' | 'error'>('verifying'); const [errorMessage, setErrorMessage] = useState(null); const handledRef = useRef(false); useEffect(() => { // React StrictMode 등에서 effect 가 두 번 호출돼 code 가 재사용될 수 있음 → ref 가드 if (handledRef.current) { return; } handledRef.current = true; const code = searchParams.get('code'); const state = searchParams.get('state'); const oauthError = searchParams.get('error'); const finishWithError = (message: string) => { setErrorMessage(message); setPhase('error'); window.setTimeout(() => { router.replace(`/sns-link?error=${encodeURIComponent(message)}`); }, 1200); }; if (oauthError) { finishWithError(oauthError); return; } if (!code) { finishWithError('Authorization code 가 없습니다.'); return; } // state 검증 let savedState: string|null = null; try { savedState = window.sessionStorage.getItem(STATE_STORAGE_KEY); window.sessionStorage.removeItem(STATE_STORAGE_KEY); } catch { // silent } if (!state || !savedState || state !== savedState) { finishWithError('state 가 일치하지 않습니다. 다시 시도해주세요.'); return; } const redirectUri = `${window.location.origin}/auth/sns/google/callback`; (async () => { try { const res = await fetchApi('/api/mypage/sns/google/connect', { method: 'POST', body: { code, redirectUri }, silent: true }); if (res.success) { setPhase('success'); window.setTimeout(() => { router.replace('/sns-link?connected=google'); }, 600); } else { finishWithError(res.message || '연동에 실패했습니다.'); } } catch { finishWithError('네트워크 오류로 연동에 실패했습니다.'); } })(); }, [router, searchParams]); return (
{phase === 'verifying' && ( <>
); }