diff --git a/admin/categories.php b/admin/categories.php new file mode 100644 index 0000000..1ac996b --- /dev/null +++ b/admin/categories.php @@ -0,0 +1,5 @@ + 'Товары', 'url' => '/admin/products.php'], ['title' => 'Категории', 'url' => '/admin/categories.php'], @@ -31,6 +34,9 @@ $sections = [

Админка

Рыбсток

Подготовлены основные разделы будущей панели управления.

+ +

Вы вошли как . Выйти

+
diff --git a/admin/login.php b/admin/login.php new file mode 100644 index 0000000..1ddb53f --- /dev/null +++ b/admin/login.php @@ -0,0 +1,5 @@ + 'Каталог продукции - Рыбсток', - 'metaDescription' => 'Полный каталог Рыбсток: рыба, морепродукты, икра, мясо, сыры, полуфабрикаты и продукты с доставкой.', - 'metaKeywords' => 'каталог Рыбсток, рыба, морепродукты, мясо, сыры, полуфабрикаты', + 'title' => 'Каталог Рыбсток - рыба, морепродукты, мясо, сыры и продукты', + 'metaDescription' => 'Полный каталог Рыбсток: рыба, морепродукты, икра, мясо, сыры, полуфабрикаты, овощи, ягоды и бакалея по стоковым ценам с доставкой по Москве и Московской области.', + 'metaKeywords' => 'каталог Рыбсток, купить рыбу Москва, морепродукты Москва, мясо доставка Москва, сыры доставка, икра, полуфабрикаты, овощи ягоды заморозка, бакалея, продукты по стоковым ценам', 'breadcrumbs' => [ ['title' => 'Главная', 'url' => '/'], ['title' => 'Каталог'], @@ -47,10 +47,12 @@ final class CatalogController return; } + $categoryName = (string) $category['name']; + view('catalog/index', [ - 'title' => ($category['seo_title'] ?: $category['name']) . ' - Рыбсток', - 'metaDescription' => $category['seo_description'] ?: '', - 'metaKeywords' => $category['seo_keywords'] ?: '', + 'title' => $category['seo_title'] ?: $categoryName . ' - купить в Рыбсток с доставкой по Москве', + 'metaDescription' => $category['seo_description'] ?: $categoryName . ' в интернет-складе Рыбсток: качественная продукция по стоковым ценам, фасовки для дома и бизнеса, доставка по Москве и Московской области.', + 'metaKeywords' => $category['seo_keywords'] ?: $categoryName . ', купить ' . $categoryName . ' Москва, ' . $categoryName . ' доставка, Рыбсток, продукты по стоковым ценам', 'breadcrumbs' => $categoryRepository->breadcrumbs((int) $category['id']), 'categories' => $categoryRepository->tree(), 'currentCategory' => $category, diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index 925c693..a4738c6 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -38,9 +38,9 @@ final class HomeController } view('pages/home', [ - 'title' => 'Рыбсток - продукты и рыба с доставкой по Москве и Московской области', - 'metaDescription' => 'Рыбсток - интернет-магазин рыбы, морепродуктов, мяса, сыров, полуфабрикатов и продуктов с доставкой по Москве и Московской области.', - 'metaKeywords' => 'Рыбсток, рыба, морепродукты, мясо, сыры, полуфабрикаты, доставка продуктов Москва', + 'title' => 'Рыбсток - качественные продукты по стоковым ценам с доставкой', + 'metaDescription' => 'Рыбсток - интернет-склад качественной рыбы, морепродуктов, мяса, сыров, овощей, ягод и бакалеи по стоковым ценам. Бесплатная доставка по Москве и Московской области, самовывоз с ул. Привольная.', + 'metaKeywords' => 'Рыбсток, рыба доставка Москва, морепродукты доставка Москва, мясо доставка Москва, сыры доставка, икра доставка Москва, овощи ягоды заморозка, бакалея с доставкой, продукты по стоковым ценам, интернет склад продуктов, бесплатная доставка продуктов Москва, доставка продуктов Московская область', 'breadcrumbs' => [ ['title' => 'Главная'], ], diff --git a/app/Controllers/PageController.php b/app/Controllers/PageController.php new file mode 100644 index 0000000..7c0d6dd --- /dev/null +++ b/app/Controllers/PageController.php @@ -0,0 +1,60 @@ + 'Доставка Рыбсток - Москва и Московская область', + 'metaDescription' => 'Условия доставки Рыбсток по Москве и Московской области: бесплатная доставка внутри МКАД от 5000 рублей, за МКАД от 10000 рублей, самовывоз и расчет доставки.', + 'metaKeywords' => 'Рыбсток доставка, доставка рыбы Москва, доставка продуктов Москва, доставка за МКАД, самовывоз Рыбсток, расчет доставки', + 'breadcrumbs' => [ + ['title' => 'Главная', 'url' => '/'], + ['title' => 'Доставка'], + ], + ]); + } + + public function payment(): void + { + view('pages/payment', [ + 'title' => 'Оплата заказа - Рыбсток', + 'metaDescription' => 'Способы оплаты заказа в Рыбсток: оплата при получении наличными или QR-кодом, безналичный расчет от 30000 рублей.', + 'metaKeywords' => 'Рыбсток оплата, оплата при получении, оплата QR кодом, безналичный расчет продукты', + 'breadcrumbs' => [ + ['title' => 'Главная', 'url' => '/'], + ['title' => 'Оплата'], + ], + ]); + } + + public function contacts(): void + { + view('pages/contacts', [ + 'title' => 'Контакты Рыбсток - телефон, адрес самовывоза, email', + 'metaDescription' => 'Контакты Рыбсток: телефоны для заказа и консультации, адрес пункта самовывоза в Москве на ул. Привольная, д. 13к1, email для заявок.', + 'metaKeywords' => 'Рыбсток контакты, телефон Рыбсток, адрес самовывоза Рыбсток, zakaz@rybstock.ru', + 'breadcrumbs' => [ + ['title' => 'Главная', 'url' => '/'], + ['title' => 'Контакты'], + ], + ]); + } + + public function returns(): void + { + view('pages/returns', [ + 'title' => 'Правила возврата товара - Рыбсток', + 'metaDescription' => 'Правила возврата товара в интернет-складе Рыбсток: условия возврата, сохранность упаковки, температурный режим и порядок обращения.', + 'metaKeywords' => 'Рыбсток возврат товара, правила возврата товара, возврат продуктов, условия возврата Рыбсток', + 'breadcrumbs' => [ + ['title' => 'Главная', 'url' => '/'], + ['title' => 'Правила возврата товара'], + ], + ]); + } +} diff --git a/app/Core/Auth.php b/app/Core/Auth.php new file mode 100644 index 0000000..f3d3275 --- /dev/null +++ b/app/Core/Auth.php @@ -0,0 +1,86 @@ +|null + */ + public static function admin(): ?array + { + self::start(); + $adminId = (int) ($_SESSION[self::SESSION_ADMIN_ID] ?? 0); + + if ($adminId <= 0) { + return null; + } + + $admin = (new AdminRepository())->findActiveById($adminId); + + if ($admin === null) { + unset($_SESSION[self::SESSION_ADMIN_ID]); + } + + return $admin; + } + + public static function attempt(string $email, string $password): bool + { + self::start(); + $repository = new AdminRepository(); + $admin = $repository->findActiveByEmail($email); + + if ($admin === null || !password_verify($password, (string) $admin['password_hash'])) { + return false; + } + + session_regenerate_id(true); + $_SESSION[self::SESSION_ADMIN_ID] = (int) $admin['id']; + $repository->touchLastLogin((int) $admin['id']); + + return true; + } + + public static function logout(): void + { + self::start(); + $_SESSION = []; + + if (ini_get('session.use_cookies')) { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], (bool) $params['secure'], (bool) $params['httponly']); + } + + session_destroy(); + } +} diff --git a/app/Repositories/AdminRepository.php b/app/Repositories/AdminRepository.php new file mode 100644 index 0000000..d8a9a92 --- /dev/null +++ b/app/Repositories/AdminRepository.php @@ -0,0 +1,87 @@ +pdo() + ->query('SELECT COUNT(*) FROM admins WHERE is_active = 1') + ->fetchColumn(); + } + + /** + * @return array|null + */ + public function findActiveByEmail(string $email): ?array + { + $statement = $this->pdo()->prepare( + 'SELECT id, name, email, password_hash, role + FROM admins + WHERE email = :email AND is_active = 1 + LIMIT 1' + ); + $statement->execute(['email' => mb_strtolower(trim($email))]); + $admin = $statement->fetch(); + + return $admin === false ? null : $admin; + } + + /** + * @return array|null + */ + public function findActiveById(int $id): ?array + { + $statement = $this->pdo()->prepare( + 'SELECT id, name, email, role + FROM admins + WHERE id = :id AND is_active = 1 + LIMIT 1' + ); + $statement->execute(['id' => $id]); + $admin = $statement->fetch(); + + return $admin === false ? null : $admin; + } + + public function createOwner(string $name, string $email, string $password): int + { + $name = trim($name); + $email = mb_strtolower(trim($email)); + + if ($name === '' || $email === '' || $password === '') { + throw new \InvalidArgumentException('Имя, email и пароль обязательны.'); + } + + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + throw new \InvalidArgumentException('Укажите корректный email.'); + } + + if (mb_strlen($password) < 8) { + throw new \InvalidArgumentException('Пароль должен быть не короче 8 символов.'); + } + + $statement = $this->pdo()->prepare( + 'INSERT INTO admins (name, email, password_hash, role, is_active) + VALUES (:name, :email, :password_hash, "owner", 1)' + ); + $statement->execute([ + 'name' => $name, + 'email' => $email, + 'password_hash' => password_hash($password, PASSWORD_DEFAULT), + ]); + + return (int) $this->pdo()->lastInsertId(); + } + + public function touchLastLogin(int $id): void + { + $statement = $this->pdo()->prepare( + 'UPDATE admins SET last_login_at = NOW() WHERE id = :id' + ); + $statement->execute(['id' => $id]); + } +} diff --git a/app/Services/PriceTierService.php b/app/Services/PriceTierService.php index 03ce157..a3ee5c1 100644 --- a/app/Services/PriceTierService.php +++ b/app/Services/PriceTierService.php @@ -38,6 +38,15 @@ final class PriceTierService ]; } + $tiers[] = [ + 'label' => 'Безналичный расчет', + 'code' => null, + 'min_order_amount' => 0.0, + 'discount_percent' => -8.0, + 'price' => round($basePrice * 1.08, 2), + 'note' => '+8% к базовой цене при безналичном расчете', + ]; + return $tiers; } diff --git a/database/seed.sql b/database/seed.sql index 670bdea..ba2d1b4 100644 --- a/database/seed.sql +++ b/database/seed.sql @@ -404,6 +404,7 @@ VALUES INSERT INTO site_settings (setting_key, setting_value, value_type, group_name) VALUES ('site_phone', '+7-977-736-33-63', 'string', 'contacts'), +('pickup_address', 'г. Москва, ул. Привольная, д. 13к1', 'string', 'contacts'), ('admin_email', '', 'string', 'mail'), ('price_request_email', '', 'string', 'mail'), ('inside_mkad_free_from', '5000', 'number', 'delivery'), diff --git a/docs/SEO_ТЕГИ.md b/docs/SEO_ТЕГИ.md new file mode 100644 index 0000000..9cfcad9 --- /dev/null +++ b/docs/SEO_ТЕГИ.md @@ -0,0 +1,55 @@ +# SEO-теги Рыбсток + +Документ фиксирует базовые теги для главных страниц и шаблоны для дальнейшего заполнения категорий и товаров. Теги со старого сайта можно переносить в админке точечно: они будут иметь приоритет над автоматическими шаблонами. + +## Главная страница + +Title: +Рыбсток - качественные продукты по стоковым ценам с доставкой + +Description: +Рыбсток - интернет-склад качественной рыбы, морепродуктов, мяса, сыров, овощей, ягод и бакалеи по стоковым ценам. Бесплатная доставка по Москве и Московской области, самовывоз с ул. Привольная. + +Keywords: +Рыбсток, рыба доставка Москва, морепродукты доставка Москва, мясо доставка Москва, сыры доставка, икра доставка Москва, овощи ягоды заморозка, бакалея с доставкой, продукты по стоковым ценам, интернет склад продуктов, бесплатная доставка продуктов Москва, доставка продуктов Московская область + +## Каталог + +Title: +Каталог Рыбсток - рыба, морепродукты, мясо, сыры и продукты + +Description: +Полный каталог Рыбсток: рыба, морепродукты, икра, мясо, сыры, полуфабрикаты, овощи, ягоды и бакалея по стоковым ценам с доставкой по Москве и Московской области. + +Keywords: +каталог Рыбсток, купить рыбу Москва, морепродукты Москва, мясо доставка Москва, сыры доставка, икра, полуфабрикаты, овощи ягоды заморозка, бакалея, продукты по стоковым ценам + +## Шаблон категории + +Title: +{Название категории} - купить в Рыбсток с доставкой по Москве + +Description: +{Название категории} в интернет-складе Рыбсток: качественная продукция по стоковым ценам, фасовки для дома и бизнеса, доставка по Москве и Московской области. + +Keywords: +{Название категории}, купить {Название категории} Москва, {Название категории} доставка, Рыбсток, продукты по стоковым ценам + +## Шаблон товара + +Title: +{Название товара} - купить в Рыбсток + +Description: +{Название товара}: фасовки, цены и наличие в Рыбсток. Качественная продукция по стоковым ценам с доставкой по Москве и Московской области. + +Keywords: +{Название товара}, купить {Название товара}, Рыбсток, доставка Москва, продукты по стоковым ценам + +## Правила заполнения + +1. Title держим коротким и понятным: сначала главный запрос, затем бренд. +2. Description пишем человеческим языком, без набора ключей подряд. +3. Keywords используем как вспомогательное поле для внутренней структуры и старых поисковых систем, но не перегружаем. +4. Для важных категорий после переноса товаров лучше написать уникальные описания вручную. +5. Для товаров используем точное название, фасовку, тип заморозки или обработки, если это важно для поиска. diff --git a/docs/ЛОГОТИП_И_БРЕНД_РЫБСТОК.md b/docs/ЛОГОТИП_И_БРЕНД_РЫБСТОК.md new file mode 100644 index 0000000..5aab9a8 --- /dev/null +++ b/docs/ЛОГОТИП_И_БРЕНД_РЫБСТОК.md @@ -0,0 +1,43 @@ +# Логотип и бренд Рыбсток + +## Смысл названия + +Рыбсток состоит из двух смыслов: + +- "Рыб" - рыба, морепродукты, свежесть, холод, складская надежность; +- "Сток" - выгодные стоковые цены, большой ассортимент, возможность купить качественную продукцию дешевле. + +Основной слоган: + +**Качественные продукты по стоковым ценам** + +## Что важно сохранить + +- Название должно читаться быстро: Рыбсток. +- Логотип должен быть уникальным, не похожим на стандартную рыбку из клипарта. +- Знак должен работать в шапке сайта, на мобильном экране, в прайс-листе, накладной и аватарке мессенджера. +- Логотип не должен выглядеть слишком ресторанным или премиально-холодным: Рыбсток - это интернет-склад качественных продуктов по выгодной цене. + +## Направление модернизации + +Подходящий образ: + +- спокойный сильный wordmark "Рыбсток"; +- небольшой знак рыбы, встроенный в букву или стоящий рядом; +- ощущение складской надежности и честной цены; +- цвета: глубокий зеленый/графитовый как база, красный можно оставить только как акцент для цены или знака. + +## Не делать + +- не использовать мультяшную рыбу; +- не делать логотип слишком премиальным, как ресторан высокой кухни; +- не перегружать знак волнами, чешуей и декоративными деталями; +- не использовать много оттенков синего, чтобы бренд не стал обычным рыбным магазином. + +## Текущий статус + +Старый логотип сохранен как временный файл: + +`public/assets/images/logo_300.png` + +Он подключен в шапке сайта. Позже его можно заменить новым файлом с тем же именем или добавить новый SVG/PNG и обновить ссылку в layout. diff --git a/docs/ШАГ_4_КАТАЛОГ_И_АДМИНКА.md b/docs/ШАГ_4_КАТАЛОГ_И_АДМИНКА.md index c38b288..ebe704b 100644 --- a/docs/ШАГ_4_КАТАЛОГ_И_АДМИНКА.md +++ b/docs/ШАГ_4_КАТАЛОГ_И_АДМИНКА.md @@ -91,6 +91,25 @@ В схему добавлены поля `discount_type`, `discount_percent`, используется `old_package_price`, а также таблица `price_requests` для будущих заявок проходных цен. +## Дополнение: главная страница и ценовые градации + +На главную добавлены требования к рабочей витрине: + +- видимое главное меню: Главная, Каталог, Доставка, Контакты, Корзина; +- контактный телефон: +7-977-736-33-63; +- пункт самовывоза: г. Москва, ул. Привольная, д. 13к1; +- первый баннер: "Рыбсток - рыба, морепродукты, мясо, сыры, бакалея. Интернет-склад качественной продукции по стоковым ценам"; +- баннеры: бесплатная доставка внутри МКАД от 5000 руб., бесплатная доставка за МКАД от 10000 руб., можно приобрести день в день, без предоплаты; +- заметное размещение промокодов; +- блок будущей заявки "Запрос проходных цен". + +Ценовых градаций теперь четыре: + +- базовая цена; +- наличный расчет от 20 000 руб. - скидка 5%; +- наличный расчет от 50 000 руб. - скидка 7%; +- безналичный расчет - +8%. + ## Следующий шаг Шаг 5: diff --git a/public/admin/categories.php b/public/admin/categories.php index d4bdbe8..11ca9fb 100644 --- a/public/admin/categories.php +++ b/public/admin/categories.php @@ -3,7 +3,10 @@ declare(strict_types=1); use App\Controllers\Admin\CategoryAdminController; +use App\Core\Auth; require_once dirname(__DIR__, 2) . '/app/bootstrap.php'; +Auth::requireAdmin(); + (new CategoryAdminController())->index(); diff --git a/public/admin/login.php b/public/admin/login.php new file mode 100644 index 0000000..f9052d5 --- /dev/null +++ b/public/admin/login.php @@ -0,0 +1,50 @@ +activeCount() === 0; + +if ($needsSetup) { + header('Location: /admin/setup.php?token=' . rawurlencode((string) app_env('INSTALL_TOKEN', ''))); + exit; +} + +$error = null; +$next = admin_safe_next((string) ($_GET['next'] ?? '/admin/')); + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $next = admin_safe_next((string) ($_POST['next'] ?? '/admin/')); + + if (Auth::attempt((string) ($_POST['email'] ?? ''), (string) ($_POST['password'] ?? ''))) { + header('Location: ' . ($next !== '' ? $next : '/admin/')); + exit; + } + + $error = 'Неверный email или пароль.'; +} + +view('admin/login', [ + 'title' => 'Вход в админку - Рыбсток', + 'breadcrumbs' => [ + ['title' => 'Главная', 'url' => '/'], + ['title' => 'Админка', 'url' => '/admin/'], + ['title' => 'Вход'], + ], + 'error' => $error, + 'next' => $next, +]); + +function admin_safe_next(string $next): string +{ + if ($next === '' || !str_starts_with($next, '/') || str_starts_with($next, '//')) { + return '/admin/'; + } + + return $next; +} diff --git a/public/admin/logout.php b/public/admin/logout.php new file mode 100644 index 0000000..7638ab3 --- /dev/null +++ b/public/admin/logout.php @@ -0,0 +1,12 @@ +index(); diff --git a/public/admin/setup.php b/public/admin/setup.php new file mode 100644 index 0000000..7aee076 --- /dev/null +++ b/public/admin/setup.php @@ -0,0 +1,51 @@ +activeCount() > 0; +$message = null; +$error = null; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (!$canRun || $hasAdmin) { + http_response_code(403); + $error = 'Создание администратора недоступно.'; + } else { + try { + $repository->createOwner( + (string) ($_POST['name'] ?? ''), + (string) ($_POST['email'] ?? ''), + (string) ($_POST['password'] ?? '') + ); + $message = 'Администратор создан. Теперь можно войти.'; + $hasAdmin = true; + } catch (Throwable $exception) { + $error = app_env('APP_DEBUG', 'false') === 'true' + ? $exception->getMessage() + : 'Не удалось создать администратора.'; + } + } +} + +view('admin/setup', [ + 'title' => 'Создание администратора - Рыбсток', + 'breadcrumbs' => [ + ['title' => 'Главная', 'url' => '/'], + ['title' => 'Админка', 'url' => '/admin/'], + ['title' => 'Создание администратора'], + ], + 'canRun' => $canRun, + 'hasAdmin' => $hasAdmin, + 'token' => $actualToken, + 'message' => $message, + 'error' => $error, +]); diff --git a/public/assets/css/app.css b/public/assets/css/app.css index 9370b1a..2d12f3e 100644 --- a/public/assets/css/app.css +++ b/public/assets/css/app.css @@ -10,8 +10,92 @@ body { background: #f7f8f4; } +.site-topbar { + position: sticky; + top: 0; + z-index: 20; + border-bottom: 1px solid #dfe5da; + background: #ffffff; + box-shadow: 0 6px 22px rgba(32, 45, 35, 0.06); +} + +.site-topbar-inner { + width: min(1180px, calc(100% - 48px)); + display: grid; + grid-template-columns: 220px minmax(0, 1fr) 290px; + gap: 24px; + align-items: center; + margin: 0 auto; + padding: 12px 0; +} + +.brand-link { + display: flex; + gap: 10px; + align-items: center; + font-size: 22px; + font-weight: 800; + color: #18201b; +} + +.brand-link:hover { + text-decoration: none; +} + +.brand-link img { + display: block; + width: 126px; + height: auto; +} + +.brand-link small { + max-width: 86px; + font-size: 10px; + font-weight: 700; + line-height: 1.25; + color: #1d5d3b; +} + +.main-nav { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + font-size: 14px; + font-weight: 700; +} + +.main-nav a { + display: inline-flex; + align-items: center; + min-height: 34px; + border-radius: 8px; + padding: 8px 10px; + color: #243229; +} + +.main-nav a:hover { + background: #f0f5eb; + text-decoration: none; +} + +.topbar-contacts { + display: grid; + gap: 4px; + justify-items: end; + font-size: 12px; + line-height: 1.35; + color: #687466; +} + +.topbar-contacts a { + font-size: 17px; + font-weight: 800; + color: #1d5d3b; +} + .site-utility { - width: min(1080px, calc(100% - 48px)); + width: min(1180px, calc(100% - 48px)); display: flex; gap: 14px; align-items: center; @@ -56,9 +140,7 @@ body { .page { min-height: 100vh; - display: grid; - place-items: center; - padding: 24px; + padding: 24px 24px 56px; } .primary-link { @@ -74,7 +156,11 @@ body { } .start-screen.wide { - width: min(1080px, 100%); + width: min(1180px, 100%); + margin: 0 auto; + border: 0; + padding: 0; + background: transparent; } .eyebrow { @@ -150,6 +236,125 @@ a:hover { background: #16492e; } +.secondary-button { + appearance: none; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid #b9c6b1; + border-radius: 8px; + padding: 13px 16px; + font-size: 16px; + font-weight: 800; + color: #1d5d3b; + background: #ffffff; +} + +.secondary-button:hover { + background: #f5f8f1; + text-decoration: none; +} + +.home-hero { + position: relative; + overflow: hidden; + padding: 44px; + border-radius: 8px; + color: #ffffff; + background: #1f5e46; +} + +.home-hero h1 { + margin-bottom: 12px; + color: #ffffff; + font-size: 52px; +} + +.home-hero .eyebrow { + color: #d8eadc; +} + +.hero-lead { + max-width: 760px; + margin: 0; + font-size: 25px; + font-weight: 700; +} + +.hero-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 24px; +} + +.hero-button { + display: inline-flex; + margin-top: 0; + background: #ffffff; + color: #1d5d3b; +} + +.hero-button:hover { + background: #eef4ea; +} + +.home-banners, +.promo-strip { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + margin-top: 18px; +} + +.home-banner, +.promo-strip div, +.price-request-panel { + padding: 18px; + border: 1px solid #dfe5da; + border-radius: 8px; + background: #ffffff; +} + +.home-banner { + display: grid; + gap: 8px; + min-height: 116px; +} + +.home-banner strong, +.promo-strip strong { + color: #18201b; +} + +.home-banner span, +.promo-strip span, +.promo-strip small { + color: #687466; +} + +.promo-strip { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.promo-strip div { + display: grid; + gap: 5px; +} + +.promo-strip strong { + font-size: 22px; +} + +.price-request-panel { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 18px; + align-items: center; + margin-top: 18px; + background: #ffffff; +} + .content-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -161,7 +366,7 @@ a:hover { padding: 20px; border: 1px solid #dfe5da; border-radius: 8px; - background: #fbfcf8; + background: #ffffff; } .products-panel { @@ -195,7 +400,7 @@ a:hover { .category-list { display: grid; - gap: 8px; + gap: 7px; margin: 0; padding: 0; list-style: none; @@ -210,10 +415,14 @@ a:hover { } .category-list li { - font-size: 16px; + font-size: 15px; font-weight: 700; } +.category-list a { + overflow-wrap: anywhere; +} + .category-list li.is-current > a, .category-list a.is-current-link { display: inline-block; @@ -224,16 +433,16 @@ a:hover { } .category-list.depth-1 li { - font-size: 14px; + font-size: 13px; font-weight: 600; } .category-list.depth-2 { - padding-left: 10px; + padding-left: 8px; } .category-list.depth-2 li { - font-size: 13px; + font-size: 12px; font-weight: 400; } @@ -241,9 +450,16 @@ a:hover { display: flex; gap: 8px; align-items: center; + min-height: 28px; + border-radius: 7px; + padding: 2px 4px; cursor: pointer; } +.category-details summary:hover { + background: #eef4ea; +} + .category-details summary a { min-width: 0; } @@ -357,7 +573,7 @@ a:hover { .admin-layout { width: min(1180px, 100%); display: grid; - grid-template-columns: 320px minmax(0, 1fr); + grid-template-columns: 300px minmax(0, 1fr); gap: 20px; align-items: start; } @@ -369,8 +585,8 @@ a:hover { .sticky-panel { position: sticky; - top: 18px; - max-height: calc(100vh - 36px); + top: 98px; + max-height: calc(100vh - 116px); overflow: auto; } @@ -390,6 +606,371 @@ a:hover { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); } +.text-page { + width: min(920px, 100%); + margin: 0 auto; +} + +.info-page { + width: min(1180px, 100%); + display: grid; + gap: 18px; + margin: 0 auto; +} + +.info-hero { + display: grid; + grid-template-columns: minmax(0, 1fr) 260px; + gap: 28px; + align-items: center; + border-radius: 8px; + padding: 38px; + color: #ffffff; + background: #1f5e46; +} + +.info-hero h1 { + margin-bottom: 14px; + color: #ffffff; + font-size: 48px; +} + +.info-hero p { + max-width: 760px; + margin: 0; +} + +.info-hero .eyebrow { + color: #d8eadc; +} + +.payment-hero { + background: #263b55; +} + +.contacts-hero { + background: #4d392a; +} + +.info-visual { + display: grid; + place-items: center; + min-height: 210px; + border: 1px solid rgba(255, 255, 255, 0.25); + border-radius: 8px; + background: rgba(255, 255, 255, 0.08); + text-align: center; +} + +.info-visual span { + display: block; + width: 82px; + height: 52px; + border: 4px solid #ffffff; + border-radius: 8px; + box-shadow: 32px 36px 0 -18px #ffffff, -34px 34px 0 -20px #ffffff; +} + +.info-visual strong { + display: block; + margin-top: 18px; + font-size: 32px; + line-height: 1; +} + +.info-visual small { + display: block; + margin-top: 6px; + color: #dfe8dc; +} + +.info-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; +} + +.info-grid.three { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.info-card { + border: 1px solid #dfe5da; + border-radius: 8px; + padding: 24px; + background: #ffffff; +} + +.info-card.compact { + min-height: 190px; +} + +.info-card p { + margin: 0 0 12px; + font-size: 16px; +} + +.info-card p:last-child { + margin-bottom: 0; +} + +.info-card-visual { + height: 118px; + margin-bottom: 18px; + border-radius: 8px; + background: #eef4ea; +} + +.delivery-city { + background: + linear-gradient(90deg, rgba(29, 93, 59, 0.2), rgba(29, 93, 59, 0.04)), + repeating-linear-gradient(90deg, #dfe8dc 0 22px, transparent 22px 44px); +} + +.delivery-region { + background: + linear-gradient(135deg, rgba(38, 59, 85, 0.2), rgba(38, 59, 85, 0.04)), + repeating-linear-gradient(135deg, #dfe8dc 0 20px, transparent 20px 46px); +} + +.payment-cash { + background: + linear-gradient(90deg, rgba(29, 93, 59, 0.16), rgba(29, 93, 59, 0.04)), + radial-gradient(circle at 22% 50%, #1d5d3b 0 24px, transparent 25px); +} + +.payment-scale { + background: + linear-gradient(90deg, rgba(77, 57, 42, 0.16), rgba(77, 57, 42, 0.04)), + linear-gradient(#dfe8dc 0 0) center 68% / 58% 10px no-repeat; +} + +.clean-list { + display: grid; + gap: 10px; + margin: 0 0 14px; + padding: 0; + list-style: none; +} + +.clean-list li { + position: relative; + padding-left: 20px; + font-size: 16px; + line-height: 1.45; +} + +.clean-list li::before { + content: ""; + position: absolute; + top: 0.65em; + left: 0; + width: 7px; + height: 7px; + border-radius: 50%; + background: #1d5d3b; +} + +.delivery-calculator { + display: grid; + gap: 18px; +} + +.calculator-note { + max-width: 260px; + font-size: 13px; + line-height: 1.45; + color: #687466; + text-align: right; +} + +.calculator-form { + display: grid; + grid-template-columns: 1.4fr 0.75fr 1fr 0.6fr; + gap: 14px; + align-items: end; +} + +.calculator-form label { + display: grid; + gap: 7px; + font-size: 14px; + font-weight: 800; + color: #394437; +} + +.calculator-form input, +.calculator-form select { + width: 100%; + border: 1px solid #cfd8c9; + border-radius: 8px; + padding: 12px; + font: inherit; + background: #ffffff; +} + +.calculator-form .checkbox-label { + align-self: center; + margin-top: 24px; +} + +.calculator-result { + display: grid; + gap: 5px; + border-radius: 8px; + padding: 18px; + color: #ffffff; + background: #1d5d3b; +} + +.calculator-result strong { + font-size: 22px; +} + +.calculator-result span { + line-height: 1.45; + color: #d8eadc; +} + +.contact-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; +} + +.contact-card.wide { + grid-column: 1 / -1; +} + +.contact-list { + display: grid; + gap: 16px; + margin: 0; +} + +.contact-list div { + display: grid; + gap: 4px; +} + +.contact-list dt { + color: #687466; +} + +.contact-list dd { + margin: 0; + font-size: 24px; + font-weight: 800; +} + +.route-card { + display: grid; + gap: 18px; +} + +.route-layout { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(300px, 0.65fr); + gap: 20px; + align-items: stretch; +} + +.map-frame { + overflow: hidden; + min-height: 360px; + border: 1px solid #dfe5da; + border-radius: 8px; + background: #eef4ea; +} + +.map-frame iframe { + display: block; + width: 100%; + height: 100%; + min-height: 360px; + border: 0; +} + +.route-steps { + display: grid; + align-content: start; + gap: 16px; + border-radius: 8px; + padding: 22px; + background: #f7f8f4; +} + +.route-steps ol { + display: grid; + gap: 12px; + margin: 0; + padding-left: 20px; +} + +.route-steps li { + font-size: 16px; + line-height: 1.45; +} + +.text-page-header, +.text-page-body { + border: 1px solid #dfe5da; + border-radius: 8px; + background: #ffffff; +} + +.text-page-header { + padding: 30px; + margin-bottom: 18px; +} + +.text-page-header h1 { + margin-bottom: 12px; +} + +.text-page-header p:last-child { + max-width: 760px; + margin-bottom: 0; +} + +.text-page-body { + display: grid; + gap: 0; + padding: 8px 30px 30px; +} + +.text-page-body section { + padding: 24px 0; + border-bottom: 1px solid #e5eadf; +} + +.text-page-body section:last-of-type { + border-bottom: 0; +} + +.text-page-body p { + margin: 0 0 12px; + font-size: 16px; +} + +.text-page-body p:last-child { + margin-bottom: 0; +} + +.notice-panel { + margin-top: 10px; + border: 1px solid #ead8c4 !important; + border-radius: 8px; + padding: 22px !important; + background: #fffaf2; +} + +.thanks-text { + margin: 22px 0 0 !important; + font-weight: 800; + color: #1d5d3b; +} + .admin-form { display: grid; gap: 14px; @@ -450,6 +1031,129 @@ a:hover { color: #687466; } +.site-footer { + margin-top: 28px; + color: #dfe8dc; + background: #18201b; +} + +.site-footer-inner { + width: min(1180px, calc(100% - 48px)); + display: grid; + grid-template-columns: 1.15fr 1.25fr 1fr 1.2fr; + gap: 34px; + margin: 0 auto; + padding: 42px 0 34px; +} + +.footer-logo { + display: inline-flex; + align-items: center; + min-height: 42px; +} + +.footer-logo img { + display: block; + width: 150px; + height: auto; + filter: brightness(0) invert(1); +} + +.footer-logo span { + font-size: 24px; + font-weight: 800; + color: #ffffff; +} + +.footer-brand p, +.footer-column p { + margin: 12px 0 0; + font-size: 14px; + line-height: 1.55; + color: #b8c5b5; +} + +.footer-column h2 { + margin: 0 0 14px; + font-size: 15px; + color: #ffffff; +} + +.footer-contacts { + display: grid; + gap: 12px; + margin: 0; +} + +.footer-contacts div { + display: grid; + gap: 3px; +} + +.footer-contacts dt { + font-size: 12px; + color: #91a08c; +} + +.footer-contacts dd { + margin: 0; + font-size: 14px; + line-height: 1.45; +} + +.site-footer a { + color: #ffffff; +} + +.site-footer a:hover { + color: #d8eadc; +} + +.footer-links { + display: grid; + align-content: start; + gap: 9px; +} + +.footer-links a { + font-size: 14px; +} + +.footer-action { + display: inline-flex; + width: fit-content; + margin-top: 18px; + border: 1px solid #526457; + border-radius: 8px; + padding: 11px 14px; + font-size: 14px; + font-weight: 800; +} + +.footer-action:hover { + border-color: #d8eadc; + text-decoration: none; +} + +.site-footer-bottom { + border-top: 1px solid #2b362f; + padding: 16px 24px 18px; + text-align: center; +} + +.site-footer-bottom p { + max-width: 1180px; + margin: 6px auto 0; + font-size: 12px; + line-height: 1.5; + color: #9eaa9a; +} + +.site-footer-bottom p:first-child { + margin-top: 0; + color: #c7d2c3; +} + @media (max-width: 760px) { .site-utility { width: calc(100% - 32px); @@ -461,6 +1165,44 @@ a:hover { grid-template-columns: 1fr; } + .site-topbar-inner, + .home-banners, + .promo-strip, + .price-request-panel { + grid-template-columns: 1fr; + } + + .site-topbar-inner { + width: calc(100% - 32px); + justify-items: start; + gap: 12px; + } + + .brand-link { + width: 100%; + justify-content: space-between; + } + + .main-nav { + justify-content: flex-start; + } + + .topbar-contacts { + justify-items: start; + } + + .hero-lead { + font-size: 20px; + } + + .home-hero { + padding: 28px; + } + + .home-hero h1 { + font-size: 40px; + } + .settings-list div { grid-template-columns: 1fr; gap: 2px; @@ -475,6 +1217,38 @@ a:hover { grid-template-columns: 1fr; } + .info-hero, + .info-grid, + .info-grid.three, + .calculator-form, + .contact-grid, + .route-layout { + grid-template-columns: 1fr; + } + + .info-hero { + padding: 28px; + } + + .info-hero h1 { + font-size: 38px; + } + + .info-visual { + min-height: 160px; + } + + .calculator-note { + max-width: none; + text-align: left; + } + + .site-footer-inner { + width: calc(100% - 32px); + grid-template-columns: 1fr; + gap: 26px; + } + .sticky-panel { position: static; max-height: none; diff --git a/public/assets/images/logo_300.png b/public/assets/images/logo_300.png new file mode 100644 index 0000000..6b22ef5 Binary files /dev/null and b/public/assets/images/logo_300.png differ diff --git a/public/check.php b/public/check.php index a232959..0c6c510 100644 --- a/public/check.php +++ b/public/check.php @@ -17,6 +17,17 @@ echo "db: " . app_env('DB_DATABASE', '') . "\n"; echo "home_text_expected: Каталог открывается уже на главной странице\n"; echo "schema_mtime: " . date('Y-m-d H:i:s', filemtime(base_path('database/schema.sql'))) . "\n"; echo "seed_mtime: " . date('Y-m-d H:i:s', filemtime(base_path('database/seed.sql'))) . "\n"; +echo "admin_auth_files:\n"; +foreach ([ + 'app/Core/Auth.php', + 'app/Repositories/AdminRepository.php', + 'public/admin/login.php', + 'public/admin/setup.php', + 'admin/login.php', + 'admin/setup.php', +] as $path) { + echo "- {$path}: " . (is_file(base_path($path)) ? 'yes' : 'no') . "\n"; +} try { $pdo = Database::pdo(); diff --git a/public/index.php b/public/index.php index 659317a..f412f62 100644 --- a/public/index.php +++ b/public/index.php @@ -4,6 +4,7 @@ declare(strict_types=1); use App\Controllers\CatalogController; use App\Controllers\HomeController; +use App\Controllers\PageController; use App\Core\Router; require_once dirname(__DIR__) . '/app/bootstrap.php'; @@ -12,6 +13,10 @@ $router = new Router(); $router->get('/', static fn () => (new HomeController())->index()); $router->get('/catalog', static fn () => (new CatalogController())->index()); +$router->get('/delivery', static fn () => (new PageController())->delivery()); +$router->get('/payment', static fn () => (new PageController())->payment()); +$router->get('/contacts', static fn () => (new PageController())->contacts()); +$router->get('/returns', static fn () => (new PageController())->returns()); $router->get('/catalog/{slug}', static fn (string $slug) => (new CatalogController())->category($slug)); $router->dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']); diff --git a/views/admin/login.php b/views/admin/login.php new file mode 100644 index 0000000..80fbb81 --- /dev/null +++ b/views/admin/login.php @@ -0,0 +1,29 @@ + +
+

