diff --git a/src/components/admin/AdminCommon.css b/src/components/admin/AdminCommon.css index 8a0bf0d..d2e1c6e 100644 --- a/src/components/admin/AdminCommon.css +++ b/src/components/admin/AdminCommon.css @@ -187,6 +187,78 @@ background: rgba(229, 115, 115, 0.08); } +/* File input */ +.admin-input-file { + padding: 8px 0; + color: rgba(255, 255, 255, 0.7); + font-size: 0.875rem; + cursor: pointer; +} + +.admin-input-file:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +/* Field hint */ +.admin-field-hint { + font-size: 0.75rem; + color: rgba(255, 255, 255, 0.3); +} + +/* Upload progress */ +.admin-upload-progress { + display: flex; + align-items: center; + gap: 10px; +} + +.admin-upload-bar { + flex: 1; + height: 6px; + background: #BACDB0; + border-radius: 3px; + transition: width 0.2s; +} + +.admin-upload-pct { + font-size: 0.8rem; + color: rgba(255, 255, 255, 0.5); + min-width: 36px; + text-align: right; +} + +/* Current build info */ +.admin-current-build { + display: flex; + flex-direction: column; + gap: 8px; + padding: 14px 16px; + background: rgba(186, 205, 176, 0.05); + border: 1px solid rgba(186, 205, 176, 0.12); + border-radius: 8px; +} + +.admin-meta-row { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.admin-meta-item { + font-size: 0.78rem; + color: rgba(186, 205, 176, 0.8); + background: rgba(186, 205, 176, 0.08); + padding: 3px 10px; + border-radius: 4px; + border: 1px solid rgba(186, 205, 176, 0.15); +} + +.admin-meta-hash { + font-family: monospace; + cursor: help; +} + /* Role badge */ .admin-role-badge { font-size: 0.7rem; diff --git a/src/components/admin/DownloadAdmin.jsx b/src/components/admin/DownloadAdmin.jsx index 103169d..87a8f73 100644 --- a/src/components/admin/DownloadAdmin.jsx +++ b/src/components/admin/DownloadAdmin.jsx @@ -1,69 +1,112 @@ import { useState, useEffect } from 'react'; import { getDownloadInfo } from '../../api/download'; -import { apiFetch } from '../../api/client'; import './AdminCommon.css'; +const BASE = import.meta.env.VITE_API_BASE_URL || ''; + export default function DownloadAdmin() { - const [form, setForm] = useState({ url: '', version: '', fileName: '', fileSize: '', fileHash: '' }); - const [loading, setLoading] = useState(false); - const [saved, setSaved] = useState(false); + 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((data) => setForm({ - url: data.url, - version: data.version, - fileName: data.fileName, - fileSize: data.fileSize, - fileHash: data.fileHash ?? '', - })) - .catch(() => {}); + getDownloadInfo().then(setInfo).catch(() => {}); }, []); - const handleSubmit = async (e) => { - e.preventDefault(); - setLoading(true); + const handleFileChange = (e) => { + setFile(e.target.files[0] || null); setError(''); - try { - await apiFetch('/api/download/info', { - method: 'PUT', - body: JSON.stringify(form), - }); - setSaved(true); - setTimeout(() => setSaved(false), 2000); - } catch (err) { - setError(err.message || '저장에 실패했습니다.'); - } finally { - setLoading(false); - } + setProgress(0); }; - const field = (label, key, placeholder) => ( -