Загрузить файлы в «static»

This commit is contained in:
2026-07-02 14:25:38 +00:00
parent b2d10ec669
commit f5f2f9e3f9
5 changed files with 1759 additions and 0 deletions

234
static/profile.html Normal file
View File

@@ -0,0 +1,234 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Robo Arena — Профиль пилота</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body class="page-app page-profile">
<nav class="topbar">
<div class="topbar-brand">
<span class="logo-icon">🤖</span>
<span>Robo Arena</span> / РСУ
</div>
<span class="topbar-user" id="topbar-email"></span>
<a class="btn btn-sm active-nav" href="/static/profile.html">Профиль</a>
<a class="btn btn-sm" href="/static/dashboard.html">Арена</a>
<button class="btn btn-sm" id="logout-btn">Выйти</button>
</nav>
<div class="main main--profile">
<div class="profile-layout">
<!-- Боковая панель -->
<aside class="profile-sidebar">
<div class="profile-sidebar-glow"></div>
<div class="profile-avatar" id="profile-avatar">👤</div>
<h2 class="profile-sidebar-name" id="sidebar-name">Пилот</h2>
<p class="profile-sidebar-email" id="sidebar-email"></p>
<div class="profile-status" id="profile-status">
<div class="profile-status-label">Заполненность профиля</div>
<div class="profile-progress-wrap">
<div class="profile-progress-bar" id="progress-bar" style="width:0%"></div>
</div>
<div class="profile-status-text" id="status-text">0 из 4 полей</div>
</div>
<div class="profile-sidebar-hints">
<div class="hint-item">
<span class="hint-icon">🛡</span>
<span>Данные нужны для идентификации пилота на арене</span>
</div>
<div class="hint-item">
<span class="hint-icon">🔒</span>
<span>Информация хранится только в вашем аккаунте</span>
</div>
</div>
</aside>
<!-- Форма -->
<div class="profile-form-card">
<div class="profile-form-header">
<div class="profile-badge">⚔ Профиль пилота</div>
<h2>Данные оператора</h2>
<p class="sub">Заполните личную информацию для участия в системе управления боевыми роботами</p>
</div>
<form id="profile-form" autocomplete="on">
<div class="form-section">
<div class="form-section-title">
<span class="form-section-icon">📋</span>
Личные данные
</div>
<div class="form-field">
<label for="full_name">ФИО</label>
<div class="input-wrap">
<span class="input-icon">👤</span>
<input type="text" id="full_name" placeholder="Иванов Иван Иванович" autocomplete="name">
</div>
</div>
<div class="form-row">
<div class="form-field">
<label for="birth_date">Дата рождения</label>
<div class="input-wrap">
<span class="input-icon">🎂</span>
<input type="date" id="birth_date">
</div>
</div>
<div class="form-field">
<label for="gender">Пол</label>
<div class="input-wrap">
<span class="input-icon"></span>
<select id="gender">
<option value="">— выберите —</option>
<option value="male">Мужской</option>
<option value="female">Женский</option>
<option value="other">Не указан</option>
</select>
</div>
</div>
</div>
</div>
<div class="form-section">
<div class="form-section-title">
<span class="form-section-icon">📞</span>
Контакты
</div>
<div class="form-field">
<label for="phone">Телефон</label>
<div class="input-wrap">
<span class="input-icon">📱</span>
<input type="tel" id="phone" placeholder="+7 (999) 123-45-67" autocomplete="tel">
</div>
<span class="field-hint">Формат: +7 или 8, минимум 7 цифр</span>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" id="save-btn">
<span>💾 Сохранить профиль</span>
</button>
<a class="btn btn-ghost btn-back" href="/static/dashboard.html">На арену</a>
</div>
<p class="err-msg" id="error-text"></p>
<p class="ok-msg" id="ok-text"></p>
</form>
</div>
</div>
</div>
<script>
const errEl = document.getElementById("error-text");
const okEl = document.getElementById("ok-text");
const saveBtn = document.getElementById("save-btn");
function updateSidebar(u) {
document.getElementById("topbar-email").textContent = u.email;
document.getElementById("sidebar-email").textContent = u.email;
if (u.full_name) {
document.getElementById("sidebar-name").textContent = u.full_name;
document.getElementById("profile-avatar").textContent = u.full_name.trim()[0].toUpperCase();
}
const fields = [u.full_name, u.birth_date, u.phone, u.gender];
const filled = fields.filter(Boolean).length;
const pct = Math.round((filled / 4) * 100);
document.getElementById("progress-bar").style.width = pct + "%";
document.getElementById("status-text").textContent = `${filled} из 4 полей`;
const statusEl = document.getElementById("profile-status");
if (filled === 4) {
statusEl.classList.add("profile-status--complete");
document.getElementById("status-text").textContent = "Профиль заполнен ✓";
} else {
statusEl.classList.remove("profile-status--complete");
}
}
async function loadMe() {
const res = await fetch("/api/me", { credentials: "include" });
if (!res.ok) { window.location.href = "/static/login.html"; return; }
const u = await res.json();
document.getElementById("full_name").value = u.full_name || "";
document.getElementById("birth_date").value = u.birth_date || "";
document.getElementById("phone").value = u.phone || "";
document.getElementById("gender").value = u.gender || "";
updateSidebar(u);
}
["full_name", "birth_date", "phone", "gender"].forEach(id => {
document.getElementById(id).addEventListener("input", () => {
updateSidebar({
full_name: document.getElementById("full_name").value.trim(),
birth_date: document.getElementById("birth_date").value,
phone: document.getElementById("phone").value.trim(),
gender: document.getElementById("gender").value,
email: document.getElementById("sidebar-email").textContent,
});
});
document.getElementById(id).addEventListener("change", () => {
document.getElementById(id).dispatchEvent(new Event("input"));
});
});
document.getElementById("profile-form").addEventListener("submit", async e => {
e.preventDefault();
errEl.textContent = "";
okEl.textContent = "";
const payload = {
full_name: document.getElementById("full_name").value.trim(),
birth_date: document.getElementById("birth_date").value,
phone: document.getElementById("phone").value.trim(),
gender: document.getElementById("gender").value,
};
saveBtn.disabled = true;
saveBtn.querySelector("span").textContent = "Сохраняем…";
try {
const res = await fetch("/api/me/profile", {
method: "PUT",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(payload)
});
const data = await res.json();
if (!res.ok) {
errEl.textContent = data.detail || "Не удалось сохранить профиль";
return;
}
okEl.textContent = "✓ Профиль сохранён! Переход на арену…";
updateSidebar(data);
setTimeout(() => { window.location.href = "/static/dashboard.html"; }, 1400);
} catch {
errEl.textContent = "Не удалось связаться с сервером";
} finally {
saveBtn.disabled = false;
saveBtn.querySelector("span").textContent = "💾 Сохранить профиль";
}
});
document.getElementById("logout-btn").addEventListener("click", async () => {
await fetch("/api/logout", { method: "POST", credentials: "include" });
window.location.href = "/static/index.html";
});
loadMe();
</script>
</body>
</html>