Админка

+

Вход

+

Введите email и пароль администратора.

+ + +
+ + +
+ + + + +
+
diff --git a/views/admin/setup.php b/views/admin/setup.php new file mode 100644 index 0000000..74e4bad --- /dev/null +++ b/views/admin/setup.php @@ -0,0 +1,47 @@ + +
+

Админка

+

Создание администратора

+ + + + + + +
+ + + +

Администратор уже создан. Создание через setup закрыто.

+

Войти в админку

+ +
Нужен корректный токен установки.
+ +

Создайте первого администратора. После этого страница станет недоступна для повторного создания.

+
+ + + + + +
+ +
diff --git a/views/layouts/main.php b/views/layouts/main.php index 8f85cac..e767ebc 100644 --- a/views/layouts/main.php +++ b/views/layouts/main.php @@ -24,6 +24,30 @@ declare(strict_types=1); +
+ +
+
@@ -35,5 +59,59 @@ declare(strict_types=1);
+ + diff --git a/views/pages/contacts.php b/views/pages/contacts.php new file mode 100644 index 0000000..de8572c --- /dev/null +++ b/views/pages/contacts.php @@ -0,0 +1,72 @@ +
+
+
+

Связь с Рыбсток

+

Контакты

+

Позвоните по вопросам заказа или продукции. Самовывоз доступен после подтверждения заказа.

