feat: роли customer/admin, админ-панель, admin@site.com

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
shop
2026-05-17 11:19:01 +03:00
parent 58c789d5f8
commit f24f35d0fc
18 changed files with 497 additions and 9 deletions
+2
View File
@@ -5,6 +5,8 @@
<div class="card account-card">
<p><strong><%= user.name %></strong></p>
<p class="muted"><%= user.email %></p>
<% const roleLabels = { customer: 'Клиент', admin: 'Администратор' }; %>
<p><span class="role-badge role-badge--<%= user.role %>"><%= roleLabels[user.role] || user.role %></span></p>
<p class="muted">С нами с <%= new Date(user.created_at).toLocaleDateString('ru-RU') %></p>
<div class="account-actions">
<a href="/orders" class="btn btn--primary">Мои заказы (<%= orderCount %>)</a>
+62
View File
@@ -0,0 +1,62 @@
<%- include('../partials/layout-start') %>
<div class="admin-header">
<h1>Админ-панель</h1>
<nav class="admin-nav">
<a href="/admin" class="admin-nav__link admin-nav__link--active">Обзор</a>
<a href="/admin/orders" class="admin-nav__link">Заказы</a>
<a href="/admin/users" class="admin-nav__link">Пользователи</a>
<a href="/admin/products" class="admin-nav__link">Товары</a>
<a href="/" class="admin-nav__link">В магазин</a>
</nav>
</div>
<div class="stats-grid">
<div class="stat-card">
<span class="stat-card__label">Пользователи</span>
<strong class="stat-card__value"><%= stats.users %></strong>
</div>
<div class="stat-card">
<span class="stat-card__label">Товары</span>
<strong class="stat-card__value"><%= stats.products %></strong>
</div>
<div class="stat-card">
<span class="stat-card__label">Заказы</span>
<strong class="stat-card__value"><%= stats.orders %></strong>
</div>
<div class="stat-card">
<span class="stat-card__label">Выручка</span>
<strong class="stat-card__value"><%= formatPrice(stats.revenue) %></strong>
</div>
</div>
<h2>Последние заказы</h2>
<% if (!recentOrders.length) { %>
<p class="muted">Заказов пока нет.</p>
<% } else { %>
<table class="cart-table">
<thead>
<tr>
<th>№</th>
<th>Клиент</th>
<th>Статус</th>
<th>Сумма</th>
<th>Дата</th>
</tr>
</thead>
<tbody>
<% const statusLabels = { pending: 'Ожидает', paid: 'Оплачен', shipped: 'Отправлен', cancelled: 'Отменён' }; %>
<% recentOrders.forEach(o => { %>
<tr>
<td>#<%= o.id %></td>
<td><%= o.customer_name %><br><span class="muted"><%= o.user_email %></span></td>
<td><span class="status status--<%= o.status %>"><%= statusLabels[o.status] || o.status %></span></td>
<td><%= formatPrice(o.total_cents) %></td>
<td><%= new Date(o.created_at).toLocaleString('ru-RU') %></td>
</tr>
<% }) %>
</tbody>
</table>
<% } %>
<%- include('../partials/layout-end') %>
+53
View File
@@ -0,0 +1,53 @@
<%- include('../partials/layout-start') %>
<div class="admin-header">
<h1>Заказы</h1>
<nav class="admin-nav">
<a href="/admin" class="admin-nav__link">Обзор</a>
<a href="/admin/orders" class="admin-nav__link admin-nav__link--active">Заказы</a>
<a href="/admin/users" class="admin-nav__link">Пользователи</a>
<a href="/admin/products" class="admin-nav__link">Товары</a>
<a href="/" class="admin-nav__link">В магазин</a>
</nav>
</div>
<% const statusLabels = { pending: 'Ожидает', paid: 'Оплачен', shipped: 'Отправлен', cancelled: 'Отменён' }; %>
<table class="cart-table">
<thead>
<tr>
<th>№</th>
<th>Клиент</th>
<th>Статус</th>
<th>Сумма</th>
<th>Дата</th>
<th>Действие</th>
</tr>
</thead>
<tbody>
<% orders.forEach(o => { %>
<tr>
<td>#<%= o.id %></td>
<td>
<%= o.customer_name %><br>
<span class="muted"><%= o.customer_email %></span>
</td>
<td><span class="status status--<%= o.status %>"><%= statusLabels[o.status] || o.status %></span></td>
<td><%= formatPrice(o.total_cents) %></td>
<td><%= new Date(o.created_at).toLocaleString('ru-RU') %></td>
<td>
<form method="post" action="/admin/orders/<%= o.id %>/status" class="inline-form admin-status-form">
<select name="status" class="input input--sm">
<% ['pending','paid','shipped','cancelled'].forEach(s => { %>
<option value="<%= s %>" <%= o.status === s ? 'selected' : '' %>><%= statusLabels[s] %></option>
<% }) %>
</select>
<button type="submit" class="btn btn--ghost btn--sm">OK</button>
</form>
</td>
</tr>
<% }) %>
</tbody>
</table>
<%- include('../partials/layout-end') %>
+39
View File
@@ -0,0 +1,39 @@
<%- include('../partials/layout-start') %>
<div class="admin-header">
<h1>Товары</h1>
<nav class="admin-nav">
<a href="/admin" class="admin-nav__link">Обзор</a>
<a href="/admin/orders" class="admin-nav__link">Заказы</a>
<a href="/admin/users" class="admin-nav__link">Пользователи</a>
<a href="/admin/products" class="admin-nav__link admin-nav__link--active">Товары</a>
<a href="/" class="admin-nav__link">В магазин</a>
</nav>
</div>
<table class="cart-table">
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Категория</th>
<th>Цена</th>
<th>Остаток</th>
<th></th>
</tr>
</thead>
<tbody>
<% products.forEach(p => { %>
<tr>
<td><%= p.id %></td>
<td><%= p.name %></td>
<td><%= p.category_name || '—' %></td>
<td><%= formatPrice(p.price_cents) %></td>
<td><%= p.stock %></td>
<td><a href="/product/<%= p.slug %>">На сайте</a></td>
</tr>
<% }) %>
</tbody>
</table>
<%- include('../partials/layout-end') %>
+39
View File
@@ -0,0 +1,39 @@
<%- include('../partials/layout-start') %>
<div class="admin-header">
<h1>Пользователи</h1>
<nav class="admin-nav">
<a href="/admin" class="admin-nav__link">Обзор</a>
<a href="/admin/orders" class="admin-nav__link">Заказы</a>
<a href="/admin/users" class="admin-nav__link admin-nav__link--active">Пользователи</a>
<a href="/admin/products" class="admin-nav__link">Товары</a>
<a href="/" class="admin-nav__link">В магазин</a>
</nav>
</div>
<table class="cart-table">
<thead>
<tr>
<th>ID</th>
<th>Имя</th>
<th>Email</th>
<th>Роль</th>
<th>Регистрация</th>
</tr>
</thead>
<tbody>
<% users.forEach(u => { %>
<tr>
<td><%= u.id %></td>
<td><%= u.name %></td>
<td><%= u.email %></td>
<td>
<span class="role-badge role-badge--<%= u.role %>"><%= roleLabels[u.role] || u.role %></span>
</td>
<td><%= new Date(u.created_at).toLocaleString('ru-RU') %></td>
</tr>
<% }) %>
</tbody>
</table>
<%- include('../partials/layout-end') %>
+3
View File
@@ -23,6 +23,9 @@
<% if (cartCount > 0) { %><span class="badge"><%= cartCount %></span><% } %>
</a>
<% if (user) { %>
<% if (typeof isAdmin !== 'undefined' && isAdmin) { %>
<a href="/admin" class="nav__link nav__admin">Админ</a>
<% } %>
<a href="/account" class="nav__link"><%= user.name %></a>
<form action="/logout" method="post" class="inline-form">
<button type="submit" class="btn btn--ghost btn--sm">Выйти</button>