From 7e4e5a180130b3af46cf55c642cd0594ceaaa68e Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Tue, 24 Feb 2026 23:34:17 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=9F=B0=EC=B2=98/=EA=B2=8C=EC=9E=84?= =?UTF-8?q?=20=EB=B6=84=EB=A6=AC=20=EB=B0=B0=ED=8F=AC=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 런처 미설치 시 launcher.exe만 다운로드 (게임 전체 zip 아님) - 관리자 페이지에 런처/게임 별도 업로드 섹션 분리 - 힌트 문구 업데이트 Co-Authored-By: Claude Sonnet 4.6 --- .claude/settings.local.json | 7 ++ src/components/DownloadSection.jsx | 10 +- src/components/admin/AdminCommon.css | 22 +++++ src/components/admin/DownloadAdmin.jsx | 132 +++++++++++++++---------- 4 files changed, 116 insertions(+), 55 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..2f1225b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(cd E:/projects/a301_launcher && \"C:/Users/98kim/sdk/go1.25.1/bin/go.exe\" build -ldflags=\"-H windowsgui -s -w\" -o launcher.exe . 2>&1)" + ] + } +} diff --git a/src/components/DownloadSection.jsx b/src/components/DownloadSection.jsx index 0fbe6fe..fd7a1cf 100644 --- a/src/components/DownloadSection.jsx +++ b/src/components/DownloadSection.jsx @@ -33,11 +33,11 @@ export default function DownloadSection() { setTimeout(() => { window.removeEventListener('blur', onBlur); setLaunching(false); - if (!launched && info?.url) { - // 런처 미설치 → 자동 다운로드 + if (!launched && info?.launcherUrl) { + // 런처 미설치 → launcher.exe 다운로드 const a = document.createElement('a'); - a.href = info.url; - a.download = ''; + a.href = info.launcherUrl; + a.download = 'launcher.exe'; a.click(); } }, 2000); @@ -58,7 +58,7 @@ export default function DownloadSection() { {launching ? '실행 중...' : '게임 시작'}

- 처음 설치하는 경우 자동으로 다운로드됩니다 + 런처 미설치 시 자동으로 다운로드됩니다. 설치 후 다시 클릭하세요.

) : ( diff --git a/src/components/admin/AdminCommon.css b/src/components/admin/AdminCommon.css index d2e1c6e..174f5c8 100644 --- a/src/components/admin/AdminCommon.css +++ b/src/components/admin/AdminCommon.css @@ -187,6 +187,28 @@ background: rgba(229, 115, 115, 0.08); } +/* Deploy block */ +.admin-deploy-block { + display: flex; + flex-direction: column; + gap: 8px; +} + +.admin-deploy-header { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.admin-deploy-label { + font-size: 0.8rem; + font-weight: 600; + color: rgba(255, 255, 255, 0.5); + text-transform: uppercase; + letter-spacing: 0.06em; +} + /* File input */ .admin-input-file { padding: 8px 0; diff --git a/src/components/admin/DownloadAdmin.jsx b/src/components/admin/DownloadAdmin.jsx index 87a8f73..c3fd922 100644 --- a/src/components/admin/DownloadAdmin.jsx +++ b/src/components/admin/DownloadAdmin.jsx @@ -4,17 +4,12 @@ import './AdminCommon.css'; const BASE = import.meta.env.VITE_API_BASE_URL || ''; -export default function DownloadAdmin() { +function UploadForm({ title, hint, accept, endpoint, onSuccess }) { const [file, setFile] = useState(null); - const [info, setInfo] = useState(null); const [uploading, setUploading] = useState(false); const [progress, setProgress] = useState(0); const [error, setError] = useState(''); - useEffect(() => { - getDownloadInfo().then(setInfo).catch(() => {}); - }, []); - const handleFileChange = (e) => { setFile(e.target.files[0] || null); setError(''); @@ -37,7 +32,7 @@ export default function DownloadAdmin() { xhr.onload = () => { setUploading(false); if (xhr.status >= 200 && xhr.status < 300) { - setInfo(JSON.parse(xhr.responseText)); + onSuccess(JSON.parse(xhr.responseText)); setFile(null); setProgress(0); } else { @@ -53,7 +48,7 @@ export default function DownloadAdmin() { setProgress(0); }; - xhr.open('POST', `${BASE}/api/download/upload?filename=${encodeURIComponent(file.name)}`); + xhr.open('POST', `${BASE}${endpoint}?filename=${encodeURIComponent(file.name)}`); xhr.setRequestHeader('Authorization', `Bearer ${token}`); setUploading(true); setError(''); @@ -61,55 +56,92 @@ export default function DownloadAdmin() { }; return ( -
-

게임 배포 관리

+
+
+ + + {hint} +
- {info && ( -
- 현재 배포 중 -
- {info.version && {info.version}} - {info.fileName && {info.fileName}} - {info.fileSize && {info.fileSize}} - {info.fileHash && ( - - SHA256: {info.fileHash.slice(0, 12)}... - - )} -
+ {uploading && ( +
+
+ {progress}%
)} - -
- - - - A301.exe가 포함된 zip 파일을 선택하세요. 버전·파일명·크기·해시가 자동으로 추출됩니다. - + {error &&

{error}

} + +
+ +
+ + ); +} + +export default function DownloadAdmin() { + const [info, setInfo] = useState(null); + + useEffect(() => { + getDownloadInfo().then(setInfo).catch(() => {}); + }, []); + + return ( +
+

게임 배포 관리

+ + {/* 런처 섹션 */} +
+
+ 런처 + {info?.launcherUrl && ( +
+ {info.launcherSize && {info.launcherSize}} +
+ )}
+ +
- {uploading && ( -
-
- {progress}% -
- )} - - {error &&

{error}

} - -
- + {/* 게임 섹션 */} +
+
+ 게임 + {info?.url && ( +
+ {info.version && {info.version}} + {info.fileName && {info.fileName}} + {info.fileSize && {info.fileSize}} + {info.fileHash && ( + + SHA256: {info.fileHash.slice(0, 12)}... + + )} +
+ )}
- + +
); }