Загрузить файлы в «static»
This commit is contained in:
234
static/profile.html
Normal file
234
static/profile.html
Normal 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>
|
||||
Reference in New Issue
Block a user