feat: интерактивный установщик install.sh (Docker / Ubuntu, админ, БД)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -29,6 +29,8 @@
|
||||
<dd><%= new Date(user.created_at).toLocaleString('ru-RU') %></dd>
|
||||
<dt>Заказов</dt>
|
||||
<dd><%= orderCount %></dd>
|
||||
<dt>Баллы лояльности</dt>
|
||||
<dd><strong><%= user.loyalty_points || 0 %></strong> <span class="muted">(1 балл = 1 коп. скидки)</span></dd>
|
||||
</dl>
|
||||
<div class="account-actions">
|
||||
<a href="/orders" class="btn btn--primary">Мои заказы</a>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<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="/admin/promo-codes" class="admin-nav__link">Промокоды</a>
|
||||
<a href="/admin/reservations" class="admin-nav__link">Бронирования</a>
|
||||
<a href="/" class="admin-nav__link">В магазин</a>
|
||||
</nav>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<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="/admin/promo-codes" class="admin-nav__link">Промокоды</a>
|
||||
<a href="/admin/reservations" class="admin-nav__link">Бронирования</a>
|
||||
<a href="/" class="admin-nav__link">В магазин</a>
|
||||
</nav>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<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="/admin/promo-codes" class="admin-nav__link">Промокоды</a>
|
||||
<a href="/admin/reservations" class="admin-nav__link">Бронирования</a>
|
||||
<a href="/" class="admin-nav__link">В магазин</a>
|
||||
</nav>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<%- 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">Товары</a>
|
||||
<a href="/admin/promo-codes" class="admin-nav__link admin-nav__link--active">Промокоды</a>
|
||||
<a href="/admin/reservations" class="admin-nav__link">Бронирования</a>
|
||||
<a href="/" class="admin-nav__link">В магазин</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<% if (created) { %><p class="alert alert--success">Промокод создан</p><% } %>
|
||||
|
||||
<section class="card account-section--narrow" style="margin-bottom:1.5rem">
|
||||
<h2>Новый промокод</h2>
|
||||
<form action="/admin/promo-codes" method="post" class="form">
|
||||
<label class="label">Код <input type="text" name="code" class="input" required placeholder="SUMMER20"></label>
|
||||
<label class="label">Описание <input type="text" name="description" class="input" placeholder="Летняя скидка"></label>
|
||||
<label class="label">Тип скидки
|
||||
<select name="discount_type" class="input">
|
||||
<option value="percent">Процент %</option>
|
||||
<option value="fixed">Фиксированная (₽)</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="label">Значение (10 = 10% или 500 ₽) <input type="number" name="discount_value" class="input" min="1" required></label>
|
||||
<label class="label">Действует дней <input type="number" name="valid_days" class="input" value="30" min="1"></label>
|
||||
<label class="label">Мин. сумма заказа (₽) <input type="number" name="min_order_rub" class="input" value="0" min="0"></label>
|
||||
<label class="label">Лимит использований (пусто = без лимита) <input type="number" name="max_uses" class="input" min="1"></label>
|
||||
<button type="submit" class="btn btn--primary">Создать</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<table class="cart-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Код</th>
|
||||
<th>Скидка</th>
|
||||
<th>До</th>
|
||||
<th>Использовано</th>
|
||||
<th>Статус</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% promos.forEach(p => { %>
|
||||
<tr>
|
||||
<td><strong><%= p.code %></strong><br><span class="muted"><%= p.description %></span></td>
|
||||
<td>
|
||||
<% if (p.discount_type === 'percent') { %><%= p.discount_value %>%<% } else { %><%= formatPrice(p.discount_value) %><% } %>
|
||||
<% if (p.min_order_cents > 0) { %><br><span class="muted">от <%= formatPrice(p.min_order_cents) %></span><% } %>
|
||||
</td>
|
||||
<td><%= new Date(p.expires_at).toLocaleString('ru-RU') %></td>
|
||||
<td><%= p.use_count %><% if (p.max_uses) { %> / <%= p.max_uses %><% } %></td>
|
||||
<td><%= p.active ? 'Активен' : 'Выкл.' %></td>
|
||||
<td>
|
||||
<form action="/admin/promo-codes/<%= p.id %>/toggle" method="post">
|
||||
<button type="submit" class="btn btn--ghost btn--sm"><%= p.active ? 'Выключить' : 'Включить' %></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<% }) %>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<%- include('../partials/layout-end') %>
|
||||
@@ -7,6 +7,7 @@
|
||||
<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="/admin/promo-codes" class="admin-nav__link">Промокоды</a>
|
||||
<a href="/admin/reservations" class="admin-nav__link admin-nav__link--active">Бронирования</a>
|
||||
<a href="/" class="admin-nav__link">В магазин</a>
|
||||
</nav>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<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="/admin/promo-codes" class="admin-nav__link">Промокоды</a>
|
||||
<a href="/admin/reservations" class="admin-nav__link">Бронирования</a>
|
||||
<a href="/" class="admin-nav__link">В магазин</a>
|
||||
</nav>
|
||||
|
||||
+68
-6
@@ -3,6 +3,8 @@
|
||||
<h1>Корзина</h1>
|
||||
|
||||
<% if (error) { %><p class="alert alert--error"><%= error %></p><% } %>
|
||||
<% if (promoOk) { %><p class="alert alert--success"><%= promoOk %></p><% } %>
|
||||
<% if (promoErr) { %><p class="alert alert--error"><%= promoErr %></p><% } %>
|
||||
|
||||
<% if (!items.length) { %>
|
||||
<p class="empty">Корзина пуста. <a href="/">Перейти в каталог</a></p>
|
||||
@@ -39,16 +41,76 @@
|
||||
<% }) %>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="cart-actions">
|
||||
<button type="submit" class="btn btn--ghost">Обновить</button>
|
||||
<p class="cart-total">Итого: <strong><%= formatPrice(total) %></strong></p>
|
||||
|
||||
<div class="cart-sidebar">
|
||||
<section class="card promo-box">
|
||||
<h2 class="promo-box__title">Промокод</h2>
|
||||
<% if (pricing.promo) { %>
|
||||
<p class="promo-box__applied">
|
||||
<strong><%= pricing.promo.code %></strong>
|
||||
<% if (pricing.promo.description) { %> — <%= pricing.promo.description %><% } %>
|
||||
</p>
|
||||
<p class="promo-box__discount">Скидка: −<%= formatPrice(pricing.promoDiscount) %></p>
|
||||
<div class="promo-countdown" data-expires="<%= pricing.promo.expires_at %>">
|
||||
<span class="promo-countdown__label">До конца акции:</span>
|
||||
<span class="promo-countdown__timer">—</span>
|
||||
</div>
|
||||
<form action="/cart/promo/remove" method="post" class="inline-form">
|
||||
<button type="submit" class="btn btn--ghost btn--sm">Убрать промокод</button>
|
||||
</form>
|
||||
<% } else { %>
|
||||
<form action="/cart/promo" method="post" class="promo-box__form">
|
||||
<input type="text" name="code" class="input" placeholder="WELCOME10" required autocomplete="off">
|
||||
<button type="submit" class="btn btn--primary">Применить</button>
|
||||
</form>
|
||||
<% } %>
|
||||
</section>
|
||||
|
||||
<% if (user) { %>
|
||||
<a href="/checkout" class="btn btn--primary btn--lg">Оформить заказ</a>
|
||||
<% } else { %>
|
||||
<p class="hint"><a href="/login?next=/checkout">Войдите</a>, чтобы оформить заказ.</p>
|
||||
<section class="card promo-box">
|
||||
<h2 class="promo-box__title">Баллы лояльности</h2>
|
||||
<p class="muted">На счёте: <strong><%= pricing.loyaltyBalance %></strong> баллов (1 балл = 1 коп.)</p>
|
||||
<% if (pricing.pointsEarned > 0) { %>
|
||||
<p class="muted">За этот заказ начислим: +<%= pricing.pointsEarned %> баллов</p>
|
||||
<% } %>
|
||||
<% if (pricing.loyaltyDiscount > 0) { %>
|
||||
<p class="promo-box__discount">Списано: −<%= formatPrice(pricing.loyaltyDiscount) %> (<%= pricing.loyaltyPointsUsed %> баллов)</p>
|
||||
<form action="/cart/loyalty/remove" method="post">
|
||||
<button type="submit" class="btn btn--ghost btn--sm">Отменить списание</button>
|
||||
</form>
|
||||
<% } else if (pricing.loyaltyBalance > 0) { %>
|
||||
<form action="/cart/loyalty" method="post" class="promo-box__form">
|
||||
<button type="submit" name="use_all" value="1" class="btn btn--ghost">Списать все доступные</button>
|
||||
</form>
|
||||
<% } %>
|
||||
</section>
|
||||
<% } %>
|
||||
|
||||
<section class="card cart-summary">
|
||||
<dl class="cart-summary__dl">
|
||||
<dt>Товары</dt>
|
||||
<dd><%= formatPrice(pricing.subtotal) %></dd>
|
||||
<% if (pricing.promoDiscount > 0) { %>
|
||||
<dt>Промокод</dt>
|
||||
<dd class="cart-summary__discount">−<%= formatPrice(pricing.promoDiscount) %></dd>
|
||||
<% } %>
|
||||
<% if (pricing.loyaltyDiscount > 0) { %>
|
||||
<dt>Лояльность</dt>
|
||||
<dd class="cart-summary__discount">−<%= formatPrice(pricing.loyaltyDiscount) %></dd>
|
||||
<% } %>
|
||||
<dt class="cart-summary__total-label">К оплате</dt>
|
||||
<dd class="cart-summary__total"><%= formatPrice(pricing.total) %></dd>
|
||||
</dl>
|
||||
<% if (user) { %>
|
||||
<a href="/checkout" class="btn btn--primary btn--lg btn--block">Оформить заказ</a>
|
||||
<% } else { %>
|
||||
<p class="hint"><a href="/login?next=/checkout">Войдите</a>, чтобы оформить заказ.</p>
|
||||
<% } %>
|
||||
</section>
|
||||
</div>
|
||||
</form>
|
||||
<% } %>
|
||||
|
||||
<script src="/js/promo-countdown.js"></script>
|
||||
|
||||
<%- include('partials/layout-end') %>
|
||||
|
||||
+29
-1
@@ -36,8 +36,36 @@
|
||||
</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<p class="checkout-total">Итого: <strong><%= formatPrice(total) %></strong></p>
|
||||
<% if (pricing.promo) { %>
|
||||
<p class="checkout-promo">
|
||||
Промокод <strong><%= pricing.promo.code %></strong>
|
||||
<span class="promo-countdown" data-expires="<%= pricing.promo.expires_at %>">
|
||||
(<span class="promo-countdown__timer">—</span>)
|
||||
</span>
|
||||
</p>
|
||||
<% } %>
|
||||
<dl class="cart-summary__dl">
|
||||
<dt>Товары</dt>
|
||||
<dd><%= formatPrice(pricing.subtotal) %></dd>
|
||||
<% if (pricing.promoDiscount > 0) { %>
|
||||
<dt>Скидка по промокоду</dt>
|
||||
<dd class="cart-summary__discount">−<%= formatPrice(pricing.promoDiscount) %></dd>
|
||||
<% } %>
|
||||
<% if (pricing.loyaltyDiscount > 0) { %>
|
||||
<dt>Баллы лояльности</dt>
|
||||
<dd class="cart-summary__discount">−<%= formatPrice(pricing.loyaltyDiscount) %></dd>
|
||||
<% } %>
|
||||
<% if (pricing.pointsEarned > 0) { %>
|
||||
<dt>Начислим баллов</dt>
|
||||
<dd>+<%= pricing.pointsEarned %></dd>
|
||||
<% } %>
|
||||
<dt class="cart-summary__total-label">К оплате</dt>
|
||||
<dd class="cart-summary__total"><%= formatPrice(pricing.total) %></dd>
|
||||
</dl>
|
||||
<p class="muted"><a href="/cart">Изменить корзину или промокод</a></p>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<script src="/js/promo-countdown.js"></script>
|
||||
|
||||
<%- include('partials/layout-end') %>
|
||||
|
||||
+13
-1
@@ -22,7 +22,19 @@
|
||||
</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<p class="checkout-total">Итого: <strong><%= formatPrice(order.total_cents) %></strong></p>
|
||||
<% const subtotal = order.subtotal_cents != null ? order.subtotal_cents : order.total_cents; %>
|
||||
<% if (order.discount_cents > 0) { %>
|
||||
<dl class="cart-summary__dl">
|
||||
<dt>Товары</dt>
|
||||
<dd><%= formatPrice(subtotal) %></dd>
|
||||
<dt>Скидка</dt>
|
||||
<dd class="cart-summary__discount">−<%= formatPrice(order.discount_cents) %></dd>
|
||||
</dl>
|
||||
<% } %>
|
||||
<% if (order.loyalty_points_earned > 0) { %>
|
||||
<p class="muted">Начислено баллов лояльности: +<%= order.loyalty_points_earned %></p>
|
||||
<% } %>
|
||||
<p class="checkout-total">К оплате: <strong><%= formatPrice(order.total_cents) %></strong></p>
|
||||
</div>
|
||||
|
||||
<p><a href="/orders" class="link-back">← Все заказы</a></p>
|
||||
|
||||
Reference in New Issue
Block a user