Integrate Bootstrap 5 and Bootstrap Icons into UI

- Add Bootstrap 5.3.3 + Icons via CDN to base.html
- Replace 315-line hand-written CSS with 35-line brand overrides
- Update all 13 templates with Bootstrap utility classes:
  - Responsive navbar with mobile hamburger menu
  - Consistent card-based layout for forms and profile
  - Proper button alignment with d-flex and d-grid utilities
  - List groups for data display (profile info)
  - Professional alerts and icons
- No backend changes, no build toolchain needed
- Responsive design works on mobile/tablet/desktop

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
mguschin
2026-03-05 21:05:30 +03:00
parent eea2d84260
commit bd0ff8f449
18 changed files with 486 additions and 338 deletions

View File

@@ -8,7 +8,7 @@ services:
ports:
- "8080:8000"
environment:
- DATABASE_URL=mysql+pymysql://${DB_USER:-evosync}:${DB_PASSWORD:-evosync}@localhost:3306/${DB_NAME:-evosync}
- DATABASE_URL=mysql+pymysql://${DB_USER}:${DB_PASSWORD}@172.25.0.1:3306/${DB_NAME}
- SECRET_KEY=${SECRET_KEY:-change-me-in-production}
- BASE_URL=${BASE_URL:-http://localhost:8080}
volumes:

View File

@@ -2,8 +2,10 @@ fastapi==0.115.0
uvicorn[standard]==0.30.0
sqlalchemy==2.0.35
pymysql==1.1.1
cryptography>=41.0.0
jinja2==3.1.4
python-multipart==0.0.12
passlib[bcrypt]==1.7.4
bcrypt==4.2.0
pydantic-settings==2.5.2
itsdangerous==2.1.2

View File

@@ -7,7 +7,7 @@ class Settings(BaseSettings):
BASE_URL: str = "http://localhost:8000"
PASSWORD_RESET_EXPIRE_MINUTES: int = 60
model_config = {"env_file": ".env"}
model_config = {"env_file": ".env", "case_sensitive": False}
settings = Settings()

View File

@@ -3,24 +3,33 @@ from fastapi.responses import RedirectResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.orm import Session
from web.auth import get_current_user
from web.auth import get_current_user, verify_password, hash_password
from web.database import get_db
from web.models import User
from web.schemas import validate_profile
from web.schemas import validate_profile, validate_reset_password
router = APIRouter()
templates = Jinja2Templates(directory="web/templates")
# VIEW PROFILE
@router.get("/profile")
def profile_view(request: Request, user: User | None = Depends(get_current_user)):
if not user:
return RedirectResponse("/login", 303)
return templates.TemplateResponse("profile.html", {"request": request, "user": user})
return templates.TemplateResponse("profile_view.html", {"request": request, "user": user})
@router.post("/profile")
async def profile_update(
# EDIT PROFILE
@router.get("/profile/edit")
def profile_edit_form(request: Request, user: User | None = Depends(get_current_user)):
if not user:
return RedirectResponse("/login", 303)
return templates.TemplateResponse("profile_edit.html", {"request": request, "user": user})
@router.post("/profile/edit")
async def profile_edit_submit(
request: Request,
db: Session = Depends(get_db),
user: User | None = Depends(get_current_user),
@@ -41,7 +50,7 @@ async def profile_update(
errors.append("Пользователь с таким телефоном уже существует")
if errors:
return templates.TemplateResponse("profile.html", {
return templates.TemplateResponse("profile_edit.html", {
"request": request, "user": user, "errors": errors, "form": data,
})
@@ -50,6 +59,87 @@ async def profile_update(
user.phone = data["phone"].strip()
db.commit()
return templates.TemplateResponse("profile.html", {
return templates.TemplateResponse("profile_edit.html", {
"request": request, "user": user, "success": "Профиль обновлен",
})
# CHANGE PASSWORD
@router.get("/profile/change-password")
def change_password_form(request: Request, user: User | None = Depends(get_current_user)):
if not user:
return RedirectResponse("/login", 303)
return templates.TemplateResponse("profile_change_password.html", {"request": request, "user": user})
@router.post("/profile/change-password")
async def change_password_submit(
request: Request,
db: Session = Depends(get_db),
user: User | None = Depends(get_current_user),
):
if not user:
return RedirectResponse("/login", 303)
form = await request.form()
data = dict(form)
errors = []
current_password = data.get("current_password", "")
if not current_password:
errors.append("Введите текущий пароль")
elif not verify_password(current_password, user.password_hash):
errors.append("Неверный текущий пароль")
password_errors = validate_reset_password(data)
errors.extend(password_errors)
if errors:
return templates.TemplateResponse("profile_change_password.html", {
"request": request, "user": user, "errors": errors,
})
user.password_hash = hash_password(data["password"])
db.commit()
return templates.TemplateResponse("profile_change_password.html", {
"request": request, "user": user, "success": "Пароль изменен",
})
# DELETE ACCOUNT
@router.get("/profile/delete")
def delete_account_form(request: Request, user: User | None = Depends(get_current_user)):
if not user:
return RedirectResponse("/login", 303)
return templates.TemplateResponse("profile_delete.html", {"request": request, "user": user})
@router.post("/profile/delete")
async def delete_account_submit(
request: Request,
db: Session = Depends(get_db),
user: User | None = Depends(get_current_user),
):
if not user:
return RedirectResponse("/login", 303)
form = await request.form()
data = dict(form)
password = data.get("password", "")
if not password:
return templates.TemplateResponse("profile_delete.html", {
"request": request, "user": user, "errors": ["Введите пароль для подтверждения"],
})
if not verify_password(password, user.password_hash):
return templates.TemplateResponse("profile_delete.html", {
"request": request, "user": user, "errors": ["Неверный пароль"],
})
db.delete(user)
db.commit()
request.session.clear()
return RedirectResponse("/", 303)

View File

@@ -1,215 +1,39 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
/* Brand overrides */
:root {
--bs-primary: #F05023;
--bs-primary-rgb: 240, 80, 35;
--bs-link-color: #0986E2;
--bs-link-hover-color: #0670c0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: #333;
background: #fff;
line-height: 1.6;
}
.container {
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
}
/* Navbar */
.navbar {
background: #fff;
border-bottom: 2px solid #F05023;
padding: 14px 0;
}
.nav-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-logo {
.brand-logo {
font-size: 22px;
font-weight: 700;
color: #F05023;
text-decoration: none;
color: #F05023 !important;
}
.nav-links a {
margin-left: 24px;
color: #333;
text-decoration: none;
font-size: 15px;
}
.nav-links a:hover {
color: #F05023;
}
/* Forms */
.form-card {
max-width: 440px;
margin: 60px auto;
background: #F4F6F8;
border-radius: 8px;
padding: 36px;
}
.form-card h1 {
font-size: 24px;
margin-bottom: 24px;
color: #333;
}
.form-group {
margin-bottom: 18px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-size: 14px;
font-weight: 500;
color: #333;
}
.form-group input {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 15px;
transition: border-color 0.2s;
}
.form-group input:focus {
outline: none;
border-color: #F05023;
}
.btn {
display: inline-block;
padding: 10px 24px;
border: none;
border-radius: 4px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
text-decoration: none;
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.85;
.brand-border {
border-color: #F05023 !important;
}
.btn-primary {
background: #F05023;
color: #fff;
width: 100%;
--bs-btn-bg: #F05023;
--bs-btn-border-color: #F05023;
--bs-btn-hover-bg: #d44420;
--bs-btn-hover-border-color: #d44420;
--bs-btn-active-bg: #c03d1c;
--bs-btn-active-border-color: #c03d1c;
}
.btn-secondary {
background: #0986E2;
color: #fff;
--bs-btn-bg: #0986E2;
--bs-btn-border-color: #0986E2;
--bs-btn-hover-bg: #0770c0;
--bs-btn-hover-border-color: #0770c0;
--bs-btn-active-bg: #065fa3;
--bs-btn-active-border-color: #065fa3;
}
/* Alerts */
.alert {
padding: 14px 18px;
border-radius: 4px;
margin: 20px 0;
font-size: 14px;
}
.alert-error {
background: #fef2f0;
border: 1px solid #F05023;
color: #c0392b;
}
.alert-success {
background: #eafaf3;
border: 1px solid #4DD1A2;
color: #1a7a4c;
}
.alert p {
margin: 2px 0;
}
/* Links */
.form-links {
margin-top: 18px;
text-align: center;
font-size: 14px;
}
.form-links a {
color: #0986E2;
text-decoration: none;
}
.form-links a:hover {
text-decoration: underline;
}
/* Message page */
.message-card {
max-width: 500px;
margin: 80px auto;
text-align: center;
background: #F4F6F8;
border-radius: 8px;
padding: 40px;
}
.message-card h1 {
font-size: 22px;
margin-bottom: 16px;
color: #333;
}
.message-card p {
font-size: 15px;
color: #555;
margin-bottom: 12px;
}
.message-card a {
color: #0986E2;
text-decoration: none;
}
.message-card a:hover {
text-decoration: underline;
}
/* Profile */
.profile-card {
max-width: 540px;
margin: 60px auto;
background: #F4F6F8;
border-radius: 8px;
padding: 36px;
}
.profile-card h1 {
font-size: 24px;
margin-bottom: 24px;
color: #333;
}
.profile-info {
margin-bottom: 18px;
font-size: 15px;
}
.profile-info span {
color: #777;
display: block;
font-size: 13px;
margin-bottom: 2px;
.nav-link:hover {
color: #F05023 !important;
}

View File

@@ -4,40 +4,57 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}EvoSync{% endblock %}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<nav class="navbar">
<div class="container nav-content">
<a href="/" class="nav-logo">EvoSync</a>
<div class="nav-links">
<nav class="navbar navbar-expand-lg bg-white border-bottom border-2 brand-border">
<div class="container">
<a href="/" class="navbar-brand brand-logo">EvoSync</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
{% if user %}
<a href="/profile">Личный кабинет</a>
<a href="/logout">Выход</a>
<li class="nav-item">
<a href="/profile" class="nav-link"><i class="bi bi-person-circle me-1"></i>Личный кабинет</a>
</li>
<li class="nav-item">
<a href="/logout" class="nav-link text-muted">Выход</a>
</li>
{% else %}
<a href="/login">Вход</a>
<a href="/register">Регистрация</a>
<li class="nav-item">
<a href="/login" class="nav-link">Вход</a>
</li>
<li class="nav-item">
<a href="/register" class="nav-link">Регистрация</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<main class="container">
<main class="container py-4">
{% if errors %}
<div class="alert alert-error">
<div class="alert alert-danger">
{% for error in errors %}
<p>{{ error }}</p>
<p class="mb-1">{{ error }}</p>
{% endfor %}
</div>
{% endif %}
{% if success %}
<div class="alert alert-success">
<p>{{ success }}</p>
<p class="mb-0">{{ success }}</p>
</div>
{% endif %}
{% block content %}{% endblock %}
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -2,9 +2,16 @@
{% block title %}Подтверждение email — EvoSync{% endblock %}
{% block content %}
<div class="message-card">
<h1>Подтвердите ваш email</h1>
<p>Ссылка для подтверждения email выведена в консоль сервера.</p>
<p>Скопируйте её и откройте в браузере.</p>
<div class="row justify-content-center">
<div class="col-sm-10 col-md-6 col-lg-5">
<div class="card shadow-sm mt-5 text-center">
<div class="card-body p-5">
<i class="bi bi-envelope-check display-4 text-primary mb-3"></i>
<h1 class="h4 mb-3">Подтвердите ваш email</h1>
<p class="text-muted">Ссылка для подтверждения email выведена в консоль сервера.</p>
<p class="text-muted">Скопируйте её и откройте в браузере.</p>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -2,9 +2,16 @@
{% block title %}Email подтвержден — EvoSync{% endblock %}
{% block content %}
<div class="message-card">
<h1>Email подтвержден!</h1>
<p>Ваш email успешно подтвержден. Теперь вы можете войти в систему.</p>
<p><a href="/login">Войти</a></p>
<div class="row justify-content-center">
<div class="col-sm-10 col-md-6 col-lg-5">
<div class="card shadow-sm mt-5 text-center">
<div class="card-body p-5">
<i class="bi bi-check-circle display-4 text-success mb-3"></i>
<h1 class="h4 mb-3">Email подтвержден!</h1>
<p class="text-muted">Ваш email успешно подтвержден. Теперь вы можете войти в систему.</p>
<a href="/login" class="btn btn-primary mt-2">Войти</a>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -2,20 +2,26 @@
{% block title %}Забыли пароль — EvoSync{% endblock %}
{% block content %}
<div class="form-card">
<h1>Забыли пароль?</h1>
<p style="font-size: 14px; color: #555; margin-bottom: 20px;">
Введите email, указанный при регистрации.
</p>
<div class="row justify-content-center">
<div class="col-sm-10 col-md-6 col-lg-5">
<div class="card shadow-sm mt-4">
<div class="card-body p-4">
<h1 class="card-title h4 mb-2">Забыли пароль?</h1>
<p class="text-muted small mb-4">Введите email, указанный при регистрации.</p>
<form method="post" action="/forgot-password">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" id="email" name="email" class="form-control" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Отправить ссылку для сброса</button>
</div>
</form>
<div class="form-links">
<div class="mt-3 text-center small">
<a href="/login">Вернуться ко входу</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -2,22 +2,31 @@
{% block title %}Вход — EvoSync{% endblock %}
{% block content %}
<div class="form-card">
<h1>Вход</h1>
<div class="row justify-content-center">
<div class="col-sm-10 col-md-6 col-lg-5">
<div class="card shadow-sm mt-4">
<div class="card-body p-4">
<h1 class="card-title h4 mb-4">Вход</h1>
<form method="post" action="/login">
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" value="{{ form.email if form else '' }}" required>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" id="email" name="email" class="form-control"
value="{{ form.email if form else '' }}" required>
</div>
<div class="form-group">
<label for="password">Пароль</label>
<input type="password" id="password" name="password" required>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Войти</button>
</div>
</form>
<div class="form-links">
<div class="mt-3 text-center small">
<a href="/forgot-password">Забыли пароль?</a><br>
<a href="/register">Зарегистрироваться</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -2,11 +2,17 @@
{% block title %}{{ title }} — EvoSync{% endblock %}
{% block content %}
<div class="message-card">
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<div class="row justify-content-center">
<div class="col-sm-10 col-md-6 col-lg-5">
<div class="card shadow-sm mt-5 text-center">
<div class="card-body p-5">
<h1 class="h4 mb-3">{{ title }}</h1>
<p class="text-muted">{{ message }}</p>
{% if link %}
<p><a href="{{ link }}">{{ link_text }}</a></p>
<a href="{{ link }}" class="btn btn-primary mt-2">{{ link_text }}</a>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,30 +0,0 @@
{% extends "base.html" %}
{% block title %}Личный кабинет — EvoSync{% endblock %}
{% block content %}
<div class="profile-card">
<h1>Личный кабинет</h1>
<form method="post" action="/profile">
<div class="form-group">
<label for="first_name">Имя</label>
<input type="text" id="first_name" name="first_name"
value="{{ form.first_name if form else user.first_name }}">
</div>
<div class="form-group">
<label for="last_name">Фамилия</label>
<input type="text" id="last_name" name="last_name"
value="{{ form.last_name if form else user.last_name }}">
</div>
<div class="form-group">
<label>Email</label>
<input type="email" value="{{ user.email }}" disabled>
</div>
<div class="form-group">
<label for="phone">Телефон</label>
<input type="tel" id="phone" name="phone"
value="{{ form.phone if form else user.phone }}">
</div>
<button type="submit" class="btn btn-primary">Сохранить</button>
</form>
</div>
{% endblock %}

View File

@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}Изменить пароль — EvoSync{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-sm-10 col-md-6 col-lg-5">
<div class="card shadow-sm mt-4">
<div class="card-header">
<h1 class="h5 mb-0"><i class="bi bi-key me-2"></i>Изменить пароль</h1>
</div>
<div class="card-body p-4">
{% if success %}
<div class="alert alert-success">{{ success }}</div>
{% endif %}
{% if errors %}
<div class="alert alert-danger">
{% for error in errors %}
<div>{{ error }}</div>
{% endfor %}
</div>
{% endif %}
<form method="post" action="/profile/change-password">
<div class="mb-3">
<label for="current_password" class="form-label">Текущий пароль</label>
<input type="password" id="current_password" name="current_password" class="form-control" required>
</div>
<div class="mb-3">
<label for="password" class="form-label">Новый пароль</label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<div class="mb-4">
<label for="password_confirm" class="form-label">Подтвердить пароль</label>
<input type="password" id="password_confirm" name="password_confirm" class="form-control" required>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Изменить пароль</button>
<a href="/profile" class="btn btn-outline-secondary">Отмена</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,41 @@
{% extends "base.html" %}
{% block title %}Удалить аккаунт — EvoSync{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-sm-10 col-md-6 col-lg-5">
<div class="card shadow-sm mt-4 border-danger">
<div class="card-header bg-danger text-white">
<h1 class="h5 mb-0"><i class="bi bi-trash me-2"></i>Удалить аккаунт</h1>
</div>
<div class="card-body p-4">
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle me-1"></i>
<strong>Внимание!</strong> Это действие необратимо. Все ваши данные будут удалены.
</div>
{% if errors %}
<div class="alert alert-danger">
{% for error in errors %}
<div>{{ error }}</div>
{% endfor %}
</div>
{% endif %}
<form method="post" action="/profile/delete">
<div class="mb-4">
<label for="password" class="form-label">Введите пароль для подтверждения</label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-danger">
<i class="bi bi-trash me-1"></i>Удалить мой аккаунт
</button>
<a href="/profile" class="btn btn-outline-secondary">Отмена</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,55 @@
{% extends "base.html" %}
{% block title %}Редактировать профиль — EvoSync{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-sm-10 col-md-7 col-lg-6">
<div class="card shadow-sm mt-4">
<div class="card-header">
<h1 class="h5 mb-0"><i class="bi bi-pencil me-2"></i>Редактировать профиль</h1>
</div>
<div class="card-body p-4">
{% if success %}
<div class="alert alert-success">{{ success }}</div>
{% endif %}
{% if errors %}
<div class="alert alert-danger">
{% for error in errors %}
<div>{{ error }}</div>
{% endfor %}
</div>
{% endif %}
<form method="post" action="/profile/edit">
<div class="row g-3 mb-3">
<div class="col-sm-6">
<label for="first_name" class="form-label">Имя</label>
<input type="text" id="first_name" name="first_name" class="form-control"
value="{{ form.first_name if form else user.first_name }}" required>
</div>
<div class="col-sm-6">
<label for="last_name" class="form-label">Фамилия</label>
<input type="text" id="last_name" name="last_name" class="form-control"
value="{{ form.last_name if form else user.last_name }}" required>
</div>
</div>
<div class="mb-3">
<label class="form-label text-muted">Email</label>
<input type="email" class="form-control" value="{{ user.email }}" disabled>
</div>
<div class="mb-4">
<label for="phone" class="form-label">Телефон</label>
<input type="tel" id="phone" name="phone" class="form-control"
value="{{ form.phone if form else user.phone }}" required>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Сохранить</button>
<a href="/profile" class="btn btn-outline-secondary">Отмена</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}Личный кабинет — EvoSync{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-sm-10 col-md-7 col-lg-6">
<div class="card shadow-sm mt-4">
<div class="card-header">
<h1 class="h5 mb-0"><i class="bi bi-person-circle me-2"></i>Личный кабинет</h1>
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item d-flex justify-content-between">
<span class="text-muted small">Имя</span>
<span>{{ user.first_name }}</span>
</li>
<li class="list-group-item d-flex justify-content-between">
<span class="text-muted small">Фамилия</span>
<span>{{ user.last_name }}</span>
</li>
<li class="list-group-item d-flex justify-content-between">
<span class="text-muted small">Email</span>
<span>{{ user.email }}</span>
</li>
<li class="list-group-item d-flex justify-content-between">
<span class="text-muted small">Телефон</span>
<span>{{ user.phone }}</span>
</li>
</ul>
<div class="card-body d-grid gap-2">
<a href="/profile/edit" class="btn btn-primary">
<i class="bi bi-pencil me-1"></i>Редактировать профиль
</a>
<a href="/profile/change-password" class="btn btn-secondary">
<i class="bi bi-key me-1"></i>Изменить пароль
</a>
<a href="/logout" class="btn btn-outline-secondary">
<i class="bi bi-box-arrow-right me-1"></i>Выход
</a>
<a href="/profile/delete" class="btn btn-outline-danger btn-sm mt-2">
<i class="bi bi-trash me-1"></i>Удалить аккаунт
</a>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -2,37 +2,51 @@
{% block title %}Регистрация — EvoSync{% endblock %}
{% block content %}
<div class="form-card">
<h1>Регистрация</h1>
<div class="row justify-content-center">
<div class="col-sm-10 col-md-7 col-lg-6">
<div class="card shadow-sm mt-4">
<div class="card-body p-4">
<h1 class="card-title h4 mb-4">Регистрация</h1>
<form method="post" action="/register">
<div class="form-group">
<label for="first_name">Имя</label>
<input type="text" id="first_name" name="first_name" value="{{ form.first_name if form else '' }}">
<div class="row g-3 mb-3">
<div class="col-sm-6">
<label for="first_name" class="form-label">Имя</label>
<input type="text" id="first_name" name="first_name" class="form-control"
value="{{ form.first_name if form else '' }}">
</div>
<div class="form-group">
<label for="last_name">Фамилия</label>
<input type="text" id="last_name" name="last_name" value="{{ form.last_name if form else '' }}">
<div class="col-sm-6">
<label for="last_name" class="form-label">Фамилия</label>
<input type="text" id="last_name" name="last_name" class="form-control"
value="{{ form.last_name if form else '' }}">
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email" value="{{ form.email if form else '' }}" required>
</div>
<div class="form-group">
<label for="phone">Телефон *</label>
<input type="tel" id="phone" name="phone" value="{{ form.phone if form else '' }}" required>
<div class="mb-3">
<label for="email" class="form-label">Email <span class="text-danger">*</span></label>
<input type="email" id="email" name="email" class="form-control"
value="{{ form.email if form else '' }}" required>
</div>
<div class="form-group">
<label for="password">Пароль *</label>
<input type="password" id="password" name="password" required>
<div class="mb-3">
<label for="phone" class="form-label">Телефон <span class="text-danger">*</span></label>
<input type="tel" id="phone" name="phone" class="form-control"
value="{{ form.phone if form else '' }}" required>
</div>
<div class="form-group">
<label for="password_confirm">Подтверждение пароля *</label>
<input type="password" id="password_confirm" name="password_confirm" required>
<div class="mb-3">
<label for="password" class="form-label">Пароль <span class="text-danger">*</span></label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<div class="mb-4">
<label for="password_confirm" class="form-label">Подтверждение пароля <span class="text-danger">*</span></label>
<input type="password" id="password_confirm" name="password_confirm" class="form-control" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Зарегистрироваться</button>
</div>
</form>
<div class="form-links">
<div class="mt-3 text-center small">
<a href="/login">Уже есть аккаунт? Войти</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -2,18 +2,26 @@
{% block title %}Новый пароль — EvoSync{% endblock %}
{% block content %}
<div class="form-card">
<h1>Новый пароль</h1>
<div class="row justify-content-center">
<div class="col-sm-10 col-md-6 col-lg-5">
<div class="card shadow-sm mt-4">
<div class="card-body p-4">
<h1 class="card-title h4 mb-4">Новый пароль</h1>
<form method="post" action="/reset-password?token={{ token }}">
<div class="form-group">
<label for="password">Новый пароль</label>
<input type="password" id="password" name="password" required>
<div class="mb-3">
<label for="password" class="form-label">Новый пароль</label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<div class="form-group">
<label for="password_confirm">Подтверждение пароля</label>
<input type="password" id="password_confirm" name="password_confirm" required>
<div class="mb-4">
<label for="password_confirm" class="form-label">Подтверждение пароля</label>
<input type="password" id="password_confirm" name="password_confirm" class="form-control" required>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Сменить пароль</button>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}