- index.html에 Meta Pixel 스크립트 및 noscript 폴백 추가 (PageView 자동 추적) - 전환 추적 유틸 추가 (trackViewContent, trackCompleteRegistration, trackFirstVideoCreated) — 서버 CAPI와 동일 event_id로 픽셀 발화해 중복제거 - 랜딩 진입 시 UTM 파라미터 first-touch 캡처 (localStorage 보존, 가입 전환 시 서버로 전달 후 제거) - 카카오 로그인 콜백에서 is_new_user 판정 시 CompleteRegistration 발화 (토큰 리다이렉트 경로 + code 콜백 경로 모두 처리) - 영상 생성 완료 시 FirstVideoCreated 발화 - KakaoCallbackResponse 타입을 백엔드 LoginResponse 구조에 맞게 수정 (user 객체 제거, is_new_user 평탄화) - logout이 refresh_token을 body로 전달하도록 수정, 실패해도 로컬 데이터는 항상 삭제되도록 try/finally 처리 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
110 lines
3.1 KiB
TypeScript
Executable File
110 lines
3.1 KiB
TypeScript
Executable File
|
|
import React, { useState, useEffect } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { getKakaoLoginUrl, kakaoCallback, trackCompleteRegistration } from '../../utils/api';
|
|
|
|
interface LoginSectionProps {
|
|
onBack: () => void;
|
|
onLogin: () => void;
|
|
}
|
|
|
|
const LoginSection: React.FC<LoginSectionProps> = ({ onBack, onLogin }) => {
|
|
const { t } = useTranslation();
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// 카카오 콜백 처리 (URL에서 code 파라미터 확인)
|
|
useEffect(() => {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const code = urlParams.get('code');
|
|
|
|
if (code) {
|
|
handleKakaoCallback(code);
|
|
}
|
|
}, []);
|
|
|
|
const handleKakaoCallback = async (code: string) => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await kakaoCallback(code);
|
|
|
|
// 신규 가입이면 Meta CompleteRegistration 전환 이벤트 발화
|
|
// (서버가 계정당 최초 1회 판정 — App.tsx 경로와 중복 호출돼도 무해)
|
|
if (response.is_new_user) {
|
|
await trackCompleteRegistration();
|
|
}
|
|
|
|
// URL에서 code 파라미터 제거
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.delete('code');
|
|
window.history.replaceState({}, document.title, url.pathname);
|
|
|
|
// 로그인 성공
|
|
onLogin();
|
|
} catch (err) {
|
|
console.error('Kakao callback failed:', err);
|
|
setError(t('login.kakaoLoginFailed'));
|
|
|
|
// URL에서 code 파라미터 제거
|
|
const url = new URL(window.location.href);
|
|
url.searchParams.delete('code');
|
|
window.history.replaceState({}, document.title, url.pathname);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleKakaoLogin = async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await getKakaoLoginUrl();
|
|
|
|
// 카카오 로그인 페이지로 리다이렉트
|
|
window.location.href = response.auth_url;
|
|
} catch (err) {
|
|
console.error('Failed to get Kakao login URL:', err);
|
|
setError(t('login.loginUrlFailed'));
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="login-container">
|
|
{/* Back Button */}
|
|
<button onClick={onBack} className="login-back-btn" disabled={isLoading}>
|
|
<img src="/assets/images/icon-back.svg" alt="Back" />
|
|
<span>{t('login.back')}</span>
|
|
</button>
|
|
|
|
<div className="login-content">
|
|
{/* Logo */}
|
|
<div className="login-logo">
|
|
<img src="/assets/images/ADO2_with Slogan_white.svg" alt="ADO2" />
|
|
</div>
|
|
|
|
{/* Error Message */}
|
|
{error && (
|
|
<div className="login-error">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Kakao Login Button */}
|
|
<button
|
|
onClick={handleKakaoLogin}
|
|
className="btn-kakao"
|
|
disabled={isLoading}
|
|
>
|
|
{isLoading ? t('login.loggingIn') : t('login.kakaoStart')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default LoginSection;
|