+
+ +
+ +
+
+

Телефоны

+
+
+
По вопросам вашего заказа
+
+7-977-736-33-63
+
+
+
Консультация по продукции
+
+7-967-198-10-94
+
+
+
+ +
+

Email

+

zakaz@rybstock.ru

+
+ +
+

Адрес пункта самовывоза

+

г. Москва, м. Лермонтовский проспект, ул. Привольная, д. 13к1

+

Вход со двора через крыльцо Wildberries. Самовывоз возможен только после подтверждения заказа.

+
+
+ +
+
+
+

Как пройти

+

Маршрут к пункту самовывоза

+
+ Открыть в Яндекс.Картах +
+ +
+
+ +
+ +
+
    +
  1. Доезжайте до станции метро “Лермонтовский проспект”.
  2. +
  3. Идите к адресу: ул. Привольная, д. 13к1.
  4. +
  5. Зайдите во двор дома.
  6. +
  7. Ориентир - крыльцо Wildberries, вход со двора.
  8. +
  9. Перед самовывозом дождитесь подтверждения заказа по телефону.
  10. +
+

Если сложно найти вход, позвоните нам: +7-977-736-33-63. Подскажем, как пройти.

+
+
+
+
diff --git a/views/pages/delivery.php b/views/pages/delivery.php new file mode 100644 index 0000000..1ede44c --- /dev/null +++ b/views/pages/delivery.php @@ -0,0 +1,169 @@ +
+
+
+

