Files
a301_client/src/components/DownloadSection.jsx
tolelom 4e0716c1cb refactor: components/ 정리
- ConfirmProvider useMemo 불필요한 래핑 제거
- DownloadAdmin useCallback 적용, toast 중복 제거, eslint-disable 정리
- UserAdmin useCallback 적용, PAGE_SIZE 컴포넌트 밖으로 이동, 페이지네이션 버튼 가독성 개선
- UploadForm 에러 처리 fail 헬퍼로 중복 제거
- DownloadSection 후행 빈 줄 제거

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 19:17:17 +09:00

117 lines
3.6 KiB
JavaScript

import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../context/useAuth';
import { getDownloadInfo } from '../api/download';
import { createLaunchTicket } from '../api/auth';
import './DownloadSection.css';
export default function DownloadSection() {
const [info, setInfo] = useState(null);
const [ready, setReady] = useState(false);
const [loadError, setLoadError] = useState(false);
const [launched, setLaunched] = useState(false);
const [launching, setLaunching] = useState(false);
const { user } = useAuth();
const navigate = useNavigate();
const loadInfo = () => {
setReady(false);
setLoadError(false);
getDownloadInfo()
.then((data) => { setInfo(data); setReady(true); })
.catch(() => { setLoadError(true); setReady(true); });
};
// eslint-disable-next-line react-hooks/set-state-in-effect -- initial data fetch on mount
useEffect(() => { loadInfo(); }, []);
const handlePlay = async () => {
if (!user) {
navigate('/login');
return;
}
setLaunching(true);
// JWT를 URL에 직접 노출하지 않고, 일회용 티켓을 발급받아 전달
try {
const ticket = await createLaunchTicket();
window.location.href = 'a301://launch?token=' + encodeURIComponent(ticket);
} catch {
// 티켓 발급 실패 시 로그인 유도
navigate('/login');
return;
}
// 런처가 실행되지 않았을 수 있으므로 안내 표시
setLaunched(true);
setLaunching(false);
};
const handleDownloadLauncher = () => {
if (info?.launcherUrl) {
const a = document.createElement('a');
a.href = info.launcherUrl;
a.download = 'launcher.exe';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
};
if (!ready) {
return (
<section className="download-section">
<div className="download-content">
<h2 className="download-title">One of the plans</h2>
<p className="download-meta">불러오는 ...</p>
</div>
</section>
);
}
return (
<section className="download-section">
<div className="download-content">
<h2 className="download-title">One of the plans</h2>
{info ? (
<>
<p className="download-meta">
{info.version} &middot; {info.fileSize}
</p>
<button onClick={handlePlay} className="btn-play" disabled={launching}>
{launching ? '준비 중...' : '게임 시작'}
</button>
{info.launcherUrl && (
<button onClick={handleDownloadLauncher} className="btn-launcher-download">
런처 다운로드
</button>
)}
{launched ? (
<p className="launch-hint launch-hint-active">
게임이 실행되지 않나요? 런처를 다운로드한 실행해주세요.
</p>
) : (
<p className="launch-hint">
처음이거나 게임이 실행되지 않으면 런처를 다운로드해주세요.
</p>
)}
</>
) : (
<>
<p className="download-preparing">
{loadError
? '서버에 연결할 수 없습니다.'
: '런처 준비 중입니다. 잠시 후 다시 확인해주세요.'}
</p>
{loadError && (
<button onClick={loadInfo} className="btn-launcher-download">
다시 시도
</button>
)}
</>
)}
</div>
</section>
);
}