feat: passkey в профиле и входе, кнопка админки в кабинете

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
shop
2026-05-17 12:58:00 +03:00
parent 711110c03b
commit e71bfa35dc
12 changed files with 757 additions and 11 deletions
+69 -1
View File
@@ -10,6 +10,7 @@
<a href="/account?tab=profile" class="account-tabs__link <%= activeTab === 'profile' ? 'account-tabs__link--active' : '' %>">Профиль</a>
<a href="/account?tab=email" class="account-tabs__link <%= activeTab === 'email' ? 'account-tabs__link--active' : '' %>">Смена email</a>
<a href="/account?tab=password" class="account-tabs__link <%= activeTab === 'password' ? 'account-tabs__link--active' : '' %>">Смена пароля</a>
<a href="/account?tab=passkey" class="account-tabs__link <%= activeTab === 'passkey' ? 'account-tabs__link--active' : '' %>">Passkey</a>
<a href="/account?tab=reservations" class="account-tabs__link <%= activeTab === 'reservations' ? 'account-tabs__link--active' : '' %>">Бронирования</a>
</nav>
@@ -29,7 +30,12 @@
<dt>Заказов</dt>
<dd><%= orderCount %></dd>
</dl>
<a href="/orders" class="btn btn--primary">Мои заказы</a>
<div class="account-actions">
<a href="/orders" class="btn btn--primary">Мои заказы</a>
<% if (isAdmin) { %>
<a href="/admin" class="btn btn--admin">Админ-панель</a>
<% } %>
</div>
</section>
<section class="card account-section">
@@ -102,6 +108,68 @@
</section>
<% } %>
<% if (activeTab === 'passkey') { %>
<section class="card account-section account-section--narrow">
<h2>Вход по passkey</h2>
<p class="muted">
Passkey — вход по отпечатку, Face ID или PIN устройства. Пароль остаётся доступен.
Включение необязательно: привяжите ключ, когда будете готовы.
</p>
<% if (user.passkey_enabled) { %>
<p class="alert alert--success" style="margin-bottom:1rem">Passkey включён</p>
<% } else { %>
<p class="muted" style="margin-bottom:1rem">Passkey не настроен</p>
<% } %>
<% if (passkeys.length) { %>
<ul class="passkey-list">
<% passkeys.forEach(pk => { %>
<li class="passkey-list__item">
<span><strong><%= pk.label %></strong> · с <%= new Date(pk.created_at).toLocaleDateString('ru-RU') %></span>
<form action="/account/passkey/credentials/<%= pk.id %>/delete" method="post" class="inline-form">
<input type="password" name="current_password" class="input input--sm" placeholder="Пароль" required autocomplete="current-password" aria-label="Пароль для удаления">
<button type="submit" class="btn btn--ghost btn--sm">Удалить</button>
</form>
</li>
<% }) %>
</ul>
<% } %>
<div class="passkey-actions">
<p id="passkey-register-error" class="alert alert--error" hidden></p>
<label class="label">
Текущий пароль (для привязки нового ключа)
<input type="password" id="passkey-register-password" class="input" autocomplete="current-password">
</label>
<button type="button" id="passkey-register-btn" class="btn btn--primary">Привязать passkey</button>
</div>
<% if (user.passkey_enabled) { %>
<hr class="divider">
<h3>Отключить passkey</h3>
<p class="muted">Все привязанные ключи будут удалены. Вход только по паролю.</p>
<form action="/account/passkey/disable" method="post" class="form">
<label class="label">
Текущий пароль
<input type="password" name="current_password" class="input" required autocomplete="current-password">
</label>
<button type="submit" class="btn btn--ghost">Отключить passkey</button>
</form>
<% } %>
</section>
<script src="/js/passkey.js"></script>
<script>
document.getElementById('passkey-register-btn')?.addEventListener('click', function () {
ShopPasskey.registerPasskey(
document.getElementById('passkey-register-password'),
document.getElementById('passkey-register-error'),
this
);
});
</script>
<% } %>
<% if (activeTab === 'password') { %>
<section class="card account-section account-section--narrow">
<h2>Смена пароля</h2>
+23 -4
View File
@@ -4,21 +4,40 @@
<form action="/login" method="post" class="form card">
<h1>Вход</h1>
<% if (error) { %><p class="alert alert--error"><%= error %></p><% } %>
<input type="hidden" name="next" value="<%= next %>">
<input type="hidden" name="next" id="login-next" value="<%= next %>">
<label class="label">
Email
<input type="email" name="email" class="input" required value="<%= values.email || '' %>">
<input type="email" name="email" id="login-email" class="input" required value="<%= values.email || '' %>">
</label>
<label class="label">
Пароль
<input type="password" name="password" class="input" required>
<input type="password" name="password" class="input" required autocomplete="current-password">
</label>
<button type="submit" class="btn btn--primary btn--block">Войти</button>
<button type="submit" class="btn btn--primary btn--block">Войти по паролю</button>
<p class="form-footer">
<a href="/forgot-password">Забыли пароль?</a><br>
Нет аккаунта? <a href="/register">Регистрация</a>
</p>
</form>
<div class="card passkey-login">
<h2 class="passkey-login__title">Или passkey</h2>
<p class="muted passkey-login__hint">Если в профиле включён passkey — войдите без пароля (нужен тот же email).</p>
<p id="passkey-login-error" class="alert alert--error" hidden></p>
<button type="button" id="passkey-login-btn" class="btn btn--ghost btn--block">Войти с passkey</button>
</div>
</div>
<script src="/js/passkey.js"></script>
<script>
document.getElementById('passkey-login-btn')?.addEventListener('click', function () {
ShopPasskey.loginWithPasskey(
document.getElementById('login-email'),
document.getElementById('login-next'),
document.getElementById('passkey-login-error'),
this
);
});
</script>
<%- include('partials/layout-end') %>