235 lines
9.1 KiB
HTML
235 lines
9.1 KiB
HTML
<!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>
|