Москва и Московская область

+

Доставка и самовывоз

+

Привозим заказы после подтверждения по телефону. В день заказа можно получить товар, если оформить заказ в будний день до 14:00.

+
+ +
+ +
+
+
+

Калькулятор

+

Рассчитать доставку

+
+ Расчет предварительный, финально подтвердит менеджер. +
+ +
+ + + + + + + + + +
+ +
+ Доставка: 0 руб. + Введите сумму заказа и условия доставки. +
+
+ +
+
+
+

Доставка внутри МКАД

+
    +
  • Заказы от 5000 руб. - бесплатно до подъезда.
  • +
  • В остальных случаях доставка 599 руб.
  • +
  • Минимальная сумма заказа на доставку внутри МКАД - 2000 руб.
  • +
  • Подъем до квартиры или офиса - 390 руб.
  • +
+

Водитель может оставить заказ у консьержа, соседа и т. д. при полной оплате по QR-коду и оплате подъема.

+
+ +
+
+

Доставка за МКАД

+
    +
  • До 10 км от МКАД бесплатная доставка от 10000 руб.
  • +
  • От 10 км и дальше: каждый километр от МКАД = 1000 руб. к сумме заказа для бесплатной доставки.
  • +
  • Если заказ не попадает под бесплатную доставку: 199 руб. + 60 руб./км.
  • +
  • Минимальная сумма заказа - 5000 руб.
  • +
  • Подъем до квартиры или офиса - 390 руб.
  • +
