/** * 프로필 페이지 * 프로필 정보 및 마인크래프트 계정 연동 */ import React, { useState, useEffect } from 'react'; import { Link, useNavigate, useLocation } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { ArrowLeft, Copy, Check, Gamepad2, Link as LinkIcon, Unlink, RefreshCw, User, Mail } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { io } from 'socket.io-client'; export default function ProfilePage() { const navigate = useNavigate(); const location = useLocation(); const { user, isLoggedIn, loading, checkAuth, logout } = useAuth(); const [linkStatus, setLinkStatus] = useState(null); const [linkToken, setLinkToken] = useState(null); const [command, setCommand] = useState(''); const [copied, setCopied] = useState(false); const [isLoading, setIsLoading] = useState(false); const [polling, setPolling] = useState(false); const [showUnlinkDialog, setShowUnlinkDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false); // 로그인 체크 (loading 완료 후에만) useEffect(() => { if (!loading && !isLoggedIn) { navigate('/login', { state: { from: location.pathname } }); } }, [loading, isLoggedIn, navigate, location.pathname]); // 연동 상태 확인 useEffect(() => { fetchLinkStatus(); }, []); // 폴링 (연동 대기 중일 때) useEffect(() => { if (!polling) return; const interval = setInterval(async () => { const status = await fetchLinkStatus(); if (status?.linked) { setPolling(false); setLinkToken(null); setCommand(''); // 유저 정보 새로고침 await checkAuth(); } }, 3000); return () => clearInterval(interval); }, [polling]); // 연동 대기 중 새로고침 방지 + 토큰 무효화 useEffect(() => { if (!linkToken) return; const handleBeforeUnload = (e) => { // 토큰 무효화 API 호출 (sendBeacon으로 페이지 이탈 전 전송) const token = localStorage.getItem('token'); navigator.sendBeacon('/link/cancel', JSON.stringify({ authToken: token })); e.preventDefault(); e.returnValue = '연동이 진행 중입니다. 페이지를 떠나시겠습니까?'; return e.returnValue; }; window.addEventListener('beforeunload', handleBeforeUnload); return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [linkToken]); // 소켓으로 닉네임 변경 감지 시 동기화 useEffect(() => { if (!linkStatus?.minecraftUuid) return; const socket = io(window.location.origin, { path: '/socket.io' }); socket.on('status', async (status) => { if (status?.online && status?.players?.list) { const playerInGame = status.players.list.find(p => p.uuid === linkStatus.minecraftUuid); // 게임 내 닉네임과 현재 저장된 닉네임이 다르면 동기화 if (playerInGame && playerInGame.name !== user?.name) { // fetchLinkStatus가 DB 업데이트 → 완료 후 checkAuth로 user.name 갱신 await fetchLinkStatus(); await checkAuth(); } } }); return () => socket.disconnect(); }, [linkStatus?.minecraftUuid, user?.name]); const fetchLinkStatus = async () => { try { const token = localStorage.getItem('token'); const res = await fetch('/link/status', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); setLinkStatus(data); return data; } catch (error) { console.error('연동 상태 확인 실패:', error); return null; } }; const handleRequestLink = async () => { setIsLoading(true); try { const token = localStorage.getItem('token'); const res = await fetch('/link/request', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); if (data.linked) { setLinkStatus({ linked: true, minecraftName: data.minecraftName }); } else { setLinkToken(data.token); setCommand(data.command); setPolling(true); } } catch (error) { console.error('연동 요청 실패:', error); } finally { setIsLoading(false); } }; const handleCopyCommand = () => { navigator.clipboard.writeText(command); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const handleUnlink = async () => { try { const token = localStorage.getItem('token'); await fetch('/link/unlink', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); setLinkStatus({ linked: false }); setShowUnlinkDialog(false); await checkAuth(); } catch (error) { console.error('연동 해제 실패:', error); } }; const handleDelete = async () => { try { const token = localStorage.getItem('token'); const res = await fetch('/auth/delete', { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); if (res.ok) { logout(); navigate('/'); } else { console.error('탈퇴 실패'); } } catch (error) { console.error('탈퇴 실패:', error); } }; if (!user) return null; return (
{/* 헤더 */}

프로필

계정 정보 및 마인크래프트 연동

{/* 프로필 정보 카드 */}

기본 정보

프로필 { e.target.src = 'https://via.placeholder.com/96'; }} />

닉네임

{user.name}

이메일

{user.email}

{/* 마인크래프트 연동 카드 */}

마인크래프트 계정 연동

{linkStatus?.linked ? ( // 연동 완료 상태

연동 완료

마인크래프트 계정: {linkStatus.minecraftName}

) : linkToken ? ( // 연동 대기 상태

마인크래프트에서 아래 명령어를 입력하세요:

{command}
연동 대기 중... (10분 후 만료)
) : ( // 연동 안됨 상태

마인크래프트 계정을 연동하면 게임 내 닉네임과 스킨이 프로필에 적용됩니다.

)}
{/* 계정 관리 섹션 */}

계정 관리

회원 탈퇴 시 모든 데이터가 삭제되며 복구할 수 없습니다.

{/* 연동 해제 다이얼로그 */} {showUnlinkDialog && ( <> setShowUnlinkDialog(false)} />

연동 해제

마인크래프트 계정 연동을 해제하시겠습니까?

)}
{/* 탈퇴 확인 다이얼로그 */} {showDeleteDialog && ( <> setShowDeleteDialog(false)} />

⚠️ 회원 탈퇴

정말 탈퇴하시겠습니까? 모든 데이터가 삭제되며 복구할 수 없습니다.

)}
); }