267 lines
10 KiB
Twig
267 lines
10 KiB
Twig
{% extends 'layouts/base.twig' %}
|
||
|
||
{% block title %}{{ title }}{% endblock %}
|
||
|
||
{% block styles %}
|
||
<style>
|
||
.profile-header {
|
||
text-align: center;
|
||
padding: 2rem 0;
|
||
}
|
||
|
||
.avatar-container {
|
||
position: relative;
|
||
width: 120px;
|
||
height: 120px;
|
||
margin: 0 auto 1rem;
|
||
}
|
||
|
||
.avatar-img {
|
||
width: 120px;
|
||
height: 120px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
border: 4px solid #fff;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.avatar-placeholder {
|
||
width: 120px;
|
||
height: 120px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 48px;
|
||
color: white;
|
||
border: 4px solid #fff;
|
||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.nav-pills .nav-link {
|
||
color: #495057;
|
||
border-radius: 0.5rem;
|
||
padding: 0.75rem 1.5rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.nav-pills .nav-link.active {
|
||
background-color: #0d6efd;
|
||
color: white;
|
||
}
|
||
|
||
.nav-pills .nav-link:hover:not(.active) {
|
||
background-color: #e9ecef;
|
||
}
|
||
|
||
.form-label {
|
||
font-weight: 500;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.session-item {
|
||
padding: 1rem;
|
||
border: 1px solid #e9ecef;
|
||
border-radius: 0.5rem;
|
||
margin-bottom: 0.75rem;
|
||
background: #fafafa;
|
||
}
|
||
|
||
.session-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.session-device {
|
||
font-weight: 500;
|
||
color: #333;
|
||
}
|
||
|
||
.session-meta {
|
||
font-size: 0.875rem;
|
||
color: #6c757d;
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
.session-current {
|
||
border-color: #198754;
|
||
background: #f0fff4;
|
||
}
|
||
|
||
.session-current .badge {
|
||
background: #198754;
|
||
}
|
||
|
||
.session-badge {
|
||
font-size: 0.75rem;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 0.25rem;
|
||
}
|
||
|
||
.empty-sessions {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
color: #6c757d;
|
||
}
|
||
|
||
.empty-sessions i {
|
||
font-size: 3rem;
|
||
margin-bottom: 1rem;
|
||
opacity: 0.5;
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="row justify-content-center">
|
||
<div class="col-md-3">
|
||
<!-- Боковая панель навигации -->
|
||
<div class="card mb-4">
|
||
<div class="card-body">
|
||
<div class="profile-header">
|
||
<div class="avatar-container">
|
||
{% if user.avatar %}
|
||
<img src="{{ base_url('/uploads/avatars/' ~ user.avatar) }}" alt="Аватар" class="avatar-img">
|
||
{% else %}
|
||
<div class="avatar-placeholder">
|
||
{{ user.name|first|upper }}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
<h5 class="mb-1">{{ user.name }}</h5>
|
||
<p class="text-muted mb-0">{{ user.email }}</p>
|
||
</div>
|
||
|
||
<hr>
|
||
|
||
<nav class="nav-pills flex-column">
|
||
<a href="{{ base_url('/profile') }}" class="nav-link {{ active_tab == 'general' ? 'active' : '' }}">
|
||
<i class="fa-solid fa-user me-2"></i> Основное
|
||
</a>
|
||
<a href="{{ base_url('/profile/organizations') }}" class="nav-link {{ active_tab == 'organizations' ? 'active' : '' }}">
|
||
<i class="fa-solid fa-building me-2"></i> Мои организации
|
||
</a>
|
||
<a href="{{ base_url('/profile/security') }}" class="nav-link {{ active_tab == 'security' ? 'active' : '' }}">
|
||
<i class="fa-solid fa-shield-halved me-2"></i> Безопасность
|
||
</a>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-md-9">
|
||
<!-- Смена пароля -->
|
||
<div class="card mb-4">
|
||
<div class="card-header bg-white">
|
||
<h4 class="mb-0"><i class="fa-solid fa-key me-2"></i>Смена пароля</h4>
|
||
</div>
|
||
<div class="card-body">
|
||
<form action="{{ base_url('/profile/change-password') }}" method="post">
|
||
{{ csrf_field()|raw }}
|
||
|
||
<div class="mb-3">
|
||
<label for="current_password" class="form-label">Текущий пароль</label>
|
||
<input type="password" class="form-control" id="current_password" name="current_password" required>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="new_password" class="form-label">Новый пароль</label>
|
||
<input type="password" class="form-control" id="new_password" name="new_password" required minlength="6">
|
||
<div class="form-text">Минимум 6 символов</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label for="confirm_password" class="form-label">Подтвердите новый пароль</label>
|
||
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
|
||
</div>
|
||
|
||
<div class="alert alert-info">
|
||
<i class="fa-solid fa-info-circle me-2"></i>
|
||
После смены пароля вы будете автоматически разлогинены на всех устройствах для безопасности.
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary">
|
||
<i class="fa-solid fa-check me-1"></i> Изменить пароль
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Активные сессии и устройства -->
|
||
<div class="card mb-4">
|
||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||
<h4 class="mb-0"><i class="fa-solid fa-desktop me-2"></i>Активные сессии</h4>
|
||
{% if sessions|length > 0 %}
|
||
<form action="{{ base_url('/profile/sessions/revoke-all') }}" method="post" onsubmit="return confirm('Вы уверены? Это завершит сессии на всех устройствах, кроме текущего.');">
|
||
{{ csrf_field()|raw }}
|
||
<button type="submit" class="btn btn-outline-danger btn-sm">
|
||
<i class="fa-solid fa-times me-1"></i>Завершить все
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
<div class="card-body">
|
||
{% if sessions|length > 0 %}
|
||
{% for session in sessions %}
|
||
<div class="session-item{% if session.is_current %} session-current{% endif %}">
|
||
<div class="d-flex justify-content-between align-items-start">
|
||
<div>
|
||
<div class="session-device">
|
||
<i class="fa-solid fa-desktop me-2"></i>{{ session.device }}
|
||
{% if session.is_current %}
|
||
<span class="badge session-badge ms-2">Текущая сессия</span>
|
||
{% endif %}
|
||
</div>
|
||
<div class="session-meta">
|
||
<i class="fa-solid fa-globe me-1"></i>{{ session.ip_address }}
|
||
{% if session.expires_at %}
|
||
|
|
||
<i class="fa-solid fa-clock me-1"></i>истекает {{ session.expires_at|date('d.m.Y H:i') }}
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% if not session.is_current %}
|
||
<form action="{{ base_url('/profile/session/revoke') }}" method="post">
|
||
{{ csrf_field()|raw }}
|
||
<input type="hidden" name="session_id" value="{{ session.id }}">
|
||
<button type="submit" class="btn btn-outline-danger btn-sm" onclick="return confirm('Завершить эту сессию?');">
|
||
<i class="fa-solid fa-times"></i>
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
|
||
<div class="alert alert-info mt-3 mb-0">
|
||
<i class="fa-solid fa-info-circle me-2"></i>
|
||
<strong>Запомненные устройства:</strong> Если вы отметили "Запомнить меня" при входе, устройство будет автоматически авторизовано в течение 30 дней. Вы можете завершить эти сессии вручную.
|
||
</div>
|
||
{% else %}
|
||
<div class="empty-sessions">
|
||
<i class="fa-solid fa-shield-check"></i>
|
||
<p>Нет активных сессий на других устройствах</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Информация о безопасности -->
|
||
<div class="card">
|
||
<div class="card-header bg-white">
|
||
<h4 class="mb-0"><i class="fa-solid fa-shield-halved me-2"></i>Рекомендации по безопасности</h4>
|
||
</div>
|
||
<div class="card-body">
|
||
<ul class="mb-0">
|
||
<li class="mb-2">Используйте пароль длиной не менее 8 символов</li>
|
||
<li class="mb-2">Комбинируйте буквы, цифры и специальные символы</li>
|
||
<li class="mb-2">Не используйте один и тот же пароль для разных сервисов</li>
|
||
<li class="mb-2">Регулярно меняйте пароль</li>
|
||
<li>Не сообщайте пароль третьим лицам</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|