+

Например, 15 км от МКАД - бесплатная доставка от 15000 руб.; 17 км - от 17000 руб.

+
+
+ +
+
+

Как мы развозим

+

Понедельник-пятница: 18:00-24:00 без ограничения по точному времени.

+

При построении маршрута учитываем комментарии к заказу, например: “буду дома после 19:00”.

+
+ +
+

Самовывоз

+

Понедельник-пятница: 18:00-21:00.

+

Минимальная сумма заказа на самовывоз - 1 руб. Самовывоз возможен только после подтверждения заказа.

+
+ +
+

Когда получить заказ

+

Заказы до 14:00 в будние дни можно получить день в день.

+

Заказы после 14:00 - на следующий или другой удобный для вас день.

+
+
+ +
+

Оплата при получении

+

На сайте ничего оплачивать не нужно. Вы оформляете заказ, чтобы мы его увидели, проверили наличие и собрали с учетом ваших пожеланий. Оплата товара производится только при получении.

+

При доставке и самовывозе можно оплатить наличными или QR-кодом. Безналичный расчет и “Меркурий” обсуждаются индивидуально для заказов от 30000 руб.

+
+
+ + diff --git a/views/pages/home.php b/views/pages/home.php index ce5cc83..e0a8931 100644 --- a/views/pages/home.php +++ b/views/pages/home.php @@ -11,15 +11,61 @@ declare(strict_types=1); /** @var string $buildVersion */ ?>
-

