шаг 5 завершен

This commit is contained in:
Alisa
2026-06-04 19:19:35 +03:00
parent 238831ad3a
commit 6216c1ee4f
34 changed files with 1859 additions and 36 deletions
+29
View File
@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/** @var string|null $error */
/** @var string $next */
?>
<section class="start-screen">
<p class="eyebrow">Админка</p>
<h1>Вход</h1>
<p class="muted">Введите email и пароль администратора.</p>
<?php if ($error !== null): ?>
<div class="status-box danger"><?= e($error) ?></div>
<?php endif; ?>
<form class="admin-form" method="post">
<input type="hidden" name="next" value="<?= e($next) ?>">
<label>
Email
<input name="email" type="email" autocomplete="username" required>
</label>
<label>
Пароль
<input name="password" type="password" autocomplete="current-password" required>
</label>
<button class="primary-button" type="submit">Войти</button>
</form>
</section>
+47
View File
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/** @var bool $canRun */
/** @var bool $hasAdmin */
/** @var string $token */
/** @var string|null $message */
/** @var string|null $error */
?>
<section class="start-screen">
<p class="eyebrow">Админка</p>
<h1>Создание администратора</h1>
<?php if ($message !== null): ?>
<div class="status-box success"><?= e($message) ?> <a href="/admin/login.php">Перейти ко входу</a></div>
<?php endif; ?>
<?php if ($error !== null): ?>
<div class="status-box danger"><?= e($error) ?></div>
<?php endif; ?>
<?php if ($hasAdmin): ?>
<p class="muted">Администратор уже создан. Создание через setup закрыто.</p>
<p><a class="primary-link" href="/admin/login.php">Войти в админку</a></p>
<?php elseif (!$canRun): ?>
<div class="status-box danger">Нужен корректный токен установки.</div>
<?php else: ?>
<p class="muted">Создайте первого администратора. После этого страница станет недоступна для повторного создания.</p>
<form class="admin-form" method="post">
<input type="hidden" name="token" value="<?= e($token) ?>">
<label>
Имя
<input name="name" autocomplete="name" required>
</label>
<label>
Email
<input name="email" type="email" autocomplete="username" required>
</label>
<label>
Пароль
<input name="password" type="password" autocomplete="new-password" minlength="8" required>
</label>
<button class="primary-button" type="submit">Создать администратора</button>
</form>
<?php endif; ?>
</section>
+78
View File
@@ -24,6 +24,30 @@ declare(strict_types=1);
<link rel="stylesheet" href="/assets/css/app.css">
</head>
<body>
<header class="site-topbar">
<div class="site-topbar-inner">
<a class="brand-link" href="/">
<?php if (is_file(base_path('public/assets/images/logo_300.png'))): ?>
<img src="/assets/images/logo_300.png" alt="Рыбсток">
<?php else: ?>
<span>Рыбсток</span>
<?php endif; ?>
<small>Качественные продукты по стоковым ценам</small>
</a>
<nav class="main-nav" aria-label="Главное меню">
<a href="/">Главная</a>
<a href="/catalog">Каталог</a>
<a href="/delivery">Доставка</a>
<a href="/contacts">Контакты</a>
<a href="/cart">Корзина</a>
</nav>
<div class="topbar-contacts">
<a href="tel:+79777363363">+7-977-736-33-63</a>
<span>Самовывоз: г. Москва, ул. Привольная, д. 13к1</span>
</div>
</div>
</header>
<header class="site-utility">
<?php if ($showBackButton): ?>
<button class="back-button" type="button" onclick="history.length > 1 ? history.back() : location.href='/'">Вернуться назад</button>
@@ -35,5 +59,59 @@ declare(strict_types=1);
<main class="page">
<?php require $templatePath; ?>
</main>
<footer class="site-footer">
<div class="site-footer-inner">
<section class="footer-brand">
<a class="footer-logo" href="/">
<?php if (is_file(base_path('public/assets/images/logo_300.png'))): ?>
<img src="/assets/images/logo_300.png" alt="Рыбсток">
<?php else: ?>
<span>Рыбсток</span>
<?php endif; ?>
</a>
<p>Качественные продукты по стоковым ценам</p>
</section>
<section class="footer-column">
<h2>Контакты</h2>
<dl class="footer-contacts">
<div>
<dt>Телефон</dt>
<dd><a href="tel:+79777363363">+7-977-736-33-63</a></dd>
</div>
<div>
<dt>Email</dt>
<dd><a href="mailto:zakaz@rybstock.ru">zakaz@rybstock.ru</a></dd>
</div>
<div>
<dt>Адрес самовывоза</dt>
<dd>г. Москва, ул. Привольная, д. 13к1</dd>
</div>
</dl>
</section>
<nav class="footer-column footer-links" aria-label="Ссылки в подвале">
<h2>Покупателям</h2>
<a href="/">Главная</a>
<a href="/catalog">Каталог</a>
<a href="/delivery">Доставка</a>
<a href="/payment">Оплата</a>
<a href="/reviews">Отзывы</a>
<a href="/returns">Правила возврата товара</a>
</nav>
<section class="footer-column">
<h2>Заказы</h2>
<p>Доставка и самовывоз происходят после подтверждения заказа по телефону.</p>
<a class="footer-action" href="/catalog">Перейти в каталог</a>
</section>
</div>
<div class="site-footer-bottom">
<p>© 2026 Рыбсток</p>
<p>Информация на сайте носит справочный характер и не является публичной офертой. Наличие, фасовки, цены и условия доставки подтверждаются менеджером при обработке заказа.</p>
</div>
</footer>
</body>
</html>
+72
View File
@@ -0,0 +1,72 @@
<section class="info-page">
<div class="info-hero contacts-hero">
<div>
<p class="eyebrow">Связь с Рыбсток</p>
<h1>Контакты</h1>
<p>Позвоните по вопросам заказа или продукции. Самовывоз доступен после подтверждения заказа.</p>
</div>
<div class="info-visual" aria-hidden="true">
<span></span>
<strong>13к1</strong>
<small>ул. Привольная</small>
</div>
</div>
<div class="contact-grid">
<section class="info-card contact-card">
<h2>Телефоны</h2>
<dl class="contact-list">
<div>
<dt>По вопросам вашего заказа</dt>
<dd><a href="tel:+79777363363">+7-977-736-33-63</a></dd>
</div>
<div>
<dt>Консультация по продукции</dt>
<dd><a href="tel:+79671981094">+7-967-198-10-94</a></dd>
</div>
</dl>
</section>
<section class="info-card contact-card">
<h2>Email</h2>
<p><a href="mailto:zakaz@rybstock.ru">zakaz@rybstock.ru</a></p>
</section>
<section class="info-card contact-card wide">
<h2>Адрес пункта самовывоза</h2>
<p>г. Москва, м. Лермонтовский проспект, ул. Привольная, д. 13к1</p>
<p class="muted">Вход со двора через крыльцо Wildberries. Самовывоз возможен только после подтверждения заказа.</p>
</section>
</div>
<section class="info-card route-card">
<div class="section-heading">
<div>
<p class="eyebrow">Как пройти</p>
<h2>Маршрут к пункту самовывоза</h2>
</div>
<a class="subtle-link" href="https://yandex.ru/maps/?text=Москва%2C%20улица%20Привольная%2C%2013%D0%BA1" target="_blank" rel="noopener">Открыть в Яндекс.Картах</a>
</div>
<div class="route-layout">
<div class="map-frame">
<iframe
src="https://yandex.ru/map-widget/v1/?text=Москва%2C%20улица%20Привольная%2C%2013%D0%BA1&z=16"
title="Карта проезда к пункту самовывоза Рыбсток"
loading="lazy">
</iframe>
</div>
<div class="route-steps">
<ol>
<li>Доезжайте до станции метро “Лермонтовский проспект”.</li>
<li>Идите к адресу: ул. Привольная, д. 13к1.</li>
<li>Зайдите во двор дома.</li>
<li>Ориентир - крыльцо Wildberries, вход со двора.</li>
<li>Перед самовывозом дождитесь подтверждения заказа по телефону.</li>
</ol>
<p class="muted">Если сложно найти вход, позвоните нам: <a href="tel:+79777363363">+7-977-736-33-63</a>. Подскажем, как пройти.</p>
</div>
</div>
</section>
</section>
+169
View File
@@ -0,0 +1,169 @@
<section class="info-page">
<div class="info-hero">
<div>
<p class="eyebrow">Москва и Московская область</p>
<h1>Доставка и самовывоз</h1>
<p>Привозим заказы после подтверждения по телефону. В день заказа можно получить товар, если оформить заказ в будний день до 14:00.</p>
</div>
<div class="info-visual" aria-hidden="true">
<span></span>
<strong>18:00-24:00</strong>
<small>Пн.-Пт.</small>
</div>
</div>
<section class="delivery-calculator panel">
<div class="section-heading">
<div>
<p class="eyebrow">Калькулятор</p>
<h2>Рассчитать доставку</h2>
</div>
<span class="calculator-note">Расчет предварительный, финально подтвердит менеджер.</span>
</div>
<form class="calculator-form" id="delivery-calculator">
<label>
Адрес доставки
<input type="text" name="address" placeholder="Например: Москва, ул. Привольная, 13к1">
</label>
<label>
Сумма заказа, руб.
<input type="number" name="order_sum" min="0" step="100" value="5000">
</label>
<label>
Способ получения
<select name="zone">
<option value="inside">Доставка внутри МКАД</option>
<option value="outside">Доставка за МКАД</option>
<option value="pickup">Самовывоз</option>
</select>
</label>
<label class="km-field">
Км от МКАД
<input type="number" name="km" min="0" step="1" value="0">
</label>
<label class="checkbox-label">
<input type="checkbox" name="lift">
Нужен подъем до квартиры/офиса
</label>
</form>
<div class="calculator-result" id="delivery-result">
<strong>Доставка: 0 руб.</strong>
<span>Введите сумму заказа и условия доставки.</span>
</div>
</section>
<div class="info-grid">
<section class="info-card">
<div class="info-card-visual delivery-city"></div>
<h2>Доставка внутри МКАД</h2>
<ul class="clean-list">
<li>Заказы от 5000 руб. - бесплатно до подъезда.</li>
<li>В остальных случаях доставка 599 руб.</li>
<li>Минимальная сумма заказа на доставку внутри МКАД - 2000 руб.</li>
<li>Подъем до квартиры или офиса - 390 руб.</li>
</ul>
<p class="muted">Водитель может оставить заказ у консьержа, соседа и т. д. при полной оплате по QR-коду и оплате подъема.</p>
</section>
<section class="info-card">
<div class="info-card-visual delivery-region"></div>
<h2>Доставка за МКАД</h2>
<ul class="clean-list">
<li>До 10 км от МКАД бесплатная доставка от 10000 руб.</li>
<li>От 10 км и дальше: каждый километр от МКАД = 1000 руб. к сумме заказа для бесплатной доставки.</li>
<li>Если заказ не попадает под бесплатную доставку: 199 руб. + 60 руб./км.</li>
<li>Минимальная сумма заказа - 5000 руб.</li>
<li>Подъем до квартиры или офиса - 390 руб.</li>
</ul>
<p class="muted">Например, 15 км от МКАД - бесплатная доставка от 15000 руб.; 17 км - от 17000 руб.</p>
</section>
</div>
<div class="info-grid three">
<section class="info-card compact">
<h2>Как мы развозим</h2>
<p>Понедельник-пятница: 18:00-24:00 без ограничения по точному времени.</p>
<p>При построении маршрута учитываем комментарии к заказу, например: “буду дома после 19:00.</p>
</section>
<section class="info-card compact">
<h2>Самовывоз</h2>
<p>Понедельник-пятница: 18:00-21:00.</p>
<p>Минимальная сумма заказа на самовывоз - 1 руб. Самовывоз возможен только после подтверждения заказа.</p>
</section>
<section class="info-card compact">
<h2>Когда получить заказ</h2>
<p>Заказы до 14:00 в будние дни можно получить день в день.</p>
<p>Заказы после 14:00 - на следующий или другой удобный для вас день.</p>
</section>
</div>
<section class="info-card">
<h2>Оплата при получении</h2>
<p>На сайте ничего оплачивать не нужно. Вы оформляете заказ, чтобы мы его увидели, проверили наличие и собрали с учетом ваших пожеланий. Оплата товара производится только при получении.</p>
<p>При доставке и самовывозе можно оплатить наличными или QR-кодом. Безналичный расчет и “Меркурий” обсуждаются индивидуально для заказов от 30000 руб.</p>
</section>
</section>
<script>
(() => {
const form = document.getElementById('delivery-calculator');
const result = document.getElementById('delivery-result');
if (!form || !result) {
return;
}
const formatRub = (value) => new Intl.NumberFormat('ru-RU').format(Math.max(0, Math.round(value))) + ' руб.';
const calculate = () => {
const data = new FormData(form);
const orderSum = Number(data.get('order_sum') || 0);
const zone = String(data.get('zone') || 'inside');
const km = Math.max(0, Number(data.get('km') || 0));
const lift = data.get('lift') === 'on';
const liftPrice = lift ? 390 : 0;
let delivery = 0;
let title = '';
let note = '';
form.querySelector('.km-field').style.display = zone === 'outside' ? 'grid' : 'none';
if (zone === 'pickup') {
delivery = 0;
title = 'Самовывоз: бесплатно';
note = orderSum < 1
? 'Минимальная сумма заказа на самовывоз - 1 руб.'
: 'Самовывоз возможен после подтверждения заказа.';
} else if (zone === 'inside') {
delivery = orderSum >= 5000 ? 0 : 599;
title = delivery === 0 ? 'Доставка внутри МКАД: бесплатно' : 'Доставка внутри МКАД: ' + formatRub(delivery);
note = orderSum < 2000
? 'Минимальная сумма заказа на доставку внутри МКАД - 2000 руб.'
: 'Бесплатная доставка внутри МКАД от 5000 руб.';
} else {
const freeThreshold = km <= 10 ? 10000 : Math.ceil(km) * 1000;
delivery = orderSum >= freeThreshold ? 0 : 199 + km * 60;
title = delivery === 0 ? 'Доставка за МКАД: бесплатно' : 'Доставка за МКАД: ' + formatRub(delivery);
note = 'Порог бесплатной доставки для этого расстояния: ' + formatRub(freeThreshold) + '. Платная доставка: 199 руб. + 60 руб./км.';
if (orderSum < 5000) {
note += ' Минимальная сумма заказа за МКАД - 5000 руб.';
}
}
const total = delivery + liftPrice;
result.innerHTML = '<strong>' + title + '</strong><span>' + note + (lift ? ' Подъем: 390 руб. Итого с подъемом: ' + formatRub(total) + '.' : '') + '</span>';
};
form.addEventListener('input', calculate);
form.addEventListener('change', calculate);
calculate();
})();
</script>
+59 -13
View File
@@ -11,15 +11,61 @@ declare(strict_types=1);
/** @var string $buildVersion */
?>
<section class="start-screen wide">
<p class="eyebrow">Шаг 3</p>
<h1>Рыбсток</h1>
<p>Каталог открывается уже на главной странице, чтобы покупатель сразу видел ассортимент и не делал лишних переходов.</p>
<p class="muted">Версия сборки: <?= e($buildVersion) ?></p>
<section class="home-hero">
<p class="eyebrow">Интернет-склад качественной продукции</p>
<h1>Рыбсток</h1>
<p class="hero-lead">Рыба, морепродукты, мясо, сыры, овощи, ягоды и бакалея по стоковым ценам.</p>
<div class="hero-actions">
<a class="primary-button hero-button" href="/catalog">Смотреть каталог</a>
<a class="secondary-button" href="tel:+79777363363">+7-977-736-33-63</a>
</div>
</section>
<div class="status-box">
<span>MariaDB:</span>
<strong><?= e($dbStatus) ?></strong>
</div>
<section class="home-banners" aria-label="Преимущества Рыбсток">
<div class="home-banner">
<strong>Бесплатная доставка внутри МКАД от 5000 руб.</strong>
<span>Привезем заказ после подтверждения по телефону.</span>
</div>
<div class="home-banner">
<strong>Бесплатная доставка за МКАД от 10000 руб.</strong>
<span>Для адресов до 10 км от МКАД, дальше расчет по расстоянию.</span>
</div>
<div class="home-banner">
<strong>Можно приобрести день в день</strong>
<span>Привезем заказ день в день, если он оформлен в будний день до 14:00.</span>
</div>
<div class="home-banner">
<strong>Без предоплаты</strong>
<span>Оплата после подтверждения заказа и получения товара.</span>
</div>
</section>
<section class="promo-strip" aria-label="Промокоды и цены">
<div>
<span>Промокод</span>
<strong>Рыбсток20000</strong>
<small>5% при наличном расчете от 20 000 руб.</small>
</div>
<div>
<span>Промокод</span>
<strong>Рыбсток50000</strong>
<small>7% при наличном расчете от 50 000 руб.</small>
</div>
<div>
<span>Безналичный расчет</span>
<strong>+8%</strong>
<small>Отображается отдельной ценовой градацией.</small>
</div>
</section>
<section class="price-request-panel" id="price-request">
<div>
<p class="eyebrow">Запрос проходных цен</p>
<h2>Пришлите список позиций, а мы сверим цены</h2>
<p class="muted">Можно вставить список текстом или приложить файл. Заявка будет уходить администратору на почту.</p>
</div>
<a class="secondary-button" href="#price-request">Подготовить форму</a>
</section>
<?php if ($notice !== null): ?>
<div class="status-box danger"><?= e($notice) ?></div>
@@ -27,7 +73,7 @@ declare(strict_types=1);
<div class="content-grid">
<section class="panel">
<h2>Категории</h2>
<h2>Каталог</h2>
<?php if ($categories === []): ?>
<p class="muted">Категории пока не найдены. Если схема уже применена, проверьте `database/seed.sql`.</p>
<?php else: ?>
@@ -41,18 +87,18 @@ declare(strict_types=1);
</section>
<section class="panel">
<h2>Настройки доставки</h2>
<h2>Доставка</h2>
<dl class="settings-list">
<div>
<dt>Внутри МКАД бесплатно от</dt>
<dt>Бесплатно внутри МКАД от</dt>
<dd><?= e((string) ($settings['inside_mkad_free_from'] ?? '5000')) ?> руб.</dd>
</div>
<div>
<dt>Внутри МКАД платно</dt>
<dt>Доставка внутри МКАД</dt>
<dd><?= e((string) ($settings['inside_mkad_paid_price'] ?? '599')) ?> руб.</dd>
</div>
<div>
<dt>За МКАД расчет</dt>
<dt>За МКАД</dt>
<dd><?= e((string) ($settings['outside_mkad_base_price'] ?? '199')) ?> руб. + <?= e((string) ($settings['outside_mkad_km_price'] ?? '60')) ?> руб./км</dd>
</div>
<div>
+43
View File
@@ -0,0 +1,43 @@
<section class="info-page">
<div class="info-hero payment-hero">
<div>
<p class="eyebrow">Без оплаты на сайте</p>
<h1>Оплата заказа</h1>
<p>На сайте вы только оформляете заказ. Оплата производится в момент получения товара после подтверждения и проверки.</p>
</div>
<div class="info-visual" aria-hidden="true">
<span></span>
<strong>QR</strong>
<small>или наличные</small>
</div>
</div>
<div class="info-grid">
<section class="info-card">
<div class="info-card-visual payment-cash"></div>
<h2>Оплата при получении</h2>
<p>Оплатить товар при доставке или самовывозе можно наличными или QR-кодом.</p>
<p>Кнопки “Оплатить” нет ни в одном разделе сайта Рыбсток: мы сначала подтверждаем заказ, наличие и фактический вес товара.</p>
</section>
<section class="info-card">
<div class="info-card-visual payment-scale"></div>
<h2>Весовая продукция</h2>
<p>Весовая продукция на сайте рассчитывается по приблизительному весу товара.</p>
<p>Перед отправкой или выдачей заказ пересчитывается по фактическому весу.</p>
</section>
</div>
<div class="info-grid">
<section class="info-card">
<h2>Безналичный расчет</h2>
<p>Выставляем счета на безналичный расчет от 30000 руб.</p>
<p>Для безналичного расчета действует отдельная ценовая градация: +8% на весь чек.</p>
</section>
<section class="info-card">
<h2>Проверка качества</h2>
<p>Мы бережно демонстрируем товар, который привезли. Если качество товара вас не устроит, вы можете не приобретать этот товар.</p>
</section>
</div>
</section>
+35
View File
@@ -0,0 +1,35 @@
<section class="text-page">
<div class="text-page-header">
<p class="eyebrow">Покупателям</p>
<h1>Правила возврата товара</h1>
<p>Процедура возврата товара регламентируется статьей 26.1 федерального закона «О защите прав потребителей».</p>
</div>
<div class="text-page-body">
<section>
<h2>Сроки возврата</h2>
<p>Потребитель вправе отказаться от товара в любое время до его передачи, а после передачи товара - в течение семи дней.</p>
<p>Покупатель вправе вернуть товар при соблюдении правил возврата в течение 7 дней после покупки путем доставки товара в наш магазин по адресу: г. Москва, ул. Привольная, д. 13к1.</p>
</section>
<section>
<h2>Условия возврата товара надлежащего качества</h2>
<p>Возврат товара надлежащего качества возможен, если сохранены его товарный вид, потребительские свойства, а также документ, подтверждающий факт и условия покупки указанного товара.</p>
<p>Возврат возможен при условии правильного хранения товара с соблюдением температурного режима. Также должны быть сохранены фирменная тара, упаковка, маркировка, внешний вид и комплектность товара.</p>
</section>
<section>
<h2>Возврат денежных средств</h2>
<p>При отказе потребителя от товара продавец возвращает денежную сумму, уплаченную потребителем по договору, за исключением расходов продавца на доставку от потребителя возвращенного товара.</p>
<p>Возврат денежных средств производится не позднее чем через десять дней со дня предъявления потребителем соответствующего требования.</p>
</section>
<section class="notice-panel">
<h2>Готовая продукция</h2>
<p>Обращаем ваше внимание, что возвраты на готовую продукцию не принимаются. Пожалуйста, осматривайте внешний вид товара при получении.</p>
<p>Если вас не устроил посол, это не является причиной для возврата. Количество соли - субъективное восприятие каждого покупателя. Вся продукция изготавливается в соответствии с техническими условиями завода-производителя.</p>
</section>
<p class="thanks-text">Спасибо за понимание.</p>
</div>
</section>