245 lines
11 KiB
HTML
245 lines
11 KiB
HTML
{{define "content"}}
|
||
<div id="authCheck" class="card text-center">
|
||
<p class="muted">Загрузка...</p>
|
||
</div>
|
||
|
||
<div id="profileContent" style="display:none">
|
||
<!-- Profile header -->
|
||
<div class="card">
|
||
<div class="profile-header">
|
||
<img class="profile-avatar" id="skinPreview" src="" alt="Аватар" onerror="this.src='data:image/svg+xml,<svg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 8 8%27><rect fill=%27%23334155%27 width=%278%27 height=%278%27/></svg>'">
|
||
<div class="profile-info">
|
||
<h2 id="profUsername">—</h2>
|
||
<p class="uuid" id="profUuid">—</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Skin & Cape -->
|
||
<h2 class="section-title">👤 Скин и плащ</h2>
|
||
<div class="grid-2">
|
||
<div class="card">
|
||
<h3>Скин</h3>
|
||
<div id="currentSkin" class="text-center" style="margin-bottom:1rem">
|
||
<img id="skinImg" src="" alt="Скин" style="width:120px;height:160px;image-rendering:pixelated;background:var(--surface-light);border-radius:var(--radius-sm)" onerror="this.style.display='none'">
|
||
<p class="muted text-sm mt-1" id="noSkinMsg">Скин не загружен</p>
|
||
</div>
|
||
<div id="alertSkin" class="alert"></div>
|
||
<form id="skinForm" enctype="multipart/form-data">
|
||
<label for="skinFile">Загрузить скин (PNG, 64×64 или 64×32)</label>
|
||
<input type="file" id="skinFile" name="file" accept=".png">
|
||
<div class="flex gap-1">
|
||
<button type="submit" class="btn btn-sm">Загрузить</button>
|
||
<button type="button" id="deleteSkin" class="btn btn-sm btn-danger">Удалить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="card">
|
||
<h3>Плащ</h3>
|
||
<div id="currentCape" class="text-center" style="margin-bottom:1rem">
|
||
<img id="capeImg" src="" alt="Плащ" style="width:100px;height:160px;image-rendering:pixelated;background:var(--surface-light);border-radius:var(--radius-sm)" onerror="this.style.display='none'">
|
||
<p class="muted text-sm mt-1" id="noCapeMsg">Плащ не загружен</p>
|
||
</div>
|
||
<div id="alertCape" class="alert"></div>
|
||
<form id="capeForm" enctype="multipart/form-data">
|
||
<label for="capeFile">Загрузить плащ (PNG, 64×32)</label>
|
||
<input type="file" id="capeFile" name="file" accept=".png">
|
||
<div class="flex gap-1">
|
||
<button type="submit" class="btn btn-sm">Загрузить</button>
|
||
<button type="button" id="deleteCape" class="btn btn-sm btn-danger">Удалить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Launcher download -->
|
||
<h2 class="section-title">💾 Лаунчер</h2>
|
||
<div class="card">
|
||
<p class="muted">Скачай лаунчер для своей операционной системы. Он автоматически загрузит все файлы модпаков.</p>
|
||
<div class="flex gap-1 mt-2" id="launcherLinks">
|
||
<a href="#" class="btn btn-sm" id="dl-win">🪟 Windows</a>
|
||
<a href="#" class="btn btn-sm" id="dl-mac">🍎 macOS</a>
|
||
<a href="#" class="btn btn-sm" id="dl-lin">🐧 Linux</a>
|
||
</div>
|
||
<p class="text-sm muted mt-2" id="latestVersion">Последняя версия: —</p>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function() {
|
||
const token = localStorage.getItem('token');
|
||
const savedUuid = localStorage.getItem('uuid');
|
||
const savedUsername = localStorage.getItem('username');
|
||
|
||
if (!token) {
|
||
document.getElementById('authCheck').innerHTML = '<h2>🔒 Требуется авторизация</h2><p class="muted">Войди в аккаунт, чтобы увидеть профиль.</p><p class="mt-2"><a href="/login" class="btn">Войти</a></p>';
|
||
return;
|
||
}
|
||
|
||
document.getElementById('authCheck').style.display = 'none';
|
||
document.getElementById('profileContent').style.display = 'block';
|
||
|
||
// Load profile
|
||
loadProfile();
|
||
|
||
async function loadProfile() {
|
||
try {
|
||
const res = await fetch('/api/web/profile/' + savedUuid, {
|
||
headers: {'Authorization': 'Bearer ' + token}
|
||
});
|
||
if (!res.ok) throw new Error('Failed to load');
|
||
const data = await res.json();
|
||
|
||
document.getElementById('profUsername').textContent = data.username || savedUsername || '—';
|
||
document.getElementById('profUuid').textContent = data.uuid || savedUuid || '—';
|
||
|
||
// Skin
|
||
if (data.textures && data.textures.skin) {
|
||
const skinImg = document.getElementById('skinImg');
|
||
const noSkinMsg = document.getElementById('noSkinMsg');
|
||
skinImg.onerror = function() { this.style.display = 'none'; noSkinMsg.style.display = 'block'; };
|
||
skinImg.onload = function() { this.style.display = 'inline'; noSkinMsg.style.display = 'none'; };
|
||
skinImg.src = '/skins/' + data.textures.skin_hash;
|
||
} else {
|
||
document.getElementById('skinImg').style.display = 'none';
|
||
document.getElementById('noSkinMsg').style.display = 'block';
|
||
}
|
||
|
||
// Cape
|
||
if (data.textures && data.textures.cape) {
|
||
const capeImg = document.getElementById('capeImg');
|
||
const noCapeMsg = document.getElementById('noCapeMsg');
|
||
capeImg.onerror = function() { this.style.display = 'none'; noCapeMsg.style.display = 'block'; };
|
||
capeImg.onload = function() { this.style.display = 'inline'; noCapeMsg.style.display = 'none'; };
|
||
capeImg.src = '/skins/' + data.textures.cape_hash;
|
||
} else {
|
||
document.getElementById('capeImg').style.display = 'none';
|
||
document.getElementById('noCapeMsg').style.display = 'block';
|
||
}
|
||
} catch (err) {
|
||
console.error('Profile load error:', err);
|
||
}
|
||
}
|
||
|
||
// Load latest launcher version
|
||
loadLauncherInfo();
|
||
|
||
async function loadLauncherInfo() {
|
||
try {
|
||
const res = await fetch('/api/launcher/latest');
|
||
if (!res.ok) return;
|
||
const data = await res.json();
|
||
document.getElementById('latestVersion').textContent = 'Последняя версия: ' + (data.version || '—');
|
||
if (data.downloads) {
|
||
if (data.downloads.windows) document.getElementById('dl-win').href = data.downloads.windows;
|
||
if (data.downloads.darwin) document.getElementById('dl-mac').href = data.downloads.darwin;
|
||
if (data.downloads.linux) document.getElementById('dl-lin').href = data.downloads.linux;
|
||
}
|
||
} catch (err) {}
|
||
}
|
||
|
||
// Skin upload
|
||
document.getElementById('skinForm').addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
const file = document.getElementById('skinFile').files[0];
|
||
if (!file) return;
|
||
const fd = new FormData();
|
||
fd.append('skin', file);
|
||
const alert = document.getElementById('alertSkin');
|
||
try {
|
||
const res = await fetch('/api/web/profile/skin', {
|
||
method: 'POST',
|
||
headers: {'Authorization': 'Bearer ' + token},
|
||
body: fd
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok) {
|
||
alert.className = 'alert alert-error show';
|
||
alert.textContent = data.error || 'Ошибка загрузки';
|
||
} else {
|
||
alert.className = 'alert alert-success show';
|
||
alert.textContent = 'Скин загружен!';
|
||
loadProfile();
|
||
}
|
||
} catch (err) {
|
||
alert.className = 'alert alert-error show';
|
||
alert.textContent = 'Ошибка сети';
|
||
}
|
||
});
|
||
|
||
// Cape upload
|
||
document.getElementById('capeForm').addEventListener('submit', async function(e) {
|
||
e.preventDefault();
|
||
const file = document.getElementById('capeFile').files[0];
|
||
if (!file) return;
|
||
const fd = new FormData();
|
||
fd.append('cape', file);
|
||
const alert = document.getElementById('alertCape');
|
||
try {
|
||
const res = await fetch('/api/web/profile/cape', {
|
||
method: 'POST',
|
||
headers: {'Authorization': 'Bearer ' + token},
|
||
body: fd
|
||
});
|
||
const data = await res.json();
|
||
if (!res.ok) {
|
||
alert.className = 'alert alert-error show';
|
||
alert.textContent = data.error || 'Ошибка загрузки';
|
||
} else {
|
||
alert.className = 'alert alert-success show';
|
||
alert.textContent = 'Плащ загружен!';
|
||
loadProfile();
|
||
}
|
||
} catch (err) {
|
||
alert.className = 'alert alert-error show';
|
||
alert.textContent = 'Ошибка сети';
|
||
}
|
||
});
|
||
|
||
// Delete skin
|
||
document.getElementById('deleteSkin').addEventListener('click', async function() {
|
||
const alert = document.getElementById('alertSkin');
|
||
try {
|
||
const res = await fetch('/api/web/profile/skin', {
|
||
method: 'DELETE',
|
||
headers: {'Authorization': 'Bearer ' + token}
|
||
});
|
||
if (!res.ok) {
|
||
alert.className = 'alert alert-error show';
|
||
alert.textContent = 'Ошибка удаления';
|
||
} else {
|
||
alert.className = 'alert alert-success show';
|
||
alert.textContent = 'Скин удалён';
|
||
loadProfile();
|
||
}
|
||
} catch (err) {
|
||
alert.className = 'alert alert-error show';
|
||
alert.textContent = 'Ошибка сети';
|
||
}
|
||
});
|
||
|
||
// Delete cape
|
||
document.getElementById('deleteCape').addEventListener('click', async function() {
|
||
const alert = document.getElementById('alertCape');
|
||
try {
|
||
const res = await fetch('/api/web/profile/cape', {
|
||
method: 'DELETE',
|
||
headers: {'Authorization': 'Bearer ' + token}
|
||
});
|
||
if (!res.ok) {
|
||
alert.className = 'alert alert-error show';
|
||
alert.textContent = 'Ошибка удаления';
|
||
} else {
|
||
alert.className = 'alert alert-success show';
|
||
alert.textContent = 'Плащ удалён';
|
||
loadProfile();
|
||
}
|
||
} catch (err) {
|
||
alert.className = 'alert alert-error show';
|
||
alert.textContent = 'Ошибка сети';
|
||
}
|
||
});
|
||
})();
|
||
</script>
|
||
{{end}}
|