Шаг 3

-

Рыбсток

-

Каталог открывается уже на главной странице, чтобы покупатель сразу видел ассортимент и не делал лишних переходов.

-

Версия сборки:

+
+

Интернет-склад качественной продукции

+

Рыбсток

+

Рыба, морепродукты, мясо, сыры, овощи, ягоды и бакалея по стоковым ценам.

+ +
-
- MariaDB: - -
+
+
+ Бесплатная доставка внутри МКАД от 5000 руб. + Привезем заказ после подтверждения по телефону. +
+
+ Бесплатная доставка за МКАД от 10000 руб. + Для адресов до 10 км от МКАД, дальше расчет по расстоянию. +
+
+ Можно приобрести день в день + Привезем заказ день в день, если он оформлен в будний день до 14:00. +
+
+ Без предоплаты + Оплата после подтверждения заказа и получения товара. +
+
+ +
+
+ Промокод + Рыбсток20000 + 5% при наличном расчете от 20 000 руб. +
+
+ Промокод + Рыбсток50000 + 7% при наличном расчете от 50 000 руб. +
+
+ Безналичный расчет + +8% + Отображается отдельной ценовой градацией. +
+
+ +
+
+

Запрос проходных цен

+

Пришлите список позиций, а мы сверим цены

+

Можно вставить список текстом или приложить файл. Заявка будет уходить администратору на почту.

+
+ Подготовить форму +
@@ -27,7 +73,7 @@ declare(strict_types=1);
-

Категории

+

Каталог

Категории пока не найдены. Если схема уже применена, проверьте `database/seed.sql`.

@@ -41,18 +87,18 @@ declare(strict_types=1);
-

Настройки доставки

+

Доставка

-
Внутри МКАД бесплатно от
+
Бесплатно внутри МКАД от
руб.
-
Внутри МКАД платно
+
Доставка внутри МКАД
руб.
-
За МКАД расчет
+
За МКАД
руб. + руб./км
diff --git a/views/pages/payment.php b/views/pages/payment.php new file mode 100644 index 0000000..c435ed6 --- /dev/null +++ b/views/pages/payment.php @@ -0,0 +1,43 @@ +
+
+
+

Без оплаты на сайте

+

Оплата заказа

+

На сайте вы только оформляете заказ. Оплата производится в момент получения товара после подтверждения и проверки.

+
+ +
+ +
+
+
+

Оплата при получении

+

Оплатить товар при доставке или самовывозе можно наличными или QR-кодом.

+

Кнопки “Оплатить” нет ни в одном разделе сайта Рыбсток: мы сначала подтверждаем заказ, наличие и фактический вес товара.

+
+ +
+
+

Весовая продукция

+

Весовая продукция на сайте рассчитывается по приблизительному весу товара.

+

Перед отправкой или выдачей заказ пересчитывается по фактическому весу.

+
+
+ +
+
+

Безналичный расчет

+

Выставляем счета на безналичный расчет от 30000 руб.

+

Для безналичного расчета действует отдельная ценовая градация: +8% на весь чек.

+
+ +
+

Проверка качества

+

Мы бережно демонстрируем товар, который привезли. Если качество товара вас не устроит, вы можете не приобретать этот товар.

+
+
+
diff --git a/views/pages/returns.php b/views/pages/returns.php new file mode 100644 index 0000000..1482a44 --- /dev/null +++ b/views/pages/returns.php @@ -0,0 +1,35 @@ +
+
+

Покупателям

+

Правила возврата товара

+

Процедура возврата товара регламентируется статьей 26.1 федерального закона «О защите прав потребителей».

+
+ +
+
+

Сроки возврата

+

Потребитель вправе отказаться от товара в любое время до его передачи, а после передачи товара - в течение семи дней.

+

Покупатель вправе вернуть товар при соблюдении правил возврата в течение 7 дней после покупки путем доставки товара в наш магазин по адресу: г. Москва, ул. Привольная, д. 13к1.

+
+ +
+

Условия возврата товара надлежащего качества

+

Возврат товара надлежащего качества возможен, если сохранены его товарный вид, потребительские свойства, а также документ, подтверждающий факт и условия покупки указанного товара.

+

Возврат возможен при условии правильного хранения товара с соблюдением температурного режима. Также должны быть сохранены фирменная тара, упаковка, маркировка, внешний вид и комплектность товара.

+
+ +
+

Возврат денежных средств

+

При отказе потребителя от товара продавец возвращает денежную сумму, уплаченную потребителем по договору, за исключением расходов продавца на доставку от потребителя возвращенного товара.

+

Возврат денежных средств производится не позднее чем через десять дней со дня предъявления потребителем соответствующего требования.

+
+ +
+

Готовая продукция

+

Обращаем ваше внимание, что возвраты на готовую продукцию не принимаются. Пожалуйста, осматривайте внешний вид товара при получении.

+

Если вас не устроил посол, это не является причиной для возврата. Количество соли - субъективное восприятие каждого покупателя. Вся продукция изготавливается в соответствии с техническими условиями завода-производителя.

+
+ +

Спасибо за понимание.

+
+
diff --git a/ПЛАН_ДЕЙСТВИЙ_РЫБСТОК.md b/ПЛАН_ДЕЙСТВИЙ_РЫБСТОК.md index 278d921..f4c9367 100644 --- a/ПЛАН_ДЕЙСТВИЙ_РЫБСТОК.md +++ b/ПЛАН_ДЕЙСТВИЙ_РЫБСТОК.md @@ -642,4 +642,6 @@ - Шаг 3 выполнен: созданы первые PHP-классы, репозитории, контроллер главной страницы, вывод категорий/настроек/популярных товаров, каталог на главной, категории старого сайта в `database/seed.sql`, SEO-поля, хлебные крошки, кнопка назад, градации выгодных цен по промокодам и документ `docs/ШАГ_3_PHP_КЛАССЫ.md`. - Перенос товаров выделен в отдельную задачу: `docs/ПЛАН_ПЕРЕНОСА_ТОВАРОВ.md`. - Шаг 4 выполнен: добавлен роутинг, страницы полного каталога и категорий, боковая навигация, вывод товаров по вложенным категориям, первые формы админки для категорий/товаров, расширенная модель атрибутов/фасовок как в старой админке, анализ первых товаров, сценарные сегменты каталога, бейджи, правила рекомендаций, события для будущего обучения и документы `docs/ШАГ_4_КАТАЛОГ_И_АДМИНКА.md`, `docs/АНАЛИЗ_ПЕРВЫХ_ТОВАРОВ_И_АТРИБУТОВ.md`, `docs/АНАЛИЗ_КАТАЛОГА_И_РЕКОМЕНДАЦИЙ.md`. +- Главная страница дополнена требованиями к первому экрану: видимые контакты, пункт самовывоза, меню `Главная / Каталог / Доставка / Контакты / Корзина`, баннеры про доставку, день-в-день и отсутствие предоплаты, видимые промокоды и блок будущего запроса проходных цен. +- Ценовые градации должны быть четыре: базовая цена, наличный расчет от 20 000 руб. со скидкой 5%, наличный расчет от 50 000 руб. со скидкой 7%, безналичный расчет с наценкой 8%. - Следующий шаг: сделать авторизацию администратора, защитить админку, добавить редактирование категорий/товаров и заготовку страницы товара.