diff --git a/ init_db.php b/ init_db.php deleted file mode 100644 index 901a438..0000000 --- a/ init_db.php +++ /dev/null @@ -1,122 +0,0 @@ -getConnection(); - -// Создаем таблицы, если они не существуют -$tables = [ - 'users' => " - CREATE TABLE IF NOT EXISTS users ( - user_id SERIAL PRIMARY KEY, - email VARCHAR(255) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, - full_name VARCHAR(100) NOT NULL, - phone VARCHAR(20), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - is_active BOOLEAN DEFAULT TRUE, - is_admin BOOLEAN DEFAULT FALSE - ) - ", - - 'categories' => " - CREATE TABLE IF NOT EXISTS categories ( - category_id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - slug VARCHAR(100) UNIQUE NOT NULL, - parent_id INTEGER REFERENCES categories(category_id), - description TEXT, - sort_order INTEGER DEFAULT 0, - is_active BOOLEAN DEFAULT TRUE - ) - ", - - 'products' => " - CREATE TABLE IF NOT EXISTS products ( - product_id SERIAL PRIMARY KEY, - category_id INTEGER REFERENCES categories(category_id), - name VARCHAR(200) NOT NULL, - slug VARCHAR(200) UNIQUE NOT NULL, - description TEXT, - price DECIMAL(10, 2) NOT NULL, - old_price DECIMAL(10, 2), - sku VARCHAR(50) UNIQUE, - stock_quantity INTEGER DEFAULT 0, - is_available BOOLEAN DEFAULT TRUE, - is_featured BOOLEAN DEFAULT FALSE, - rating DECIMAL(3, 2) DEFAULT 0, - review_count INTEGER DEFAULT 0, - image_url VARCHAR(500), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - " -]; - -foreach ($tables_to_create as $table_name => $sql) { - try { - $db->exec($sql); - echo "Таблица '$table_name' создана/проверена
"; - } catch (PDOException $e) { - echo "Ошибка создания таблицы '$table_name': " . $e->getMessage() . "
"; - } -} - -// Добавляем тестовые данные -// Проверяем, есть ли уже категории -$check_categories = $db->query("SELECT COUNT(*) FROM categories")->fetchColumn(); - -if ($check_categories == 0) { - // Добавляем категории - $categories = [ - ['Мягкая мебель', 'myagkaya-mebel', NULL, 'Диваны, кресла, пуфы'], - ['Диваны', 'divany', 1, 'Прямые и угловые диваны'], - ['Кресла', 'kresla', 1, 'Кресла для гостиной и офиса'], - ['Спальня', 'spalnya', NULL, 'Кровати, тумбы, комоды'], - ['Кровати', 'krovati', 4, 'Односпальные и двуспальные кровати'] - ]; - - foreach ($categories as $category) { - $stmt = $db->prepare("INSERT INTO categories (name, slug, parent_id, description) VALUES (?, ?, ?, ?)"); - $stmt->execute($category); - } - echo "Добавлены категории
"; -} - -// Проверяем, есть ли уже товары -$check_products = $db->query("SELECT COUNT(*) FROM products")->fetchColumn(); - -if ($check_products == 0) { - // Добавляем товары - $products = [ - [2, 'Диван VELVET', 'divan-velvet', 'Прямой диван с тканевой обивкой', 45999, 54999, 'DIV-VEL-001', 10], - [2, 'Диван MODERN', 'divan-modern', 'Угловой диван с кожаной обивкой', 78999, 89999, 'DIV-MOD-002', 5], - [3, 'Кресло OPPORTUNITY', 'kreslo-opportunity', 'Кресло с деревянными ножками', 16999, 19999, 'KRES-OPP-001', 15], - [3, 'Кресло GOLDEN', 'kreslo-golden', 'Золотистое кресло для гостиной', 19999, 23999, 'KRES-GOL-002', 8], - [5, 'Кровать CLASSIC', 'krovat-classic', 'Двуспальная кровать из массива дуба', 64999, 74999, 'KROV-CLA-001', 3] - ]; - - foreach ($products as $product) { - $stmt = $db->prepare("INSERT INTO products (category_id, name, slug, description, price, old_price, sku, stock_quantity) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute($product); - } - echo "Добавлены товары
"; -} - -// Проверяем, есть ли администратор -$check_admin = $db->prepare("SELECT COUNT(*) FROM users WHERE email = ?"); -$check_admin->execute(['admin@aeterna.ru']); - -if ($check_admin->fetchColumn() == 0) { - // Добавляем администратора (пароль: admin123) - $admin_password = password_hash('admin123', PASSWORD_DEFAULT); - $stmt = $db->prepare("INSERT INTO users (email, password_hash, full_name, phone, is_admin) - VALUES (?, ?, ?, ?, ?)"); - $stmt->execute(['admin@aeterna.ru', $admin_password, 'Администратор AETERNA', '+79129991223', true]); - echo "Добавлен администратор (email: admin@aeterna.ru, пароль: admin123)
"; -} - -echo "

База данных успешно инициализирована!

"; -echo "Перейти в каталог"; -?> \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..06c7753 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +.git +.gitignore +.DS_Store +node_modules +*.log +.env +.env.local + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a1811fb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM php:8.2-apache + +RUN apt-get update && apt-get install -y libpq-dev \ + && docker-php-ext-install pdo pdo_pgsql \ + && apt-get clean && rm -rf /var/lib/apt/lists/* + +RUN a2enmod rewrite headers alias + +WORKDIR /var/www/html + diff --git a/admin_actions.php b/admin_actions.php deleted file mode 100644 index ea9563b..0000000 --- a/admin_actions.php +++ /dev/null @@ -1,170 +0,0 @@ -getConnection(); -$action = $_GET['action'] ?? ''; - -try { - switch ($action) { - case 'delete_product': - if (isset($_GET['id'])) { - $productId = intval($_GET['id']); - // Делаем товар недоступным - $stmt = $db->prepare(" - UPDATE products - SET is_available = FALSE, stock_quantity = 0, updated_at = CURRENT_TIMESTAMP - WHERE product_id = ? - "); - $stmt->execute([$productId]); - header('Location: admin_panel.php?action=products&message=Товар помечен как недоступный'); - exit(); - } - break; - - case 'restore_product': - if (isset($_GET['id'])) { - $productId = intval($_GET['id']); - // Восстанавливаем товар - $stmt = $db->prepare(" - UPDATE products - SET is_available = TRUE, stock_quantity = 10, updated_at = CURRENT_TIMESTAMP - WHERE product_id = ? - "); - $stmt->execute([$productId]); - header('Location: admin_panel.php?action=products&message=Товар восстановлен'); - exit(); - } - break; - - case 'delete_category': - if (isset($_GET['id'])) { - $categoryId = intval($_GET['id']); - - try { - // 1. Проверяем, есть ли товары в этой категории - $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?"); - $checkProducts->execute([$categoryId]); - $productCount = $checkProducts->fetchColumn(); - - if ($productCount > 0) { - // Если есть товары, делаем категорию неактивной - $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); - $stmt->execute([$categoryId]); - header('Location: admin_panel.php?action=categories&message=Категория скрыта (содержит товары)'); - exit(); - } - - // 2. Проверяем, есть ли дочерние категории - $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?"); - $checkChildren->execute([$categoryId]); - $childCount = $checkChildren->fetchColumn(); - - if ($childCount > 0) { - // Вариант A: Делаем категорию неактивной - $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); - $stmt->execute([$categoryId]); - header('Location: admin_panel.php?action=categories&message=Категория скрыта (имеет дочерние категории)'); - exit(); - - // Вариант B: Удаляем вместе с дочерними (раскомментируйте если нужно) - /* - // Сначала удаляем дочерние категории - $stmt = $db->prepare("DELETE FROM categories WHERE parent_id = ?"); - $stmt->execute([$categoryId]); - - // Затем удаляем саму категорию - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория и её дочерние категории удалены'); - exit(); - */ - } - - // 3. Если нет товаров и дочерних категорий, удаляем - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория удалена'); - exit(); - - } catch (PDOException $e) { - header('Location: admin_panel.php?action=categories&error=' . urlencode($e->getMessage())); - exit(); - } - } - break; - - case 'delete_category_force': - // Принудительное удаление с дочерними категориями - if (isset($_GET['id'])) { - $categoryId = intval($_GET['id']); - - try { - // Сначала перемещаем товары в другую категорию (например, в первую) - $firstCategory = $db->query("SELECT category_id FROM categories WHERE category_id != ? LIMIT 1")->fetchColumn(); - - if ($firstCategory) { - $moveProducts = $db->prepare("UPDATE products SET category_id = ? WHERE category_id = ?"); - $moveProducts->execute([$firstCategory, $categoryId]); - } - - // Обнуляем parent_id у дочерних категорий - $stmt = $db->prepare("UPDATE categories SET parent_id = NULL WHERE parent_id = ?"); - $stmt->execute([$categoryId]); - - // Удаляем категорию - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория удалена. Товары перемещены.'); - exit(); - - } catch (PDOException $e) { - header('Location: admin_panel.php?action=categories&error=' . urlencode($e->getMessage())); - exit(); - } - } - break; - - case 'toggle_user': - if (isset($_GET['id'])) { - $userId = intval($_GET['id']); - $stmt = $db->prepare(" - UPDATE users - SET is_active = NOT is_active, updated_at = CURRENT_TIMESTAMP - WHERE user_id = ? - "); - $stmt->execute([$userId]); - header('Location: admin_panel.php?action=users&message=Статус пользователя изменен'); - exit(); - } - break; - - case 'make_admin': - if (isset($_GET['id'])) { - $userId = intval($_GET['id']); - $stmt = $db->prepare("UPDATE users SET is_admin = TRUE WHERE user_id = ?"); - $stmt->execute([$userId]); - header('Location: admin_panel.php?action=users&message=Пользователь назначен администратором'); - exit(); - } - break; - } -} catch (PDOException $e) { - header('Location: admin_panel.php?error=' . urlencode($e->getMessage())); - exit(); -} - -// Если действие не распознано -header('Location: admin_panel.php'); -exit(); -?> \ No newline at end of file diff --git a/catalog_admin.php b/catalog_admin.php deleted file mode 100644 index 524f499..0000000 --- a/catalog_admin.php +++ /dev/null @@ -1,924 +0,0 @@ -getConnection(); - - // ОБРАБОТКА POST ЗАПРОСОВ ДЛЯ КАТЕГОРИЙ - if ($_SERVER['REQUEST_METHOD'] === 'POST' && $is_admin) { - $form_action = $_POST['action'] ?? ''; - - if ($form_action === 'add_category' || $form_action === 'edit_category') { - $name = trim($_POST['name'] ?? ''); - $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : null; - $description = trim($_POST['description'] ?? ''); - $sort_order = (int)($_POST['sort_order'] ?? 0); - $is_active = isset($_POST['is_active']) ? 1 : 0; - $category_id_post = (int)($_POST['category_id'] ?? 0); - - // Валидация - if (empty($name)) { - $_SESSION['error'] = 'Название категории обязательно'; - header('Location: catalog_admin.php?action=' . $form_action . ($category_id_post ? '&id=' . $category_id_post : '')); - exit(); - } - - // Создаем slug - $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); - $slug = preg_replace('/^-+|-+$/', '', $slug); // Убираем дефисы по краям - - if ($form_action === 'add_category') { - // Проверяем существование slug - $check = $db->prepare("SELECT COUNT(*) FROM categories WHERE slug = ?"); - $check->execute([$slug]); - if ($check->fetchColumn() > 0) { - $slug = $slug . '-' . time(); // Добавляем timestamp для уникальности - } - - $sql = "INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active) - VALUES (?, ?, ?, ?, ?, ?)"; - $stmt = $db->prepare($sql); - $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]); - - $_SESSION['message'] = 'Категория успешно добавлена'; - header('Location: catalog_admin.php?action=categories'); - exit(); - - } elseif ($form_action === 'edit_category' && $category_id_post > 0) { - // Проверяем существование slug для других категорий - $check = $db->prepare("SELECT COUNT(*) FROM categories WHERE slug = ? AND category_id != ?"); - $check->execute([$slug, $category_id_post]); - if ($check->fetchColumn() > 0) { - $slug = $slug . '-' . $category_id_post; - } - - $sql = "UPDATE categories SET - name = ?, slug = ?, parent_id = ?, description = ?, - sort_order = ?, is_active = ?, updated_at = CURRENT_TIMESTAMP - WHERE category_id = ?"; - $stmt = $db->prepare($sql); - $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id_post]); - - $_SESSION['message'] = 'Категория успешно обновлена'; - header('Location: catalog_admin.php?action=categories'); - exit(); - } - } - - // Удаление категории - if ($form_action === 'delete_category') { - $category_id_del = (int)($_POST['category_id'] ?? 0); - - if ($category_id_del > 0) { - // Проверяем наличие активных товаров - $check_products = $db->prepare(" - SELECT COUNT(*) as product_count - FROM products - WHERE category_id = ? AND is_available = TRUE - "); - $check_products->execute([$category_id_del]); - $active_products = $check_products->fetchColumn(); - - if ($active_products > 0) { - $_SESSION['error'] = 'Невозможно удалить категорию с активными товарами'; - header('Location: catalog_admin.php?action=categories'); - exit(); - } - - // Проверяем дочерние категории - $check_children = $db->prepare(" - SELECT COUNT(*) as child_count - FROM categories - WHERE parent_id = ? AND is_active = TRUE - "); - $check_children->execute([$category_id_del]); - $active_children = $check_children->fetchColumn(); - - if ($active_children > 0) { - $_SESSION['error'] = 'Невозможно удалить категорию с активными дочерними категориями'; - header('Location: catalog_admin.php?action=categories'); - exit(); - } - - // Удаляем категорию - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$category_id_del]); - - $_SESSION['message'] = 'Категория успешно удалена'; - header('Location: catalog_admin.php?action=categories'); - exit(); - } - } - } - - // Получаем категории для отображения - $categories_stmt = $db->query(" - SELECT c1.*, c2.name as parent_name, - (SELECT COUNT(*) FROM products WHERE category_id = c1.category_id) as product_count - FROM categories c1 - LEFT JOIN categories c2 ON c1.parent_id = c2.category_id - ORDER BY c1.sort_order, c1.name - "); - $categories = $categories_stmt->fetchAll(); - - // Для редактирования категории - if ($action === 'edit_category' && $product_id > 0) { - $cat_stmt = $db->prepare("SELECT * FROM categories WHERE category_id = ?"); - $cat_stmt->execute([$product_id]); - $current_category = $cat_stmt->fetch(); - - if (!$current_category) { - $_SESSION['error'] = 'Категория не найдена'; - header('Location: catalog_admin.php?action=categories'); - exit(); - } - } - - // Получаем товары - $sql = "SELECT p.*, c.name as category_name FROM products p - LEFT JOIN categories c ON p.category_id = c.category_id - WHERE p.is_available = TRUE"; - - $params = []; - if ($category_id && is_numeric($category_id)) { - $sql .= " AND p.category_id = ?"; - $params[] = $category_id; - } - - $sql .= " ORDER BY p.product_id DESC"; - - if ($params) { - $stmt = $db->prepare($sql); - $stmt->execute($params); - } else { - $stmt = $db->query($sql); - } - - $products = $stmt->fetchAll(); - - // Сообщения из сессии - $message = $_SESSION['message'] ?? ''; - $error = $_SESSION['error'] ?? ''; - - // Очищаем сообщения после использования - unset($_SESSION['message']); - unset($_SESSION['error']); - -} catch (PDOException $e) { - $error = "Ошибка подключения к базе данных: " . $e->getMessage(); -} -?> - - - - - - - AETERNA - Каталог - - - - - - - -
-
-
- -
-
- Все категории - -
- -
-
- - - 0 - - - - -
-
-
-
- -
-
- -
-
- - - - - - - -
- -
- - - -
- -
- - - - -
-

- - -
- -
- - -
- - - - - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- - - - Отмена - -
-
- - - -
- - -
- - -
-

Управление категориями

- - Добавить категорию - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
IDНазваниеРодительскаяТоваровПорядокСтатусДействия
- -
-
- товаров - - - Активна - - Неактивна - - - - - - -
- - - - - - - -
-
- - - -
- -
- - - - -
- <?= htmlspecialchars($product['name']) ?> - - $product['price']): ?> - - -% - - - - -
- -
-
-
- ... -
-
- $product['price']): ?> - - ₽ -
- - ₽ -
- -
- В наличии: шт. -
- - -
-
- -
- -
-
-
-
- - - - - - - -getConnection(); -$action = $_POST['action'] ?? ''; - -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - try { - switch ($action) { - case 'add_category': - $name = $_POST['name'] ?? ''; - $parent_id = $_POST['parent_id'] ?: null; - $description = $_POST['description'] ?? null; - $sort_order = $_POST['sort_order'] ?? 0; - $is_active = isset($_POST['is_active']) ? 1 : 0; - - // Создаем slug из названия - $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); - - $stmt = $db->prepare(" - INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active) - VALUES (?, ?, ?, ?, ?, ?) - "); - - $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]); - - header('Location: catalog_admin.php?action=categories&message=Категория успешно добавлена'); - exit(); - - case 'edit_category': - $category_id = $_POST['category_id'] ?? 0; - $name = $_POST['name'] ?? ''; - $parent_id = $_POST['parent_id'] ?: null; - $description = $_POST['description'] ?? null; - $sort_order = $_POST['sort_order'] ?? 0; - $is_active = isset($_POST['is_active']) ? 1 : 0; - - // Создаем slug из названия - $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); - - $stmt = $db->prepare(" - UPDATE categories SET - name = ?, - slug = ?, - parent_id = ?, - description = ?, - sort_order = ?, - is_active = ? - WHERE category_id = ? - "); - - $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id]); - - header('Location: catalog_admin.php?action=categories&message=Категория успешно обновлена'); - exit(); - - case 'delete_category': - $category_id = $_POST['category_id'] ?? 0; - - // Проверяем, есть ли активные товары в этой категории - $checkStmt = $db->prepare(" - SELECT COUNT(*) - FROM products - WHERE category_id = ? AND is_available = TRUE - "); - $checkStmt->execute([$category_id]); - $active_products = $checkStmt->fetchColumn(); - - if ($active_products > 0) { - header('Location: catalog_admin.php?action=categories&error=Невозможно удалить категорию с активными товарами'); - exit(); - } - - // Проверяем дочерние категории - $checkChildStmt = $db->prepare(" - SELECT COUNT(*) - FROM categories - WHERE parent_id = ? AND is_active = TRUE - "); - $checkChildStmt->execute([$category_id]); - $active_children = $checkChildStmt->fetchColumn(); - - if ($active_children > 0) { - header('Location: catalog_admin.php?action=categories&error=Невозможно удалить категорию с активными дочерними категориями'); - exit(); - } - - // Удаляем категорию - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$category_id]); - - header('Location: catalog_admin.php?action=categories&message=Категория успешно удалена'); - exit(); - } - - } catch (PDOException $e) { - header('Location: catalog_admin.php?action=categories&error=' . urlencode('Ошибка базы данных: ' . $e->getMessage())); - exit(); - } -} - -header('Location: catalog_admin.php'); -exit(); -?> \ No newline at end of file diff --git a/check_admin.php b/check_admin.php deleted file mode 100644 index e64c585..0000000 --- a/check_admin.php +++ /dev/null @@ -1,17 +0,0 @@ - \ No newline at end of file diff --git a/check_auth_status.php b/check_auth_status.php deleted file mode 100644 index 5ddf663..0000000 --- a/check_auth_status.php +++ /dev/null @@ -1,21 +0,0 @@ - isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true -]; - -if ($response['loggedIn']) { - $response['user'] = [ - 'user_id' => $_SESSION['user_id'] ?? 0, - 'email' => $_SESSION['user_email'] ?? '', - 'full_name' => $_SESSION['full_name'] ?? '', - 'is_admin' => $_SESSION['isAdmin'] ?? false, - 'login_time' => $_SESSION['login_time'] ?? time() - ]; -} - -header('Content-Type: application/json'); -echo json_encode($response); -?> \ No newline at end of file diff --git a/check_categories_table.php b/check_categories_table.php deleted file mode 100644 index 6fa72ca..0000000 --- a/check_categories_table.php +++ /dev/null @@ -1,51 +0,0 @@ -getConnection(); - -echo "

Проверка категорий в базе данных

"; - -try { - $stmt = $db->query("SELECT category_id, name, slug, parent_id FROM categories ORDER BY category_id"); - $categories = $stmt->fetchAll(); - - if (empty($categories)) { - echo "

Категорий нет! Нужно сначала добавить категории.

"; - - // Добавим тестовые категории - $insert_sql = " - INSERT INTO categories (name, slug, parent_id, description) VALUES - ('Мягкая мебель', 'myagkaya-mebel', NULL, 'Диваны, кресла, пуфы'), - ('Диваны', 'divany', 1, 'Прямые и угловые диваны'), - ('Кресла', 'kresla', 1, 'Кресла для гостиной и офиса'), - ('Спальня', 'spalnya', NULL, 'Кровати, тумбы, комоды'), - ('Кровати', 'krovati', 4, 'Односпальные и двуспальные кровати') - RETURNING category_id - "; - - $db->exec($insert_sql); - echo "

Добавлены тестовые категории

"; - - // Снова проверим - $stmt = $db->query("SELECT category_id, name, slug, parent_id FROM categories ORDER BY category_id"); - $categories = $stmt->fetchAll(); - } - - echo ""; - echo ""; - - foreach ($categories as $category) { - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - } - - echo "
IDНазваниеSlugРодитель
" . $category['category_id'] . "" . htmlspecialchars($category['name']) . "" . htmlspecialchars($category['slug']) . "" . ($category['parent_id'] ?: '-') . "
"; - -} catch (PDOException $e) { - echo "Ошибка: " . $e->getMessage(); -} -?> \ No newline at end of file diff --git a/config/check_auth.js b/config/check_auth.js deleted file mode 100644 index e67bbe1..0000000 --- a/config/check_auth.js +++ /dev/null @@ -1,114 +0,0 @@ -// check_auth.js -$(document).ready(function() { - // Проверка авторизации при загрузке страницы - checkAuthStatus(); - - // Обработка формы входа - $('#loginForm').on('submit', function(e) { - e.preventDefault(); - - const email = $('#login-email').val(); - const password = $('#login-password').val(); - const remember = $('#remember').is(':checked'); - - $.ajax({ - url: 'login_handler.php', - method: 'POST', - data: { - email: email, - password: password - }, - success: function(response) { - try { - const result = JSON.parse(response); - if (result.success) { - // Сохраняем в localStorage если выбрано "Запомнить меня" - if (remember) { - localStorage.setItem('rememberedEmail', email); - } else { - localStorage.removeItem('rememberedEmail'); - } - - // Перенаправляем - window.location.href = result.redirect || 'catalog.php'; - } else { - showMessage('error', result.message || 'Ошибка авторизации'); - } - } catch(e) { - showMessage('error', 'Ошибка обработки ответа'); - } - }, - error: function() { - showMessage('error', 'Ошибка сервера'); - } - }); - }); - - // Проверка статуса авторизации - function checkAuthStatus() { - $.ajax({ - url: 'check_auth_status.php', - method: 'GET', - success: function(response) { - try { - const result = JSON.parse(response); - if (result.loggedIn) { - updateUserProfile(result.user); - } - } catch(e) { - console.error('Ошибка проверки авторизации', e); - } - } - }); - } - - // Обновление профиля пользователя - function updateUserProfile(user) { - // Обновляем шапку, если есть элементы для профиля - if ($('#userEmail').length) { - $('#userEmail').text(user.email); - } - if ($('#userName').length) { - $('#userName').text(user.full_name); - } - } - - // Показать сообщение - function showMessage(type, text) { - const $message = $('#' + type + 'Message'); - if ($message.length) { - $message.text(text).fadeIn(); - setTimeout(() => $message.fadeOut(), 5000); - } else { - alert(text); - } - } - - // Проверка авторизации для ссылок - function checkAuth(redirectUrl) { - $.ajax({ - url: 'check_auth_status.php', - method: 'GET', - success: function(response) { - try { - const result = JSON.parse(response); - if (result.loggedIn) { - window.location.href = redirectUrl; - } else { - // Показываем модальное окно или перенаправляем на вход - showLoginModal(redirectUrl); - } - } catch(e) { - showLoginModal(redirectUrl); - } - } - }); - return false; - } - - // Показать модальное окно входа - function showLoginModal(redirectUrl) { - // Можно реализовать модальное окно или перенаправить на страницу входа - window.location.href = 'вход.php?redirect=' + encodeURIComponent(redirectUrl); - } -}); \ No newline at end of file diff --git a/debug_db.php b/debug_db.php deleted file mode 100644 index 32ca002..0000000 --- a/debug_db.php +++ /dev/null @@ -1,55 +0,0 @@ -getConnection(); - -echo "

Проверка базы данных:

"; - -// Проверка таблиц -$tables = ['users', 'categories', 'products', 'orders', 'order_items', 'cart']; -foreach ($tables as $table) { - try { - $result = $db->query("SELECT COUNT(*) FROM $table")->fetchColumn(); - echo "✅ Таблица '$table': $result записей
"; - } catch (Exception $e) { - echo "❌ Таблица '$table': НЕ СУЩЕСТВУЕТ
"; - } -} - -echo "

Содержимое таблиц:

"; - -// Показать категории -echo "

Категории:

"; -try { - $categories = $db->query("SELECT * FROM categories")->fetchAll(); - if (empty($categories)) { - echo "Категорий нет!
"; - } else { - echo ""; - foreach ($categories as $cat) { - echo ""; - } - echo "
IDНазваниеSlugРодитель
{$cat['category_id']}{$cat['name']}{$cat['slug']}{$cat['parent_id']}
"; - } -} catch (Exception $e) { - echo "Ошибка: " . $e->getMessage(); -} - -// Показать товары -echo "

Товары:

"; -try { - $products = $db->query("SELECT * FROM products")->fetchAll(); - if (empty($products)) { - echo "Товаров нет!
"; - } else { - echo ""; - foreach ($products as $product) { - echo ""; - } - echo "
IDНазваниеЦенаКатегорияСтатус
{$product['product_id']}{$product['name']}{$product['price']}{$product['category_id']}" . ($product['is_available'] ? 'Активен' : 'Неактивен') . "
"; - } -} catch (Exception $e) { - echo "Ошибка: " . $e->getMessage(); -} -?> \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d89d53b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +version: '3.8' + +services: + apache: + build: + context: . + dockerfile: Dockerfile + container_name: cite_practica_apache + ports: + - "80:80" + volumes: + - ./public:/var/www/html/cite_practica:rw + - ./docker/apache/vhosts.conf:/etc/apache2/sites-available/000-default.conf:ro + environment: + - APACHE_DOCUMENT_ROOT=/var/www/html/cite_practica + command: > + bash -c " + echo '127.0.0.1 admin' >> /etc/hosts && + apache2-foreground + " + networks: + - cite_practica_network + restart: unless-stopped + +networks: + cite_practica_network: + driver: bridge diff --git a/docker/apache/entrypoint.sh b/docker/apache/entrypoint.sh new file mode 100755 index 0000000..f3844b9 --- /dev/null +++ b/docker/apache/entrypoint.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +a2enmod rewrite +a2enmod headers + +echo "127.0.0.1 admin" >> /etc/hosts + +a2ensite 000-default + +exec apache2-foreground + diff --git a/docker/apache/vhosts.conf b/docker/apache/vhosts.conf new file mode 100644 index 0000000..c5833bd --- /dev/null +++ b/docker/apache/vhosts.conf @@ -0,0 +1,26 @@ + + ServerName admin + ServerAlias localhost + DocumentRoot /var/www/html + Alias /cite_practica /var/www/html/cite_practica + + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + + + + Options Indexes FollowSymLinks + AllowOverride All + Require all granted + DirectoryIndex cite_mebel.php index.php index.html + + + + SetHandler application/x-httpd-php + + + ErrorLog ${APACHE_LOG_DIR}/cite_practica_error.log + CustomLog ${APACHE_LOG_DIR}/cite_practica_access.log combined + diff --git a/fix_categories.php b/fix_categories.php deleted file mode 100644 index 62503c1..0000000 --- a/fix_categories.php +++ /dev/null @@ -1,69 +0,0 @@ -getConnection(); - -echo "

Исправление проблем с категориями

"; - -try { - // 1. Удаляем категорию с ID=0 если она есть - $db->exec("DELETE FROM categories WHERE category_id = 0"); - - // 2. Проверяем, есть ли категории - $catCount = $db->query("SELECT COUNT(*) FROM categories")->fetchColumn(); - - if ($catCount == 0) { - echo "

Добавляем основные категории...

"; - $db->exec(" - INSERT INTO categories (name, slug, description, is_active) VALUES - ('Диваны', 'divany', 'Мягкая мебель для гостиной', TRUE), - ('Кресла', 'kresla', 'Кресла для гостиной и офиса', TRUE), - ('Кровати', 'krovati', 'Мебель для спальни', TRUE), - ('Столы', 'stoly', 'Обеденные и рабочие столы', TRUE), - ('Стулья', 'stulya', 'Стулья для кухни и офиса', TRUE) - "); - echo "

✓ Категории добавлены

"; - } - - // 3. Исправляем товары с category_id = 0 или NULL - $badProducts = $db->query(" - SELECT COUNT(*) FROM products - WHERE category_id IS NULL OR category_id = 0 OR - category_id NOT IN (SELECT category_id FROM categories) - ")->fetchColumn(); - - if ($badProducts > 0) { - echo "

Исправляем товары с некорректными категориями ($badProducts шт)...

"; - - // Получаем первую категорию - $firstCat = $db->query("SELECT category_id FROM categories LIMIT 1")->fetchColumn(); - - if ($firstCat) { - $db->exec(" - UPDATE products - SET category_id = $firstCat - WHERE category_id IS NULL OR category_id = 0 OR - category_id NOT IN (SELECT category_id FROM categories) - "); - echo "

✓ Товары исправлены (category_id установлен в $firstCat)

"; - } - } - - // 4. Показываем текущее состояние - echo "

Текущие категории:

"; - $cats = $db->query("SELECT category_id, name FROM categories ORDER BY category_id")->fetchAll(); - - echo ""; - echo ""; - foreach ($cats as $cat) { - echo ""; - } - echo "
IDНазвание
{$cat['category_id']}{$cat['name']}
"; - - echo "

✓ База данных исправлена!

"; - -} catch (PDOException $e) { - echo "

Ошибка: " . $e->getMessage() . "

"; -} -?> \ No newline at end of file diff --git a/fix_database.php b/fix_database.php deleted file mode 100644 index 9edc526..0000000 --- a/fix_database.php +++ /dev/null @@ -1,89 +0,0 @@ -getConnection(); - -echo "

Исправление проблем с базой данных

"; - -try { - // 1. Проверяем есть ли категории - $stmt = $db->query("SELECT COUNT(*) FROM categories"); - $cat_count = $stmt->fetchColumn(); - - if ($cat_count == 0) { - echo "

Добавляем тестовые категории...

"; - $db->exec(" - INSERT INTO categories (name, slug, description) VALUES - ('Диваны', 'divany', 'Мягкая мебель для гостиной'), - ('Кресла', 'kresla', 'Кресла для гостиной и офиса'), - ('Кровати', 'krovati', 'Мебель для спальни') - "); - echo "

✓ Категории добавлены

"; - } - - // 2. Проверяем товары с некорректными category_id - $stmt = $db->query(" - SELECT COUNT(*) as bad_count - FROM products - WHERE category_id IS NULL OR category_id NOT IN (SELECT category_id FROM categories) - "); - $bad_count = $stmt->fetchColumn(); - - if ($bad_count > 0) { - echo "

Исправляем товары с некорректными категориями ($bad_count шт)...

"; - - // Устанавливаем первую доступную категорию - $stmt = $db->query("SELECT category_id FROM categories LIMIT 1"); - $first_cat = $stmt->fetchColumn(); - - if ($first_cat) { - $db->exec(" - UPDATE products - SET category_id = $first_cat - WHERE category_id IS NULL OR category_id NOT IN (SELECT category_id FROM categories) - "); - echo "

✓ Товары исправлены (установлена категория ID: $first_cat)

"; - } - } - - // 3. Показываем текущее состояние - echo "

Текущее состояние:

"; - - // Категории - $stmt = $db->query("SELECT category_id, name FROM categories ORDER BY category_id"); - echo "

Категории:

"; - - // Товары - $stmt = $db->query(" - SELECT p.product_id, p.name, p.category_id, c.name as cat_name - FROM products p - LEFT JOIN categories c ON p.category_id = c.category_id - ORDER BY p.product_id - "); - - echo "

Товары:

"; - echo ""; - echo ""; - - while ($row = $stmt->fetch()) { - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - } - - echo "
IDНазваниеКатегория IDКатегория
{$row['product_id']}" . htmlspecialchars($row['name']) . "" . ($row['category_id'] ?: 'NULL') . "" . ($row['cat_name'] ?: 'Без категории') . "
"; - - echo "

✓ База данных исправлена!

"; - -} catch (PDOException $e) { - echo "

Ошибка: " . $e->getMessage() . "

"; -} -?> \ No newline at end of file diff --git a/header_common.php b/header_common.php deleted file mode 100644 index 9b197d9..0000000 --- a/header_common.php +++ /dev/null @@ -1,142 +0,0 @@ - - -
-
-
- - -
- - -
- - -
-
- -
- -
-
\ No newline at end of file diff --git a/image_upload.php b/image_upload.php deleted file mode 100644 index b9833a9..0000000 --- a/image_upload.php +++ /dev/null @@ -1,53 +0,0 @@ - false, 'message' => 'Доступ запрещен']); - exit(); -} - -if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['image'])) { - $uploadDir = 'uploads/products/'; - - // Создаем директорию если не существует - if (!file_exists($uploadDir)) { - mkdir($uploadDir, 0777, true); - } - - $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; - $maxSize = 5 * 1024 * 1024; // 5MB - - $file = $_FILES['image']; - - // Проверка типа файла - if (!in_array($file['type'], $allowedTypes)) { - echo json_encode(['success' => false, 'message' => 'Допустимые форматы: JPEG, PNG, GIF, WebP']); - exit(); - } - - // Проверка размера - if ($file['size'] > $maxSize) { - echo json_encode(['success' => false, 'message' => 'Максимальный размер файла: 5MB']); - exit(); - } - - // Генерируем уникальное имя - $extension = pathinfo($file['name'], PATHINFO_EXTENSION); - $fileName = 'product_' . time() . '_' . rand(1000, 9999) . '.' . $extension; - $filePath = $uploadDir . $fileName; - - if (move_uploaded_file($file['tmp_name'], $filePath)) { - echo json_encode([ - 'success' => true, - 'url' => $filePath, - 'name' => $fileName - ]); - } else { - echo json_encode(['success' => false, 'message' => 'Ошибка загрузки файла']); - } -} else { - echo json_encode(['success' => false, 'message' => 'Файл не получен']); -} -?> \ No newline at end of file diff --git a/img/2_2.jpg b/img/2_2.jpg deleted file mode 100644 index 1f9b783..0000000 Binary files a/img/2_2.jpg and /dev/null differ diff --git a/img/3.jpg b/img/3.jpg deleted file mode 100644 index 134dd4e..0000000 Binary files a/img/3.jpg and /dev/null differ diff --git a/img/4.jpg b/img/4.jpg deleted file mode 100644 index c462942..0000000 Binary files a/img/4.jpg and /dev/null differ diff --git a/img/6.jpg b/img/6.jpg deleted file mode 100644 index f94188c..0000000 Binary files a/img/6.jpg and /dev/null differ diff --git a/img/7.jpg b/img/7.jpg deleted file mode 100644 index 8ba2fe5..0000000 Binary files a/img/7.jpg and /dev/null differ diff --git a/img/8.jpg b/img/8.jpg deleted file mode 100644 index e869ff9..0000000 Binary files a/img/8.jpg and /dev/null differ diff --git a/img/9.jpg b/img/9.jpg deleted file mode 100644 index 967e002..0000000 Binary files a/img/9.jpg and /dev/null differ diff --git a/login.php b/login.php deleted file mode 100644 index 9023dc1..0000000 --- a/login.php +++ /dev/null @@ -1,48 +0,0 @@ -getConnection(); - - try { - // Проверяем пользователя - $stmt = $db->prepare(" - SELECT user_id, email, password_hash, full_name - FROM users - WHERE email = ? AND is_active = TRUE - "); - $stmt->execute([$email]); - $user = $stmt->fetch(); - - if ($user && password_verify($password, $user['password_hash'])) { - // Сохраняем в сессию - $_SESSION['user_id'] = $user['user_id']; - $_SESSION['user_email'] = $user['email']; - $_SESSION['full_name'] = $user['full_name']; - $_SESSION['isLoggedIn'] = true; - $_SESSION['login_time'] = time(); - - // Обновляем время последнего входа - $update_stmt = $db->prepare(" - UPDATE users - SET updated_at = CURRENT_TIMESTAMP - WHERE user_id = ? - "); - $update_stmt->execute([$user['user_id']]); - - header('Location: catalog.php'); - exit(); - } else { - header('Location: вход.php?error=invalid_credentials'); - exit(); - } - } catch (PDOException $e) { - header('Location: вход.php?error=db_error'); - exit(); - } -} -?> \ No newline at end of file diff --git a/logout.php b/logout.php deleted file mode 100644 index 3b5d01e..0000000 --- a/logout.php +++ /dev/null @@ -1,22 +0,0 @@ - \ No newline at end of file diff --git a/migrations/001_initial_schema.sql b/migrations/001_initial_schema.sql new file mode 100644 index 0000000..3b55c9b --- /dev/null +++ b/migrations/001_initial_schema.sql @@ -0,0 +1,64 @@ +CREATE TABLE IF NOT EXISTS users ( + user_id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + full_name VARCHAR(100) NOT NULL, + phone VARCHAR(20), + city VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + last_login TIMESTAMP, + is_active BOOLEAN DEFAULT TRUE, + is_admin BOOLEAN DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS categories ( + category_id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + slug VARCHAR(100) UNIQUE NOT NULL, + parent_id INTEGER REFERENCES categories(category_id) ON DELETE SET NULL, + description TEXT, + sort_order INTEGER DEFAULT 0, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS subcategories ( + subcategory_id SERIAL PRIMARY KEY, + category_id INTEGER REFERENCES categories(category_id) ON DELETE CASCADE, + name VARCHAR(100) NOT NULL, + slug VARCHAR(100) UNIQUE NOT NULL, + description TEXT, + sort_order INTEGER DEFAULT 0, + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS products ( + product_id SERIAL PRIMARY KEY, + category_id INTEGER REFERENCES categories(category_id) ON DELETE SET NULL, + name VARCHAR(200) NOT NULL, + slug VARCHAR(200) UNIQUE NOT NULL, + description TEXT, + price DECIMAL(10, 2) NOT NULL, + old_price DECIMAL(10, 2), + sku VARCHAR(50) UNIQUE, + stock_quantity INTEGER DEFAULT 0, + is_available BOOLEAN DEFAULT TRUE, + is_featured BOOLEAN DEFAULT FALSE, + rating DECIMAL(3, 2) DEFAULT 0, + review_count INTEGER DEFAULT 0, + image_url VARCHAR(500), + color VARCHAR(50), + material VARCHAR(100), + card_size VARCHAR(20) DEFAULT 'small', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_products_category ON products(category_id); +CREATE INDEX IF NOT EXISTS idx_products_available ON products(is_available); +CREATE INDEX IF NOT EXISTS idx_products_price ON products(price); +CREATE INDEX IF NOT EXISTS idx_categories_parent ON categories(parent_id); +CREATE INDEX IF NOT EXISTS idx_categories_active ON categories(is_active); diff --git a/migrations/002_add_cart_orders.sql b/migrations/002_add_cart_orders.sql new file mode 100644 index 0000000..7f741f6 --- /dev/null +++ b/migrations/002_add_cart_orders.sql @@ -0,0 +1,50 @@ +CREATE TABLE IF NOT EXISTS cart ( + cart_id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users(user_id) ON DELETE CASCADE, + product_id INTEGER REFERENCES products(product_id) ON DELETE CASCADE, + quantity INTEGER DEFAULT 1 CHECK (quantity > 0), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, product_id) +); + +CREATE TABLE IF NOT EXISTS orders ( + order_id SERIAL PRIMARY KEY, + order_number VARCHAR(50) UNIQUE NOT NULL, + user_id INTEGER REFERENCES users(user_id) ON DELETE SET NULL, + customer_name VARCHAR(100) NOT NULL, + customer_email VARCHAR(255) NOT NULL, + customer_phone VARCHAR(20) NOT NULL, + delivery_address TEXT NOT NULL, + delivery_region VARCHAR(100), + postal_code VARCHAR(20), + delivery_method VARCHAR(50) DEFAULT 'courier', + payment_method VARCHAR(50) DEFAULT 'card', + subtotal DECIMAL(10, 2) NOT NULL, + discount_amount DECIMAL(10, 2) DEFAULT 0, + delivery_price DECIMAL(10, 2) DEFAULT 0, + final_amount DECIMAL(10, 2) NOT NULL, + promo_code VARCHAR(50), + status VARCHAR(30) DEFAULT 'pending', + notes TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + completed_at TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS order_items ( + item_id SERIAL PRIMARY KEY, + order_id INTEGER REFERENCES orders(order_id) ON DELETE CASCADE, + product_id INTEGER REFERENCES products(product_id) ON DELETE SET NULL, + product_name VARCHAR(200) NOT NULL, + product_price DECIMAL(10, 2) NOT NULL, + quantity INTEGER NOT NULL CHECK (quantity > 0), + total_price DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_cart_user ON cart(user_id); +CREATE INDEX IF NOT EXISTS idx_orders_user ON orders(user_id); +CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status); +CREATE INDEX IF NOT EXISTS idx_orders_created ON orders(created_at); +CREATE INDEX IF NOT EXISTS idx_order_items_order ON order_items(order_id); diff --git a/migrations/003_add_product_fields.sql b/migrations/003_add_product_fields.sql new file mode 100644 index 0000000..0b337cd --- /dev/null +++ b/migrations/003_add_product_fields.sql @@ -0,0 +1,43 @@ +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_name = 'products' AND column_name = 'color') THEN + ALTER TABLE products ADD COLUMN color VARCHAR(50); + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_name = 'products' AND column_name = 'material') THEN + ALTER TABLE products ADD COLUMN material VARCHAR(100); + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_name = 'products' AND column_name = 'card_size') THEN + ALTER TABLE products ADD COLUMN card_size VARCHAR(20) DEFAULT 'small'; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'city') THEN + ALTER TABLE users ADD COLUMN city VARCHAR(100); + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'last_login') THEN + ALTER TABLE users ADD COLUMN last_login TIMESTAMP; + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_name = 'categories' AND column_name = 'updated_at') THEN + ALTER TABLE categories ADD COLUMN updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP; + END IF; + + IF NOT EXISTS (SELECT 1 FROM information_schema.columns + WHERE table_name = 'categories' AND column_name = 'created_at') THEN + ALTER TABLE categories ADD COLUMN created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP; + END IF; +END $$; diff --git a/migrations/004_grant_admin_to_admin_mail.sql b/migrations/004_grant_admin_to_admin_mail.sql new file mode 100644 index 0000000..f60a702 --- /dev/null +++ b/migrations/004_grant_admin_to_admin_mail.sql @@ -0,0 +1,42 @@ +UPDATE users +SET is_admin = TRUE, + is_active = TRUE, + updated_at = CURRENT_TIMESTAMP +WHERE email = 'admin@mail.ru'; + +DO $$ +DECLARE + updated_count INTEGER; + user_info RECORD; +BEGIN + GET DIAGNOSTICS updated_count = ROW_COUNT; + + IF updated_count > 0 THEN + SELECT user_id, email, full_name, is_admin, is_active + INTO user_info + FROM users + WHERE email = 'admin@mail.ru'; + + RAISE NOTICE 'Пользователь % (ID: %) успешно получил права администратора', + user_info.email, user_info.user_id; + RAISE NOTICE 'ФИО: %, Админ: %, Активен: %', + user_info.full_name, user_info.is_admin, user_info.is_active; + ELSE + INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active) + VALUES ( + 'admin@mail.ru', + '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 'Администратор', + '+79129991223', + 'Москва', + TRUE, + TRUE + ) + ON CONFLICT (email) DO UPDATE + SET is_admin = TRUE, + is_active = TRUE, + updated_at = CURRENT_TIMESTAMP; + + RAISE NOTICE 'Пользователь admin@mail.ru создан/обновлен с правами администратора'; + END IF; +END $$; diff --git a/migrations/grant_admin.php b/migrations/grant_admin.php new file mode 100644 index 0000000..40d7258 --- /dev/null +++ b/migrations/grant_admin.php @@ -0,0 +1,85 @@ +getConnection(); + echo "[OK] Подключение к базе данных успешно\n\n"; + + $email = 'admin@mail.ru'; + + $checkStmt = $db->prepare("SELECT user_id, email, full_name, is_admin, is_active FROM users WHERE email = ?"); + $checkStmt->execute([$email]); + $user = $checkStmt->fetch(PDO::FETCH_ASSOC); + + if ($user) { + echo "[INFO] Найден пользователь:\n"; + echo " Email: {$user['email']}\n"; + echo " ФИО: {$user['full_name']}\n"; + echo " Админ: " . ($user['is_admin'] ? 'ДА' : 'НЕТ') . "\n"; + echo " Активен: " . ($user['is_active'] ? 'ДА' : 'НЕТ') . "\n\n"; + + if ($user['is_admin']) { + echo "[INFO] Пользователь уже имеет права администратора\n"; + } else { + + $updateStmt = $db->prepare(" + UPDATE users + SET is_admin = TRUE, + is_active = TRUE, + updated_at = CURRENT_TIMESTAMP + WHERE email = ? + "); + $updateStmt->execute([$email]); + + echo "[SUCCESS] Права администратора успешно назначены!\n"; + } + } else { + echo "[WARN] Пользователь с email $email не найден\n"; + echo "[INFO] Создаю нового пользователя с правами администратора...\n"; + + $password_hash = password_hash('admin123', PASSWORD_DEFAULT); + + $insertStmt = $db->prepare(" + INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active) + VALUES (?, ?, ?, ?, ?, CAST(? AS boolean), TRUE) + RETURNING user_id + "); + + $insertStmt->execute([ + $email, + $password_hash, + 'Администратор', + '+79129991223', + 'Москва', + 'true' + ]); + + $user_id = $insertStmt->fetchColumn(); + echo "[SUCCESS] Пользователь создан с ID: $user_id\n"; + echo "[INFO] Email: $email\n"; + echo "[INFO] Пароль по умолчанию: admin123\n"; + echo "[WARN] Рекомендуется сменить пароль после первого входа!\n"; + } + + $verifyStmt = $db->prepare("SELECT user_id, email, full_name, is_admin, is_active FROM users WHERE email = ?"); + $verifyStmt->execute([$email]); + $finalUser = $verifyStmt->fetch(PDO::FETCH_ASSOC); + + echo "\n===========================================\n"; + echo " Итоговый статус:\n"; + echo "===========================================\n"; + echo " Email: {$finalUser['email']}\n"; + echo " ФИО: {$finalUser['full_name']}\n"; + echo " Админ: " . ($finalUser['is_admin'] ? 'ДА ✓' : 'НЕТ ✗') . "\n"; + echo " Активен: " . ($finalUser['is_active'] ? 'ДА ✓' : 'НЕТ ✗') . "\n"; + echo "===========================================\n"; + +} catch (PDOException $e) { + echo "[ERROR] Ошибка: " . $e->getMessage() . "\n"; + exit(1); +} diff --git a/migrations/migrate.php b/migrations/migrate.php new file mode 100644 index 0000000..7f009a7 --- /dev/null +++ b/migrations/migrate.php @@ -0,0 +1,95 @@ +getConnection(); + echo "[OK] Подключение к базе данных успешно\n\n"; + + $db->exec(" + CREATE TABLE IF NOT EXISTS migrations ( + id SERIAL PRIMARY KEY, + filename VARCHAR(255) NOT NULL UNIQUE, + applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + "); + echo "[OK] Таблица migrations готова\n"; + + $stmt = $db->query("SELECT filename FROM migrations ORDER BY filename"); + $applied = $stmt->fetchAll(PDO::FETCH_COLUMN); + echo "[INFO] Уже применено миграций: " . count($applied) . "\n\n"; + + $migrationFiles = glob(__DIR__ . '/*.sql'); + sort($migrationFiles); + + $newMigrations = 0; + + foreach ($migrationFiles as $file) { + $filename = basename($file); + + if ($filename === 'seed_data.sql') { + continue; + } + + if (in_array($filename, $applied)) { + echo "[SKIP] $filename (уже применена)\n"; + continue; + } + + echo "[RUN] Применяю $filename... "; + + $sql = file_get_contents($file); + + try { + $db->exec($sql); + + $stmt = $db->prepare("INSERT INTO migrations (filename) VALUES (?)"); + $stmt->execute([$filename]); + + echo "OK\n"; + $newMigrations++; + } catch (PDOException $e) { + echo "ОШИБКА!\n"; + echo " Причина: " . $e->getMessage() . "\n"; + echo "\n[!] Миграция остановлена из-за ошибки\n"; + exit(1); + } + } + + echo "\n-------------------------------------------\n"; + + if ($newMigrations > 0) { + echo "[SUCCESS] Применено новых миграций: $newMigrations\n"; + } else { + echo "[INFO] Все миграции уже применены\n"; + } + + $seedFile = __DIR__ . '/seed_data.sql'; + if (file_exists($seedFile)) { + echo "\n[?] Хотите загрузить начальные данные (seed_data.sql)?\n"; + echo " Запустите: php migrations/migrate.php --seed\n"; + + if (isset($argv[1]) && $argv[1] === '--seed') { + echo "\n[RUN] Загружаю seed_data.sql... "; + try { + $sql = file_get_contents($seedFile); + $db->exec($sql); + echo "OK\n"; + } catch (PDOException $e) { + echo "ОШИБКА: " . $e->getMessage() . "\n"; + } + } + } + + echo "\n===========================================\n"; + echo " Миграции завершены!\n"; + echo "===========================================\n"; + +} catch (PDOException $e) { + echo "[ERROR] Ошибка подключения к БД: " . $e->getMessage() . "\n"; + exit(1); +} diff --git a/migrations/seed_data.sql b/migrations/seed_data.sql new file mode 100644 index 0000000..0cc3ea1 --- /dev/null +++ b/migrations/seed_data.sql @@ -0,0 +1,56 @@ +INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active) +VALUES ( + 'admin@aeterna.ru', + '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', + 'Администратор AETERNA', + '+79129991223', + 'Москва', + TRUE, + TRUE +) ON CONFLICT (email) DO NOTHING; + +INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active) +VALUES ( + 'user@test.com', + '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', + 'Тестовый Пользователь', + '+79111234567', + 'Санкт-Петербург', + FALSE, + TRUE +) ON CONFLICT (email) DO NOTHING; + +INSERT INTO categories (name, slug, description, sort_order, is_active) VALUES + ('Диваны', 'divany', 'Прямые и угловые диваны для гостиной', 1, TRUE), + ('Кресла', 'kresla', 'Кресла для гостиной и офиса', 2, TRUE), + ('Кровати', 'krovati', 'Односпальные и двуспальные кровати', 3, TRUE), + ('Столы', 'stoly', 'Обеденные и рабочие столы', 4, TRUE), + ('Стулья', 'stulya', 'Стулья для кухни и офиса', 5, TRUE), + ('Светильники', 'svetilniki', 'Торшеры, люстры и настольные лампы', 6, TRUE) +ON CONFLICT (slug) DO NOTHING; + +INSERT INTO products (category_id, name, slug, description, price, old_price, sku, stock_quantity, is_available, image_url, color, material, card_size) VALUES + (1, 'Светильник MINNIGHT', 'svetilnik-minnight', 'Настольный светильник в современном стиле', 7999, 9999, 'LAMP-MIN-001', 15, TRUE, 'img2/1_2.png', 'Черный', 'Металл', 'small'), + (3, 'Кровать MODER', 'krovat-moder', 'Двуспальная кровать с мягким изголовьем', 45999, 55999, 'BED-MOD-001', 5, TRUE, 'img2/3_3.png', 'Серый', 'Дерево/Ткань', 'large'), + (6, 'Торшер MARCIA', 'torsher-marcia', 'Напольный торшер с регулируемой высотой', 11999, 14999, 'LAMP-MAR-001', 8, TRUE, 'img2/2_2.png', 'Золотой', 'Металл', 'tall'), + (6, 'Светильник POLET', 'svetilnik-polet', 'Подвесной светильник для гостиной', 5499, NULL, 'LAMP-POL-001', 20, TRUE, 'img2/4.jpg', 'Белый', 'Стекло', 'wide'), + (4, 'Стол NORD', 'stol-nord', 'Обеденный стол в скандинавском стиле', 23999, 28999, 'TABLE-NOR-001', 7, TRUE, 'img2/5_5.png', 'Натуральный', 'Дерево', 'small1'), + (1, 'Диван ROYALTY', 'divan-royalty', 'Роскошный угловой диван с велюровой обивкой', 78999, 95999, 'SOFA-ROY-001', 3, TRUE, 'img2/6_6.png', 'Зеленый', 'Велюр', 'wide2'), + (2, 'Кресло MINIMAL', 'kreslo-minimal', 'Кресло в минималистичном стиле', 29999, 35999, 'ARM-MIN-001', 10, TRUE, 'img2/7_7.png', 'Бежевый', 'Ткань', 'wide3'), + (4, 'Стол LONKI', 'stol-lonki', 'Журнальный столик с мраморной столешницей', 34999, NULL, 'TABLE-LON-001', 12, TRUE, 'img2/8_8.png', 'Белый мрамор', 'Мрамор/Металл', 'wide2_1'), + (1, 'Диван HEMMINS', 'divan-hemmins', 'Большой модульный диван для всей семьи', 89999, 110000, 'SOFA-HEM-001', 2, TRUE, 'img2/9_9.png', 'Темно-серый', 'Ткань', 'full-width') +ON CONFLICT (slug) DO NOTHING; + +DO $$ +DECLARE + users_count INTEGER; + categories_count INTEGER; + products_count INTEGER; +BEGIN + SELECT COUNT(*) INTO users_count FROM users; + SELECT COUNT(*) INTO categories_count FROM categories; + SELECT COUNT(*) INTO products_count FROM products; + + RAISE NOTICE 'Загружено: % пользователей, % категорий, % товаров', + users_count, categories_count, products_count; +END $$; diff --git a/print_order.php b/print_order.php deleted file mode 100644 index 9e92cf9..0000000 --- a/print_order.php +++ /dev/null @@ -1,208 +0,0 @@ -getConnection(); - -try { - $orderStmt = $db->prepare(" - SELECT o.*, u.email, u.full_name - FROM orders o - LEFT JOIN users u ON o.user_id = u.user_id - WHERE o.order_id = ? - "); - $orderStmt->execute([$orderId]); - $order = $orderStmt->fetch(); - - if (!$order) { - die('Заказ не найден'); - } - - $itemsStmt = $db->prepare(" - SELECT * FROM order_items - WHERE order_id = ? - "); - $itemsStmt->execute([$orderId]); - $order_items = $itemsStmt->fetchAll(); - -} catch (PDOException $e) { - die('Ошибка базы данных: ' . $e->getMessage()); -} -?> - - - - - - Печать заказа #<?= $order['order_number'] ?> - - - - - -
-
AETERNA
-
г. Москва, ул. Примерная, д. 123
-
Телефон: +7(912)999-12-23 | Email: aeterna@mail.ru
-
ИНН: 1234567890 | ОГРН: 1234567890123
-
- -
-

Заказ #

-

Дата:

-

Статус:

-
- -
-
-

Информация о клиенте

-

ФИО:

-

Email:

-

Телефон:

-
- -
-

Информация о доставке

-

Адрес:

-

Способ доставки:

-

Способ оплаты:

-
-
- -

Состав заказа

- - - - - - - - - - - - - - - - - - - - - - - - - - - - 0): ?> - - - - - - 0): ?> - - - - - - - - - - -
ТоварКол-воЦенаСумма
Сумма товаров:
Скидка:-
Доставка:
Итого к оплате:
- - -
-

Примечания

-

-
- - - - - - - \ No newline at end of file diff --git a/product_modern.php b/product_modern.php deleted file mode 100644 index ebc63d9..0000000 --- a/product_modern.php +++ /dev/null @@ -1,350 +0,0 @@ -getConnection(); - -// Получаем информацию о товаре "Диван MODERN" (ID 2 из базы данных) -try { - $productStmt = $db->prepare(" - SELECT p.*, c.name as category_name - FROM products p - LEFT JOIN categories c ON p.category_id = c.category_id - WHERE p.product_id = 2 AND p.is_available = TRUE - "); - $productStmt->execute(); - $product = $productStmt->fetch(); - - if (!$product) { - header('Location: catalog.php?error=product_not_found'); - exit(); - } - - // Получаем похожие товары - $similarStmt = $db->prepare(" - SELECT * FROM products - WHERE category_id = ? AND product_id != ? AND is_available = TRUE - ORDER BY RANDOM() - LIMIT 3 - "); - $similarStmt->execute([$product['category_id'], $product['product_id']]); - $similarProducts = $similarStmt->fetchAll(); - -} catch (PDOException $e) { - die("Ошибка базы данных: " . $e->getMessage()); -} -?> - - - - - - AETERNA - <?= htmlspecialchars($product['name']) ?> - - - - - - - - - -
- - -
- - -
-

Диван MODERN

- -
-
- - - - - -
- 4.5 - (24 отзыва) -
- -
- - ₽ - - $product['price']): ?> - - ₽ - - - -% - - -
- -
-
- Артикул: - -
-
- Категория: - Диваны -
-
- Цвет: - Серый, Бежевый, Темно-синий -
-
- Материал: - Экокожа, Дерево -
-
- Размеры (Ш×Г×В): - 220 × 95 × 85 см -
-
- -
- 0): ?> - В наличии: шт. - - Нет в наличии - -
- -

- Угловой диван MODERN – сочетание современного дизайна и бескомпромиссного комфорта. - Каркас из массива дерева обеспечивает долговечность, а наполнитель из высокоэластичного - пенополиуретана гарантирует оптимальную поддержку спины. Диван оснащен механизмом - трансформации «еврокнижка», что позволяет использовать его как спальное место. -

- Особенности: - • Прочная конструкция из массива дерева - • Механизм трансформации «еврокнижка» - • Наполнитель: пенополиуретан + периотек - • Съемные чехлы для легкой чистки - • Встроенные подлокотники с полками -

- - 0): ?> -
-
- - - -
- -
- - -
-
- -
- -
- -
-
- - -
-

Похожие товары

-
- -
-
- <?= htmlspecialchars($similar['name']) ?> -
-
-

-

- ... -

-

- ₽ -

- - Подробнее - -
-
- -
-
- -
- - - - - - \ No newline at end of file diff --git a/public/admin/fix_delete_category.php b/public/admin/fix_delete_category.php new file mode 100644 index 0000000..436536b --- /dev/null +++ b/public/admin/fix_delete_category.php @@ -0,0 +1,42 @@ + false, 'message' => 'Доступ запрещен']); + exit(); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $categoryId = $_POST['category_id'] ?? 0; + + if (!$categoryId) { + echo json_encode(['success' => false, 'message' => 'Категория не указана']); + exit(); + } + + try { + $db = Database::getInstance()->getConnection(); + + $checkStmt = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?"); + $checkStmt->execute([$categoryId]); + $productCount = $checkStmt->fetchColumn(); + + if ($productCount > 0) { + echo json_encode(['success' => false, 'message' => 'Нельзя удалить категорию с товарами. Сначала удалите или переместите товары.']); + exit(); + } + + $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); + $stmt->execute([$categoryId]); + + echo json_encode(['success' => true, 'message' => 'Категория удалена']); + } catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]); + } +} else { + echo json_encode(['success' => false, 'message' => 'Неверный запрос']); +} + diff --git a/admin_panel.php b/public/admin/index.php similarity index 58% rename from admin_panel.php rename to public/admin/index.php index f8a4252..fd44b8d 100644 --- a/admin_panel.php +++ b/public/admin/index.php @@ -1,772 +1,988 @@ -Сначала добавьте категории!'; -} - -// Проверка прав администратора -if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) { - echo ""; - exit(); -} - -$db = Database::getInstance()->getConnection(); - -// Обработка действий -$action = $_GET['action'] ?? 'dashboard'; -$message = $_GET['message'] ?? ''; -$error = $_GET['error'] ?? ''; - -// Обработка POST запросов - ДОБАВЛЕНО ПРОСТОЕ И РАБОТАЮЩЕЕ! -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $post_action = $_POST['action'] ?? ''; - - try { - if ($post_action === 'add_category') { - $name = trim($_POST['name'] ?? ''); - $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); - $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL; - $description = trim($_POST['description'] ?? ''); - $sort_order = (int)($_POST['sort_order'] ?? 0); - $is_active = isset($_POST['is_active']) ? 1 : 0; - - if (empty($name)) { - throw new Exception('Название категории обязательно'); - } - - $stmt = $db->prepare(" - INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active) - VALUES (?, ?, ?, ?, ?, ?) - "); - - $result = $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]); - - if ($result) { - header('Location: admin_panel.php?action=categories&message=Категория+успешно+добавлена'); - exit(); - } - } - - // ИСПРАВЬТЕ БЛОК edit_category или добавьте его если его нет: - if ($post_action === 'edit_category' && isset($_POST['category_id'])) { - $category_id = (int)$_POST['category_id']; - $name = trim($_POST['name'] ?? ''); - $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL; - $description = trim($_POST['description'] ?? ''); - $sort_order = (int)($_POST['sort_order'] ?? 0); - $is_active = isset($_POST['is_active']) ? 1 : 0; - - if (empty($name)) { - throw new Exception('Название категории обязательно'); - } - - $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); - - $stmt = $db->prepare(" - UPDATE categories SET - name = ?, - slug = ?, - parent_id = ?, - description = ?, - sort_order = ?, - is_active = ?, - updated_at = CURRENT_TIMESTAMP - WHERE category_id = ? - "); - - $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id]); - - header('Location: admin_panel.php?action=categories&message=Категория+обновлена'); - exit(); - } - - if ($post_action === 'add_product') { - $name = trim($_POST['name'] ?? ''); - $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); - $category_id = (int)($_POST['category_id'] ?? 0); - $description = trim($_POST['description'] ?? ''); - $price = (float)($_POST['price'] ?? 0); - $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL; - $sku = trim($_POST['sku'] ?? ''); - $stock_quantity = (int)($_POST['stock_quantity'] ?? 0); - $is_available = isset($_POST['is_available']) ? 1 : 0; - $is_featured = isset($_POST['is_featured']) ? 1 : 0; - $image_url = trim($_POST['image_url'] ?? ''); - $color = trim($_POST['color'] ?? ''); - $material = trim($_POST['material'] ?? ''); - $card_size = trim($_POST['card_size'] ?? 'small'); - - - // ВАЖНО: Проверяем category_id - if ($category_id <= 0) { - $_SESSION['error'] = 'Выберите корректную категорию'; - header('Location: admin_panel.php?action=add_product'); - exit(); - } - - // Проверяем существование категории - $check_category = $db->prepare("SELECT COUNT(*) FROM categories WHERE category_id = ?"); - $check_category->execute([$category_id]); - if ($check_category->fetchColumn() == 0) { - $_SESSION['error'] = 'Выбранная категория не существует'; - header('Location: admin_panel.php?action=add_product'); - exit(); - } - - if (empty($name)) throw new Exception('Название товара обязательно'); - if ($price <= 0) throw new Exception('Цена должна быть больше 0'); - - // Генерируем SKU если пустой - if (empty($sku)) { - $sku = 'PROD-' . strtoupper(substr(preg_replace('/[^a-z0-9]/i', '', $name), 0, 6)) . '-' . rand(100, 999); - } - - $stmt = $db->prepare(" - INSERT INTO products ( - category_id, name, slug, description, price, old_price, - sku, stock_quantity, is_available, is_featured, image_url, - color, material, card_size - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - "); - - $result = $stmt->execute([ - $category_id, $name, $slug, $description, $price, $old_price, - $sku, $stock_quantity, $is_available, $is_featured, $image_url, - $color, $material, $card_size - ]); - - if ($result) { - $_SESSION['message'] = 'Товар успешно добавлен'; - header('Location: admin_panel.php?action=products'); - exit(); - } - } - - // ИСПРАВЛЕННЫЙ КОД для edit_product в admin_panel.php: - if ($post_action === 'edit_product' && isset($_POST['product_id'])) { - $product_id = (int)$_POST['product_id']; - $name = trim($_POST['name'] ?? ''); - $category_id = (int)($_POST['category_id'] ?? 1); // ПО УМОЛЧАНИЮ 1, чтобы избежать 0 - $description = trim($_POST['description'] ?? ''); - $price = (float)($_POST['price'] ?? 0); - $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL; - $stock_quantity = (int)($_POST['stock_quantity'] ?? 0); - $is_available = isset($_POST['is_available']) ? 1 : 0; - $image_url = trim($_POST['image_url'] ?? ''); - $color = trim($_POST['color'] ?? ''); - $material = trim($_POST['material'] ?? ''); - - // ВАЖНО: Проверяем category_id - if ($category_id <= 0) { - // Если category_id = 0, устанавливаем первую доступную категорию - $firstCat = $db->query("SELECT category_id FROM categories LIMIT 1")->fetchColumn(); - $category_id = $firstCat ?: 1; - } - - $stmt = $db->prepare(" - UPDATE products SET - name = ?, - category_id = ?, - description = ?, - price = ?, - old_price = ?, - stock_quantity = ?, - is_available = ?, - image_url = ?, - color = ?, - material = ?, - updated_at = CURRENT_TIMESTAMP - WHERE product_id = ? - "); - - $stmt->execute([ - $name, $category_id, $description, $price, $old_price, - $stock_quantity, $is_available, $image_url, $color, $material, $product_id - ]); - - header('Location: admin_panel.php?action=products&message=Товар+обновлен'); - exit(); - } - - if ($post_action === 'delete_category' && isset($_POST['category_id'])) { - $categoryId = intval($_POST['category_id']); - - // 1. Проверяем, есть ли товары в этой категории - $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?"); - $checkProducts->execute([$categoryId]); - $productCount = $checkProducts->fetchColumn(); - - // 2. Проверяем, есть ли дочерние категории - $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?"); - $checkChildren->execute([$categoryId]); - $childCount = $checkChildren->fetchColumn(); - - if ($productCount > 0) { - // Если есть товары, делаем категорию неактивной вместо удаления - $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория+скрыта+(содержит+товары)'); - exit(); - } elseif ($childCount > 0) { - // Если есть дочерние категории, делаем неактивной - $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория+скрыта+(имеет+дочерние+категории)'); - exit(); - } else { - // Если нет товаров и дочерних категорий, удаляем - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория+удалена'); - exit(); - } - } - } catch (PDOException $e) { - header('Location: admin_panel.php?action=' . $action . '&error=' . urlencode('Ошибка БД: ' . $e->getMessage())); - exit(); - } catch (Exception $e) { - header('Location: admin_panel.php?action=' . $action . '&error=' . urlencode($e->getMessage())); - exit(); - } - } - - -// Получение данных для отображения -try { - // Статистика - $stats = [ - 'total_products' => $db->query("SELECT COUNT(*) FROM products")->fetchColumn(), - 'active_products' => $db->query("SELECT COUNT(*) FROM products WHERE is_available = TRUE")->fetchColumn(), - 'total_orders' => $db->query("SELECT COUNT(*) FROM orders")->fetchColumn(), - 'total_users' => $db->query("SELECT COUNT(*) FROM users")->fetchColumn(), - 'revenue' => $db->query("SELECT COALESCE(SUM(final_amount), 0) FROM orders WHERE status = 'completed'")->fetchColumn() - ]; - - // Получаем все категории - $allCategories = $db->query("SELECT * FROM categories WHERE is_active = TRUE ORDER BY name")->fetchAll(); - - // Получаем родительские категории - $parentCategories = $db->query("SELECT * FROM categories WHERE parent_id IS NULL AND is_active = TRUE ORDER BY name")->fetchAll(); - - switch ($action) { - case 'products': - $showAll = isset($_GET['show_all']) && $_GET['show_all'] == '1'; - $sql = $showAll - ? "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id ORDER BY p.created_at DESC" - : "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id WHERE p.is_available = TRUE ORDER BY p.created_at DESC"; - $data = $db->query($sql)->fetchAll(); - break; - - case 'categories': - $data = $db->query(" - SELECT c1.*, c2.name as parent_name, - (SELECT COUNT(*) FROM products p WHERE p.category_id = c1.category_id) as product_count - FROM categories c1 - LEFT JOIN categories c2 ON c1.parent_id = c2.category_id - ORDER BY c1.sort_order, c1.name - ")->fetchAll(); - break; - - case 'orders': - $data = $db->query(" - SELECT o.*, u.email as user_email - FROM orders o - LEFT JOIN users u ON o.user_id = u.user_id - ORDER BY o.created_at DESC - LIMIT 50 - ")->fetchAll(); - break; - - case 'users': - $data = $db->query("SELECT * FROM users ORDER BY created_at DESC LIMIT 50")->fetchAll(); - break; - - case 'add_product': - case 'edit_product': - if ($action === 'edit_product' && isset($_GET['id'])) { - $productId = (int)$_GET['id']; - $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?"); - $stmt->execute([$productId]); - $edit_data = $stmt->fetch(); - } - break; - - case 'add_category': - case 'edit_category': - if ($action === 'edit_category' && isset($_GET['id'])) { - $categoryId = (int)$_GET['id']; - $stmt = $db->prepare("SELECT * FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - $edit_data = $stmt->fetch(); - } - break; - - } - -} catch (PDOException $e) { - $error = "Ошибка базы данных: " . $e->getMessage(); -} -?> - - - - - - AETERNA - Админ-панель - - - - -
-

Админ-панель AETERNA

-
- - В каталог - Выйти -
-
- -
- - Дашборд - - - Товары - - - Категории - - - Заказы - - - Пользователи - -
- -
- -
- -
- - - -
- -
- - - - -

Статистика

-
-
-

-

Всего товаров

-
-
-

-

Активных товаров

-
-
-

-

Заказов

-
-
-

-

Пользователей

-
-
- -
- - Добавить новый товар - - - Добавить категорию - -
- - - -
-

Управление товарами

-
- - Добавить товар - - - Только активные - - Показать все - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
IDНазваниеКатегорияЦенаНа складеСтатусДействия
- 0): ?> - ✓ Доступен - - ✗ Недоступен - - ⚠ Нет на складе - - - - - - -
- - - - -
- -
- - - - -
- -
- - - -
-

Управление категориями

- - Добавить категорию - -
- - - - - - - - - - - - - - - - - - - - - - - - -
IDНазваниеSlugРодительскаяТоваровДействия
- - - Редактировать - - - - -
- - - -
-

- -
- - - - - - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- - - Отмена -
-
- - - -
-

- -
- - - - - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- - - Отмена -
-
- - - -

Заказы

- - - - - - - - - - - - - - - - - - - - - - - -
№ заказаКлиентСуммаСтатусДатаДействия
- - - -
- - - -

Пользователи

- - - - - - - - - - - - - - - - - - - - - -
IDEmailФИОДата регистрацииСтатус
- - ✓ Активен - - ✗ Неактивен - -
- - -
- - +alert('Требуется авторизация администратора'); window.location.href = '../login.php';"; + exit(); +} + +$db = Database::getInstance()->getConnection(); + +$action = $_GET['action'] ?? 'dashboard'; +$message = $_GET['message'] ?? ''; +$error = $_GET['error'] ?? ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $post_action = $_POST['action'] ?? ''; + + try { + if ($post_action === 'add_category') { + $name = trim($_POST['name'] ?? ''); + $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); + $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL; + $description = trim($_POST['description'] ?? ''); + $sort_order = (int)($_POST['sort_order'] ?? 0); + $is_active = isset($_POST['is_active']) ? 1 : 0; + + if (empty($name)) { + throw new Exception('Название категории обязательно'); + } + + $stmt = $db->prepare(" + INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active) + VALUES (?, ?, ?, ?, ?, ?) + "); + + $result = $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]); + + if ($result) { + header('Location: index.php?action=categories&message=Категория+успешно+добавлена'); + exit(); + } + } + + if ($post_action === 'edit_category' && isset($_POST['category_id'])) { + $category_id = (int)$_POST['category_id']; + $name = trim($_POST['name'] ?? ''); + $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL; + $description = trim($_POST['description'] ?? ''); + $sort_order = (int)($_POST['sort_order'] ?? 0); + $is_active = isset($_POST['is_active']) ? 1 : 0; + + if (empty($name)) { + throw new Exception('Название категории обязательно'); + } + + $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); + + $stmt = $db->prepare(" + UPDATE categories SET + name = ?, + slug = ?, + parent_id = ?, + description = ?, + sort_order = ?, + is_active = ?, + updated_at = CURRENT_TIMESTAMP + WHERE category_id = ? + "); + + $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id]); + + header('Location: index.php?action=categories&message=Категория+обновлена'); + exit(); + } + + if ($post_action === 'add_product') { + $name = trim($_POST['name'] ?? ''); + $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); + $category_id = (int)($_POST['category_id'] ?? 0); + $description = trim($_POST['description'] ?? ''); + $price = (float)($_POST['price'] ?? 0); + $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL; + $sku = trim($_POST['sku'] ?? ''); + $stock_quantity = (int)($_POST['stock_quantity'] ?? 0); + $is_available = isset($_POST['is_available']) ? 1 : 0; + $is_featured = isset($_POST['is_featured']) ? 1 : 0; + $image_url = trim($_POST['image_url'] ?? ''); + $color = trim($_POST['color'] ?? ''); + $material = trim($_POST['material'] ?? ''); + $card_size = trim($_POST['card_size'] ?? 'small'); + + if ($category_id <= 0) { + $_SESSION['error'] = 'Выберите корректную категорию'; + header('Location: index.php?action=add_product'); + exit(); + } + + $check_category = $db->prepare("SELECT COUNT(*) FROM categories WHERE category_id = ?"); + $check_category->execute([$category_id]); + if ($check_category->fetchColumn() == 0) { + $_SESSION['error'] = 'Выбранная категория не существует'; + header('Location: index.php?action=add_product'); + exit(); + } + + if (empty($name)) throw new Exception('Название товара обязательно'); + if ($price <= 0) throw new Exception('Цена должна быть больше 0'); + + if (empty($sku)) { + $sku = 'PROD-' . strtoupper(substr(preg_replace('/[^a-z0-9]/i', '', $name), 0, 6)) . '-' . rand(100, 999); + } + + $stmt = $db->prepare(" + INSERT INTO products ( + category_id, name, slug, description, price, old_price, + sku, stock_quantity, is_available, is_featured, image_url, + color, material, card_size + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + "); + + $result = $stmt->execute([ + $category_id, $name, $slug, $description, $price, $old_price, + $sku, $stock_quantity, $is_available, $is_featured, $image_url, + $color, $material, $card_size + ]); + + if ($result) { + $_SESSION['message'] = 'Товар успешно добавлен'; + header('Location: index.php?action=products'); + exit(); + } + } + + if ($post_action === 'edit_product' && isset($_POST['product_id'])) { + $product_id = (int)$_POST['product_id']; + $name = trim($_POST['name'] ?? ''); + $category_id = (int)($_POST['category_id'] ?? 1); + $description = trim($_POST['description'] ?? ''); + $price = (float)($_POST['price'] ?? 0); + $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL; + $stock_quantity = (int)($_POST['stock_quantity'] ?? 0); + $is_available = isset($_POST['is_available']) ? 1 : 0; + $image_url = trim($_POST['image_url'] ?? ''); + $color = trim($_POST['color'] ?? ''); + $material = trim($_POST['material'] ?? ''); + + if ($category_id <= 0) { + $firstCat = $db->query("SELECT category_id FROM categories LIMIT 1")->fetchColumn(); + $category_id = $firstCat ?: 1; + } + + $stmt = $db->prepare(" + UPDATE products SET + name = ?, + category_id = ?, + description = ?, + price = ?, + old_price = ?, + stock_quantity = ?, + is_available = ?, + image_url = ?, + color = ?, + material = ?, + updated_at = CURRENT_TIMESTAMP + WHERE product_id = ? + "); + + $stmt->execute([ + $name, $category_id, $description, $price, $old_price, + $stock_quantity, $is_available, $image_url, $color, $material, $product_id + ]); + + header('Location: index.php?action=products&message=Товар+обновлен'); + exit(); + } + + if ($post_action === 'delete_category' && isset($_POST['category_id'])) { + $categoryId = intval($_POST['category_id']); + + $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?"); + $checkProducts->execute([$categoryId]); + $productCount = $checkProducts->fetchColumn(); + + $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?"); + $checkChildren->execute([$categoryId]); + $childCount = $checkChildren->fetchColumn(); + + if ($productCount > 0) { + $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); + $stmt->execute([$categoryId]); + header('Location: index.php?action=categories&message=Категория+скрыта+(содержит+товары)'); + exit(); + } elseif ($childCount > 0) { + $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); + $stmt->execute([$categoryId]); + header('Location: index.php?action=categories&message=Категория+скрыта+(имеет+дочерние+категории)'); + exit(); + } else { + $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); + $stmt->execute([$categoryId]); + header('Location: index.php?action=categories&message=Категория+удалена'); + exit(); + } + } + } catch (PDOException $e) { + header('Location: index.php?action=' . $action . '&error=' . urlencode('Ошибка БД: ' . $e->getMessage())); + exit(); + } catch (Exception $e) { + header('Location: index.php?action=' . $action . '&error=' . urlencode($e->getMessage())); + exit(); + } + } + +try { + $stats = [ + 'total_products' => $db->query("SELECT COUNT(*) FROM products")->fetchColumn(), + 'active_products' => $db->query("SELECT COUNT(*) FROM products WHERE is_available = TRUE")->fetchColumn(), + 'total_orders' => $db->query("SELECT COUNT(*) FROM orders")->fetchColumn(), + 'total_users' => $db->query("SELECT COUNT(*) FROM users")->fetchColumn(), + 'revenue' => $db->query("SELECT COALESCE(SUM(final_amount), 0) FROM orders WHERE status = 'completed'")->fetchColumn() + ]; + + $allCategories = $db->query("SELECT * FROM categories WHERE is_active = TRUE ORDER BY name")->fetchAll(); + + $parentCategories = $db->query("SELECT * FROM categories WHERE parent_id IS NULL AND is_active = TRUE ORDER BY name")->fetchAll(); + + switch ($action) { + case 'products': + $showAll = isset($_GET['show_all']) && $_GET['show_all'] == '1'; + $sql = $showAll + ? "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id ORDER BY p.created_at DESC" + : "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id WHERE p.is_available = TRUE ORDER BY p.created_at DESC"; + $data = $db->query($sql)->fetchAll(); + break; + + case 'categories': + $data = $db->query(" + SELECT c1.*, c2.name as parent_name, + (SELECT COUNT(*) FROM products p WHERE p.category_id = c1.category_id) as product_count + FROM categories c1 + LEFT JOIN categories c2 ON c1.parent_id = c2.category_id + ORDER BY c1.sort_order, c1.name + ")->fetchAll(); + break; + + case 'orders': + $data = $db->query(" + SELECT o.*, u.email as user_email + FROM orders o + LEFT JOIN users u ON o.user_id = u.user_id + ORDER BY o.created_at DESC + LIMIT 50 + ")->fetchAll(); + break; + + case 'users': + $data = $db->query("SELECT * FROM users ORDER BY created_at DESC LIMIT 50")->fetchAll(); + break; + + case 'add_product': + case 'edit_product': + if ($action === 'edit_product' && isset($_GET['id'])) { + $productId = (int)$_GET['id']; + $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?"); + $stmt->execute([$productId]); + $edit_data = $stmt->fetch(); + } + break; + + case 'add_category': + case 'edit_category': + if ($action === 'edit_category' && isset($_GET['id'])) { + $categoryId = (int)$_GET['id']; + $stmt = $db->prepare("SELECT * FROM categories WHERE category_id = ?"); + $stmt->execute([$categoryId]); + $edit_data = $stmt->fetch(); + } + break; + + case 'order_details': + if (isset($_GET['id'])) { + $orderId = (int)$_GET['id']; + + // Получаем информацию о заказе + $stmt = $db->prepare(" + SELECT o.*, u.email as user_email, u.full_name as user_full_name + FROM orders o + LEFT JOIN users u ON o.user_id = u.user_id + WHERE o.order_id = ? + "); + $stmt->execute([$orderId]); + $order = $stmt->fetch(); + + // Получаем товары в заказе + if ($order) { + $stmt = $db->prepare(" + SELECT oi.*, p.image_url + FROM order_items oi + LEFT JOIN products p ON oi.product_id = p.product_id + WHERE oi.order_id = ? + "); + $stmt->execute([$orderId]); + $order_items = $stmt->fetchAll(); + } + } + break; + + } + +} catch (PDOException $e) { + $error = "Ошибка базы данных: " . $e->getMessage(); +} +?> + + + + + + + AETERNA - Админ-панель + + + + + +
+

Админ-панель AETERNA

+
+ + В каталог + Выйти +
+
+ +
+ + Дашборд + + + Товары + + + Категории + + + Заказы + + + Пользователи + +
+ +
+ +
+ +
+ + + +
+ +
+ + + + +

Статистика

+
+
+

+

Всего товаров

+
+
+

+

Активных товаров

+
+
+

+

Заказов

+
+
+

+

Пользователей

+
+
+ +
+ + Добавить новый товар + + + Добавить категорию + +
+ + + +
+

Управление товарами

+
+ + Добавить товар + + + Только активные + + Показать все + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
IDНазваниеКатегорияЦенаНа складеСтатусДействия
+ 0): ?> + ✓ Доступен + + ✗ Недоступен + + ⚠ Нет на складе + + + + + + +
+ + + + +
+ +
+ + + + +
+ +
+ + + +
+

Управление категориями

+ + Добавить категорию + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
IDНазваниеSlugРодительскаяТоваровДействия
+ + Редактировать + + +
+ + + +
+

+ +
+ + + + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+ +
+ +
+ + + Отмена +
+
+ + + +
+

+ +
+ + + + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + Отмена +
+
+ + + +

Заказы

+ + + + + + + + + + + + + + + + + + + + + + + +
№ заказаКлиентСуммаСтатусДатаДействия
+ + + +
+ + + +

Пользователи

+ + + + + + + + + + + + + + + + + + + + + +
IDEmailФИОДата регистрацииСтатус
+ + ✓ Активен + + ✗ Неактивен + +
+ + + + +
+ + Назад к заказам + +
+ +

Детали заказа #

+ +
+ +
+

Информация о заказе

+ + + + + + + + + + + + + + + + + + + + + +
Номер заказа:
Дата создания:
Статус: + + + +
Способ оплаты:
Способ доставки:
+
+ + +
+

Информация о клиенте

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
ФИО:
Email:
Телефон:
Регион:
Адрес доставки:
Индекс:
+
+
+ + +
+

Товары в заказе

+ + + + + + + + + + + + + + + + + + + + + +
ИзображениеТоварЦенаКоличествоСумма
+ + <?= htmlspecialchars($item['product_name']) ?> + +
+ +
+ +
шт.
+
+ + +
+

Итого

+ + + + + + 0): ?> + + + + + + + + + + + + + +
Товары:
Скидка:-
Доставка:
Итого к оплате:
+
+ + +
+

Примечания

+

+
+ + + +
+ Заказ не найден +
+ Вернуться к списку заказов + + + +
+ + \ No newline at end of file diff --git a/add_to_cart.php b/public/api/add_to_cart.php similarity index 84% rename from add_to_cart.php rename to public/api/add_to_cart.php index 4ff29d8..a452dcc 100644 --- a/add_to_cart.php +++ b/public/api/add_to_cart.php @@ -1,116 +1,112 @@ - false, 'message' => 'Требуется авторизация']); - exit(); -} - -if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['product_id'])) { - $product_id = intval($_POST['product_id']); - $quantity = intval($_POST['quantity'] ?? 1); - $user_id = $_SESSION['user_id'] ?? 0; - - if ($user_id == 0) { - echo json_encode(['success' => false, 'message' => 'Пользователь не найден']); - exit(); - } - - $db = Database::getInstance()->getConnection(); - - try { - // Проверяем наличие товара на складе - $checkStock = $db->prepare(" - SELECT stock_quantity, name, price - FROM products - WHERE product_id = ? AND is_available = TRUE - "); - $checkStock->execute([$product_id]); - $product = $checkStock->fetch(); - - if (!$product) { - echo json_encode(['success' => false, 'message' => 'Товар не найден']); - exit(); - } - - if ($product['stock_quantity'] < $quantity) { - echo json_encode(['success' => false, 'message' => 'Недостаточно товара на складе']); - exit(); - } - - // Проверяем, есть ли товар уже в корзине пользователя - $checkCart = $db->prepare(" - SELECT cart_id, quantity - FROM cart - WHERE user_id = ? AND product_id = ? - "); - $checkCart->execute([$user_id, $product_id]); - $cartItem = $checkCart->fetch(); - - if ($cartItem) { - // Обновляем количество - $newQuantity = $cartItem['quantity'] + $quantity; - - // Проверяем общее количество - if ($newQuantity > $product['stock_quantity']) { - echo json_encode(['success' => false, 'message' => 'Превышено доступное количество']); - exit(); - } - - $updateStmt = $db->prepare(" - UPDATE cart - SET quantity = ?, updated_at = CURRENT_TIMESTAMP - WHERE cart_id = ? - "); - $updateStmt->execute([$newQuantity, $cartItem['cart_id']]); - } else { - // Добавляем новый товар - $insertStmt = $db->prepare(" - INSERT INTO cart (user_id, product_id, quantity) - VALUES (?, ?, ?) - "); - $insertStmt->execute([$user_id, $product_id, $quantity]); - } - - // Обновляем сессию - if (!isset($_SESSION['cart'])) { - $_SESSION['cart'] = []; - } - - if (isset($_SESSION['cart'][$product_id])) { - $_SESSION['cart'][$product_id]['quantity'] += $quantity; - } else { - $_SESSION['cart'][$product_id] = [ - 'quantity' => $quantity, - 'name' => $product['name'], - 'price' => $product['price'], - 'added_at' => time() - ]; - } - - // Получаем общее количество товаров в корзине - $cartCountStmt = $db->prepare(" - SELECT SUM(quantity) as total - FROM cart - WHERE user_id = ? - "); - $cartCountStmt->execute([$user_id]); - $cart_count = $cartCountStmt->fetchColumn() ?: 0; - - echo json_encode([ - 'success' => true, - 'cart_count' => $cart_count, - 'message' => 'Товар добавлен в корзину' - ]); - - } catch (PDOException $e) { - echo json_encode([ - 'success' => false, - 'message' => 'Ошибка базы данных: ' . $e->getMessage() - ]); - } -} else { - echo json_encode(['success' => false, 'message' => 'Неверный запрос']); -} + false, 'message' => 'Требуется авторизация']); + exit(); +} + +if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['product_id'])) { + $product_id = intval($_POST['product_id']); + $quantity = intval($_POST['quantity'] ?? 1); + $user_id = $_SESSION['user_id'] ?? 0; + + if ($user_id == 0) { + echo json_encode(['success' => false, 'message' => 'Пользователь не найден']); + exit(); + } + + $db = Database::getInstance()->getConnection(); + + try { + + $checkStock = $db->prepare(" + SELECT stock_quantity, name, price + FROM products + WHERE product_id = ? AND is_available = TRUE + "); + $checkStock->execute([$product_id]); + $product = $checkStock->fetch(); + + if (!$product) { + echo json_encode(['success' => false, 'message' => 'Товар не найден']); + exit(); + } + + if ($product['stock_quantity'] < $quantity) { + echo json_encode(['success' => false, 'message' => 'Недостаточно товара на складе']); + exit(); + } + + $checkCart = $db->prepare(" + SELECT cart_id, quantity + FROM cart + WHERE user_id = ? AND product_id = ? + "); + $checkCart->execute([$user_id, $product_id]); + $cartItem = $checkCart->fetch(); + + if ($cartItem) { + + $newQuantity = $cartItem['quantity'] + $quantity; + + if ($newQuantity > $product['stock_quantity']) { + echo json_encode(['success' => false, 'message' => 'Превышено доступное количество']); + exit(); + } + + $updateStmt = $db->prepare(" + UPDATE cart + SET quantity = ?, updated_at = CURRENT_TIMESTAMP + WHERE cart_id = ? + "); + $updateStmt->execute([$newQuantity, $cartItem['cart_id']]); + } else { + + $insertStmt = $db->prepare(" + INSERT INTO cart (user_id, product_id, quantity) + VALUES (?, ?, ?) + "); + $insertStmt->execute([$user_id, $product_id, $quantity]); + } + + if (!isset($_SESSION['cart'])) { + $_SESSION['cart'] = []; + } + + if (isset($_SESSION['cart'][$product_id])) { + $_SESSION['cart'][$product_id]['quantity'] += $quantity; + } else { + $_SESSION['cart'][$product_id] = [ + 'quantity' => $quantity, + 'name' => $product['name'], + 'price' => $product['price'], + 'added_at' => time() + ]; + } + + $cartCountStmt = $db->prepare(" + SELECT SUM(quantity) as total + FROM cart + WHERE user_id = ? + "); + $cartCountStmt->execute([$user_id]); + $cart_count = $cartCountStmt->fetchColumn() ?: 0; + + echo json_encode([ + 'success' => true, + 'cart_count' => $cart_count, + 'message' => 'Товар добавлен в корзину' + ]); + + } catch (PDOException $e) { + echo json_encode([ + 'success' => false, + 'message' => 'Ошибка базы данных: ' . $e->getMessage() + ]); + } +} else { + echo json_encode(['success' => false, 'message' => 'Неверный запрос']); +} ?> \ No newline at end of file diff --git a/login_handler.php b/public/api/auth.php similarity index 83% rename from login_handler.php rename to public/api/auth.php index 3f63de6..5285940 100644 --- a/login_handler.php +++ b/public/api/auth.php @@ -1,66 +1,69 @@ - false, 'message' => 'Заполните все поля']); - exit(); - } - - $db = Database::getInstance()->getConnection(); - - try { - // Проверяем пользователя в базе данных - $stmt = $db->prepare(" - SELECT user_id, email, password_hash, full_name, phone, city, is_admin, is_active - FROM users - WHERE email = ? - "); - $stmt->execute([$email]); - $user = $stmt->fetch(); - - if (!$user) { - echo json_encode(['success' => false, 'message' => 'Пользователь не найден']); - exit(); - } - - if (!$user['is_active']) { - echo json_encode(['success' => false, 'message' => 'Аккаунт заблокирован']); - exit(); - } - - // Проверяем пароль - if (!password_verify($password, $user['password_hash'])) { - echo json_encode(['success' => false, 'message' => 'Неверный пароль']); - exit(); - } - - // Сохраняем в сессию - $_SESSION['user_id'] = $user['user_id']; - $_SESSION['user_email'] = $user['email']; - $_SESSION['full_name'] = $user['full_name']; - $_SESSION['user_phone'] = $user['phone'] ?? ''; - $_SESSION['user_city'] = $user['city'] ?? ''; - $_SESSION['isLoggedIn'] = true; - $_SESSION['isAdmin'] = (bool)$user['is_admin']; - $_SESSION['login_time'] = time(); - - // Обновляем время последнего входа - $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?"); - $updateStmt->execute([$user['user_id']]); - - echo json_encode(['success' => true, 'redirect' => 'catalog.php']); - - } catch (PDOException $e) { - echo json_encode(['success' => false, 'message' => 'Ошибка базы данных']); - } - -} else { - echo json_encode(['success' => false, 'message' => 'Неверный запрос']); -} + false, 'message' => 'Заполните все поля']); + exit(); + } + + $db = Database::getInstance()->getConnection(); + + try { + + $stmt = $db->prepare(" + SELECT user_id, email, password_hash, full_name, phone, city, is_admin, is_active + FROM users + WHERE email = ? + "); + $stmt->execute([$email]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$user) { + echo json_encode(['success' => false, 'message' => 'Пользователь не найден']); + exit(); + } + + if (!$user['is_active']) { + echo json_encode(['success' => false, 'message' => 'Аккаунт заблокирован']); + exit(); + } + + if (empty($user['password_hash'])) { + echo json_encode(['success' => false, 'message' => 'Ошибка: пароль не найден в базе данных']); + exit(); + } + + if (!password_verify($password, $user['password_hash'])) { + echo json_encode(['success' => false, 'message' => 'Неверный пароль']); + exit(); + } + + $_SESSION['user_id'] = $user['user_id']; + $_SESSION['user_email'] = $user['email']; + $_SESSION['full_name'] = $user['full_name']; + $_SESSION['user_phone'] = $user['phone'] ?? ''; + $_SESSION['user_city'] = $user['city'] ?? ''; + $_SESSION['isLoggedIn'] = true; + $_SESSION['isAdmin'] = (bool)$user['is_admin']; + $_SESSION['login_time'] = time(); + + $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?"); + $updateStmt->execute([$user['user_id']]); + + echo json_encode(['success' => true, 'redirect' => 'catalog.php']); + + } catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => 'Ошибка базы данных']); + } + +} else { + echo json_encode(['success' => false, 'message' => 'Неверный запрос']); +} ?> \ No newline at end of file diff --git a/public/api/cart.php b/public/api/cart.php new file mode 100644 index 0000000..cd68d7b --- /dev/null +++ b/public/api/cart.php @@ -0,0 +1,127 @@ + false, 'message' => 'Требуется авторизация']); + exit(); +} + +$userId = $_SESSION['user_id'] ?? 0; +$action = $_GET['action'] ?? $_POST['action'] ?? ''; + +$db = Database::getInstance()->getConnection(); + +try { + switch ($action) { + case 'add': + $productId = (int)($_POST['product_id'] ?? 0); + $quantity = (int)($_POST['quantity'] ?? 1); + + if ($productId <= 0) { + echo json_encode(['success' => false, 'message' => 'Неверный ID товара']); + exit(); + } + + $checkProduct = $db->prepare("SELECT product_id, stock_quantity FROM products WHERE product_id = ? AND is_available = TRUE"); + $checkProduct->execute([$productId]); + $product = $checkProduct->fetch(); + + if (!$product) { + echo json_encode(['success' => false, 'message' => 'Товар не найден']); + exit(); + } + + $checkCart = $db->prepare("SELECT cart_id, quantity FROM cart WHERE user_id = ? AND product_id = ?"); + $checkCart->execute([$userId, $productId]); + $cartItem = $checkCart->fetch(); + + if ($cartItem) { + + $newQuantity = $cartItem['quantity'] + $quantity; + $stmt = $db->prepare("UPDATE cart SET quantity = ?, updated_at = CURRENT_TIMESTAMP WHERE cart_id = ?"); + $stmt->execute([$newQuantity, $cartItem['cart_id']]); + } else { + + $stmt = $db->prepare("INSERT INTO cart (user_id, product_id, quantity) VALUES (?, ?, ?)"); + $stmt->execute([$userId, $productId, $quantity]); + } + + echo json_encode(['success' => true, 'message' => 'Товар добавлен в корзину']); + break; + + case 'update': + $productId = (int)($_POST['product_id'] ?? 0); + $quantity = (int)($_POST['quantity'] ?? 1); + + if ($quantity <= 0) { + + $stmt = $db->prepare("DELETE FROM cart WHERE user_id = ? AND product_id = ?"); + $stmt->execute([$userId, $productId]); + } else { + $stmt = $db->prepare("UPDATE cart SET quantity = ?, updated_at = CURRENT_TIMESTAMP WHERE user_id = ? AND product_id = ?"); + $stmt->execute([$quantity, $userId, $productId]); + } + + echo json_encode(['success' => true, 'message' => 'Корзина обновлена']); + break; + + case 'remove': + $productId = (int)($_POST['product_id'] ?? 0); + + $stmt = $db->prepare("DELETE FROM cart WHERE user_id = ? AND product_id = ?"); + $stmt->execute([$userId, $productId]); + + echo json_encode(['success' => true, 'message' => 'Товар удален из корзины']); + break; + + case 'get': + $stmt = $db->prepare(" + SELECT c.cart_id, c.product_id, c.quantity, p.name, p.price, p.image_url, p.stock_quantity + FROM cart c + JOIN products p ON c.product_id = p.product_id + WHERE c.user_id = ? AND p.is_available = TRUE + ORDER BY c.created_at DESC + "); + $stmt->execute([$userId]); + $items = $stmt->fetchAll(); + + $total = 0; + foreach ($items as &$item) { + $item['subtotal'] = $item['price'] * $item['quantity']; + $total += $item['subtotal']; + } + + echo json_encode([ + 'success' => true, + 'items' => $items, + 'total' => $total, + 'count' => array_sum(array_column($items, 'quantity')) + ]); + break; + + case 'count': + $stmt = $db->prepare("SELECT COALESCE(SUM(quantity), 0) FROM cart WHERE user_id = ?"); + $stmt->execute([$userId]); + $count = $stmt->fetchColumn(); + + echo json_encode(['success' => true, 'count' => (int)$count]); + break; + + case 'clear': + $stmt = $db->prepare("DELETE FROM cart WHERE user_id = ?"); + $stmt->execute([$userId]); + + echo json_encode(['success' => true, 'message' => 'Корзина очищена']); + break; + + default: + echo json_encode(['success' => false, 'message' => 'Неизвестное действие']); + } + +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]); +} diff --git a/get_cart.php b/public/api/get_cart.php similarity index 88% rename from get_cart.php rename to public/api/get_cart.php index dbef740..2d43ea4 100644 --- a/get_cart.php +++ b/public/api/get_cart.php @@ -1,62 +1,61 @@ - false, 'message' => 'Требуется авторизация']); - exit(); -} - -$user_id = $_SESSION['user_id'] ?? 0; - -if ($user_id == 0) { - echo json_encode(['success' => false, 'message' => 'Пользователь не найден']); - exit(); -} - -$db = Database::getInstance()->getConnection(); - -try { - // Получаем корзину из БД - $stmt = $db->prepare(" - SELECT - c.cart_id, - c.product_id, - c.quantity, - p.name, - p.price, - p.image_url, - p.stock_quantity - FROM cart c - JOIN products p ON c.product_id = p.product_id - WHERE c.user_id = ? AND p.is_available = TRUE - ORDER BY c.created_at DESC - "); - $stmt->execute([$user_id]); - $cart_items = $stmt->fetchAll(); - - // Обновляем сессию - $_SESSION['cart'] = []; - foreach ($cart_items as $item) { - $_SESSION['cart'][$item['product_id']] = [ - 'quantity' => $item['quantity'], - 'name' => $item['name'], - 'price' => $item['price'], - 'added_at' => time() - ]; - } - - echo json_encode([ - 'success' => true, - 'cart_items' => $cart_items, - 'total_items' => count($cart_items) - ]); - -} catch (PDOException $e) { - echo json_encode([ - 'success' => false, - 'message' => 'Ошибка базы данных: ' . $e->getMessage() - ]); -} + false, 'message' => 'Требуется авторизация']); + exit(); +} + +$user_id = $_SESSION['user_id'] ?? 0; + +if ($user_id == 0) { + echo json_encode(['success' => false, 'message' => 'Пользователь не найден']); + exit(); +} + +$db = Database::getInstance()->getConnection(); + +try { + + $stmt = $db->prepare(" + SELECT + c.cart_id, + c.product_id, + c.quantity, + p.name, + p.price, + p.image_url, + p.stock_quantity + FROM cart c + JOIN products p ON c.product_id = p.product_id + WHERE c.user_id = ? AND p.is_available = TRUE + ORDER BY c.created_at DESC + "); + $stmt->execute([$user_id]); + $cart_items = $stmt->fetchAll(); + + $_SESSION['cart'] = []; + foreach ($cart_items as $item) { + $_SESSION['cart'][$item['product_id']] = [ + 'quantity' => $item['quantity'], + 'name' => $item['name'], + 'price' => $item['price'], + 'added_at' => time() + ]; + } + + echo json_encode([ + 'success' => true, + 'cart_items' => $cart_items, + 'total_items' => count($cart_items) + ]); + +} catch (PDOException $e) { + echo json_encode([ + 'success' => false, + 'message' => 'Ошибка базы данных: ' . $e->getMessage() + ]); +} ?> \ No newline at end of file diff --git a/get_cart_count.php b/public/api/get_cart_count.php similarity index 88% rename from get_cart_count.php rename to public/api/get_cart_count.php index 39cdc01..f1d59c2 100644 --- a/get_cart_count.php +++ b/public/api/get_cart_count.php @@ -1,22 +1,22 @@ - false, 'cart_count' => 0]); - exit(); -} - -$user_id = $_SESSION['user_id'] ?? 0; -$db = Database::getInstance()->getConnection(); - -try { - $stmt = $db->prepare("SELECT SUM(quantity) as total FROM cart WHERE user_id = ?"); - $stmt->execute([$user_id]); - $cart_count = $stmt->fetchColumn() ?: 0; - - echo json_encode(['success' => true, 'cart_count' => $cart_count]); -} catch (PDOException $e) { - echo json_encode(['success' => false, 'cart_count' => 0]); + false, 'cart_count' => 0]); + exit(); +} + +$user_id = $_SESSION['user_id'] ?? 0; +$db = Database::getInstance()->getConnection(); + +try { + $stmt = $db->prepare("SELECT SUM(quantity) as total FROM cart WHERE user_id = ?"); + $stmt->execute([$user_id]); + $cart_count = $stmt->fetchColumn() ?: 0; + + echo json_encode(['success' => true, 'cart_count' => $cart_count]); +} catch (PDOException $e) { + echo json_encode(['success' => false, 'cart_count' => 0]); } \ No newline at end of file diff --git a/get_product.php b/public/api/get_product.php similarity index 85% rename from get_product.php rename to public/api/get_product.php index e801cd7..0cd3ca6 100644 --- a/get_product.php +++ b/public/api/get_product.php @@ -1,33 +1,32 @@ - false, 'message' => 'Доступ запрещен']); - exit(); -} - -if (!isset($_GET['id'])) { - echo json_encode(['success' => false, 'message' => 'ID не указан']); - exit(); -} - -try { - $db = Database::getInstance()->getConnection(); - $product_id = $_GET['id']; - - $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?"); - $stmt->execute([$product_id]); - $product = $stmt->fetch(); - - if ($product) { - echo json_encode($product); - } else { - echo json_encode(['success' => false, 'message' => 'Товар не найден']); - } - -} catch (PDOException $e) { - echo json_encode(['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]); -} + false, 'message' => 'Доступ запрещен']); + exit(); +} + +if (!isset($_GET['id'])) { + echo json_encode(['success' => false, 'message' => 'ID не указан']); + exit(); +} + +try { + $db = Database::getInstance()->getConnection(); + $product_id = $_GET['id']; + + $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?"); + $stmt->execute([$product_id]); + $product = $stmt->fetch(); + + if ($product) { + echo json_encode($product); + } else { + echo json_encode(['success' => false, 'message' => 'Товар не найден']); + } + +} catch (PDOException $e) { + echo json_encode(['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]); +} ?> \ No newline at end of file diff --git a/process_order.php b/public/api/process_order.php similarity index 65% rename from process_order.php rename to public/api/process_order.php index e7f5805..1e59cb3 100644 --- a/process_order.php +++ b/public/api/process_order.php @@ -1,134 +1,136 @@ -getConnection(); - - try { - $db->beginTransaction(); - - // Получаем данные из формы - $customer_name = $_POST['full_name'] ?? ''; - $customer_email = $_POST['email'] ?? ''; - $customer_phone = $_POST['phone'] ?? ''; - $delivery_address = $_POST['address'] ?? ''; - $region = $_POST['region'] ?? ''; - $payment_method = $_POST['payment'] ?? 'card'; - $delivery_method = $_POST['delivery'] ?? 'courier'; - $notes = $_POST['notes'] ?? ''; - $discount_amount = floatval($_POST['discount'] ?? 0); - $delivery_cost = floatval($_POST['delivery_price'] ?? 2000); - - // Генерируем номер заказа - $order_number = 'ORD-' . date('Ymd-His') . '-' . rand(1000, 9999); - - // Получаем корзину пользователя - $cartStmt = $db->prepare(" - SELECT - c.product_id, - c.quantity, - p.name, - p.price, - p.stock_quantity - FROM cart c - JOIN products p ON c.product_id = p.product_id - WHERE c.user_id = ? - "); - $cartStmt->execute([$user_id]); - $cart_items = $cartStmt->fetchAll(); - - if (empty($cart_items)) { - throw new Exception('Корзина пуста'); - } - - // Рассчитываем итоги - $total_amount = 0; - foreach ($cart_items as $item) { - $total_amount += $item['price'] * $item['quantity']; - } - - $final_amount = $total_amount - $discount_amount + $delivery_cost; - - // Создаем заказ - $orderStmt = $db->prepare(" - INSERT INTO orders ( - user_id, order_number, total_amount, discount_amount, - delivery_cost, final_amount, status, payment_method, - delivery_method, delivery_address, customer_name, - customer_email, customer_phone, notes - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - RETURNING order_id - "); - - $orderStmt->execute([ - $user_id, $order_number, $total_amount, $discount_amount, - $delivery_cost, $final_amount, 'pending', $payment_method, - $delivery_method, $delivery_address, $customer_name, - $customer_email, $customer_phone, $notes - ]); - - $order_id = $orderStmt->fetchColumn(); - - // Добавляем товары в заказ и обновляем остатки - foreach ($cart_items as $item) { - // Добавляем в order_items - $itemStmt = $db->prepare(" - INSERT INTO order_items ( - order_id, product_id, product_name, - quantity, unit_price, total_price - ) VALUES (?, ?, ?, ?, ?, ?) - "); - - $item_total = $item['price'] * $item['quantity']; - $itemStmt->execute([ - $order_id, $item['product_id'], $item['name'], - $item['quantity'], $item['price'], $item_total - ]); - - // Обновляем остатки на складе - $updateStmt = $db->prepare(" - UPDATE products - SET stock_quantity = stock_quantity - ?, - updated_at = CURRENT_TIMESTAMP - WHERE product_id = ? - "); - $updateStmt->execute([$item['quantity'], $item['product_id']]); - } - - // Очищаем корзину - $clearCartStmt = $db->prepare("DELETE FROM cart WHERE user_id = ?"); - $clearCartStmt->execute([$user_id]); - - // Очищаем сессию - unset($_SESSION['cart']); - - $db->commit(); - - // Перенаправляем на страницу успеха - header('Location: order_success.php?id=' . $order_id); - exit(); - - } catch (Exception $e) { - $db->rollBack(); - header('Location: оформление_заказа.php?error=' . urlencode($e->getMessage())); - exit(); - } -} else { - header('Location: оформление_заказа.php'); - exit(); -} + false, 'message' => 'Требуется авторизация']); + exit(); +} + +$user_id = $_SESSION['user_id'] ?? 0; + +if ($user_id == 0) { + echo json_encode(['success' => false, 'message' => 'Пользователь не найден']); + exit(); +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $db = Database::getInstance()->getConnection(); + + try { + $db->beginTransaction(); + + $customer_name = $_POST['full_name'] ?? ''; + $customer_email = $_POST['email'] ?? ''; + $customer_phone = $_POST['phone'] ?? ''; + $delivery_address = $_POST['address'] ?? ''; + $region = $_POST['region'] ?? ''; + $postal_code = $_POST['postal_code'] ?? ''; + $payment_method = $_POST['payment'] ?? 'card'; + $delivery_method = $_POST['delivery'] ?? 'courier'; + $promo_code = $_POST['promo_code'] ?? ''; + $notes = $_POST['notes'] ?? ''; + $discount_amount = floatval($_POST['discount'] ?? 0); + $delivery_cost = floatval($_POST['delivery_price'] ?? 2000); + + $order_number = 'ORD-' . date('Ymd-His') . '-' . rand(1000, 9999); + + $cartStmt = $db->prepare(" + SELECT + c.product_id, + c.quantity, + p.name, + p.price, + p.stock_quantity + FROM cart c + JOIN products p ON c.product_id = p.product_id + WHERE c.user_id = ? + "); + $cartStmt->execute([$user_id]); + $cart_items = $cartStmt->fetchAll(); + + if (empty($cart_items)) { + throw new Exception('Корзина пуста'); + } + + $total_amount = 0; + foreach ($cart_items as $item) { + $total_amount += $item['price'] * $item['quantity']; + } + + $final_amount = $total_amount - $discount_amount + $delivery_cost; + + $orderStmt = $db->prepare(" + INSERT INTO orders ( + user_id, order_number, subtotal, discount_amount, + delivery_price, final_amount, status, payment_method, + delivery_method, delivery_address, delivery_region, + postal_code, promo_code, customer_name, customer_email, + customer_phone, notes + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + RETURNING order_id + "); + + $orderStmt->execute([ + $user_id, $order_number, $total_amount, $discount_amount, + $delivery_cost, $final_amount, 'pending', $payment_method, + $delivery_method, $delivery_address, $region, $postal_code, + $promo_code, $customer_name, $customer_email, $customer_phone, $notes + ]); + + $order_id = $orderStmt->fetchColumn(); + + foreach ($cart_items as $item) { + + $itemStmt = $db->prepare(" + INSERT INTO order_items ( + order_id, product_id, product_name, + quantity, product_price, total_price + ) VALUES (?, ?, ?, ?, ?, ?) + "); + + $item_total = $item['price'] * $item['quantity']; + $itemStmt->execute([ + $order_id, $item['product_id'], $item['name'], + $item['quantity'], $item['price'], $item_total + ]); + + $updateStmt = $db->prepare(" + UPDATE products + SET stock_quantity = stock_quantity - ?, + updated_at = CURRENT_TIMESTAMP + WHERE product_id = ? + "); + $updateStmt->execute([$item['quantity'], $item['product_id']]); + } + + $clearCartStmt = $db->prepare("DELETE FROM cart WHERE user_id = ?"); + $clearCartStmt->execute([$user_id]); + + unset($_SESSION['cart']); + + $db->commit(); + + echo json_encode([ + 'success' => true, + 'order_id' => $order_id, + 'order_number' => $order_number, + 'message' => 'Заказ успешно оформлен' + ]); + exit(); + + } catch (Exception $e) { + $db->rollBack(); + echo json_encode([ + 'success' => false, + 'message' => $e->getMessage() + ]); + exit(); + } +} else { + echo json_encode(['success' => false, 'message' => 'Неверный метод запроса']); + exit(); +} ?> \ No newline at end of file diff --git a/register_handler.php b/public/api/register_handler.php similarity index 52% rename from register_handler.php rename to public/api/register_handler.php index 76ce3b9..d1cfb81 100644 --- a/register_handler.php +++ b/public/api/register_handler.php @@ -1,182 +1,162 @@ - $full_name, - 'city' => $city, - 'email' => $email, - 'phone' => $phone - ]; - header('Location: профиль.php'); - exit(); - } - - // Подключаемся к базе данных - $db = Database::getInstance()->getConnection(); - - try { - // Проверяем, существует ли пользователь с таким email - $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?"); - $checkStmt->execute([$email]); - - if ($checkStmt->fetch()) { - $_SESSION['registration_errors'] = ['Пользователь с таким email уже существует']; - $_SESSION['old_data'] = [ - 'fio' => $full_name, - 'city' => $city, - 'email' => $email, - 'phone' => $phone - ]; - header('Location: профиль.php'); - exit(); - } - - // Хэшируем пароль - $password_hash = password_hash($password, PASSWORD_DEFAULT); - - // Определяем, является ли пользователь администратором - $is_admin = false; - $admin_emails = ['admin@aeterna.ru', 'administrator@aeterna.ru', 'aeterna@mail.ru']; - if (in_array(strtolower($email), $admin_emails)) { - $is_admin = true; - } - - // РАЗНЫЕ ВАРИАНТЫ ДЛЯ ТЕСТИРОВАНИЯ - РАСКОММЕНТИРУЙТЕ НУЖНЫЙ - - // Вариант 1: С явным преобразованием boolean (самый надежный) - $stmt = $db->prepare(" - INSERT INTO users (email, password_hash, full_name, phone, city, is_admin) - VALUES (?, ?, ?, ?, ?, ?) - RETURNING user_id - "); - - // Преобразуем boolean в integer для PostgreSQL - $stmt->execute([ - $email, - $password_hash, - $full_name, - $phone, - $city, - $is_admin ? 1 : 0 // Преобразуем в integer (1 или 0) - ]); - - // Вариант 2: С использованием CAST в SQL (альтернатива) - /* - $stmt = $db->prepare(" - INSERT INTO users (email, password_hash, full_name, phone, city, is_admin) - VALUES (?, ?, ?, ?, ?, CAST(? AS boolean)) - RETURNING user_id - "); - - $stmt->execute([ - $email, - $password_hash, - $full_name, - $phone, - $city, - $is_admin ? 'true' : 'false' // Строковые значения true/false - ]); - */ - - $user_id = $stmt->fetchColumn(); - - if ($user_id) { - // Автоматически авторизуем пользователя - $_SESSION['user_id'] = $user_id; - $_SESSION['user_email'] = $email; - $_SESSION['full_name'] = $full_name; - $_SESSION['user_phone'] = $phone; - $_SESSION['user_city'] = $city; - $_SESSION['isLoggedIn'] = true; - $_SESSION['isAdmin'] = $is_admin; - $_SESSION['login_time'] = time(); - - // Обновляем время последнего входа - $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?"); - $updateStmt->execute([$user_id]); - - // Перенаправляем на главную или каталог - $_SESSION['registration_success'] = 'Регистрация прошла успешно! ' . - ($is_admin ? 'Вы зарегистрированы как администратор.' : 'Добро пожаловать в AETERNA!'); - - header('Location: catalog.php'); - exit(); - } else { - throw new Exception('Ошибка при создании пользователя'); - } - - } catch (PDOException $e) { - // Логируем полную ошибку для отладки - error_log("Registration DB Error: " . $e->getMessage()); - error_log("SQL State: " . $e->getCode()); - error_log("Email: " . $email); - - $_SESSION['registration_errors'] = ['Ошибка базы данных: ' . $e->getMessage()]; - $_SESSION['old_data'] = [ - 'fio' => $full_name, - 'city' => $city, - 'email' => $email, - 'phone' => $phone - ]; - header('Location: профиль.php'); - exit(); - } catch (Exception $e) { - error_log("Registration Error: " . $e->getMessage()); - - $_SESSION['registration_errors'] = [$e->getMessage()]; - header('Location: профиль.php'); - exit(); - } - -} else { - // Если запрос не POST, перенаправляем на форму регистрации - header('Location: профиль.php'); - exit(); -} + $full_name, + 'city' => $city, + 'email' => $email, + 'phone' => $phone + ]; + header('Location: ../register.php'); + exit(); + } + + $db = Database::getInstance()->getConnection(); + + try { + + $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?"); + $checkStmt->execute([$email]); + + if ($checkStmt->fetch()) { + $_SESSION['registration_errors'] = ['Пользователь с таким email уже существует']; + $_SESSION['old_data'] = [ + 'fio' => $full_name, + 'city' => $city, + 'email' => $email, + 'phone' => $phone + ]; + header('Location: ../register.php'); + exit(); + } + + $password_hash = password_hash($password, PASSWORD_DEFAULT); + + $is_admin = false; + $admin_emails = ['admin@aeterna.ru', 'administrator@aeterna.ru', 'aeterna@mail.ru']; + if (in_array(strtolower($email), $admin_emails)) { + $is_admin = true; + } + + $stmt = $db->prepare(" + INSERT INTO users (email, password_hash, full_name, phone, city, is_admin, is_active) + VALUES (?, ?, ?, ?, ?, CAST(? AS boolean), TRUE) + RETURNING user_id + "); + + $stmt->execute([ + $email, + $password_hash, + $full_name, + $phone, + $city, + $is_admin ? 'true' : 'false' + ]); + + $user_id = $stmt->fetchColumn(); + + if (!$user_id) { + throw new Exception('Ошибка при создании пользователя: user_id не получен'); + } + + $verifyStmt = $db->prepare("SELECT user_id, email, password_hash FROM users WHERE user_id = ?"); + $verifyStmt->execute([$user_id]); + $verifyUser = $verifyStmt->fetch(PDO::FETCH_ASSOC); + + if (!$verifyUser) { + throw new Exception('Ошибка: пользователь не найден после создания'); + } + + if (empty($verifyUser['password_hash'])) { + throw new Exception('Ошибка: пароль не сохранен'); + } + + $_SESSION['user_id'] = $user_id; + $_SESSION['user_email'] = $email; + $_SESSION['full_name'] = $full_name; + $_SESSION['user_phone'] = $phone; + $_SESSION['user_city'] = $city; + $_SESSION['isLoggedIn'] = true; + $_SESSION['isAdmin'] = (bool)$is_admin; + $_SESSION['login_time'] = time(); + + $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?"); + $updateStmt->execute([$user_id]); + + $_SESSION['registration_success'] = 'Регистрация прошла успешно! ' . + ($is_admin ? 'Вы зарегистрированы как администратор.' : 'Добро пожаловать в AETERNA!'); + + header('Location: ../catalog.php'); + exit(); + + } catch (PDOException $e) { + + error_log("Registration DB Error: " . $e->getMessage()); + error_log("SQL State: " . $e->getCode()); + error_log("Email: " . $email); + + $_SESSION['registration_errors'] = ['Ошибка базы данных: ' . $e->getMessage()]; + $_SESSION['old_data'] = [ + 'fio' => $full_name, + 'city' => $city, + 'email' => $email, + 'phone' => $phone + ]; + header('Location: ../register.php'); + exit(); + } catch (Exception $e) { + error_log("Registration Error: " . $e->getMessage()); + + $_SESSION['registration_errors'] = [$e->getMessage()]; + header('Location: ../register.php'); + exit(); + } + +} else { + + header('Location: register.php'); + exit(); +} ?> \ No newline at end of file diff --git a/update_cart.php b/public/api/update_cart.php similarity index 92% rename from update_cart.php rename to public/api/update_cart.php index 8ab9f4a..0063946 100644 --- a/update_cart.php +++ b/public/api/update_cart.php @@ -1,6 +1,6 @@ false, 'message' => 'Требуется авторизация']); diff --git a/img2/1 — копия.jpg b/public/assets/img/1 — копия.jpg similarity index 100% rename from img2/1 — копия.jpg rename to public/assets/img/1 — копия.jpg diff --git a/img/1.jpg b/public/assets/img/1.jpg similarity index 100% rename from img/1.jpg rename to public/assets/img/1.jpg diff --git a/img2/100.jpg b/public/assets/img/100.jpg similarity index 100% rename from img2/100.jpg rename to public/assets/img/100.jpg diff --git a/img2/11.jpg b/public/assets/img/11.jpg similarity index 100% rename from img2/11.jpg rename to public/assets/img/11.jpg diff --git a/img2/111.jpg b/public/assets/img/111.jpg similarity index 100% rename from img2/111.jpg rename to public/assets/img/111.jpg diff --git a/img2/11_1.png b/public/assets/img/11_1.png similarity index 100% rename from img2/11_1.png rename to public/assets/img/11_1.png diff --git a/img/1_1.jpg b/public/assets/img/1_1.jpg similarity index 100% rename from img/1_1.jpg rename to public/assets/img/1_1.jpg diff --git a/img2/1_2.jpg b/public/assets/img/1_2.jpg similarity index 100% rename from img2/1_2.jpg rename to public/assets/img/1_2.jpg diff --git a/img2/1_2.png b/public/assets/img/1_2.png similarity index 100% rename from img2/1_2.png rename to public/assets/img/1_2.png diff --git a/img/2.jpg b/public/assets/img/2.jpg similarity index 100% rename from img/2.jpg rename to public/assets/img/2.jpg diff --git a/img2/22.jpg b/public/assets/img/22.jpg similarity index 100% rename from img2/22.jpg rename to public/assets/img/22.jpg diff --git a/img2/25.jpg b/public/assets/img/25.jpg similarity index 100% rename from img2/25.jpg rename to public/assets/img/25.jpg diff --git a/img2/2_2.jpg b/public/assets/img/2_2.jpg similarity index 100% rename from img2/2_2.jpg rename to public/assets/img/2_2.jpg diff --git a/img2/2_2.png b/public/assets/img/2_2.png similarity index 100% rename from img2/2_2.png rename to public/assets/img/2_2.png diff --git a/img2/3.jpg b/public/assets/img/3.jpg similarity index 100% rename from img2/3.jpg rename to public/assets/img/3.jpg diff --git a/img/3_3.jpg b/public/assets/img/3_3.jpg similarity index 100% rename from img/3_3.jpg rename to public/assets/img/3_3.jpg diff --git a/img2/3_3.png b/public/assets/img/3_3.png similarity index 100% rename from img2/3_3.png rename to public/assets/img/3_3.png diff --git a/img2/4.jpg b/public/assets/img/4.jpg similarity index 100% rename from img2/4.jpg rename to public/assets/img/4.jpg diff --git a/img2/44.jpg b/public/assets/img/44.jpg similarity index 100% rename from img2/44.jpg rename to public/assets/img/44.jpg diff --git a/img2/444 b/public/assets/img/444 similarity index 100% rename from img2/444 rename to public/assets/img/444 diff --git a/img2/444 (1).png b/public/assets/img/444 (1).png similarity index 100% rename from img2/444 (1).png rename to public/assets/img/444 (1).png diff --git a/img2/444.jpg b/public/assets/img/444.jpg similarity index 100% rename from img2/444.jpg rename to public/assets/img/444.jpg diff --git a/img2/444.png b/public/assets/img/444.png similarity index 100% rename from img2/444.png rename to public/assets/img/444.png diff --git a/img2/4_1.jpg b/public/assets/img/4_1.jpg similarity index 100% rename from img2/4_1.jpg rename to public/assets/img/4_1.jpg diff --git a/img/5.jpg b/public/assets/img/5.jpg similarity index 100% rename from img/5.jpg rename to public/assets/img/5.jpg diff --git a/img/5_5.jpg b/public/assets/img/5_5.jpg similarity index 100% rename from img/5_5.jpg rename to public/assets/img/5_5.jpg diff --git a/img2/5_5.png b/public/assets/img/5_5.png similarity index 100% rename from img2/5_5.png rename to public/assets/img/5_5.png diff --git a/img2/6.jpg b/public/assets/img/6.jpg similarity index 100% rename from img2/6.jpg rename to public/assets/img/6.jpg diff --git a/img/6_6.jpg b/public/assets/img/6_6.jpg similarity index 100% rename from img/6_6.jpg rename to public/assets/img/6_6.jpg diff --git a/img2/6_6.png b/public/assets/img/6_6.png similarity index 100% rename from img2/6_6.png rename to public/assets/img/6_6.png diff --git a/img2/7.jpg b/public/assets/img/7.jpg similarity index 100% rename from img2/7.jpg rename to public/assets/img/7.jpg diff --git a/img2/77.jpg b/public/assets/img/77.jpg similarity index 100% rename from img2/77.jpg rename to public/assets/img/77.jpg diff --git a/img2/777 (1).png b/public/assets/img/777 (1).png similarity index 100% rename from img2/777 (1).png rename to public/assets/img/777 (1).png diff --git a/img2/777.jpg b/public/assets/img/777.jpg similarity index 100% rename from img2/777.jpg rename to public/assets/img/777.jpg diff --git a/img2/777.png b/public/assets/img/777.png similarity index 100% rename from img2/777.png rename to public/assets/img/777.png diff --git a/img/7_7.jpg b/public/assets/img/7_7.jpg similarity index 100% rename from img/7_7.jpg rename to public/assets/img/7_7.jpg diff --git a/img2/7_7.png b/public/assets/img/7_7.png similarity index 100% rename from img2/7_7.png rename to public/assets/img/7_7.png diff --git a/img2/8.jpg b/public/assets/img/8.jpg similarity index 100% rename from img2/8.jpg rename to public/assets/img/8.jpg diff --git a/img2/88.jpg b/public/assets/img/88.jpg similarity index 100% rename from img2/88.jpg rename to public/assets/img/88.jpg diff --git a/img2/888 (1).png b/public/assets/img/888 (1).png similarity index 100% rename from img2/888 (1).png rename to public/assets/img/888 (1).png diff --git a/img2/888.jpg b/public/assets/img/888.jpg similarity index 100% rename from img2/888.jpg rename to public/assets/img/888.jpg diff --git a/img2/888.png b/public/assets/img/888.png similarity index 100% rename from img2/888.png rename to public/assets/img/888.png diff --git a/img2/8_8.png b/public/assets/img/8_8.png similarity index 100% rename from img2/8_8.png rename to public/assets/img/8_8.png diff --git a/img2/9.jpg b/public/assets/img/9.jpg similarity index 100% rename from img2/9.jpg rename to public/assets/img/9.jpg diff --git a/img2/99.jpg b/public/assets/img/99.jpg similarity index 100% rename from img2/99.jpg rename to public/assets/img/99.jpg diff --git a/img2/99.png b/public/assets/img/99.png similarity index 100% rename from img2/99.png rename to public/assets/img/99.png diff --git a/img2/99_1.jpg b/public/assets/img/99_1.jpg similarity index 100% rename from img2/99_1.jpg rename to public/assets/img/99_1.jpg diff --git a/img2/99_2.jpg b/public/assets/img/99_2.jpg similarity index 100% rename from img2/99_2.jpg rename to public/assets/img/99_2.jpg diff --git a/img2/99_3.png b/public/assets/img/99_3.png similarity index 100% rename from img2/99_3.png rename to public/assets/img/99_3.png diff --git a/img/9_9.jpg b/public/assets/img/9_9.jpg similarity index 100% rename from img/9_9.jpg rename to public/assets/img/9_9.jpg diff --git a/img2/9_9.png b/public/assets/img/9_9.png similarity index 100% rename from img2/9_9.png rename to public/assets/img/9_9.png diff --git a/img2/black.png b/public/assets/img/black.png similarity index 100% rename from img2/black.png rename to public/assets/img/black.png diff --git a/img2/black1.png b/public/assets/img/black1.png similarity index 100% rename from img2/black1.png rename to public/assets/img/black1.png diff --git a/img2/black2.png b/public/assets/img/black2.png similarity index 100% rename from img2/black2.png rename to public/assets/img/black2.png diff --git a/img2/brown.png b/public/assets/img/brown.png similarity index 100% rename from img2/brown.png rename to public/assets/img/brown.png diff --git a/img2/brown1.png b/public/assets/img/brown1.png similarity index 100% rename from img2/brown1.png rename to public/assets/img/brown1.png diff --git a/img2/brown2.png b/public/assets/img/brown2.png similarity index 100% rename from img2/brown2.png rename to public/assets/img/brown2.png diff --git a/img/chair.PNG b/public/assets/img/chair.PNG similarity index 100% rename from img/chair.PNG rename to public/assets/img/chair.PNG diff --git a/img2/gray.png b/public/assets/img/gray.png similarity index 100% rename from img2/gray.png rename to public/assets/img/gray.png diff --git a/img2/gray1.png b/public/assets/img/gray1.png similarity index 100% rename from img2/gray1.png rename to public/assets/img/gray1.png diff --git a/img2/gray2.png b/public/assets/img/gray2.png similarity index 100% rename from img2/gray2.png rename to public/assets/img/gray2.png diff --git a/img/диван.jpg b/public/assets/img/диван.jpg similarity index 100% rename from img/диван.jpg rename to public/assets/img/диван.jpg diff --git a/img/диван_1.jpg b/public/assets/img/диван_1.jpg similarity index 100% rename from img/диван_1.jpg rename to public/assets/img/диван_1.jpg diff --git a/img/кресло.jpg b/public/assets/img/кресло.jpg similarity index 100% rename from img/кресло.jpg rename to public/assets/img/кресло.jpg diff --git a/img/кресло_1.jpg b/public/assets/img/кресло_1.jpg similarity index 100% rename from img/кресло_1.jpg rename to public/assets/img/кресло_1.jpg diff --git a/img/слайдер_1.jpg b/public/assets/img/слайдер_1.jpg similarity index 100% rename from img/слайдер_1.jpg rename to public/assets/img/слайдер_1.jpg diff --git a/img/слайдер_2.jpg b/public/assets/img/слайдер_2.jpg similarity index 100% rename from img/слайдер_2.jpg rename to public/assets/img/слайдер_2.jpg diff --git a/img/слайдер_3.jpg b/public/assets/img/слайдер_3.jpg similarity index 100% rename from img/слайдер_3.jpg rename to public/assets/img/слайдер_3.jpg diff --git a/img/слайдер_4.jpg b/public/assets/img/слайдер_4.jpg similarity index 100% rename from img/слайдер_4.jpg rename to public/assets/img/слайдер_4.jpg diff --git a/img/слайдер_5.jpg b/public/assets/img/слайдер_5.jpg similarity index 100% rename from img/слайдер_5.jpg rename to public/assets/img/слайдер_5.jpg diff --git a/img/слайдер_6.jpg b/public/assets/img/слайдер_6.jpg similarity index 100% rename from img/слайдер_6.jpg rename to public/assets/img/слайдер_6.jpg diff --git a/img/спальня.jpg b/public/assets/img/спальня.jpg similarity index 100% rename from img/спальня.jpg rename to public/assets/img/спальня.jpg diff --git a/img/стили_оформления.css b/public/assets/img/стили_оформления.css similarity index 87% rename from img/стили_оформления.css rename to public/assets/img/стили_оформления.css index 581822b..2e1d09c 100644 --- a/img/стили_оформления.css +++ b/public/assets/img/стили_оформления.css @@ -1,62 +1,61 @@ - -.error-message { - color: #ff0000; - font-size: 12px; - margin-top: 5px; - display: none; -} - -.form__input.error { - border-color: #ff0000; -} - -.form__group { - position: relative; - margin-bottom: 15px; -} - -/* Стили для сообщений внизу страницы */ -.page-messages { - position: fixed; - bottom: 20px; - left: 50%; - transform: translateX(-50%); - z-index: 1000; - width: 90%; - max-width: 500px; -} - -.message { - padding: 15px; - margin: 10px 0; - border-radius: 5px; - text-align: center; - font-weight: bold; - display: none; -} - -.message.error { - background-color: #ffebee; - color: #c62828; - border: 1px solid #ffcdd2; -} - -.message.success { - background-color: #e8f5e9; - color: #453227; - border: 1px solid #c8e6c9; -} - -.message.warning { - background-color: #fff3e0; - color: #ef6c00; - border: 1px solid #ffe0b2; -} - -.privacy-error { - color: #ff0000; - font-size: 12px; - margin-top: 5px; - display: none; - text-align: center; -} + +.error-message { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; +} + +.form__input.error { + border-color: #ff0000; +} + +.form__group { + position: relative; + margin-bottom: 15px; +} + +.page-messages { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + width: 90%; + max-width: 500px; +} + +.message { + padding: 15px; + margin: 10px 0; + border-radius: 5px; + text-align: center; + font-weight: bold; + display: none; +} + +.message.error { + background-color: #ffebee; + color: #c62828; + border: 1px solid #ffcdd2; +} + +.message.success { + background-color: #e8f5e9; + color: #453227; + border: 1px solid #c8e6c9; +} + +.message.warning { + background-color: #fff3e0; + color: #ef6c00; + border: 1px solid #ffe0b2; +} + +.privacy-error { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; + text-align: center; +} diff --git a/оформление_заказа_jquery.js b/public/assets/js/checkout.js similarity index 73% rename from оформление_заказа_jquery.js rename to public/assets/js/checkout.js index 74e8305..24f4590 100644 --- a/оформление_заказа_jquery.js +++ b/public/assets/js/checkout.js @@ -1,346 +1,340 @@ -// script.js - -$(document).ready(function() { - // Инициализация корзины - let cart = { - items: [ - { id: 1, name: 'Кресло OPPORTUNITY', price: 16999, quantity: 1 }, - { id: 2, name: 'Кресло GOLDEN', price: 19999, quantity: 1 }, - { id: 3, name: 'Светильник POLET', price: 7999, quantity: 1 } - ], - delivery: 2000, - discount: 0 - }; - - // Функция обновления общей суммы - function updateTotal() { - let productsTotal = 0; - let totalCount = 0; - - // Пересчитываем товары - $('.products__item').each(function() { - const $item = $(this); - const price = parseInt($item.data('price')); - const quantity = parseInt($item.find('.products__qty-value').text()); - - productsTotal += price * quantity; - totalCount += quantity; - }); - - // Обновляем отображение - $('.products-total').text(productsTotal + ' ₽'); - $('.summary-count').text(totalCount); - $('.total-count').text(totalCount + ' шт.'); - $('.cart-count').text(totalCount); - - // Обновляем итоговую сумму - const finalTotal = productsTotal + cart.delivery - cart.discount; - $('.final-total').text(finalTotal + ' ₽'); - } - - // Функция валидации email - function validateEmail(email) { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); - } - - // Функция валидации имени (ФИО) - function validateFullName(name) { - // Проверяем, что имя содержит только буквы, пробелы, дефисы и апострофы - const nameRegex = /^[a-zA-Zа-яА-ЯёЁ\s\-']+$/; - - // Проверяем, что имя состоит минимум из 2 слов - const words = name.trim().split(/\s+/); - - return nameRegex.test(name) && words.length >= 2; - } - - // Функция валидации телефона - function validatePhone(phone) { - // Российский формат телефона: +7XXXXXXXXXX - const phoneRegex = /^\+7\d{10}$/; - return phoneRegex.test(phone); - } - - // Функция отображения сообщения - function showMessage(messageId, duration = 5000) { - // Скрываем все сообщения - $('.message').hide(); - - // Показываем нужное сообщение - $(messageId).fadeIn(300); - - // Автоматически скрываем через указанное время - if (duration > 0) { - setTimeout(() => { - $(messageId).fadeOut(300); - }, duration); - } - } - - // Функция показа ошибки приватности - function showPrivacyError(show) { - if (show) { - $('#privacy-error').show(); - } else { - $('#privacy-error').hide(); - } - } - - // Функция отображения ошибки конкретного поля - function showFieldError(fieldId, message) { - // Убираем старые ошибки - $(fieldId).removeClass('error-input'); - $(fieldId + '-error').remove(); - - if (message) { - $(fieldId).addClass('error-input'); - $(fieldId).after('
' + message + '
'); - } - } - - // Валидация поля при потере фокуса с указанием конкретной ошибки - $('#fullname').on('blur', function() { - const value = $(this).val().trim(); - if (value) { - if (!validateFullName(value)) { - showFieldError('#fullname', 'ФИО должно содержать только буквы и состоять минимум из 2 слов'); - } else { - showFieldError('#fullname', ''); - } - } else { - showFieldError('#fullname', ''); - } - }); - - $('#email').on('blur', function() { - const value = $(this).val().trim(); - if (value) { - if (!validateEmail(value)) { - showFieldError('#email', 'Введите корректный email адрес (например: example@mail.ru)'); - } else { - showFieldError('#email', ''); - } - } else { - showFieldError('#email', ''); - } - }); - - $('#phone').on('blur', function() { - const value = $(this).val().trim(); - if (value) { - if (!validatePhone(value)) { - showFieldError('#phone', 'Введите номер в формате +7XXXXXXXXXX (10 цифр после +7)'); - } else { - showFieldError('#phone', ''); - } - } else { - showFieldError('#phone', ''); - } - }); - - // Валидация обязательных полей - $('#region').on('blur', function() { - const value = $(this).val().trim(); - if (!value) { - showFieldError('#region', 'Укажите регион доставки'); - } else { - showFieldError('#region', ''); - } - }); - - $('#address').on('blur', function() { - const value = $(this).val().trim(); - if (!value) { - showFieldError('#address', 'Укажите адрес доставки (улица, дом, квартира)'); - } else { - showFieldError('#address', ''); - } - }); - - // Очистка ошибки при начале ввода - $('.form__input').on('input', function() { - const fieldId = '#' + $(this).attr('id'); - showFieldError(fieldId, ''); - }); - - // Обработчик увеличения количества - $('.products__qty-btn.plus').click(function() { - const $qtyValue = $(this).siblings('.products__qty-value'); - let quantity = parseInt($qtyValue.text()); - $qtyValue.text(quantity + 1); - updateTotal(); - }); - - // Обработчик уменьшения количества - $('.products__qty-btn.minus').click(function() { - const $qtyValue = $(this).siblings('.products__qty-value'); - let quantity = parseInt($qtyValue.text()); - if (quantity > 1) { - $qtyValue.text(quantity - 1); - updateTotal(); - } - }); - - // Обработчик удаления товара - $('.remove-from-cart').click(function() { - const $productItem = $(this).closest('.products__item'); - $productItem.fadeOut(300, function() { - $(this).remove(); - updateTotal(); - - // Показываем сообщение, если корзина пуста - if ($('.products__item').length === 0) { - $('.products__list').html('
Корзина пуста
'); - } - }); - }); - - // Обработчик применения промокода - $('.promo__btn').click(function() { - const promoCode = $('.promo__input').val().toUpperCase(); - - if (promoCode === 'SALE10') { - cart.discount = Math.round(parseInt($('.products-total').text()) * 0.1); - $('.discount-total').text(cart.discount + ' ₽'); - showMessage('#form-error', 3000); - $('#form-error').text('Промокод применен! Скидка 10%').removeClass('error').addClass('success'); - } else if (promoCode === 'FREE') { - cart.delivery = 0; - $('.delivery-price').text('0 ₽'); - showMessage('#form-error', 3000); - $('#form-error').text('Промокод применен! Бесплатная доставка').removeClass('error').addClass('success'); - } else if (promoCode) { - showMessage('#form-error', 3000); - $('#form-error').text('Промокод недействителен').removeClass('success').addClass('error'); - } - - updateTotal(); - }); - - // Обработчик выбора доставки - $('input[name="delivery"]').change(function() { - if ($(this).val() === 'pickup') { - cart.delivery = 0; - $('.delivery-price').text('0 ₽'); - } else { - cart.delivery = 2000; - $('.delivery-price').text('2000 ₽'); - } - updateTotal(); - }); - - // Функция проверки всех полей формы - function validateForm() { - let isValid = true; - let errorMessages = []; - - // Очищаем все старые ошибки - $('.field-error').remove(); - $('.form__input').removeClass('error-input'); - - // Проверка обязательных полей - const requiredFields = [ - { - id: '#fullname', - value: $('#fullname').val().trim(), - validator: validateFullName, - required: true, - message: 'ФИО должно содержать только буквы и состоять минимум из 2 слов' - }, - { - id: '#phone', - value: $('#phone').val().trim(), - validator: validatePhone, - required: true, - message: 'Введите корректный номер телефона в формате +7XXXXXXXXXX' - }, - { - id: '#email', - value: $('#email').val().trim(), - validator: validateEmail, - required: true, - message: 'Введите корректный email адрес' - }, - { - id: '#region', - value: $('#region').val().trim(), - validator: (val) => val.length > 0, - required: true, - message: 'Поле "Регион" обязательно для заполнения' - }, - { - id: '#address', - value: $('#address').val().trim(), - validator: (val) => val.length > 0, - required: true, - message: 'Поле "Адрес" обязательно для заполнения' - } - ]; - - // Проверяем каждое поле - requiredFields.forEach(field => { - if (field.required && (!field.value || !field.validator(field.value))) { - isValid = false; - errorMessages.push(field.message); - showFieldError(field.id, field.message); - } - }); - - // Проверка согласия на обработку данных - if (!$('#privacy-checkbox').is(':checked')) { - isValid = false; - showPrivacyError(true); - errorMessages.push('Необходимо согласие на обработку персональных данных'); - } else { - showPrivacyError(false); - } - - // Показываем общее сообщение, если есть ошибки - if (!isValid && errorMessages.length > 0) { - showMessage('#form-error', 5000); - $('#form-error').text('Исправьте следующие ошибки: ' + errorMessages.join('; ')).removeClass('success').addClass('error'); - - // Прокручиваем к первой ошибке - $('html, body').animate({ - scrollTop: $('.error-input').first().offset().top - 100 - }, 500); - } - - return isValid; - } - - // Обработчик оформления заказа - $('#submit-order').click(function() { - // Проверка валидации всех полей - if (!validateForm()) { - return; - } - - // Симуляция отправки заказа - $(this).prop('disabled', true).text('ОБРАБОТКА...'); - - setTimeout(() => { - showMessage('#order-success', 5000); - $(this).prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ'); - - // Здесь можно добавить редирект на страницу благодарности - // window.location.href = 'спасибо.html'; - }, 2000); - }); - - // Маска для телефона - $('#phone').on('input', function() { - let phone = $(this).val().replace(/\D/g, ''); - if (phone.length > 0) { - if (phone[0] !== '7' && phone[0] !== '8') { - phone = '7' + phone; - } - phone = '+7' + phone.substring(1, 11); - $(this).val(phone); - } - }); - - // Инициализация при загрузке - updateTotal(); -}); \ No newline at end of file +$(document).ready(function() { + let cart = { + items: [ + { id: 1, name: 'Кресло OPPORTUNITY', price: 16999, quantity: 1 }, + { id: 2, name: 'Кресло GOLDEN', price: 19999, quantity: 1 }, + { id: 3, name: 'Светильник POLET', price: 7999, quantity: 1 } + ], + delivery: 2000, + discount: 0 + }; + + function updateTotal() { + let productsTotal = 0; + let totalCount = 0; + + $('.products__item').each(function() { + const $item = $(this); + const price = parseInt($item.data('price')); + const quantity = parseInt($item.find('.products__qty-value').text()); + + productsTotal += price * quantity; + totalCount += quantity; + }); + + $('.products-total').text(productsTotal + ' ₽'); + $('.summary-count').text(totalCount); + $('.total-count').text(totalCount + ' шт.'); + if ($('.cart-count').length) { + $('.cart-count').text(totalCount); + } + if ($('#cartCount').length) { + $('#cartCount').text(totalCount); + } + + const finalTotal = productsTotal + cart.delivery - cart.discount; + $('.final-total').text(finalTotal + ' ₽'); + } + + function validateEmail(email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + } + + function validateFullName(name) { + const nameRegex = /^[a-zA-Zа-яА-ЯёЁ\s\-']+$/; + const words = name.trim().split(/\s+/); + + return nameRegex.test(name) && words.length >= 2; + } + + function validatePhone(phone) { + const phoneRegex = /^\+7\d{10}$/; + return phoneRegex.test(phone); + } + + function showMessage(messageId, duration = 5000) { + $('.message').hide(); + + $(messageId).fadeIn(300); + + if (duration > 0) { + setTimeout(() => { + $(messageId).fadeOut(300); + }, duration); + } + } + + function showPrivacyError(show) { + if (show) { + $('#privacy-error').show(); + } else { + $('#privacy-error').hide(); + } + } + + function showFieldError(fieldId, message) { + $(fieldId).removeClass('error-input'); + $(fieldId + '-error').remove(); + + if (message) { + $(fieldId).addClass('error-input'); + $(fieldId).after('
' + message + '
'); + } + } + + $('#fullname').on('blur', function() { + const value = $(this).val().trim(); + if (value) { + if (!validateFullName(value)) { + showFieldError('#fullname', 'ФИО должно содержать только буквы и состоять минимум из 2 слов'); + } else { + showFieldError('#fullname', ''); + } + } else { + showFieldError('#fullname', ''); + } + }); + + $('#email').on('blur', function() { + const value = $(this).val().trim(); + if (value) { + if (!validateEmail(value)) { + showFieldError('#email', 'Введите корректный email адрес (например: example@mail.ru)'); + } else { + showFieldError('#email', ''); + } + } else { + showFieldError('#email', ''); + } + }); + + $('#phone').on('blur', function() { + const value = $(this).val().trim(); + if (value) { + if (!validatePhone(value)) { + showFieldError('#phone', 'Введите номер в формате +7XXXXXXXXXX (10 цифр после +7)'); + } else { + showFieldError('#phone', ''); + } + } else { + showFieldError('#phone', ''); + } + }); + + $('#region').on('blur', function() { + const value = $(this).val().trim(); + if (!value) { + showFieldError('#region', 'Укажите регион доставки'); + } else { + showFieldError('#region', ''); + } + }); + + $('#address').on('blur', function() { + const value = $(this).val().trim(); + if (!value) { + showFieldError('#address', 'Укажите адрес доставки (улица, дом, квартира)'); + } else { + showFieldError('#address', ''); + } + }); + + $('.form__input').on('input', function() { + const fieldId = '#' + $(this).attr('id'); + showFieldError(fieldId, ''); + }); + + $('.products__qty-btn.plus').click(function() { + const $qtyValue = $(this).siblings('.products__qty-value'); + let quantity = parseInt($qtyValue.text()); + $qtyValue.text(quantity + 1); + updateTotal(); + }); + + $('.products__qty-btn.minus').click(function() { + const $qtyValue = $(this).siblings('.products__qty-value'); + let quantity = parseInt($qtyValue.text()); + if (quantity > 1) { + $qtyValue.text(quantity - 1); + updateTotal(); + } + }); + + $('.remove-from-cart').click(function() { + const $productItem = $(this).closest('.products__item'); + $productItem.fadeOut(300, function() { + $(this).remove(); + updateTotal(); + + if ($('.products__item').length === 0) { + $('.products__list').html('
Корзина пуста
'); + } + }); + }); + + $('.promo__btn').click(function() { + const promoCode = $('.promo__input').val().toUpperCase(); + + if (promoCode === 'SALE10') { + cart.discount = Math.round(parseInt($('.products-total').text()) * 0.1); + $('.discount-total').text(cart.discount + ' ₽'); + showMessage('#form-error', 3000); + $('#form-error').text('Промокод применен! Скидка 10%').removeClass('error').addClass('success'); + } else if (promoCode === 'FREE') { + cart.delivery = 0; + $('.delivery-price').text('0 ₽'); + showMessage('#form-error', 3000); + $('#form-error').text('Промокод применен! Бесплатная доставка').removeClass('error').addClass('success'); + } else if (promoCode) { + showMessage('#form-error', 3000); + $('#form-error').text('Промокод недействителен').removeClass('success').addClass('error'); + } + + updateTotal(); + }); + + $('input[name="delivery"]').change(function() { + if ($(this).val() === 'pickup') { + cart.delivery = 0; + $('.delivery-price').text('0 ₽'); + } else { + cart.delivery = 2000; + $('.delivery-price').text('2000 ₽'); + } + updateTotal(); + }); + + function validateForm() { + let isValid = true; + let errorMessages = []; + + $('.field-error').remove(); + $('.form__input').removeClass('error-input'); + + const requiredFields = [ + { + id: '#fullname', + value: $('#fullname').val().trim(), + validator: validateFullName, + required: true, + message: 'ФИО должно содержать только буквы и состоять минимум из 2 слов' + }, + { + id: '#phone', + value: $('#phone').val().trim(), + validator: validatePhone, + required: true, + message: 'Введите корректный номер телефона в формате +7XXXXXXXXXX' + }, + { + id: '#email', + value: $('#email').val().trim(), + validator: validateEmail, + required: true, + message: 'Введите корректный email адрес' + }, + { + id: '#region', + value: $('#region').val().trim(), + validator: (val) => val.length > 0, + required: true, + message: 'Поле "Регион" обязательно для заполнения' + }, + { + id: '#address', + value: $('#address').val().trim(), + validator: (val) => val.length > 0, + required: true, + message: 'Поле "Адрес" обязательно для заполнения' + } + ]; + + requiredFields.forEach(field => { + if (field.required && (!field.value || !field.validator(field.value))) { + isValid = false; + errorMessages.push(field.message); + showFieldError(field.id, field.message); + } + }); + + if (!$('#privacy-checkbox').is(':checked')) { + isValid = false; + showPrivacyError(true); + errorMessages.push('Необходимо согласие на обработку персональных данных'); + } else { + showPrivacyError(false); + } + + if (!isValid && errorMessages.length > 0) { + showMessage('#form-error', 5000); + $('#form-error').text('Исправьте следующие ошибки: ' + errorMessages.join('; ')).removeClass('success').addClass('error'); + + $('html, body').animate({ + scrollTop: $('.error-input').first().offset().top - 100 + }, 500); + } + + return isValid; + } + + $('#submit-order').click(function() { + if (!validateForm()) { + return; + } + + const $btn = $(this); + $btn.prop('disabled', true).text('ОБРАБОТКА...'); + + // Собрать данные заказа + const orderData = { + full_name: $('#fullname').val().trim(), + phone: $('#phone').val().trim(), + email: $('#email').val().trim(), + region: $('#region').val().trim(), + address: $('#address').val().trim(), + delivery_method: $('input[name="delivery"]:checked').val(), + comment: $('#comment').val().trim() + }; + + // Отправить на сервер + $.ajax({ + url: 'api/process_order.php', + method: 'POST', + data: orderData, + dataType: 'json', + success: function(response) { + if (response.success) { + showMessage('#order-success', 5000); + $('#order-success').text('Заказ успешно оформлен! Номер заказа: ' + (response.order_id || '')); + // Очистить корзину и форму + setTimeout(() => { + window.location.href = 'catalog.php'; + }, 2000); + } else { + showMessage('#form-error', 5000); + $('#form-error').text(response.message || 'Ошибка оформления заказа').removeClass('success').addClass('error'); + $btn.prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ'); + } + }, + error: function(xhr, status, error) { + showMessage('#form-error', 5000); + $('#form-error').text('Ошибка сервера. Попробуйте позже.').removeClass('success').addClass('error'); + $btn.prop('disabled', false).text('ОФОРМИТЬ ЗАКАЗ'); + } + }); + }); + + $('#phone').on('input', function() { + let phone = $(this).val().replace(/\D/g, ''); + if (phone.length > 0) { + if (phone[0] !== '7' && phone[0] !== '8') { + phone = '7' + phone; + } + phone = '+7' + phone.substring(1, 11); + $(this).val(phone); + } + }); + + updateTotal(); +}); diff --git a/профиль_jquery.js b/public/assets/js/profile.js similarity index 78% rename from профиль_jquery.js rename to public/assets/js/profile.js index 4aa7ff1..509db1a 100644 --- a/профиль_jquery.js +++ b/public/assets/js/profile.js @@ -1,384 +1,348 @@ -$(document).ready(function() { - // Функции для отображения сообщений - function showMessage(type, text) { - const messageId = type + 'Message'; - const $message = $('#' + messageId); - $message.text(text).fadeIn(300); - setTimeout(() => { - $message.fadeOut(300); - }, 5000); - } - - // Проверка, является ли email администратора - function isAdminEmail(email) { - const adminEmails = ['admin@aeterna.ru', 'administrator@aeterna.ru', 'aeterna@mail.ru']; - return adminEmails.includes(email.toLowerCase()); - } - - // Валидация ФИО (без цифр) - function validateFIO(fio) { - const words = fio.trim().split(/\s+/); - // Проверяем, что минимум 2 слова и нет цифр - const hasNoDigits = !/\d/.test(fio); - return words.length >= 2 && words.every(word => word.length >= 2) && hasNoDigits; - } - - // Валидация города - function validateCity(city) { - return city.trim().length >= 2 && /^[а-яА-ЯёЁ\s-]+$/.test(city); - } - - // Валидация email - function validateEmail(email) { - const localPart = email.split('@')[0]; - - // Проверка на кириллические символы перед @ - if (/[а-яА-ЯёЁ]/.test(localPart)) { - showError('email', 'В тексте перед знаком "@" не должно быть кириллических символов'); - return false; - } - - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email)) { - showError('email', 'Введите корректный email адрес'); - return false; - } - - hideError('email'); - return true; - } - - // Валидация телефона - function validatePhone(phone) { - const phoneRegex = /^(\+7|8)[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/; - return phoneRegex.test(phone.replace(/\s/g, '')); - } - - // Валидация пароля - function validatePassword(password) { - return password.length >= 6; - } - - // Показать/скрыть ошибку - function showError(fieldId, message) { - $('#' + fieldId).addClass('error'); - $('#' + fieldId + '-error').text(message).show(); - } - - function hideError(fieldId) { - $('#' + fieldId).removeClass('error'); - $('#' + fieldId + '-error').hide(); - } - - // Валидация формы - function validateForm() { - let isValid = true; - - // Валидация ФИО - const fio = $('#fio').val(); - if (!validateFIO(fio)) { - if (/\d/.test(fio)) { - showError('fio', 'ФИО не должно содержать цифры'); - } else { - showError('fio', 'ФИО должно содержать минимум 2 слова (каждое от 2 символов)'); - } - isValid = false; - } else { - hideError('fio'); - } - - // Валидация города - const city = $('#city').val(); - if (!validateCity(city)) { - showError('city', 'Укажите корректное название города (только русские буквы)'); - isValid = false; - } else { - hideError('city'); - } - - // Валидация email - const email = $('#email').val(); - if (!validateEmail(email)) { - showError('email', 'Введите корректный email адрес'); - isValid = false; - } else { - hideError('email'); - } - - // Валидация телефона - const phone = $('#phone').val(); - if (!validatePhone(phone)) { - showError('phone', 'Введите номер в формате: +7(912)999-12-23'); - isValid = false; - } else { - hideError('phone'); - } - - // Валидация пароля - const password = $('#password').val(); - if (!validatePassword(password)) { - showError('password', 'Пароль должен содержать минимум 6 символов'); - isValid = false; - } else { - hideError('password'); - } - - // Проверка совпадения паролей - const confirmPassword = $('#confirm-password').val(); - if (password !== confirmPassword) { - showError('confirm-password', 'Пароли не совпадают'); - isValid = false; - } else { - hideError('confirm-password'); - } - - // Проверка согласия с условиями - if (!$('#privacy').is(':checked')) { - $('#privacy-error').show(); - isValid = false; - } else { - $('#privacy-error').hide(); - } - - return isValid; - } - - // Реальная валидация при вводе - $('input').on('blur', function() { - const fieldId = $(this).attr('id'); - const value = $(this).val(); - - switch(fieldId) { - case 'fio': - if (!validateFIO(value)) { - if (/\d/.test(value)) { - showError(fieldId, 'ФИО не должно содержать цифры'); - } else { - showError(fieldId, 'ФИО должно содержать минимум 2 слова'); - } - } else { - hideError(fieldId); - } - break; - case 'city': - if (!validateCity(value)) { - showError(fieldId, 'Укажите корректное название города'); - } else { - hideError(fieldId); - } - break; - case 'email': - if (!validateEmail(value)) { - showError(fieldId, 'Введите корректный email адрес'); - } else { - hideError(fieldId); - } - break; - case 'phone': - if (!validatePhone(value)) { - showError(fieldId, 'Введите номер в формате: +7(912)999-12-23'); - } else { - hideError(fieldId); - } - break; - case 'password': - if (!validatePassword(value)) { - showError(fieldId, 'Пароль должен содержать минимум 6 символов'); - } else { - hideError(fieldId); - } - break; - case 'confirm-password': - const password = $('#password').val(); - if (value !== password) { - showError(fieldId, 'Пароли не совпадают'); - } else { - hideError(fieldId); - } - break; - } - }); - - // Обработка отправки формы - $('#registrationForm').on('submit', function(e) { - e.preventDefault(); - - if (validateForm()) { - const email = $('#email').val(); - const isAdmin = isAdminEmail(email); - - // Сохраняем данные пользователя в localStorage - const userData = { - email: email, - fio: $('#fio').val(), - phone: $('#phone').val(), - isAdmin: isAdmin, - registered: new Date().toISOString() - }; - - localStorage.setItem('userData', JSON.stringify(userData)); - localStorage.setItem('isLoggedIn', 'true'); - localStorage.setItem('isAdmin', isAdmin.toString()); - - // Эмуляция успешной регистрации - showMessage('success', 'Регистрация прошла успешно! ' + - (isAdmin ? 'Вы зарегистрированы как администратор.' : 'Добро пожаловать в AETERNA!')); - - setTimeout(() => { - // Перенаправление на главную страницу - window.location.href = 'cite_mebel.php'; - }, 2000); - } else { - showMessage('error', 'Пожалуйста, исправьте ошибки в форме'); - } - }); - - // Плавная прокрутка к якорям - $('a[href^="#"]').on('click', function(event) { - var target = $(this.getAttribute('href')); - if (target.length) { - event.preventDefault(); - $('html, body').stop().animate({ - scrollTop: target.offset().top - }, 1000); - } - }); - - // Переключение между регистрацией и входом - $('.login-btn').on('click', function() { - showMessage('warning', 'Переход к форме входа...'); - setTimeout(() => { - window.location.href = 'вход.php'; - }, 1000); - }); - - // Обработка ссылки "Сменить пароль" - $('.password-link').on('click', function(e) { - e.preventDefault(); - showMessage('warning', 'Функция смены пароля будет доступна после регистрации'); - }); - - // Маска для телефона - $('#phone').on('input', function() { - let value = $(this).val().replace(/\D/g, ''); - if (value.startsWith('7') || value.startsWith('8')) { - value = value.substring(1); - } - if (value.length > 0) { - value = '+7(' + value; - if (value.length > 6) value = value.substring(0, 6) + ')' + value.substring(6); - if (value.length > 10) value = value.substring(0, 10) + '-' + value.substring(10); - if (value.length > 13) value = value.substring(0, 13) + '-' + value.substring(13); - if (value.length > 16) value = value.substring(0, 16); - } - $(this).val(value); - }); - - // Запрет ввода цифр в поле ФИО - $('#fio').on('input', function() { - let value = $(this).val(); - // Удаляем цифры из значения - value = value.replace(/\d/g, ''); - $(this).val(value); - }); -}); - -// Для входа (файл вход.html) -$(document).ready(function() { - // Проверяем, если пользователь уже вошел - if (localStorage.getItem('isLoggedIn') === 'true') { - const userData = JSON.parse(localStorage.getItem('userData') || '{}'); - if (userData.email) { - $('#login-email').val(userData.email); - } - } - - // Функция проверки пароля администратора - function checkAdminPassword(email, password) { - // Административные аккаунты - const adminAccounts = { - 'admin@aeterna.ru': 'admin123', - 'administrator@aeterna.ru': 'admin123', - 'aeterna@mail.ru': 'admin123' - }; - - return adminAccounts[email.toLowerCase()] === password; - } - - // Валидация формы входа - $('#loginForm').on('submit', function(e) { - e.preventDefault(); - - let isValid = true; - const email = $('#login-email').val(); - const password = $('#login-password').val(); - - // Валидация email - if (!isValidEmail(email)) { - $('#email-error').show(); - isValid = false; - } else { - $('#email-error').hide(); - } - - // Валидация пароля - if (password.length < 6) { - $('#password-error').show(); - isValid = false; - } else { - $('#password-error').hide(); - } - - if (isValid) { - // Здесь обычно отправка данных на сервер - showMessage('success', 'Вы успешно вошли в систему!'); - - // Перенаправление на главную страницу через 1.5 секунды - setTimeout(function() { - window.location.href = 'cite_mebel.php'; - }, 1500); - } - }); - - // Функция показа сообщений - function showMessage(type, text) { - const messageId = type + 'Message'; - const $message = $('#' + messageId); - - $message.text(text).show(); - - setTimeout(function() { - $message.fadeOut(); - }, 3000); - } - - // Скрываем сообщения об ошибках при фокусе на полях - $('input').on('focus', function() { - $(this).next('.error-message').hide(); - }); - - // Обработка чекбокса "Запомнить меня" - $('#remember').on('change', function() { - if ($(this).is(':checked')) { - const email = $('#login-email').val(); - if (email) { - localStorage.setItem('rememberedEmail', email); - } - } else { - localStorage.removeItem('rememberedEmail'); - } - }); - - // Автозаполнение email, если пользователь выбрал "Запомнить меня" - const rememberedEmail = localStorage.getItem('rememberedEmail'); - if (rememberedEmail) { - $('#login-email').val(rememberedEmail); - $('#remember').prop('checked', true); - } - - // Обработка ссылки "Забыли пароль?" - $('.forgot-password').on('click', function(e) { - e.preventDefault(); - showMessage('info', 'Для восстановления пароля обратитесь к администратору'); - }); -}); \ No newline at end of file +$(document).ready(function() { + + function showMessage(type, text) { + const messageId = type + 'Message'; + const $message = $('#' + messageId); + $message.text(text).fadeIn(300); + setTimeout(() => { + $message.fadeOut(300); + }, 5000); + } + + function isAdminEmail(email) { + const adminEmails = ['admin@aeterna.ru', 'administrator@aeterna.ru', 'aeterna@mail.ru']; + return adminEmails.includes(email.toLowerCase()); + } + + function validateFIO(fio) { + const words = fio.trim().split(/\s+/); + + const hasNoDigits = !/\d/.test(fio); + return words.length >= 2 && words.every(word => word.length >= 2) && hasNoDigits; + } + + function validateCity(city) { + return city.trim().length >= 2 && /^[а-яА-ЯёЁ\s-]+$/.test(city); + } + + function validateEmail(email) { + const localPart = email.split('@')[0]; + + if (/[а-яА-ЯёЁ]/.test(localPart)) { + showError('email', 'В тексте перед знаком "@" не должно быть кириллических символов'); + return false; + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + showError('email', 'Введите корректный email адрес'); + return false; + } + + hideError('email'); + return true; + } + + function validatePhone(phone) { + const phoneRegex = /^(\+7|8)[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/; + return phoneRegex.test(phone.replace(/\s/g, '')); + } + + function validatePassword(password) { + return password.length >= 6; + } + + function showError(fieldId, message) { + $('#' + fieldId).addClass('error'); + $('#' + fieldId + '-error').text(message).show(); + } + + function hideError(fieldId) { + $('#' + fieldId).removeClass('error'); + $('#' + fieldId + '-error').hide(); + } + + function validateForm() { + let isValid = true; + + const fio = $('#fio').val(); + if (!validateFIO(fio)) { + if (/\d/.test(fio)) { + showError('fio', 'ФИО не должно содержать цифры'); + } else { + showError('fio', 'ФИО должно содержать минимум 2 слова (каждое от 2 символов)'); + } + isValid = false; + } else { + hideError('fio'); + } + + const city = $('#city').val(); + if (!validateCity(city)) { + showError('city', 'Укажите корректное название города (только русские буквы)'); + isValid = false; + } else { + hideError('city'); + } + + const email = $('#email').val(); + if (!validateEmail(email)) { + showError('email', 'Введите корректный email адрес'); + isValid = false; + } else { + hideError('email'); + } + + const phone = $('#phone').val(); + if (!validatePhone(phone)) { + showError('phone', 'Введите номер в формате: +7(912)999-12-23'); + isValid = false; + } else { + hideError('phone'); + } + + const password = $('#password').val(); + if (!validatePassword(password)) { + showError('password', 'Пароль должен содержать минимум 6 символов'); + isValid = false; + } else { + hideError('password'); + } + + const confirmPassword = $('#confirm-password').val(); + if (password !== confirmPassword) { + showError('confirm-password', 'Пароли не совпадают'); + isValid = false; + } else { + hideError('confirm-password'); + } + + if (!$('#privacy').is(':checked')) { + $('#privacy-error').show(); + isValid = false; + } else { + $('#privacy-error').hide(); + } + + return isValid; + } + + $('input').on('blur', function() { + const fieldId = $(this).attr('id'); + const value = $(this).val(); + + switch(fieldId) { + case 'fio': + if (!validateFIO(value)) { + if (/\d/.test(value)) { + showError(fieldId, 'ФИО не должно содержать цифры'); + } else { + showError(fieldId, 'ФИО должно содержать минимум 2 слова'); + } + } else { + hideError(fieldId); + } + break; + case 'city': + if (!validateCity(value)) { + showError(fieldId, 'Укажите корректное название города'); + } else { + hideError(fieldId); + } + break; + case 'email': + if (!validateEmail(value)) { + showError(fieldId, 'Введите корректный email адрес'); + } else { + hideError(fieldId); + } + break; + case 'phone': + if (!validatePhone(value)) { + showError(fieldId, 'Введите номер в формате: +7(912)999-12-23'); + } else { + hideError(fieldId); + } + break; + case 'password': + if (!validatePassword(value)) { + showError(fieldId, 'Пароль должен содержать минимум 6 символов'); + } else { + hideError(fieldId); + } + break; + case 'confirm-password': + const password = $('#password').val(); + if (value !== password) { + showError(fieldId, 'Пароли не совпадают'); + } else { + hideError(fieldId); + } + break; + } + }); + + $('#registrationForm').on('submit', function(e) { + e.preventDefault(); + + if (validateForm()) { + const email = $('#email').val(); + const isAdmin = isAdminEmail(email); + + const userData = { + email: email, + fio: $('#fio').val(), + phone: $('#phone').val(), + isAdmin: isAdmin, + registered: new Date().toISOString() + }; + + localStorage.setItem('userData', JSON.stringify(userData)); + localStorage.setItem('isLoggedIn', 'true'); + localStorage.setItem('isAdmin', isAdmin.toString()); + + showMessage('success', 'Регистрация прошла успешно! ' + + (isAdmin ? 'Вы зарегистрированы как администратор.' : 'Добро пожаловать в AETERNA!')); + + setTimeout(() => { + + window.location.href = 'cite_mebel.php'; + }, 2000); + } else { + showMessage('error', 'Пожалуйста, исправьте ошибки в форме'); + } + }); + + $('a[href^="#"]').on('click', function(event) { + var target = $(this.getAttribute('href')); + if (target.length) { + event.preventDefault(); + $('html, body').stop().animate({ + scrollTop: target.offset().top + }, 1000); + } + }); + + $('.login-btn').on('click', function() { + showMessage('warning', 'Переход к форме входа...'); + setTimeout(() => { + window.location.href = 'login.php'; + }, 1000); + }); + + $('.password-link').on('click', function(e) { + e.preventDefault(); + showMessage('warning', 'Функция смены пароля будет доступна после регистрации'); + }); + + $('#phone').on('input', function() { + let value = $(this).val().replace(/\D/g, ''); + if (value.startsWith('7') || value.startsWith('8')) { + value = value.substring(1); + } + if (value.length > 0) { + value = '+7(' + value; + if (value.length > 6) value = value.substring(0, 6) + ')' + value.substring(6); + if (value.length > 10) value = value.substring(0, 10) + '-' + value.substring(10); + if (value.length > 13) value = value.substring(0, 13) + '-' + value.substring(13); + if (value.length > 16) value = value.substring(0, 16); + } + $(this).val(value); + }); + + $('#fio').on('input', function() { + let value = $(this).val(); + + value = value.replace(/\d/g, ''); + $(this).val(value); + }); +}); + +$(document).ready(function() { + + if (localStorage.getItem('isLoggedIn') === 'true') { + const userData = JSON.parse(localStorage.getItem('userData') || '{}'); + if (userData.email) { + $('#login-email').val(userData.email); + } + } + + function checkAdminPassword(email, password) { + + const adminAccounts = { + 'admin@aeterna.ru': 'admin123', + 'administrator@aeterna.ru': 'admin123', + 'aeterna@mail.ru': 'admin123' + }; + + return adminAccounts[email.toLowerCase()] === password; + } + + $('#loginForm').on('submit', function(e) { + e.preventDefault(); + + let isValid = true; + const email = $('#login-email').val(); + const password = $('#login-password').val(); + + if (!isValidEmail(email)) { + $('#email-error').show(); + isValid = false; + } else { + $('#email-error').hide(); + } + + if (password.length < 6) { + $('#password-error').show(); + isValid = false; + } else { + $('#password-error').hide(); + } + + if (isValid) { + + showMessage('success', 'Вы успешно вошли в систему!'); + + setTimeout(function() { + window.location.href = 'cite_mebel.php'; + }, 1500); + } + }); + + function showMessage(type, text) { + const messageId = type + 'Message'; + const $message = $('#' + messageId); + + $message.text(text).show(); + + setTimeout(function() { + $message.fadeOut(); + }, 3000); + } + + $('input').on('focus', function() { + $(this).next('.error-message').hide(); + }); + + $('#remember').on('change', function() { + if ($(this).is(':checked')) { + const email = $('#login-email').val(); + if (email) { + localStorage.setItem('rememberedEmail', email); + } + } else { + localStorage.removeItem('rememberedEmail'); + } + }); + + const rememberedEmail = localStorage.getItem('rememberedEmail'); + if (rememberedEmail) { + $('#login-email').val(rememberedEmail); + $('#remember').prop('checked', true); + } + + $('.forgot-password').on('click', function(e) { + e.preventDefault(); + showMessage('info', 'Для восстановления пароля обратитесь к администратору'); + }); +}); diff --git a/public/assets/less/checkout.less b/public/assets/less/checkout.less new file mode 100644 index 0000000..a6fe073 --- /dev/null +++ b/public/assets/less/checkout.less @@ -0,0 +1,137 @@ +.error-message { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; +} + +.form__input.error { + border-color: #ff0000; +} + +.form__group { + position: relative; + margin-bottom: 15px; +} + +.page-messages { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + width: 90%; + max-width: 500px; +} + +.message { + padding: 15px; + margin: 10px 0; + border-radius: 5px; + text-align: center; + font-weight: bold; + display: none; +} + +.message.error { + background-color: #ffebee; + color: #c62828; + border: 1px solid #ffcdd2; +} + +.message.success { + background-color: #e8f5e9; + color: #453227; + border: 1px solid #c8e6c9; +} + +.message.warning { + background-color: #fff3e0; + color: #ef6c00; + border: 1px solid #ffe0b2; +} + +.privacy-error { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; + text-align: center; +} + +.input-group { + position: relative; + margin-bottom: 20px; +} + +.profile-form input.error { + border-color: #ff0000; + background-color: #fff5f5; +} + +.privacy-checkbox { + margin: 20px 0; + text-align: center; +} + +.privacy-checkbox label { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; + font-size: 14px; +} + +.privacy-checkbox input[type="checkbox"] { + margin: 0; +} + +.profile-page-main { + padding: 40px 0; + min-height: calc(100vh - 200px); +} + +.profile-container { + margin: 0 auto; + position: relative; + z-index: 1; +} + +.input-hint { + font-size: 12px; + color: #666; + margin-top: 5px; +} + +.form-options { + display: flex; + justify-content: space-between; + align-items: center; + margin: 20px 0; +} + +.remember-me { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #453227; +} + +.remember-me input[type="checkbox"] { + width: 16px; + height: 16px; + cursor: pointer; +} + +.forgot-password { + font-size: 14px; + color: #453227; + text-decoration: underline; +} + +.forgot-password:hover { + color: #617365; + text-decoration: none; +} \ No newline at end of file diff --git a/public/assets/less/mixins.less b/public/assets/less/mixins.less new file mode 100644 index 0000000..0e1f70b --- /dev/null +++ b/public/assets/less/mixins.less @@ -0,0 +1,82 @@ +@color-primary: #617365; +@color-secondary: #D1D1D1; +@color-accent: #453227; +@color-text-dark: #333; +@color-text-light: #fff; +@color-button: @color-accent; +@color-beige: #A2A09A; + +@font-logo: 'Anek Kannada', sans-serif; +@font-main: 'Anonymous Pro', monospace; +@font-heading: 'Playfair Display', serif; + +@shadow-light: 0 5px 15px rgba(0, 0, 0, 0.2); +@shadow-dark: 2px 2px 4px rgba(0, 0, 0, 0.3); + +.flex-center(@gap: 0) { + display: flex; + align-items: center; + justify-content: center; + gap: @gap; +} + +.flex-between() { + display: flex; + align-items: center; + justify-content: space-between; +} + +.flex-column() { + display: flex; + flex-direction: column; +} + +.icon-base(@size: 18px, @hover-scale: 1.1) { + cursor: pointer; + transition: all 0.3s ease; + font-size: @size; + &:hover { + transform: scale(@hover-scale); + } +} + +.image-overlay() { + position: absolute; + inset: 0; + .flex-center(15px); + flex-direction: column; + text-align: center; + background-color: rgba(0, 0, 0, 0.4); + padding: 20px; + color: @color-text-light; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7); + transition: all 0.3s ease; +} + +.menu-base() { + position: absolute; + top: 100%; + left: 0; + width: 250px; + background: white; + border-radius: 8px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); + padding: 15px; + z-index: 1000; + margin-top: 5px; + display: none; +} + +.input-base() { + width: 100%; + padding: 12px 15px; + border: 1px solid #ccc; + background-color: #fff; + font-family: @font-main; + font-size: 14px; + outline: none; + transition: border-color 0.3s ease; + &:focus { + border-color: @color-primary; + } +} diff --git a/public/assets/less/style.less b/public/assets/less/style.less new file mode 100644 index 0000000..9e57559 --- /dev/null +++ b/public/assets/less/style.less @@ -0,0 +1,3387 @@ +@import "mixins.less"; +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + height: 100%; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: @font-main; + background-color: @color-secondary; + color: @color-text-dark; + line-height: 1.6; + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.container { + max-width: 1210px; + margin: 0 auto; + padding: 0 20px; +} + +ul { + list-style: none; +} + +a { + text-decoration: none; + color: inherit; + transition: all 0.3s ease; +} + +h1, h2, h3, h4, h5, h6 { + font-family: @font-heading; + margin: 0; +} + +p, li, span { + font-family: @font-main; +} + +// ======================= +// === КОМПОНЕНТЫ === +// ======================= + +.logo, .footer-logo { + font: bold 32px/1 @font-logo; + letter-spacing: 2px; + text-shadow: @shadow-dark; + flex-shrink: 0; +} + +.btn { + padding: 12px 30px; + border: none; + cursor: pointer; + font-size: 14px; + text-transform: uppercase; + transition: all 0.3s ease; + font-family: @font-main; + + &.primary-btn { + background-color: @color-button; + color: @color-text-light; + + &:hover { + background-color: lighten(@color-button, 10%); + transform: translateY(-2px); + box-shadow: @shadow-light; + } + } +} + +.number-circle { + .flex-center(); + width: 28px; + height: 28px; + border-radius: 50%; + background-color: @color-button; + color: @color-text-light; + font-size: 16px; + font-weight: bold; + flex-shrink: 0; +} + +.breadcrumbs { + font-size: 14px; + margin-bottom: 20px; + color: #666; + + a { + color: #666; + opacity: 0.7; + &:hover { opacity: 1; } + } + + .current-page { + font-weight: bold; + color: @color-text-dark; + } +} + +// ======================= +// === ШАПКА САЙТА === +// ======================= +.header { + background-color: @color-secondary; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + z-index: 1000; + + &__top, &__bottom { + padding: 15px 0; + .container { + .flex-between(); + gap: 20px; + } + } + + &__bottom { + padding: 10px 0; + border-top: 1px solid rgba(0, 0, 0, 0.05); + + .catalog-link.active-catalog { + background-color: rgba(0, 0, 0, 0.08); + pointer-events: none; + } + } + + .search-catalog { + .flex-center(); + border: 2px solid @color-text-dark; + background-color: #fff; + max-width: 600px; + width: 100%; + margin: 0 auto; + overflow: hidden; + + .catalog-dropdown { + position: relative; + background-color: #f8f8f8; + padding: 10px 15px 10px 25px; + font-size: 18px; + cursor: pointer; + border-right: 1px solid @color-text-dark; + .flex-center(10px); + width: 200px; + flex-shrink: 0; + + &__menu { + .menu-base(); + li { + padding: 8px 0; + cursor: pointer; + transition: color 0.3s; + border-bottom: 1px solid #f0f0f0; + &:last-child { border-bottom: none; } + &:hover { color: @color-accent; } + } + } + &:hover &__menu { display: block; } + } + + .search-box { + .flex-center(); + padding: 0 15px; + flex-grow: 1; + position: relative; + font-size: 15px; + + input { + border: none; + padding: 10px 30px 10px 0; + outline: none; + font-size: 16px; + width: 100%; + text-align: left; + } + + .search-icon { + font-size: 20px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + } + }} + + &__icons--top { + .flex-center(15px); + flex-shrink: 0; + .icon { .icon-base(); font-size: 20px;} + } + + .nav-list { + .flex-center(30px); + font-size: 18px; + a { + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); + &:hover { text-shadow: @shadow-dark; } + &.active { + border-bottom: 2px solid @color-button; + padding-bottom: 5px; + text-shadow: @shadow-dark; + } + &[href="#footer"] { + cursor: pointer; + } + } + } + + .catalog-link { + .flex-center(10px); + border-radius: 4px; + white-space: nowrap; + font-size: 18px; + padding: 10px 18px; + &:hover { background-color: rgba(0, 0, 0, 0.05); } + } + + .header-phone { + font-weight: bold; + color: @color-button; + flex-shrink: 0; + } +} + +// ======================= +// === ОСНОВНЫЕ СЕКЦИИ === +// ======================= +.hero { + padding: 15px 0; + + &__content { + .flex-center(50px); + min-height: 60vh; + align-items: center; + } + + &__image-block { + position: relative; + flex: 0 0 40%; + max-width: 600px; + height: 600px; + .flex-center(); + + .hero__circle { + position: absolute; + width: 450px; + height: 450px; + background-color: @color-primary; + border-radius: 50%; + z-index: 1; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + .hero__img { + position: relative; + width: 100%; + height: 100%; + object-fit: contain; + z-index: 2; + } + } + + &__text-block { + flex: 0 0 60%; + padding-left: 50px; + + h1 { + font-size: 42px; + font-weight: normal; + margin-bottom: 25px; + line-height: 1.3; + } + + .hero__usp-text { + position: relative; + padding-left: 50px; + margin-bottom: 35px; + line-height: 1.7; + .flex-center(); + justify-content: flex-start; + min-height: 40px; + font-size: 16px; + + &::before { + content: "✓"; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 32px; + height: 32px; + border: 2px solid @color-button; + background-color: transparent; + color: @color-button; + border-radius: 50%; + .flex-center(); + font-size: 16px; + font-weight: bold; + } + } + + .btn.primary-btn { + margin: 25px 0 0 50px; + padding: 14px 35px; + font-size: 15px; + } + } +} + +.advantages { + padding: 30px 0 40px; + + &__header { + display: flex; + align-items: center; + gap: 50px; + margin-bottom: 40px; + h2 { + font-size: 32px; + font-weight: normal; + flex: 0 0 30%; + } + } + + &__items { + flex: 0 0 70%; + display: flex; + gap: 30px; + } + + .advantage-item { + flex: 1; + text-align: left; + position: relative; + padding-top: 30px; + + &__number { + .number-circle(); + position: absolute; + top: 0; + left: 0; + } + + h4 { + font-weight: 600; + margin-bottom: 10px; + } + } +} + +.promo-images { + display: flex; + gap: 20px; + margin-top: 50px; + + .promo-image-col { + position: relative; + overflow: hidden; + border-radius: 8px; + flex: 1; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-5px); + box-shadow: @shadow-light; + .image-overlay-text { background-color: rgba(0, 0, 0, 0.6); } + img { transform: scale(1.05); } + .image-overlay-text h4, + .image-overlay-text .overlay-link { transform: translateY(0); } + } + + img { + width: 100%; + height: 350px; + object-fit: cover; + display: block; + transition: transform 0.5s ease; + } + + .image-overlay-text { + .image-overlay(); + h4 { + font-size: 24px; + text-transform: uppercase; + line-height: 1.2; + margin-bottom: 15px; + transform: translateY(20px); + transition: transform 0.3s ease; + } + } + + .overlay-link { + display: inline-block; + text-transform: uppercase; + font-weight: bold; + border-radius: 3px; + margin-top: 15px; + padding: 10px 25px; + background-color: @color-button; + color: @color-text-light; + font-size: 12px; + transform: translateY(20px); + transition: all 0.3s ease; + + &:hover { + background-color: lighten(@color-button, 10%); + transform: translateY(-2px); + box-shadow: @shadow-light; + } + } + } +} + +.about { + padding: 40px 0 80px; + + &__content { + display: flex; + align-items: flex-start; + gap: 50px; + } + + &__column { + display: flex; + flex-direction: column; + gap: 20px; + &--left { flex: 0 0 40%; margin-bottom: 30px; } + &--right { + flex: 0 0 60%; + .about__caption { + padding-right: 50px; + } + } + } + + &__text-block { + margin-bottom: 30px; + h2 { margin-bottom: 15px; } + } + + &__img { + width: 93%; + object-fit: cover; + display: block; + &--small { height: 300px; } + &--large { height: 450px; } + } + + .text-justified { + text-align: justify; + color: #555; + } +} + +.solutions { + padding: 0; + background-color: @color-secondary; + + &-slider { + position: relative; + width: 100%; + max-width: 1200px; + margin: 40px auto; + border-radius: 8px; + overflow: hidden; + + &__slides { + display: flex; + width: 200%; + height: 100%; + animation: slideLeftRight 10s infinite ease-in-out; + } + + &__slide { + width: 50%; + flex-shrink: 0; + position: relative; + overflow: hidden; + transition: transform 0.5s ease, box-shadow 0.5s ease; + + &:hover { + transform: scale(1.02); + box-shadow: 0 10px 25px rgba(0,0,0,0.3); + .solution-img { + transform: scale(1.05); + filter: brightness(0.8); + } + .solution-text-overlay { + opacity: 1; + transform: translateY(-5px); + } + .solution-image-link { + transform: translateX(-50%) translateY(-6px); + background-color: rgba(255,255,255,0.9); + color: @color-text-dark; + } + } + } + + .solution-img { + width: 100%; + height: auto; + object-fit: cover; + display: block; + } + + .solution-text-overlay { + position: absolute; + top: 15%; + left: 8%; + color: #493131; + text-shadow: 2px 2px 4px rgba(0,0,0,0.6); + z-index: 2; + opacity: 0.9; + transition: opacity 0.5s ease, transform 0.5s ease; + h2 { + font-size: 35px; + text-transform: uppercase; + margin-bottom: 10px; + } + p { + font-size: 25px; + text-transform: uppercase; + } + } + + .solution-image-link { + position: absolute; + bottom: 40px; + left: 50%; + transform: translateX(-50%); + padding: 12px 30px; + border: 2px solid @color-text-light; + color: #493131; + text-transform: uppercase; + font-size: 16px; + font-weight: bold; + background: transparent; + transition: 0.4s ease; + z-index: 2; + &:hover { + background: @color-text-light; + color: @color-text-dark; + transform: translateX(-50%) translateY(-2px); + } + } + } +} + +@keyframes slideLeftRight { + 0%, 40% { transform: translateX(0); } + 50%, 90% { transform: translateX(-50%); } + 100% { transform: translateX(0); } +} + +.stats { + padding: 0; + margin-top: 20px; + + .container { + display: flex; + justify-content: flex-end; + } + + &__items { + display: flex; + gap: 20px; + .stat-item { + text-align: left; + .stat-number { + font-size: 36px; + font-weight: bold; + color: @color-text-dark; + margin-bottom: 5px; + } + .stat-label { color: @color-text-dark; } + } + } +} + +.faq { + padding: 50px 0; + + h2 { + text-align: left; + font-size: 32px; + font-weight: normal; + margin-bottom: 40px; + } + + &__items { + display: flex; + flex-wrap: wrap; + gap: 40px 60px; + margin-bottom: 40px; + } + + .faq-item { + flex: 0 0 calc(50% - 30px); + .flex-center(15px); + align-items: flex-start; + &__content h4 { + font-weight: 600; + margin-bottom: 10px; + } + } + + .btn.primary-btn { + display: block; + width: 100%; + margin: 20px auto 80px; + } +} + +// ======================= +// === СТИЛИ КАТАЛОГА === +// ======================= +.catalog-main { + padding: 30px 0 60px; + background-color: lighten(@color-secondary, 5%); +} + +.catalog-wrapper { + display: flex; + gap: 20px; +} + +.catalog-sidebar { + flex: 0 0 250px; + background-color: #fff; + padding: 20px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); + height: fit-content; +} + +.filter-group { + margin-bottom: 30px; +} + +.filter-title { + font-size: 16px; + font-weight: bold; + margin-bottom: 15px; + text-transform: uppercase; +} + +.filter-list li { + padding: 5px 0; + font-size: 16px; + a { + color: #555; + transition: color 0.2s; + &:hover { color: @color-accent; } + &.active-category { + font-weight: bold; + color: @color-primary; + } + } +} + +.price-range { + display: flex; + flex-direction: column; + gap: 15px; + width: 100%; + + .range-slider { + width: 100%; + + input[type="range"] { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 5px; + background: @color-primary; + border-radius: 5px; + outline: none; + margin: 0; + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + background: @color-accent; + border: 2px solid #fff; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + transition: all 0.3s ease; + + &:hover { + transform: scale(1.1); + background: lighten(@color-accent, 10%); + } + } + + &::-moz-range-thumb { + width: 20px; + height: 20px; + background: @color-accent; + border: 2px solid #fff; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + transition: all 0.3s ease; + + &:hover { + transform: scale(1.1); + background: lighten(@color-accent, 10%); + } + } + } + } + + .price-display { + font-size: 14px; + font-weight: bold; + text-align: center; + color: @color-text-dark; + padding: 10px; + background: #f8f8f8; + border-radius: 4px; + } +} + +.filter-options { + list-style: none; + li { + display: flex; + align-items: center; + padding: 4px 0; + font-size: 14px; + } + label { + margin-left: 10px; + cursor: pointer; + color: #555; + } + input[type="checkbox"] { + width: 15px; + height: 15px; + cursor: pointer; + accent-color: @color-primary; + &:checked + label { + font-weight: bold; + color: @color-primary; + } + } +} + +.filter-apply-btn { + width: 100%; + margin-top: 20px; +} + +.catalog-products { + flex-grow: 1; +} + +.products-container { + display: flex; + flex-wrap: wrap; + gap: 20px; +} + +.product-card { + background-color: #fff; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: column; + position: relative; + transition: transform 0.3s ease; + box-sizing: border-box; + + &:hover { + transform: translateY(-5px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); + .product-img { transform: scale(1.05); } + } +} + +.product-image-container { + position: relative; + overflow: hidden; + margin-bottom: 0; + padding: 0; + height: 250px; + .flex-center(); +} + +.product-img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; + transition: transform 0.3s ease; + margin: 0; +} + +.product-img1 { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: transform 0.3s ease; + margin: 0; +} + +.product-discount { + position: absolute; + top: 10px; + right: 10px; + background-color: @color-button; + color: @color-text-light; + padding: 3px 8px; + font-size: 12px; + font-weight: bold; + z-index: 10; +} + +.product-wishlist-icon { + position: absolute; + top: 10px; + left: 10px; + color: #333; + font-size: 18px; + cursor: pointer; + transition: color 0.3s ease; + z-index: 10; + &:hover { color: @color-accent; } +} + +.product-name { + font-size: 16px; + font-weight: bold; + margin-bottom: 5px; +} + +.product-details { + font-size: 13px; + color: #777; + margin-bottom: 10px; + flex-grow: 1; +} + +.product-price { + font-size: 18px; + font-weight: bold; + color: @color-button; +} + +.product-card.small { flex: 0 0 300px; max-width: 300px; height: 200px; } +.product-card.small1 { flex: 0 0 320px; max-width: 320px; height: 250px;width: 320px; } +.product-card.large { flex: 0 0 580px; max-width: 580px; height: 380px; } +.product-card.wide { flex: 0 0 240px; max-width: 240px; height: 250px; } +.product-card.wide1 { flex: 0 0 350px; max-width: 350px; height: 250px; } +.product-card.wide2 { flex: 0 0 560px; max-width: 560px; height: 260px; } +.product-card.wide2_1 { flex: 0 0 560px; max-width: 560px; height: 260px; margin: -280px 0 0; } +.product-card.wide3 { + flex: 0 0 320px; max-width: 320px; height: 540px; + .product-image-container { height: 580px; } +} +.product-card.wide4 { + flex: 0 0 545px; max-width: 545px; margin: -270px 0 0; height: 250px; + .product-image-container { padding: 0; justify-content: flex-start; } + .product-img { margin-left: 0; align-self: flex-start; object-position: left center; } +} +.product-card.tall { flex: 0 0 300px; max-width: 300px; margin: -180px 0 0; height: 430px; } +.product-card.full-width { flex: 0 0 100%; margin: -20px 0 0; max-width: 900px; height: 300px;} + +.product-card.full-width { + flex: 0 0 100%; + max-width: 100%; + height: 300px; + + .product-image-container { + height: 100%; + padding: 0; + margin: 0; + + .product-img1 { + width: 100%; + height: 100%; + object-fit: cover; + margin: 0; + padding: 0; + } + } +} + +.product-card.tall .product-image-container, +.product-card.large .product-image-container { height: 430px; } + +// ======================= +// === СТРАНИЦА ТОВАРА === +// ======================= +.product__section { + display: grid; + grid-template-columns: 600px 1fr; + gap: 40px; + margin: 40px 0; + padding: 20px 0; +} + +.product__gallery { + position: relative; +} + +.product__main-image { + width: 100%; + height: 600px; + background: #f8f9fa; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 20px; + border: 1px solid #e9ecef; + + img { + width: 100%; + height: 100%; + object-fit: contain; + padding: 20px; + transition: transform 0.3s ease; + + &:hover { + transform: scale(1.02); + } + } +} + +.product__thumbnails { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 12px; +} + +.product__thumbnail { + height: 120px; + background: #f8f9fa; + border: 2px solid #e9ecef; + border-radius: 8px; + overflow: hidden; + cursor: pointer; + padding: 0; + transition: all 0.3s ease; + + &:hover { + border-color: #453227; + box-shadow: 0 2px 8px rgba(69, 50, 39, 0.2); + } + + &.active { + border-color: #453227; + box-shadow: 0 0 0 2px rgba(69, 50, 39, 0.1); + } + + img { + width: 100%; + height: 100%; + object-fit: cover; + } +} + +.product__info { + padding: 0; + + h1 { + font-size: 32px; + font-weight: 600; + color: #212529; + margin-bottom: 20px; + line-height: 1.3; + } +} + +.product__rating { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 25px; + padding-bottom: 25px; + border-bottom: 1px solid #e9ecef; + + .stars { + display: flex; + gap: 4px; + + .star { + font-size: 18px; + color: #ffc107; + + &.filled { + color: #ffc107; + } + } + } + + .rating-value { + font-weight: 600; + font-size: 16px; + color: #212529; + } + + .reviews-count { + color: #6c757d; + font-size: 14px; + } +} + +.product__price { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 25px; + flex-wrap: wrap; + + .current-price { + font-size: 36px; + font-weight: 700; + color: #453227; + } + + .old-price { + font-size: 24px; + color: #6c757d; + text-decoration: line-through; + } + + .discount-badge { + background: #dc3545; + color: white; + padding: 6px 12px; + border-radius: 6px; + font-size: 14px; + font-weight: 600; + } +} + +.product__color-selector { + display: flex; + gap: 10px; + margin-bottom: 40px; +} + +.product__color-option { + width: 45px; + height: 45px; + border-radius: 50%; + border: 2px solid transparent; + cursor: pointer; + transition: transform 0.3s ease; + + &:hover{ + transform: translateY(-2px); + } +} + +.product__color-option.active { + border-color: @color-primary; +} + +.product__description { + margin-bottom: 65px; + line-height: 1.5; +} + +.product__details-link { + display: inline-block; + margin-bottom: 20px; + color: @color-primary; + font-weight: bold; +} + +.product__purchase { + display: flex; + justify-content: space-between; + margin-bottom: 35px; +} + +.product__price { + font-size: 24px; + font-weight: bold; +} + +.product__quantity { + display: flex; + align-items: center; + gap: 10px; +} + +.product__qty-btn { + width: 30px; + height: 30px; + background: @color-button; + color: @color-text-light; + border: none; + border-radius: 50%; + cursor: pointer; + font-weight: bold; + transition: all 0.3s ease; + + &:hover { + background: lighten(@color-button, 10%); + transform: scale(1.1); + } +} + +.product__qty-value { + font-weight: bold; + min-width: 30px; + text-align: center; +} + +.product__actions { + display: flex; + gap: 15px; +} + +.product__btn { + flex: 1; + padding: 12px 20px; + border: none; + border-radius: 4px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-2px); + box-shadow: @shadow-light; + } +} + +.product__btn.primary { + background: @color-button; + color: @color-text-light; + + &:hover { + background: lighten(@color-button, 10%); + } +} + +.product__btn.secondary { + background: transparent; + border: 1px solid @color-button; + color: @color-button; + + &:hover { + background: @color-button; + color: @color-text-light; + } +} + +.similar { + margin: 60px 0; +} + +.similar__title { + margin-bottom: 30px; + font-size: 28px; + font-weight: bold; +} + +.similar__grid { + display: flex; + gap: 25px; + flex-wrap: wrap; + justify-content: space-between; +} + +.similar__card { + flex: 0 0 calc(33.333% - 17px); + min-width: 320px; + background: @color-secondary; + border-radius: 12px; + overflow: hidden; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + + &:hover { + transform: translateY(-8px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); + } +} + +.similar__card-image { + height: 300px; + overflow: hidden; + background: white; + + img { + width: 100%; + height: 100%; + object-fit: contain; + transition: transform 0.3s ease; + + &:hover { + transform: scale(1.05); + } + } +} + +.similar__card-content { + padding: 25px; +} + +.similar__card-title { + font-weight: bold; + margin-bottom: 10px; + font-size: 20px; + color: @color-text-dark; +} + +.similar__card-description { + font-size: 15px; + margin-bottom: 15px; + color: #666; + line-height: 1.5; +} + +.similar__card-price { + font-weight: bold; + font-size: 22px; + color: @color-button; +} + +@media (max-width: 1024px) { + .similar { + &__card { + flex: 0 0 calc(50% - 13px); + min-width: 280px; + } + } +} + +@media (max-width: 768px) { + .similar { + &__grid { + justify-content: center; + } + + &__card { + flex: 0 0 100%; + max-width: 400px; + } + } +} + +// ======================= +// === КОРЗИНА И ЗАКАЗ === +// ======================= +.main__content { + display: flex; + gap: 40px; + margin: 30px 0; + + .products { + flex: 1; + } + + .order { + flex: 0 0 65%; + padding: 40px; + + &__header { + .flex-between(); + margin-bottom: 20px; + } + + &__title { + font-family: @font-logo; + font-size: 28px; + color: @color-text-dark; + margin: 0; + } + + &__total { + font-weight: bold; + color: @color-text-dark; + } + + &__section { + margin-bottom: 25px; + } + + &__section-title { + font-family: @font-logo; + margin-bottom: 15px; + font-size: 18px; + color: @color-text-dark; + } + } +} + +.products { + &__title { + font-family: @font-logo; + margin-bottom: 20px; + font-size: 24px; + color: @color-text-dark; + } + + &__list { + .flex-column(); + gap: 20px; + } + + &__item { + background-color: @color-secondary; + border-radius: 8px; + padding: 20px; + display: flex; + gap: 15px; + border: 1px solid @color-secondary; + transition: transform 0.3s ease; + align-items: flex-start; + position: relative; + + &:hover { + transform: translateY(-2px); + } + } + + &__image { + width: 300px; + height: 200px; + border-radius: 4px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: flex-start; + } + + .product-img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: transform 0.3s ease; + margin: 0; + } + + &__details { + flex: 1; + .flex-column(); + justify-content: space-between; + align-items: flex-start; + min-height: 200px; + } + + &__name { + font-weight: bold; + margin-bottom: 5px; + color: @color-accent; + font-size: 18px; + font-family: @font-main; + } + + &__price { + font-weight: bold; + font-size: 18px; + margin-bottom: 15px; + color: @color-text-dark; + } + + &__controls { + display: flex; + align-items: center; + gap: 15px; + margin-top: auto; + width: 100%; + justify-content: space-between; + } + + &__quantity { + display: flex; + align-items: center; + gap: 10px; + } + + &__qty-btn { + width: 30px; + height: 30px; + background-color: @color-text-dark; + color: @color-text-light; + border: none; + border-radius: 50%; + cursor: pointer; + .flex-center(); + font-family: @font-main; + font-weight: bold; + transition: all 0.3s ease; + + &:hover { + transform: scale(1.1); + } + } + + &__qty-value { + font-weight: bold; + min-width: 30px; + text-align: center; + font-size: 16px; + } + + &__cart-icon { + background-color: transparent; + color: @color-text-dark; + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid @color-text-dark; + margin-left: 20px; + + &:hover { + transform: scale(1.1); + } + + i { font-size: 18px; } + } +} + +.form { + &__group { margin-bottom: 15px; } + &__label { + display: block; + margin-bottom: 5px; + font-weight: bold; + color: #000000; + } + &__input { + width: 100%; + padding: 14px 16px; + border: 2px solid #ccc; + font-family: @font-main; + font-size: 15px; + transition: border-color 0.3s ease; + + &:focus { + border-color: @color-primary; + } + + &:hover { + border-color: darken(#ccc, 10%); + } + &::placeholder { + font-style: italic; + color: #999; + } + } + &__row { + display: flex; + gap: 20px; + justify-content: space-between; + } + &__input--half { + width: 100%; + } + &__radio-group { + display: flex; + justify-content: space-between; + margin-bottom: 20px; + margin-top: 20px; + } + &__radio-label { + display: flex; + align-items: center; + cursor: pointer; + color: @color-text-dark; + position: relative; + padding-left: 30px; + flex: 1; + + &:hover { + .form__custom-radio { + border-color: lighten(@color-accent, 10%); + } + } + } + &__radio-input { + position: absolute; + opacity: 0; + cursor: pointer; + } + &__custom-radio { + position: absolute; + left: 0; + height: 20px; + width: 20px; + background-color: @color-secondary; + border: 2px solid @color-accent; + border-radius: 50%; + transition: border-color 0.3s ease; + } + &__radio-input:checked ~ &__custom-radio { + background-color: @color-accent; + + &:after { + content: ""; + position: absolute; + display: block; + top: 4px; + left: 4px; + width: 8px; + height: 8px; + border-radius: 50%; + background: white; + } + } +} + +.divider { + height: 1px; + background-color: #999; + margin: 20px 0; +} + +.promo { + display: flex; + margin-bottom: 20px; + + &__input { + flex: 1; + padding: 10px; + border: 1px solid #000; + background-color: @color-secondary; + font-family: @font-main; + height: auto; + min-height: 48px; + + &:hover { + border-color: @color-primary; + } + + &::placeholder { + font-style: italic; + color: #999; + } + } + + &__btn { + background-color: @color-accent; + color: @color-secondary; + border: none; + padding: 10px 60px; + cursor: pointer; + font-family: @font-main; + font-size: 18px; + transition: all 0.3s ease; + + &:hover { + background-color: lighten(@color-accent, 10%); + transform: translateY(-2px); + } + } +} + +.summary { + margin-bottom: 20px; + + &__item { + display: flex; + justify-content: space-between; + margin-bottom: 10px; + color: @color-text-dark; + + &.total { + font-weight: bold; + font-size: 18px; + padding-top: 10px; + margin-top: 10px; + } + } +} + +.order-btn { + width: 100%; + background-color: @color-accent; + color: @color-secondary; + border: none; + padding: 15px; + border-radius: 4px; + font-size: 18px; + cursor: pointer; + margin-bottom: 10px; + font-family: @font-main; + transition: all 0.3s ease; + + &:hover { + background-color: lighten(@color-accent, 10%); + transform: translateY(-2px); + } +} + +.privacy { + display: flex; + gap: 8px; + font-size: 16px; + color: #666; + margin-bottom: 20px; + + input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; + } +} + +.services { + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; + gap: 24px; + margin-bottom: 24px; + + &__title { + font-family: @font-logo; + margin-bottom: 10px; + font-size: 18px; + color: @color-text-dark; + display: block; + width: 100%; + } + + &__item { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + color: @color-text-dark; + width: 100%; + } +} + +.cart-icon { + position: relative; +} + +.cart-count { + position: absolute; + top: -8px; + right: -8px; + background: @color-accent; + color: @color-text-light; + border-radius: 50%; + width: 18px; + height: 18px; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; +} + +.form__input.error { + border-color: #ff4444; + box-shadow: 0 0 5px rgba(255, 68, 68, 0.3); +} + +.empty-cart { + text-align: center; + padding: 40px; + color: #666; + font-size: 18px; +} + +// ======================= +// === АВТОРИЗАЦИЯ === +// ======================= +.profile-page-main { + .flex-center(); + min-height: 80vh; + padding: 40px 0; + background-color: lighten(@color-secondary, 5%); + z-index: 1; + + .profile-container { + display: flex; + width: 100%; + max-width: 1000px; + min-height: 600px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + background-color: @color-text-light; + } + + .profile-left-col { + flex: 0 0 35%; + background-color: @color-primary; + color: @color-text-light; + display: flex; + justify-content: flex-start; + align-items: flex-start; + padding: 40px; + .logo { + font-size: 32px; + font-weight: normal; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4); + color: @color-text-light; + } + } + + .profile-right-col { + flex: 0 0 65%; + .flex-center(); + padding: 40px; + .profile-form-block { + width: 100%; + max-width: 400px; + h2 { + font-size: 28px; + font-weight: normal; + margin-bottom: 40px; + text-align: left; + color: @color-text-dark; + } + } + } + + .profile-form { + .input-group { + margin-bottom: 20px; + label { + display: block; + font-size: 12px; + font-weight: bold; + color: @color-text-dark; + margin-bottom: 5px; + text-transform: uppercase; + } + } + + input[type="text"], + input[type="email"], + input[type="tel"] { + .input-base(); + } + + .password-link { + display: block; + text-align: left; + font-size: 13px; + color: @color-text-dark; + text-decoration: underline; + margin: 10px 0 20px; + &:hover { + color: @color-accent; + text-decoration: none; + } + } + + .save-btn { + padding: 15px 30px; + border: none; + cursor: pointer; + font-size: 15px; + text-transform: uppercase; + transition: all 0.3s ease; + font-family: @font-main; + width: 100%; + margin-top: 20px; + background-color: @color-primary; + color: @color-text-light; + + &:hover { + background-color: lighten(@color-primary, 10%); + transform: translateY(-2px); + box-shadow: @shadow-light; + } + } + + .auth-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 25px; + padding-top: 20px; + border-top: 1px solid #eee; + + .auth-text { + font-size: 13px; + color: @color-text-dark; + } + + .login-btn { + background-color: transparent; + color: @color-accent; + border: 1px solid @color-accent; + padding: 10px 25px; + font-size: 13px; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + background-color: @color-primary; + color: @color-text-light; + } + } + } + } +} + +// ======================= +// === СЕКЦИЯ УСЛУГ === +// ======================= +.services-section { + padding: 60px 0; + background-color: @color-secondary; +} + +.services__wrapper { + display: flex; + flex-direction: column; + gap: 30px; +} + +.services__top-row { + display: flex; + gap: 30px; + justify-content: center; + + @media (max-width: 768px) { + flex-direction: column; + align-items: center; + } +} + +.service-card { + border-radius: 8px; + padding: 40px; + min-height: 200px; + text-align: center; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-5px); + box-shadow: @shadow-light; + } + + &--green { + background: @color-primary; + color: @color-text-light; + flex: 1; + max-width: 450px; + } + + &--beige { + background: @color-beige; + color: @color-text-light; + width: 100%; + max-width: 930px; + margin: 0 auto; + } + + &__title { + font-family: @font-logo; + font-size: 24px; + font-weight: bold; + margin-bottom: 15px; + text-transform: uppercase; + } + + &__text { + font-family: @font-main; + font-size: 16px; + line-height: 1.6; + margin: 0; + } +} + +// ======================= +// === ФУТЕР === +// ======================= +.footer { + background-color: @color-primary; + color: black; + padding: 40px 0 10px; + position: relative; + z-index: 1000; + + &::before { + content: ''; + display: block; + position: absolute; + top: -80px; + left: 0; + width: 100%; + height: 1px; + visibility: hidden; + } + + &__content { + display: flex; + gap: 20px; + padding-bottom: 30px; + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + } + + &__col { + flex: 1; + &--logo { flex: 1.5; } + h5 { + margin-bottom: 15px; + font-size: 14px; + text-transform: uppercase; + } + ul li { + margin-bottom: 8px; + a:hover { text-decoration: underline; } + } + .social-icons, + .payment-icons { + .flex-center(15px); + justify-content: flex-start; + margin-top: 10px; + } + .social-icons .icon { + .icon-base(20px, 1.1); + color: black; + &:hover { color: @color-accent; } + } + .payment-icons .pay-icon { + .icon-base(24px, 1.05); + color: black; + } + } + + .copyright { + text-align: center; + font-size: 12px; + padding-top: 20px; + color: rgba(255, 255, 255, 0.6); + } +} + +// ======================= +// === ДОСТАВКА === +// ======================= +.delivery-content { + max-width: 1200px; + margin: 0 auto; + padding: 40px 20px; +} + +.delivery-content h1 { + font-family: @font-logo; + font-size: 42px; + text-align: center; + margin-bottom: 50px; + color: #453227; +} + +.delivery-section { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 30px; + margin-bottom: 60px; +} + +.delivery-card { + background: white; + padding: 30px; + border-radius: 12px; + box-shadow: 0 5px 15px rgba(0,0,0,0.1); + text-align: center; + transition: transform 0.3s ease; + flex: 1; + min-width: 350px; + max-width: 400px; +} + +.delivery-card:hover { + transform: translateY(-5px); +} + +.delivery-icon { + font-size: 48px; + color: #617365; + margin-bottom: 20px; +} + +.delivery-card h3 { + font-family: @font-logo; + font-size: 24px; + margin-bottom: 20px; + color: #453227; +} + +.delivery-details { + text-align: left; +} + +.detail-item { + display: flex; + justify-content: space-between; + margin-bottom: 12px; + padding-bottom: 12px; + border-bottom: 1px solid #f0f0f0; +} + +.detail-label { + font-weight: bold; + color: #333; +} + +.detail-value { + color: #617365; + text-align: right; +} + +// ======================= +// === ГАРАНТИЯ === +// ======================= +.warranty-content { + max-width: 1200px; + margin: 0 auto; + padding: 40px 20px; +} + +.warranty-content h1 { + font-family: 'Anek Kannada', sans-serif; + font-size: 42px; + text-align: center; + margin-bottom: 50px; + color: #453227; +} + +.warranty-overview { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 25px; + margin-bottom: 60px; +} + +.warranty-card { + background: white; + padding: 30px; + border-radius: 12px; + text-align: center; + box-shadow: 0 5px 15px rgba(0,0,0,0.1); + transition: transform 0.3s ease; + flex: 1; + min-width: 250px; + max-width: 280px; + display: flex; + flex-direction: column; + align-items: center; +} + +.warranty-card:hover { + transform: translateY(-5px); +} + +.warranty-icon { + font-size: 48px; + color: #617365; + margin-bottom: 20px; +} + +.warranty-card h3 { + font-family: 'Anek Kannada', sans-serif; + font-size: 20px; + margin-bottom: 15px; + color: #453227; +} + +.warranty-period { + font-size: 24px; + font-weight: bold; + color: #617365; + margin-top: auto; +} + +.coverage-section { + display: flex; + flex-wrap: wrap; + gap: 40px; + margin-bottom: 60px; +} + +.coverage-covered, +.coverage-not-covered { + flex: 1; + min-width: 300px; +} + +.coverage-section h2 { + font-family: 'Anek Kannada', sans-serif; + font-size: 24px; + margin-bottom: 25px; + color: #453227; +} + +.coverage-list { + display: flex; + flex-direction: column; + gap: 20px; +} + +.coverage-item { + display: flex; + align-items: flex-start; + gap: 15px; + padding: 20px; + border-radius: 8px; + background: white; + box-shadow: 0 3px 10px rgba(0,0,0,0.1); +} + +.coverage-item.covered i { + color: #28a745; + font-size: 20px; + margin-top: 2px; + flex-shrink: 0; +} + +.coverage-item.not-covered i { + color: #dc3545; + font-size: 20px; + margin-top: 2px; + flex-shrink: 0; +} + +.coverage-text { + flex: 1; +} + +.coverage-item h4 { + font-family: 'Anek Kannada', sans-serif; + font-size: 16px; + margin-bottom: 5px; + color: #333; +} + +.card { + min-height: 250px; + display: flex; + align-items: center; + justify-content: center; + color: @color-text-light; + text-align: center; + + &--green { + background: @color-primary; + flex: 0 1 450px; + max-width: 450px; + } + + &--beige { + background: @color-beige; + color: @color-text-dark; + flex: 0 1 925px; + max-width: 925px; + } +} + +.design-section { + display: flex; + justify-content: center; + margin-bottom: 40px; + .card { width: 100%; } +} + +// ======================= +// === АДАПТИВНОСТЬ === +// ======================= +@media (max-width: 1240px) { + .catalog-wrapper { gap: 20px; } + .catalog-sidebar { flex: 0 0 200px; } + .products-container { + gap: 15px; + display: flex; + flex-wrap: wrap; + } + + .product-card.small1 { + margin-top: 100px; + } + + .product-card.small, + .product-card.small1, + .product-card.large, + .product-card.wide, + .product-card.wide1, + .product-card.wide2, + .product-card.wide2_1, + .product-card.wide4 { + flex: 0 0 calc(33.333% - 10px); + max-width: calc(33.333% - 10px); + height: 180px; + margin: 0; + + .product-image-container { + height: 180px; + } + } + + .product-card.wide3 { + flex: 0 0 calc(25% - 10px); + max-width: calc(25% - 10px); + height: 300px; + margin: 0; + + .product-image-container { + height: 350px; + } + } + + .product-card.tall { + flex: 0 0 calc(25% - 10px); + max-width: calc(25% - 10px); + height: 300px; + margin: 0; + + .product-image-container { + height: 300px; + } + } + + .product-card.full-width { + flex: 0 0 100%; + max-width: 100%; + height: 300px; + margin: 0; + + .product-image-container { + height: 300px; + } + } + + .product-card.small { order: 1; } + .product-card.large { order: 2; } + .product-card.wide { order: 3; } + + .product-card.small1 { order: 11; } + .product-card.wide2 { order: 12; } + .product-card.wide2_1 { order: 13; } + + .product-card.wide3 { order: 21; } + .product-card.tall { order: 22; } + + .product-card.wide3 { order: 31; } + + .product-card.full-width { order: 41; flex-basis: 100%; } + + .main__content { + gap: 20px; + .products { + flex: 0 0 35%; + .products__image { + width: 250px; + height: 180px; + } + } + .order { + flex: 0 0 60%; + padding: 30px; + + .order__title { + font-size: 24px; + } + + .order__section-title { + font-size: 16px; + } + } + } + + .solutions-slider { + &__slide { + .solution-text-overlay { + top: 10%; + left: 5%; + h2 { + font-size: 26px; + margin-bottom: 5px; + line-height: 1.2; + } + p { + font-size: 18px; + line-height: 1.2; + } + } + .solution-image-link { + bottom: 70px; + padding: 10px 25px; + font-size: 14px; + } + } + } + + .product__image { + width: 350px; + height: 250px; + } + + .product__thumbnail img { + width: 170px; + height: 120px; + } +} + +@media (max-width: 1024px) { + .main__content { + gap: 25px; + .products { + flex: 0 0 30%; + .products__image { + width: 200px; + height: 150px; + } + + .products__name { + font-size: 16px; + } + + .products__price { + font-size: 16px; + } + } + .order { + flex: 0 0 60%; + padding: 25px; + + .order__title { + font-size: 22px; + } + + .form__input { + padding: 12px 14px; + font-size: 14px; + } + + .promo__btn { + padding: 10px 40px; + font-size: 16px; + } + } + } +} + +@media (max-width: 768px) { + .container { padding: 0 15px; } + + .delivery-section { + flex-direction: column; + align-items: center; + } + + .delivery-card { + min-width: 100%; + max-width: 100%; + } + + .delivery-content h1 { + font-size: 32px; + } + + .warranty-overview { + flex-direction: column; + align-items: center; + } + + .warranty-card { + max-width: 100%; + width: 100%; + } + + .coverage-section { + flex-direction: column; + } + + .warranty-content h1 { + font-size: 32px; + } + + .header__top .container, + .header__bottom .container, + .hero__content, + .advantages__header, + .about__content, + .advantages__items, + .promo-images, + .stats__items, + .faq__items, + .catalog-wrapper, + .main__content { + flex-direction: column; + gap: 30px; + } + + .search-catalog { + order: 3; + width: 100%; + max-width: 100%; + } + + .nav-list { + flex-wrap: wrap; + justify-content: center; + gap: 15px; + } + + .hero { + &__image-block { + flex: none; + max-width: 400px; + height: 400px; + } + &__circle { + width: 380px; + height: 380px; + } + &__text-block { + flex: none; + padding-left: 0; + text-align: center; + h1 { font-size: 32px; } + .hero__usp-text { + padding-left: 0; + justify-content: center; + &::before { display: none; } + } + .btn.primary-btn { margin-left: 0; } + } + } + + .advantages__header h2, + .faq h2 { font-size: 28px; } + + .faq-item, + .stat-item { + flex: none; + .flex-center(); + text-align: center; + } + + .stats .container { justify-content: center; } + .catalog-dropdown__menu { width: 200px; } + + .catalog-sidebar { width: 100%; flex: none; } + .products-container { gap: 15px; } + + .product-card.small, + .product-card.small1, + .product-card.large, + .product-card.wide, + .product-card.wide1, + .product-card.wide2, + .product-card.wide2_1, + .product-card.wide3, + .product-card.wide4, + .product-card.tall, + .product-card.full-width { + flex: 0 0 100%; + max-width: 100%; + height: 250px; + margin: 0; + + .product-image-container { + height: 200px; + } + } + + .main__content { + flex-direction: column; + gap: 20px; + + .products, + .order { + flex: 0 0 100%; + width: 100%; + } + + .products { + .products__item { + flex-direction: column; + text-align: center; + gap: 15px; + } + + .products__image { + width: 100%; + height: 200px; + justify-content: center; + } + + .products__details { + min-height: auto; + align-items: center; + } + + .products__controls { + justify-content: center; + margin-top: 15px; + } + + .products__cart-icon { + margin-left: 0; + } + } + + .order { + padding: 20px; + + .order__title { + font-size: 20px; + text-align: center; + } + + .order__total { + text-align: center; + } + + .form__radio-group { + flex-direction: column; + gap: 15px; + } + + .form__radio-label { + flex: none; + justify-content: flex-start; + } + + .promo { + flex-direction: column; + gap: 10px; + + &__btn { + width: 100%; + padding: 12px; + } + } + + .order-btn { + padding: 12px; + font-size: 16px; + } + + .services { + flex-direction: column; + align-items: center; + } + } + } + + .product-image-container { height: 200px; } + .product-card.tall .product-image-container, + .product-card.large .product-image-container { height: 250px; } + + .profile-page-main { + .profile-container { + flex-direction: column; + min-height: auto; + max-width: 100%; + box-shadow: none; + } + .profile-left-col { + flex: none; + width: 100%; + height: 100px; + .flex-center(); + padding: 0; + } + .profile-right-col { + flex: none; + width: 100%; + padding: 30px 20px; + } + .profile-form-block { max-width: 100%; } + } + + .form__row { flex-direction: column; } + .form__input--half { flex: 0 0 100%; max-width: 100%; } + .services { flex-direction: column; align-items: center; } + + .services-section { + padding: 40px 0; + } + + .service-card { + padding: 30px 20px; + min-height: 180px; + + &--green, + &--beige { + max-width: 100%; + } + + &__title { + font-size: 20px; + } + + &__text { + font-size: 14px; + } + } + .solutions-slider { + margin: 20px auto; + &__slide { + .solution-text-overlay { + top: 8%; + left: 4%; + h2 { + font-size: 20px; + margin-bottom: 3px; + line-height: 1.1; + } + p { + font-size: 15px; + line-height: 1.1; + } + } + .solution-image-link { + bottom: 90px; + padding: 8px 20px; + font-size: 13px; + } + } + } +} + +// Стили для ошибок полей +.error-input { + border-color: #ff4444 !important; + box-shadow: 0 0 0 1px #ff4444; +} + +.field-error { + color: #ff4444; + font-size: 12px; + margin-top: 5px; + margin-bottom: 10px; +} + +// Стили для сообщений +.message { + padding: 15px; + margin: 20px 0; + border-radius: 5px; + display: none; +} + +.message.error { + background-color: #ffebee; + color: #c62828; + border: 1px solid #ffcdd2; +} + +.message.success { + background-color: #e8f5e9; + color: #2e7d32; + border: 1px solid #c8e6c9; +} + +// Добавьте в конец файла +.access-denied { + text-align: center; + padding: 80px 20px; + background: white; + border-radius: 10px; + box-shadow: 0 5px 15px rgba(0,0,0,0.1); + margin: 50px 0; + + h2 { + color: #dc3545; + margin-bottom: 30px; + font-size: 28px; + } + + p { + color: #666; + margin-bottom: 40px; + font-size: 18px; + line-height: 1.6; + } + + .btn { + margin: 5px; + min-width: 200px; + } +} +// ======================= +// === ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ === +// ======================= + +.user-profile-dropdown { + position: relative; + display: inline-block; + + &__toggle { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 8px 12px; + border-radius: 4px; + transition: all 0.3s ease; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + } + + .user-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background-color: @color-primary; + color: @color-text-light; + .flex-center(); + font-weight: bold; + } + + .user-info { + display: flex; + flex-direction: column; + + .user-email { + font-size: 12px; + color: #666; + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .user-status { + font-size: 10px; + padding: 2px 6px; + border-radius: 10px; + text-transform: uppercase; + + &.admin { + background-color: #617365; + color: white; + } + + &.user { + background-color: #28a745; + color: white; + } + } + } + } + + &__menu { + .menu-base(); + width: 220px; + top: 100%; + right: 0; + + .user-details { + padding: 15px; + border-bottom: 1px solid #eee; + + .user-name { + font-weight: bold; + margin-bottom: 5px; + } + + .user-registered { + font-size: 11px; + color: #999; + } + } + + ul { + padding: 10px 0; + + li { + padding: 8px 15px; + cursor: pointer; + display: flex; + align-items: center; + gap: 10px; + transition: background-color 0.3s ease; + + &:hover { + background-color: #f5f5f5; + } + + &.logout { + color: #dc3545; + border-top: 1px solid #eee; + margin-top: 5px; + padding-top: 12px; + + &:hover { + background-color: #ffe6e6; + } + } + } + } + } + + &:hover &__menu { + display: block; + } +} + +// ======================= +// === КАРТОЧКА ТОВАРА === +// ======================= + +.product-image-container { + position: relative; + overflow: hidden; + margin-bottom: 0; + padding: 0; + height: 250px; + .flex-center(); + + &:hover { + .product-overlay-info { + opacity: 1; + transform: translateY(0); + } + } +} + +.product-overlay-info { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(to top, rgba(0,0,0,0.8), transparent); + color: white; + padding: 15px; + opacity: 0; + transform: translateY(10px); + transition: all 0.3s ease; + + .product-overlay-name { + font-weight: bold; + font-size: 14px; + margin-bottom: 5px; + } + + .product-overlay-price { + font-size: 16px; + font-weight: bold; + + .old-price { + text-decoration: line-through; + font-size: 12px; + color: #ccc; + margin-right: 5px; + } + + .current-price { + color: #ffd700; + } + } + + .product-overlay-category { + font-size: 11px; + opacity: 0.8; + margin-top: 3px; + } + + .product-overlay-stock { + font-size: 11px; + margin-top: 5px; + + &.out-of-stock { + color: #ff6b6b; + } + + i { + margin-right: 5px; + } + } +} + +.product-card-details { + padding: 15px; + background: white; + flex-grow: 1; + display: flex; + flex-direction: column; + + .product-card-name { + font-weight: bold; + font-size: 16px; + margin-bottom: 8px; + color: @color-text-dark; + } + + .product-card-description { + font-size: 13px; + color: #777; + margin-bottom: 10px; + flex-grow: 1; + } + + .product-card-attributes { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 10px; + + .attribute { + font-size: 11px; + background: #f5f5f5; + padding: 3px 8px; + border-radius: 12px; + color: #666; + + i { + margin-right: 3px; + } + } + } + + .product-card-price { + margin-bottom: 10px; + + .old-price { + text-decoration: line-through; + font-size: 14px; + color: #999; + margin-right: 8px; + } + + .current-price { + font-size: 18px; + font-weight: bold; + color: @color-button; + } + } + + .add-to-cart-btn { + width: 100%; + padding: 8px; + font-size: 14px; + } + + .admin-actions { + display: flex; + gap: 5px; + margin-top: 10px; + + .admin-btn { + flex: 1; + font-size: 12px; + padding: 6px; + + &.delete-btn { + background: #dc3545; + + &:hover { + background: #c82333; + } + } + } + } +} + +// ======================= +// === ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ === +// ======================= + +.user-profile-dropdown { + position: relative; + display: inline-block; + + .user-profile-toggle { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + padding: 8px 12px; + border-radius: 4px; + transition: all 0.3s ease; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + + .dropdown-arrow { + transform: rotate(180deg); + } + } + + .user-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, #617365 0%, #453227 100%); + color: @color-text-light; + .flex-center(); + font-weight: bold; + font-size: 16px; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + } + + .user-info { + display: flex; + flex-direction: column; + + .user-email { + font-size: 12px; + color: #666; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .user-status { + font-size: 10px; + padding: 2px 8px; + border-radius: 12px; + text-transform: uppercase; + font-weight: bold; + text-align: center; + margin-top: 2px; + + &.admin { + background-color: #617365; + color: white; + border: 1px solid #617365; + } + + &.user { + background-color: #28a745; + color: white; + border: 1px solid #28a745; + } + } + } + + .dropdown-arrow { + font-size: 10px; + color: #666; + transition: transform 0.3s ease; + } + } + + .user-profile-menu { + .menu-base(); + width: 280px; + top: 100%; + right: 0; + margin-top: 10px; + padding: 0; + overflow: hidden; + + .user-profile-header { + padding: 20px; + background: linear-gradient(135deg, #617365 0%, #453227 100%); + color: white; + + .user-profile-name { + font-weight: bold; + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 10px; + font-size: 16px; + } + + .user-profile-details { + small { + display: block; + opacity: 0.8; + margin-bottom: 5px; + font-size: 11px; + + i { + margin-right: 5px; + width: 14px; + text-align: center; + } + } + } + } + + .user-profile-links { + list-style: none; + padding: 10px 0; + + li { + border-bottom: 1px solid #f0f0f0; + + &:last-child { + border-bottom: none; + } + + a { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 20px; + color: #333; + transition: all 0.3s ease; + + &:hover { + background-color: #f8f9fa; + color: @color-primary; + text-decoration: none; + + i { + transform: scale(1.1); + } + } + + i { + width: 20px; + text-align: center; + font-size: 14px; + color: #617365; + transition: transform 0.3s ease; + } + + span { + flex-grow: 1; + } + } + } + + .logout-item { + border-top: 2px solid #f0f0f0; + margin-top: 5px; + + a { + color: #dc3545; + + &:hover { + background-color: #ffe6e6; + color: #c82333; + + i { + color: #dc3545; + } + } + } + } + } + } + + &:hover .user-profile-menu { + display: block; + animation: fadeIn 0.3s ease; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +// Для мобильных устройств +@media (max-width: 768px) { + .user-profile-dropdown { + .user-profile-toggle { + .user-info { + display: none; + } + + .dropdown-arrow { + display: none; + } + } + + .user-profile-menu { + width: 250px; + right: -50px; + } + } +} +// Добавьте в конец файла +.unavailable-product { + position: relative; + opacity: 0.6; + filter: grayscale(0.7); + + &::before { + content: "ТОВАР ЗАКОНЧИЛСЯ"; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.85); + color: white; + padding: 15px 25px; + border-radius: 5px; + font-weight: bold; + font-size: 16px; + text-align: center; + z-index: 100; + white-space: nowrap; + pointer-events: none; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + } + + .product-name-overlay { + .name, .price { + color: #999 !important; + text-shadow: none !important; + } + } + + .add-to-cart-btn { + display: none !important; + } + + &:hover { + transform: none !important; + cursor: not-allowed; + } +} + +.out-of-stock-badge { + position: absolute; + top: 10px; + left: 10px; + background: #6c757d; + color: white; + padding: 5px 10px; + border-radius: 4px; + font-size: 12px; + font-weight: bold; + z-index: 10; +} + +// Для админ-таблицы +.admin-table tr.unavailable { + background-color: #f8f9fa !important; + opacity: 0.7; + + td { + color: #999; + } +} + +.status-unavailable { + background-color: #6c757d !important; + color: white !important; +} + +// Похожие товары +.similar-products { + margin: 40px 0; + + h2 { + font-size: 24px; + margin-bottom: 20px; + color: #453227; + } +} + +.products-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 20px; +} + +.product-card { + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; + + &:hover { + transform: translateY(-5px); + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); + } +} + +.product-image { + width: 100%; + height: 250px; + overflow: hidden; + background: #f5f5f5; + + img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; + } + + &:hover img { + transform: scale(1.05); + } +} + +.product-info { + padding: 15px; + + h3 { + font-size: 16px; + margin: 0 0 10px; + color: #333; + min-height: 40px; + } + + .product-price { + font-size: 20px; + font-weight: bold; + color: #453227; + margin: 10px 0; + } + + .btn { + width: 100%; + padding: 10px; + text-align: center; + margin-top: 10px; + } +} + +// Адаптивность для страницы продукта +@media (max-width: 1200px) { + .product__section { + grid-template-columns: 500px 1fr; + gap: 30px; + } + + .product__main-image { + height: 500px; + } +} + +@media (max-width: 992px) { + .product__section { + grid-template-columns: 1fr; + gap: 30px; + } + + .product__main-image { + height: 450px; + } + + .product__thumbnails { + grid-template-columns: repeat(4, 1fr); + } +} + +@media (max-width: 576px) { + .product__main-image { + height: 350px; + } + + .product__thumbnails { + grid-template-columns: repeat(3, 1fr); + } + + .product__info h1 { + font-size: 24px; + } + + .product__price .current-price { + font-size: 28px; + } +} \ No newline at end of file diff --git a/catalog.php b/public/catalog.php similarity index 84% rename from catalog.php rename to public/catalog.php index f731d5f..75c9c09 100644 --- a/catalog.php +++ b/public/catalog.php @@ -1,1382 +1,1302 @@ -getConnection(); - -// Получаем параметры фильтрации -$category_id = $_GET['category'] ?? 0; -$search = $_GET['search'] ?? ''; -$min_price = $_GET['min_price'] ?? 0; -$max_price = $_GET['max_price'] ?? 1000000; -$colors = isset($_GET['colors']) ? (array)$_GET['colors'] : []; -$materials = isset($_GET['materials']) ? (array)$_GET['materials'] : []; -$show_all = isset($_GET['show_all']) && $_GET['show_all'] == '1'; - -// Проверяем уведомления -$success_message = $_GET['success'] ?? ''; -$error_message = $_GET['error'] ?? ''; - -try { - // Получаем информацию о пользователе - $user_id = $_SESSION['user_id'] ?? 0; - $userStmt = $db->prepare("SELECT * FROM users WHERE user_id = ?"); - $userStmt->execute([$user_id]); - $user = $userStmt->fetch(); - - $isAdmin = isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true; - $userEmail = $_SESSION['user_email'] ?? ''; - $fullName = $_SESSION['full_name'] ?? $userEmail; - $loginTime = $_SESSION['login_time'] ?? time(); - - // Получаем категории (ИСПРАВЛЕНО: убрано дублирование fetchAll) - try { - $categoriesStmt = $db->prepare(" - SELECT * FROM categories - WHERE is_active = TRUE - ORDER BY sort_order, name - "); - $categoriesStmt->execute(); - $categories = $categoriesStmt->fetchAll(); - } catch (PDOException $e) { - $categories = []; - error_log("Ошибка получения категорий: " . $e->getMessage()); - } - - // Получаем подкатегории для текущей категории (если выбрана) - $subcategories = []; - if ($category_id > 0) { - $subStmt = $db->prepare(" - SELECT * FROM subcategories - WHERE category_id = ? AND is_active = TRUE - ORDER BY sort_order, name - "); - $subStmt->execute([$category_id]); - $subcategories = $subStmt->fetchAll(); - } - - // Проверяем наличие столбцов color и material в таблице products - $checkColumns = $db->query(" - SELECT column_name - FROM information_schema.columns - WHERE table_name = 'products' - AND column_name IN ('color', 'material') - "); - $existingColumns = $checkColumns->fetchAll(PDO::FETCH_COLUMN); - - $hasColorColumn = in_array('color', $existingColumns); - $hasMaterialColumn = in_array('material', $existingColumns); - - // Получаем доступные цвета из базы данных (если столбец существует) - $availableColors = []; - if ($hasColorColumn) { - $colorsStmt = $db->query(" - SELECT DISTINCT color FROM products - WHERE color IS NOT NULL AND color != '' AND is_available = TRUE - ORDER BY color - "); - $availableColors = $colorsStmt->fetchAll(PDO::FETCH_COLUMN); - } - - // Получаем доступные материалы из базы данных (если столбец существует) - $availableMaterials = []; - if ($hasMaterialColumn) { - $materialsStmt = $db->query(" - SELECT DISTINCT material FROM products - WHERE material IS NOT NULL AND material != '' AND is_available = TRUE - ORDER BY material - "); - $availableMaterials = $materialsStmt->fetchAll(PDO::FETCH_COLUMN); - } - - // Получаем ВСЕ товары ИЗ БАЗЫ ДАННЫХ с фильтрами - $sql = "SELECT p.*, c.name as category_name - FROM products p - LEFT JOIN categories c ON p.category_id = c.category_id - WHERE 1=1"; - - $params = []; - $hasFilters = false; - - // Фильтрация по доступности (если не админ и не показываем все) - if (!$show_all && !$isAdmin) { - $sql .= " AND p.is_available = TRUE"; - } - - // Фильтрация по категории - if ($category_id > 0) { - $sql .= " AND p.category_id = ?"; - $params[] = $category_id; - $hasFilters = true; - } - - // Фильтрация по цене - if ($min_price > 0 || $max_price < 1000000) { - $sql .= " AND p.price BETWEEN ? AND ?"; - $params[] = $min_price; - $params[] = $max_price; - $hasFilters = true; - } - - // Фильтрация по цвету - if ($hasColorColumn && !empty($colors)) { - $placeholders = implode(',', array_fill(0, count($colors), '?')); - $sql .= " AND p.color IN ($placeholders)"; - $params = array_merge($params, $colors); - $hasFilters = true; - } - - // Фильтрация по материалу - if ($hasMaterialColumn && !empty($materials)) { - $placeholders = implode(',', array_fill(0, count($materials), '?')); - $sql .= " AND p.material IN ($placeholders)"; - $params = array_merge($params, $materials); - $hasFilters = true; - } - - // Поиск - if (!empty($search)) { - $sql .= " AND (p.name LIKE ? OR p.description LIKE ?)"; - $params[] = "%$search%"; - $params[] = "%$search%"; - $hasFilters = true; - } - - // Получаем товары из базы данных с фильтрами - $sql = "SELECT p.*, c.name as category_name - FROM products p - LEFT JOIN categories c ON p.category_id = c.category_id - WHERE 1=1"; - - $params = []; - - // Фильтрация по доступности - if (!$show_all && !$isAdmin) { - $sql .= " AND p.is_available = TRUE"; - } - - // Фильтрация по категории - if ($category_id > 0) { - $sql .= " AND p.category_id = ?"; - $params[] = $category_id; - } - - // Фильтрация по цене - if ($min_price > 0 || $max_price < 1000000) { - $sql .= " AND p.price BETWEEN ? AND ?"; - $params[] = $min_price; - $params[] = $max_price; - } - - // Поиск - if (!empty($search)) { - $sql .= " AND (p.name LIKE ? OR p.description LIKE ?)"; - $params[] = "%$search%"; - $params[] = "%$search%"; - } - - $sql .= " ORDER BY p.product_id ASC LIMIT 9"; // Ограничиваем 9 товаров - - $stmt = $db->prepare($sql); - $stmt->execute($params); - $filteredProducts = $stmt->fetchAll(); - - // Оригинальные размеры для первых 9 товаров (ID 1-9) - $originalSizes = [ - 1 => 'small', // Светильник MINNIGHT - 2 => 'large', // Диван MODERN (Кровать MODER) - 3 => 'tall align-right', // Торшер MARCIA - 4 => 'wide', // Светильник POLET - 5 => 'small1', // Стол NORD - 6 => 'wide2', // Диван ROYALTY - 7 => 'wide3', // Кресло MINIMAL - 8 => 'wide2_1', // Стол LONKI - 9 => 'full-width' // Диван HEMMINS - ]; - - // Классы для изображений - $imgClasses = ['small1', 'wide2', 'wide3', 'wide2_1']; - -} catch (PDOException $e) { - die("Ошибка базы данных: " . $e->getMessage()); -} -?> - - - - - - AETERNA - Каталог - - - - - - - -
- -
-
-
- - -
-
- Все категории - -
- -
- -
- - - 0 - - - -
-
-
- -
- -
-
- -
-
- - - -
- 'Категория успешно добавлена!', - 'category_updated' => 'Категория успешно обновлена!', - 'category_deleted' => 'Категория успешно удалена!', - 'product_added' => 'Товар успешно добавлен!', - 'product_updated' => 'Товар успешно обновлен!', - 'product_deleted' => 'Товар успешно удален!' - ]; - echo $messages[$success_message] ?? 'Операция выполнена успешно!'; - ?> -
- - - -
- Ошибка: -
- - - - - - -
-

- Настройки отображения -

- - - Скрыть недоступные товары - - - - Показать все товары - - - - Недоступные товары отмечены серым цветом - -
- - -
- Добро пожаловать, ! - - - Администратор - - - - - Показаны все товары - - -
- -
- - -
-
-

- Каталог мебели - - ( товаров) - -

- - -

- Результаты поиска по запросу: "" - - Очистить поиск - -

- - - -
- - - Показаны все товары, включая недоступные. Недоступные товары отмечены серым цветом. - -
- -
- - -
- ['name' => 'Светильник MINNIGHT', 'price' => 7999, 'image' => 'img2/1_2.png', 'size' => 'small'], - 2 => ['name' => 'Кровать MODER', 'price' => 45999, 'image' => 'img2/3_3.png', 'size' => 'large'], - 3 => ['name' => 'Торшер MARCIA', 'price' => 11999, 'image' => 'img2/2_2.png', 'size' => 'tall align-right'], - 4 => ['name' => 'Светильник POLET', 'price' => 5499, 'image' => 'img2/4.jpg', 'size' => 'wide'], - 5 => ['name' => 'Стол NORD', 'price' => 23999, 'image' => 'img2/5_5.png', 'size' => 'small1'], - 6 => ['name' => 'Диван ROYALTY', 'price' => 78999, 'image' => 'img2/6_6.png', 'size' => 'wide2'], - 7 => ['name' => 'Кресло MINIMAL', 'price' => 29999, 'image' => 'img2/7_7.png', 'size' => 'wide3'], - 8 => ['name' => 'Стол LONKI', 'price' => 34999, 'image' => 'img2/8_8.png', 'size' => 'wide2_1'], - 9 => ['name' => 'Диван HEMMINS', 'price' => 89999, 'image' => 'img2/9_9.png', 'size' => 'full-width'] - ]; - - // Если в базе данных товаров нет, показываем оригинальные - if (empty($filteredProducts)) { - foreach ($originalProducts as $id => $product): ?> -
-
- <?= $product['name'] ?> - -
-
-
-
- - -
-
- -
-
- <?= htmlspecialchars($product['name']) ?> - -
-
-
-
- - -
-
- -
-
-
-
-
- - - - - +getConnection(); + +$category_id = $_GET['category'] ?? 0; +$search = $_GET['search'] ?? ''; +$min_price = $_GET['min_price'] ?? 0; +$max_price = $_GET['max_price'] ?? 1000000; +$colors = isset($_GET['colors']) ? (array)$_GET['colors'] : []; +$materials = isset($_GET['materials']) ? (array)$_GET['materials'] : []; +$show_all = isset($_GET['show_all']) && $_GET['show_all'] == '1'; + +$success_message = $_GET['success'] ?? ''; +$error_message = $_GET['error'] ?? ''; + +try { + + $user_id = $_SESSION['user_id'] ?? 0; + $userStmt = $db->prepare("SELECT * FROM users WHERE user_id = ?"); + $userStmt->execute([$user_id]); + $user = $userStmt->fetch(); + + $isAdmin = isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true; + $userEmail = $_SESSION['user_email'] ?? ''; + $fullName = $_SESSION['full_name'] ?? $userEmail; + $loginTime = $_SESSION['login_time'] ?? time(); + + try { + $categoriesStmt = $db->prepare(" + SELECT * FROM categories + WHERE is_active = TRUE + ORDER BY sort_order, name + "); + $categoriesStmt->execute(); + $categories = $categoriesStmt->fetchAll(); + } catch (PDOException $e) { + $categories = []; + error_log("Ошибка получения категорий: " . $e->getMessage()); + } + + $subcategories = []; + if ($category_id > 0) { + $subStmt = $db->prepare(" + SELECT * FROM subcategories + WHERE category_id = ? AND is_active = TRUE + ORDER BY sort_order, name + "); + $subStmt->execute([$category_id]); + $subcategories = $subStmt->fetchAll(); + } + + $checkColumns = $db->query(" + SELECT column_name + FROM information_schema.columns + WHERE table_name = 'products' + AND column_name IN ('color', 'material') + "); + $existingColumns = $checkColumns->fetchAll(PDO::FETCH_COLUMN); + + $hasColorColumn = in_array('color', $existingColumns); + $hasMaterialColumn = in_array('material', $existingColumns); + + $availableColors = []; + if ($hasColorColumn) { + $colorsStmt = $db->query(" + SELECT DISTINCT color FROM products + WHERE color IS NOT NULL AND color != '' AND is_available = TRUE + ORDER BY color + "); + $availableColors = $colorsStmt->fetchAll(PDO::FETCH_COLUMN); + } + + $availableMaterials = []; + if ($hasMaterialColumn) { + $materialsStmt = $db->query(" + SELECT DISTINCT material FROM products + WHERE material IS NOT NULL AND material != '' AND is_available = TRUE + ORDER BY material + "); + $availableMaterials = $materialsStmt->fetchAll(PDO::FETCH_COLUMN); + } + + $sql = "SELECT p.*, c.name as category_name + FROM products p + LEFT JOIN categories c ON p.category_id = c.category_id + WHERE 1=1"; + + $params = []; + + if (!$show_all && !$isAdmin) { + $sql .= " AND p.is_available = TRUE"; + } + + if ($category_id > 0) { + $sql .= " AND p.category_id = ?"; + $params[] = $category_id; + } + + if ($min_price > 0 || $max_price < 1000000) { + $sql .= " AND p.price BETWEEN ? AND ?"; + $params[] = $min_price; + $params[] = $max_price; + } + + if ($hasColorColumn && !empty($colors)) { + $placeholders = implode(',', array_fill(0, count($colors), '?')); + $sql .= " AND p.color IN ($placeholders)"; + $params = array_merge($params, $colors); + } + + if ($hasMaterialColumn && !empty($materials)) { + $placeholders = implode(',', array_fill(0, count($materials), '?')); + $sql .= " AND p.material IN ($placeholders)"; + $params = array_merge($params, $materials); + } + + if (!empty($search)) { + $sql .= " AND (p.name LIKE ? OR p.description LIKE ?)"; + $params[] = "%$search%"; + $params[] = "%$search%"; + } + + $sql .= " ORDER BY p.product_id ASC LIMIT 50"; + + $stmt = $db->prepare($sql); + $stmt->execute($params); + $filteredProducts = $stmt->fetchAll(); + + $originalSizes = [ + 1 => 'small', + 2 => 'large', + 3 => 'tall align-right', + 4 => 'wide', + 5 => 'small1', + 6 => 'wide2', + 7 => 'wide3', + 8 => 'wide2_1', + 9 => 'full-width' + ]; + + $imgClasses = ['small1', 'wide2', 'wide3', 'wide2_1']; + +} catch (PDOException $e) { + die("Ошибка базы данных: " . $e->getMessage()); +} +?> + + + + + + + AETERNA - Каталог + + + + + + + +
+ +
+
+
+ + +
+
+ Все категории + +
+ +
+ +
+ + + 0 + + + +
+
+
+ +
+ +
+
+ +
+
+ + + +
+ 'Категория успешно добавлена!', + 'category_updated' => 'Категория успешно обновлена!', + 'category_deleted' => 'Категория успешно удалена!', + 'product_added' => 'Товар успешно добавлен!', + 'product_updated' => 'Товар успешно обновлен!', + 'product_deleted' => 'Товар успешно удален!' + ]; + echo $messages[$success_message] ?? 'Операция выполнена успешно!'; + ?> +
+ + + +
+ Ошибка: +
+ + + + + + +
+

+ Настройки отображения +

+ + + Скрыть недоступные товары + + + + Показать все товары + + + + Недоступные товары отмечены серым цветом + +
+ + +
+ Добро пожаловать, ! + + + Администратор + + + + + Показаны все товары + + +
+ +
+ + +
+
+

+ Каталог мебели + + ( товаров) + +

+ + +

+ Результаты поиска по запросу: "" + + Очистить поиск + +

+ + + +
+ + + Показаны все товары, включая недоступные. Недоступные товары отмечены серым цветом. + +
+ +
+ +
+ ['name' => 'Светильник MINNIGHT', 'price' => 7999, 'image' => 'img2/1_2.png', 'size' => 'small'], + 2 => ['name' => 'Кровать MODER', 'price' => 45999, 'image' => 'img2/3_3.png', 'size' => 'large'], + 3 => ['name' => 'Торшер MARCIA', 'price' => 11999, 'image' => 'img2/2_2.png', 'size' => 'tall align-right'], + 4 => ['name' => 'Светильник POLET', 'price' => 5499, 'image' => 'img2/4.jpg', 'size' => 'wide'], + 5 => ['name' => 'Стол NORD', 'price' => 23999, 'image' => 'img2/5_5.png', 'size' => 'small1'], + 6 => ['name' => 'Диван ROYALTY', 'price' => 78999, 'image' => 'img2/6_6.png', 'size' => 'wide2'], + 7 => ['name' => 'Кресло MINIMAL', 'price' => 29999, 'image' => 'img2/7_7.png', 'size' => 'wide3'], + 8 => ['name' => 'Стол LONKI', 'price' => 34999, 'image' => 'img2/8_8.png', 'size' => 'wide2_1'], + 9 => ['name' => 'Диван HEMMINS', 'price' => 89999, 'image' => 'img2/9_9.png', 'size' => 'full-width'] + ]; + + if (empty($filteredProducts)) { + foreach ($originalProducts as $id => $product): ?> +
+
+ <?= $product['name'] ?> + +
+
+
+
+ + +
+
+ +
+
+ <?= htmlspecialchars($product['name']) ?> + +
+
+
+
+ + +
+
+ +
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/оформление_заказа.php b/public/checkout.php similarity index 81% rename from оформление_заказа.php rename to public/checkout.php index b3fe155..7214617 100644 --- a/оформление_заказа.php +++ b/public/checkout.php @@ -1,484 +1,501 @@ -getConnection(); - -// Получаем корзину пользователя -$cart_items = []; -$total_amount = 0; -$total_quantity = 0; - -try { - $stmt = $db->prepare(" - SELECT - c.cart_id, - c.product_id, - c.quantity, - p.name, - p.price, - p.image_url, - p.stock_quantity - FROM cart c - JOIN products p ON c.product_id = p.product_id - WHERE c.user_id = ? AND p.is_available = TRUE - ORDER BY c.created_at DESC - "); - $stmt->execute([$user_id]); - $cart_items = $stmt->fetchAll(); - - // Рассчитываем общую сумму - foreach ($cart_items as $item) { - $total_amount += $item['price'] * $item['quantity']; - $total_quantity += $item['quantity']; - } - -} catch (PDOException $e) { - $error = "Ошибка загрузки корзины: " . $e->getMessage(); -} -?> - - - - - - AETERNA - Оформление заказа - - - - - - - - - -
-
-
- -

Товары в корзине

- - -
- -

Ваша корзина пуста

- -
- -
- -
-
- <?= htmlspecialchars($item['name']) ?> -
-
-
-
-
-
- - - -
- -
-
-
- -
- -
- - -
-
-
-

Оформление заказа

-
Товары,
-
- -
-

СПОСОБ ДОСТАВКИ

-
- - -
- -
- - -
-
-
- -
-
- -
-
-
- -
-

СПОСОБ ОПЛАТЫ

-
- - -
- -
-
- -
-
- -
-
-
- -
-
- -
- -
- - -
- -
-
- Товары, шт. - -
-
- Скидка - 0 ₽ - -
-
- Доставка - 2000 ₽ - -
-
- ИТОГО: - -
-
- - - - - - -
-

УСЛУГИ

-
- Доставка - 2000 ₽ -
-
- Сборка - 1000 ₽ -
-
-
-
- -
-
- -
- - -
- - - - - - - +getConnection(); + +$cart_items = []; +$total_amount = 0; +$total_quantity = 0; + +try { + $stmt = $db->prepare(" + SELECT + c.cart_id, + c.product_id, + c.quantity, + p.name, + p.price, + p.image_url, + p.stock_quantity + FROM cart c + JOIN products p ON c.product_id = p.product_id + WHERE c.user_id = ? AND p.is_available = TRUE + ORDER BY c.created_at DESC + "); + $stmt->execute([$user_id]); + $cart_items = $stmt->fetchAll(); + + foreach ($cart_items as $item) { + $total_amount += $item['price'] * $item['quantity']; + $total_quantity += $item['quantity']; + } + +} catch (PDOException $e) { + $error = "Ошибка загрузки корзины: " . $e->getMessage(); +} +?> + + + + + + + AETERNA - Оформление заказа + + + + + + + + + +
+
+
+ +

Товары в корзине

+ + +
+ +

Ваша корзина пуста

+ +
+ +
+ +
+
+ <?= htmlspecialchars($item['name']) ?> +
+
+
+
+
+
+ + + +
+ +
+
+
+ +
+ +
+ + +
+
+
+

Оформление заказа

+
Товары,
+
+ +
+

СПОСОБ ДОСТАВКИ

+
+ + +
+ +
+ + +
+
+
+ +
+
+ +
+
+
+ +
+

СПОСОБ ОПЛАТЫ

+
+ + +
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+ +
+ + +
+ +
+
+ Товары, шт. + +
+
+ Скидка + 0 ₽ + +
+
+ Доставка + 2000 ₽ + +
+
+ ИТОГО: + +
+
+ + + + + + +
+

УСЛУГИ

+
+ Доставка + 2000 ₽ +
+
+ Сборка + 1000 ₽ +
+
+
+
+ +
+
+ +
+ + +
+ + + + + + + \ No newline at end of file diff --git a/cite_mebel.php b/public/cite_mebel.php similarity index 86% rename from cite_mebel.php rename to public/cite_mebel.php index ad53c38..8c22cbe 100644 --- a/cite_mebel.php +++ b/public/cite_mebel.php @@ -1,762 +1,760 @@ - - - - - - AETERNA - Мебель и Интерьер - - - - - - - - -
-
-
- - -
-
- Все категории - -
- -
- - - - -
-
- -
- -
-
- -
-
-
-
-
- Кресло и торшер -
-
-

ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР

-

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

- - - ПЕРЕЙТИ В КАТАЛОГ - - ПЕРЕЙТИ В КАТАЛОГ - -
-
-
- -
-
-
-

ПОЧЕМУ
ВЫБИРАЮТ НАС?

-
-
- 1 -

ГАРАНТИЯ ВЫСОЧАЙШЕГО КАЧЕСТВА

-

Собственное производство и строгий контроль на всех этапах.

-
-
- 2 -

ИСПОЛЬЗОВАНИЕ НАДЕЖНЫХ МАТЕРИАЛОВ

-

Гарантия безопасности и долговечности.

-
-
- 3 -

ИНДИВИДУАЛЬНЫЙ ПОДХОД И ГИБКОСТЬ УСЛОВИЙ

-

Реализуем проекты любой сложности по вашим техническим заданиям.

-
-
-
- -
-
- Кровать и тумба -
-

НОВИНКИ В КАТЕГОРИЯХ
МЯГКАЯ МЕБЕЛЬ

- ПЕРЕЙТИ -
-
-
- Диван в гостиной -
-

РАСПРОДАЖА
ПРЕДМЕТЫ ДЕКОРА

- ПЕРЕЙТИ -
-
-
-
-
- -
-
-
-
-

О НАС

-

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

-
- Фиолетовое кресло -
- -
- Белый диван с подушками -

Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.

-
-
-
- -
-
-
-
-
- Готовое решение для гостиной -
-

ГОТОВОЕ РЕШЕНИЕ
ДЛЯ ВАШЕЙ ГОСТИНОЙ


-

УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС

-
- Подробнее -
- -
- Готовое решение для спальни -
-

ГОТОВОЕ РЕШЕНИЕ
ДЛЯ ВАШЕЙ СПАЛЬНИ


-

УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС

-
- Подробнее -
-
-
-
-
- -
-
-
-
-
10+
-
Лет работы
-
-
-
30 000+
-
Довольных покупателей
-
-
-
4500+
-
Реализованных заказов
-
-
-
-
- -
-
-

ОТВЕТЫ НА ВОПРОСЫ

-
-
- 1 -
-

Сколько времени занимает доставка?

-

Доставка готовых позиций занимает 1-3 дня. Мебель на заказ изготавливается от 14 до 45 рабочих дней, в зависимости от сложности. Точные сроки озвучит ваш менеджер при оформлении заказа.

-
-
-
- 2 -
-

Нужно ли вносить предоплату?

-

Да, для запуска заказа в производство необходима предоплата в размере 50-70% от стоимости, в зависимости от изделия. Оставшаяся сумма оплачивается при доставке и приемке мебели.

-
-
-
- 3 -
-

Предоставляется ли рассрочка или кредит?

-

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

-
-
-
- 4 -
-

Что делать, если мебель пришла с дефектом?

-

В этом случае необходимо в течение 7 дней со дня доставки сообщить нам о проблеме, прислать фото/видео дефекта. Мы оперативно решим вопрос о бесплатной замене или ремонте изделия.

-
-
-
- -
-
-
- - - - - - - - - - - - - -
-

Быстрый вход:

- - -
- - + + + + + + + AETERNA - Мебель и Интерьер + + + + + + + + +
+
+
+ + +
+
+ Все категории + +
+ +
+ + +
+
+ +
+ +
+
+ +
+
+
+
+
+ Кресло и торшер +
+
+

ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР

+

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

+ + + ПЕРЕЙТИ В КАТАЛОГ + + ПЕРЕЙТИ В КАТАЛОГ + +
+
+
+ +
+
+
+

ПОЧЕМУ
ВЫБИРАЮТ НАС?

+
+
+ 1 +

ГАРАНТИЯ ВЫСОЧАЙШЕГО КАЧЕСТВА

+

Собственное производство и строгий контроль на всех этапах.

+
+
+ 2 +

ИСПОЛЬЗОВАНИЕ НАДЕЖНЫХ МАТЕРИАЛОВ

+

Гарантия безопасности и долговечности.

+
+
+ 3 +

ИНДИВИДУАЛЬНЫЙ ПОДХОД И ГИБКОСТЬ УСЛОВИЙ

+

Реализуем проекты любой сложности по вашим техническим заданиям.

+
+
+
+ +
+
+ Кровать и тумба +
+

НОВИНКИ В КАТЕГОРИЯХ
МЯГКАЯ МЕБЕЛЬ

+ ПЕРЕЙТИ +
+
+
+ Диван в гостиной +
+

РАСПРОДАЖА
ПРЕДМЕТЫ ДЕКОРА

+ ПЕРЕЙТИ +
+
+
+
+
+ +
+
+
+
+

О НАС

+

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

+
+ Фиолетовое кресло +
+ +
+ Белый диван с подушками +

Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.

+
+
+
+ +
+
+
+
+
+ Готовое решение для гостиной +
+

ГОТОВОЕ РЕШЕНИЕ
ДЛЯ ВАШЕЙ ГОСТИНОЙ


+

УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС

+
+ Подробнее +
+ +
+ Готовое решение для спальни +
+

ГОТОВОЕ РЕШЕНИЕ
ДЛЯ ВАШЕЙ СПАЛЬНИ


+

УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС

+
+ Подробнее +
+
+
+
+
+ +
+
+
+
+
10+
+
Лет работы
+
+
+
30 000+
+
Довольных покупателей
+
+
+
4500+
+
Реализованных заказов
+
+
+
+
+ +
+
+

ОТВЕТЫ НА ВОПРОСЫ

+
+
+ 1 +
+

Сколько времени занимает доставка?

+

Доставка готовых позиций занимает 1-3 дня. Мебель на заказ изготавливается от 14 до 45 рабочих дней, в зависимости от сложности. Точные сроки озвучит ваш менеджер при оформлении заказа.

+
+
+
+ 2 +
+

Нужно ли вносить предоплату?

+

Да, для запуска заказа в производство необходима предоплата в размере 50-70% от стоимости, в зависимости от изделия. Оставшаяся сумма оплачивается при доставке и приемке мебели.

+
+
+
+ 3 +
+

Предоставляется ли рассрочка или кредит?

+

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

+
+
+
+ 4 +
+

Что делать, если мебель пришла с дефектом?

+

В этом случае необходимо в течение 7 дней со дня доставки сообщить нам о проблеме, прислать фото/видео дефекта. Мы оперативно решим вопрос о бесплатной замене или ремонте изделия.

+
+
+
+ +
+
+
+ + + + + + + + + + + + + +
+

Быстрый вход:

+ +
+ + + \ No newline at end of file diff --git a/config/database.php b/public/config/database.php similarity index 81% rename from config/database.php rename to public/config/database.php index d7e632b..5223e40 100644 --- a/config/database.php +++ b/public/config/database.php @@ -1,32 +1,31 @@ -connection = new PDO( - "pgsql:host=localhost;dbname=aeterna_db;", - "postgres", - "1234" - ); - $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - $this->connection->exec("SET NAMES 'utf8'"); - } catch(PDOException $e) { - die("Ошибка подключения: " . $e->getMessage()); - } - } - - public static function getInstance() { - if (self::$instance == null) { - self::$instance = new Database(); - } - return self::$instance; - } - - public function getConnection() { - return $this->connection; - } -} -?> \ No newline at end of file +connection = new PDO( + "pgsql:host=185.130.224.177;port=5481;dbname=postgres", + "admin", + "38feaad2840ccfda0e71243a6faaecfd" + ); + $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->connection->exec("SET NAMES 'utf8'"); + } catch(PDOException $e) { + die("Ошибка подключения: " . $e->getMessage()); + } + } + + public static function getInstance() { + if (self::$instance == null) { + self::$instance = new Database(); + } + return self::$instance; + } + + public function getConnection() { + return $this->connection; + } +} \ No newline at end of file diff --git a/Доставка.php b/public/delivery.php similarity index 90% rename from Доставка.php rename to public/delivery.php index ba659c5..2db4c67 100644 --- a/Доставка.php +++ b/public/delivery.php @@ -1,174 +1,171 @@ - - - - - - AETERNA - Доставка и оплата - - - - - - - - - - - -
-
- - -
-

ДОСТАВКА И ОПЛАТА

- -
-
-
- -
-

Курьерская доставка

-
-
- Бесплатная доставка: - при заказе от 30 000 ₽ -
-
- В пределах МКАД: - 1 500 ₽ -
-
- За МКАД: - 1 500 ₽ + 50 ₽/км -
-
- Время доставки: - с 9:00 до 21:00 -
-
-
- -
-
- -
-

Самовывоз из шоурума

-
-
- Адрес: - г. Москва, ул. Дизайнерская, 15 -
-
- Стоимость: - Бесплатно -
-
- Время получения: - в течение 2 часов после подтверждения -
-
- Парковка: - Бесплатная для клиентов -
-
-
- -
-
- -
-

Доставка по России

-
-
- Стоимость: - рассчитывается индивидуально -
-
- Сроки: - от 3 до 14 дней -
-
- Транспортные компании: - СДЭК, Boxberry, Деловые Линии -
-
-
-
- -
-
-
- - - + + + + + + + AETERNA - Доставка и оплата + + + + + + + + + + +
+
+ + +
+

ДОСТАВКА И ОПЛАТА

+ +
+
+
+ +
+

Курьерская доставка

+
+
+ Бесплатная доставка: + при заказе от 30 000 ₽ +
+
+ В пределах МКАД: + 1 500 ₽ +
+
+ За МКАД: + 1 500 ₽ + 50 ₽/км +
+
+ Время доставки: + с 9:00 до 21:00 +
+
+
+ +
+
+ +
+

Самовывоз из шоурума

+
+
+ Адрес: + г. Москва, ул. Дизайнерская, 15 +
+
+ Стоимость: + Бесплатно +
+
+ Время получения: + в течение 2 часов после подтверждения +
+
+ Парковка: + Бесплатная для клиентов +
+
+
+ +
+
+ +
+

Доставка по России

+
+
+ Стоимость: + рассчитывается индивидуально +
+
+ Сроки: + от 3 до 14 дней +
+
+ Транспортные компании: + СДЭК, Boxberry, Деловые Линии +
+
+
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/public/header_common.php b/public/header_common.php new file mode 100644 index 0000000..9952ff0 --- /dev/null +++ b/public/header_common.php @@ -0,0 +1,161 @@ + +
+
+
+ + +
+
+ Все категории + +
+ +
+ +
+ + + + + 0 + + + + + + Войти + +
+
+
+ +
+ +
+
+ + + + diff --git a/public/img/1 — копия.jpg b/public/img/1 — копия.jpg new file mode 100644 index 0000000..68992d6 Binary files /dev/null and b/public/img/1 — копия.jpg differ diff --git a/img2/1.jpg b/public/img/1.jpg similarity index 100% rename from img2/1.jpg rename to public/img/1.jpg diff --git a/public/img/100.jpg b/public/img/100.jpg new file mode 100644 index 0000000..646480d Binary files /dev/null and b/public/img/100.jpg differ diff --git a/public/img/11.jpg b/public/img/11.jpg new file mode 100644 index 0000000..3a8270e Binary files /dev/null and b/public/img/11.jpg differ diff --git a/public/img/111.jpg b/public/img/111.jpg new file mode 100644 index 0000000..33dc453 Binary files /dev/null and b/public/img/111.jpg differ diff --git a/public/img/11_1.png b/public/img/11_1.png new file mode 100644 index 0000000..4735774 Binary files /dev/null and b/public/img/11_1.png differ diff --git a/public/img/1_1.jpg b/public/img/1_1.jpg new file mode 100644 index 0000000..3f9e1a1 Binary files /dev/null and b/public/img/1_1.jpg differ diff --git a/public/img/1_2.jpg b/public/img/1_2.jpg new file mode 100644 index 0000000..2d7a54a Binary files /dev/null and b/public/img/1_2.jpg differ diff --git a/public/img/1_2.png b/public/img/1_2.png new file mode 100644 index 0000000..9a8a6c0 Binary files /dev/null and b/public/img/1_2.png differ diff --git a/public/img/2.jpg b/public/img/2.jpg new file mode 100644 index 0000000..417df4a Binary files /dev/null and b/public/img/2.jpg differ diff --git a/public/img/22.jpg b/public/img/22.jpg new file mode 100644 index 0000000..f59b320 Binary files /dev/null and b/public/img/22.jpg differ diff --git a/public/img/25.jpg b/public/img/25.jpg new file mode 100644 index 0000000..03eddcf Binary files /dev/null and b/public/img/25.jpg differ diff --git a/public/img/2_2.jpg b/public/img/2_2.jpg new file mode 100644 index 0000000..76ba9ba Binary files /dev/null and b/public/img/2_2.jpg differ diff --git a/public/img/2_2.png b/public/img/2_2.png new file mode 100644 index 0000000..8fa6e8c Binary files /dev/null and b/public/img/2_2.png differ diff --git a/public/img/3.jpg b/public/img/3.jpg new file mode 100644 index 0000000..973449d Binary files /dev/null and b/public/img/3.jpg differ diff --git a/public/img/3_3.jpg b/public/img/3_3.jpg new file mode 100644 index 0000000..7e56605 Binary files /dev/null and b/public/img/3_3.jpg differ diff --git a/public/img/3_3.png b/public/img/3_3.png new file mode 100644 index 0000000..ba16e91 Binary files /dev/null and b/public/img/3_3.png differ diff --git a/public/img/4.jpg b/public/img/4.jpg new file mode 100644 index 0000000..da2945c Binary files /dev/null and b/public/img/4.jpg differ diff --git a/public/img/44.jpg b/public/img/44.jpg new file mode 100644 index 0000000..e38c994 Binary files /dev/null and b/public/img/44.jpg differ diff --git a/public/img/444 b/public/img/444 new file mode 100644 index 0000000..dc6e3fa Binary files /dev/null and b/public/img/444 differ diff --git a/public/img/444 (1).png b/public/img/444 (1).png new file mode 100644 index 0000000..ddf7e92 Binary files /dev/null and b/public/img/444 (1).png differ diff --git a/public/img/444.jpg b/public/img/444.jpg new file mode 100644 index 0000000..dc6e3fa Binary files /dev/null and b/public/img/444.jpg differ diff --git a/public/img/444.png b/public/img/444.png new file mode 100644 index 0000000..cd53804 Binary files /dev/null and b/public/img/444.png differ diff --git a/public/img/4_1.jpg b/public/img/4_1.jpg new file mode 100644 index 0000000..69f2236 Binary files /dev/null and b/public/img/4_1.jpg differ diff --git a/img2/5.jpg b/public/img/5.jpg similarity index 100% rename from img2/5.jpg rename to public/img/5.jpg diff --git a/public/img/5_5.jpg b/public/img/5_5.jpg new file mode 100644 index 0000000..9ced54f Binary files /dev/null and b/public/img/5_5.jpg differ diff --git a/public/img/5_5.png b/public/img/5_5.png new file mode 100644 index 0000000..369b63b Binary files /dev/null and b/public/img/5_5.png differ diff --git a/public/img/6.jpg b/public/img/6.jpg new file mode 100644 index 0000000..550e17d Binary files /dev/null and b/public/img/6.jpg differ diff --git a/public/img/6_6.jpg b/public/img/6_6.jpg new file mode 100644 index 0000000..0aa5c4b Binary files /dev/null and b/public/img/6_6.jpg differ diff --git a/public/img/6_6.png b/public/img/6_6.png new file mode 100644 index 0000000..af949f9 Binary files /dev/null and b/public/img/6_6.png differ diff --git a/public/img/7.jpg b/public/img/7.jpg new file mode 100644 index 0000000..17150c7 Binary files /dev/null and b/public/img/7.jpg differ diff --git a/public/img/77.jpg b/public/img/77.jpg new file mode 100644 index 0000000..d9bf304 Binary files /dev/null and b/public/img/77.jpg differ diff --git a/public/img/777 (1).png b/public/img/777 (1).png new file mode 100644 index 0000000..6b01d3f Binary files /dev/null and b/public/img/777 (1).png differ diff --git a/public/img/777.jpg b/public/img/777.jpg new file mode 100644 index 0000000..908d164 Binary files /dev/null and b/public/img/777.jpg differ diff --git a/public/img/777.png b/public/img/777.png new file mode 100644 index 0000000..41d57c8 Binary files /dev/null and b/public/img/777.png differ diff --git a/public/img/7_7.jpg b/public/img/7_7.jpg new file mode 100644 index 0000000..5ee1524 Binary files /dev/null and b/public/img/7_7.jpg differ diff --git a/public/img/7_7.png b/public/img/7_7.png new file mode 100644 index 0000000..d50782e Binary files /dev/null and b/public/img/7_7.png differ diff --git a/public/img/8.jpg b/public/img/8.jpg new file mode 100644 index 0000000..8b41c63 Binary files /dev/null and b/public/img/8.jpg differ diff --git a/public/img/88.jpg b/public/img/88.jpg new file mode 100644 index 0000000..713eeec Binary files /dev/null and b/public/img/88.jpg differ diff --git a/public/img/888 (1).png b/public/img/888 (1).png new file mode 100644 index 0000000..127e80d Binary files /dev/null and b/public/img/888 (1).png differ diff --git a/public/img/888.jpg b/public/img/888.jpg new file mode 100644 index 0000000..e8f33f7 Binary files /dev/null and b/public/img/888.jpg differ diff --git a/public/img/888.png b/public/img/888.png new file mode 100644 index 0000000..35e3403 Binary files /dev/null and b/public/img/888.png differ diff --git a/public/img/8_8.png b/public/img/8_8.png new file mode 100644 index 0000000..9f86b2c Binary files /dev/null and b/public/img/8_8.png differ diff --git a/public/img/9.jpg b/public/img/9.jpg new file mode 100644 index 0000000..0b772e3 Binary files /dev/null and b/public/img/9.jpg differ diff --git a/public/img/99.jpg b/public/img/99.jpg new file mode 100644 index 0000000..ed309ba Binary files /dev/null and b/public/img/99.jpg differ diff --git a/public/img/99.png b/public/img/99.png new file mode 100644 index 0000000..8fd29b2 Binary files /dev/null and b/public/img/99.png differ diff --git a/public/img/99_1.jpg b/public/img/99_1.jpg new file mode 100644 index 0000000..0247358 Binary files /dev/null and b/public/img/99_1.jpg differ diff --git a/public/img/99_2.jpg b/public/img/99_2.jpg new file mode 100644 index 0000000..468fd51 Binary files /dev/null and b/public/img/99_2.jpg differ diff --git a/public/img/99_3.png b/public/img/99_3.png new file mode 100644 index 0000000..ae2cd8e Binary files /dev/null and b/public/img/99_3.png differ diff --git a/img2/9_9.jpg b/public/img/9_9.jpg similarity index 100% rename from img2/9_9.jpg rename to public/img/9_9.jpg diff --git a/public/img/9_9.png b/public/img/9_9.png new file mode 100644 index 0000000..733a453 Binary files /dev/null and b/public/img/9_9.png differ diff --git a/public/img/black.png b/public/img/black.png new file mode 100644 index 0000000..a10af51 Binary files /dev/null and b/public/img/black.png differ diff --git a/public/img/black1.png b/public/img/black1.png new file mode 100644 index 0000000..f529bd3 Binary files /dev/null and b/public/img/black1.png differ diff --git a/public/img/black2.png b/public/img/black2.png new file mode 100644 index 0000000..75758a2 Binary files /dev/null and b/public/img/black2.png differ diff --git a/public/img/brown.png b/public/img/brown.png new file mode 100644 index 0000000..9ef4db4 Binary files /dev/null and b/public/img/brown.png differ diff --git a/public/img/brown1.png b/public/img/brown1.png new file mode 100644 index 0000000..0b178f1 Binary files /dev/null and b/public/img/brown1.png differ diff --git a/public/img/brown2.png b/public/img/brown2.png new file mode 100644 index 0000000..346b22f Binary files /dev/null and b/public/img/brown2.png differ diff --git a/img2/chair.PNG b/public/img/chair.PNG similarity index 100% rename from img2/chair.PNG rename to public/img/chair.PNG diff --git a/public/img/gray.png b/public/img/gray.png new file mode 100644 index 0000000..55ab5bb Binary files /dev/null and b/public/img/gray.png differ diff --git a/public/img/gray1.png b/public/img/gray1.png new file mode 100644 index 0000000..3137e25 Binary files /dev/null and b/public/img/gray1.png differ diff --git a/public/img/gray2.png b/public/img/gray2.png new file mode 100644 index 0000000..95b8781 Binary files /dev/null and b/public/img/gray2.png differ diff --git a/img2/диван.jpg b/public/img/диван.jpg similarity index 100% rename from img2/диван.jpg rename to public/img/диван.jpg diff --git a/img2/диван_1.jpg b/public/img/диван_1.jpg similarity index 100% rename from img2/диван_1.jpg rename to public/img/диван_1.jpg diff --git a/img2/кресло.jpg b/public/img/кресло.jpg similarity index 100% rename from img2/кресло.jpg rename to public/img/кресло.jpg diff --git a/img2/кресло_1.jpg b/public/img/кресло_1.jpg similarity index 100% rename from img2/кресло_1.jpg rename to public/img/кресло_1.jpg diff --git a/img2/слайдер_1.jpg b/public/img/слайдер_1.jpg similarity index 100% rename from img2/слайдер_1.jpg rename to public/img/слайдер_1.jpg diff --git a/img2/слайдер_2.jpg b/public/img/слайдер_2.jpg similarity index 100% rename from img2/слайдер_2.jpg rename to public/img/слайдер_2.jpg diff --git a/img2/слайдер_3.jpg b/public/img/слайдер_3.jpg similarity index 100% rename from img2/слайдер_3.jpg rename to public/img/слайдер_3.jpg diff --git a/img2/слайдер_4.jpg b/public/img/слайдер_4.jpg similarity index 100% rename from img2/слайдер_4.jpg rename to public/img/слайдер_4.jpg diff --git a/img2/слайдер_5.jpg b/public/img/слайдер_5.jpg similarity index 100% rename from img2/слайдер_5.jpg rename to public/img/слайдер_5.jpg diff --git a/img2/слайдер_6.jpg b/public/img/слайдер_6.jpg similarity index 100% rename from img2/слайдер_6.jpg rename to public/img/слайдер_6.jpg diff --git a/img2/спальня.jpg b/public/img/спальня.jpg similarity index 100% rename from img2/спальня.jpg rename to public/img/спальня.jpg diff --git a/public/img/стили_оформления.css b/public/img/стили_оформления.css new file mode 100644 index 0000000..2e1d09c --- /dev/null +++ b/public/img/стили_оформления.css @@ -0,0 +1,61 @@ + +.error-message { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; +} + +.form__input.error { + border-color: #ff0000; +} + +.form__group { + position: relative; + margin-bottom: 15px; +} + +.page-messages { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + width: 90%; + max-width: 500px; +} + +.message { + padding: 15px; + margin: 10px 0; + border-radius: 5px; + text-align: center; + font-weight: bold; + display: none; +} + +.message.error { + background-color: #ffebee; + color: #c62828; + border: 1px solid #ffcdd2; +} + +.message.success { + background-color: #e8f5e9; + color: #453227; + border: 1px solid #c8e6c9; +} + +.message.warning { + background-color: #fff3e0; + color: #ef6c00; + border: 1px solid #ffe0b2; +} + +.privacy-error { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; + text-align: center; +} diff --git a/public/img1 b/public/img1 new file mode 120000 index 0000000..cf2d769 --- /dev/null +++ b/public/img1 @@ -0,0 +1 @@ +img \ No newline at end of file diff --git a/public/img2/1 — копия.jpg b/public/img2/1 — копия.jpg new file mode 100644 index 0000000..68992d6 Binary files /dev/null and b/public/img2/1 — копия.jpg differ diff --git a/public/img2/1.jpg b/public/img2/1.jpg new file mode 100644 index 0000000..1df7668 Binary files /dev/null and b/public/img2/1.jpg differ diff --git a/public/img2/100.jpg b/public/img2/100.jpg new file mode 100644 index 0000000..646480d Binary files /dev/null and b/public/img2/100.jpg differ diff --git a/public/img2/11.jpg b/public/img2/11.jpg new file mode 100644 index 0000000..3a8270e Binary files /dev/null and b/public/img2/11.jpg differ diff --git a/public/img2/111.jpg b/public/img2/111.jpg new file mode 100644 index 0000000..33dc453 Binary files /dev/null and b/public/img2/111.jpg differ diff --git a/public/img2/11_1.png b/public/img2/11_1.png new file mode 100644 index 0000000..4735774 Binary files /dev/null and b/public/img2/11_1.png differ diff --git a/public/img2/1_1.jpg b/public/img2/1_1.jpg new file mode 100644 index 0000000..3f9e1a1 Binary files /dev/null and b/public/img2/1_1.jpg differ diff --git a/public/img2/1_2.jpg b/public/img2/1_2.jpg new file mode 100644 index 0000000..2d7a54a Binary files /dev/null and b/public/img2/1_2.jpg differ diff --git a/public/img2/1_2.png b/public/img2/1_2.png new file mode 100644 index 0000000..9a8a6c0 Binary files /dev/null and b/public/img2/1_2.png differ diff --git a/public/img2/2.jpg b/public/img2/2.jpg new file mode 100644 index 0000000..417df4a Binary files /dev/null and b/public/img2/2.jpg differ diff --git a/public/img2/22.jpg b/public/img2/22.jpg new file mode 100644 index 0000000..f59b320 Binary files /dev/null and b/public/img2/22.jpg differ diff --git a/public/img2/25.jpg b/public/img2/25.jpg new file mode 100644 index 0000000..03eddcf Binary files /dev/null and b/public/img2/25.jpg differ diff --git a/public/img2/2_2.jpg b/public/img2/2_2.jpg new file mode 100644 index 0000000..76ba9ba Binary files /dev/null and b/public/img2/2_2.jpg differ diff --git a/public/img2/2_2.png b/public/img2/2_2.png new file mode 100644 index 0000000..8fa6e8c Binary files /dev/null and b/public/img2/2_2.png differ diff --git a/public/img2/3.jpg b/public/img2/3.jpg new file mode 100644 index 0000000..973449d Binary files /dev/null and b/public/img2/3.jpg differ diff --git a/public/img2/3_3.jpg b/public/img2/3_3.jpg new file mode 100644 index 0000000..7e56605 Binary files /dev/null and b/public/img2/3_3.jpg differ diff --git a/public/img2/3_3.png b/public/img2/3_3.png new file mode 100644 index 0000000..ba16e91 Binary files /dev/null and b/public/img2/3_3.png differ diff --git a/public/img2/4.jpg b/public/img2/4.jpg new file mode 100644 index 0000000..da2945c Binary files /dev/null and b/public/img2/4.jpg differ diff --git a/public/img2/44.jpg b/public/img2/44.jpg new file mode 100644 index 0000000..e38c994 Binary files /dev/null and b/public/img2/44.jpg differ diff --git a/public/img2/444 b/public/img2/444 new file mode 100644 index 0000000..dc6e3fa Binary files /dev/null and b/public/img2/444 differ diff --git a/public/img2/444 (1).png b/public/img2/444 (1).png new file mode 100644 index 0000000..ddf7e92 Binary files /dev/null and b/public/img2/444 (1).png differ diff --git a/public/img2/444.jpg b/public/img2/444.jpg new file mode 100644 index 0000000..dc6e3fa Binary files /dev/null and b/public/img2/444.jpg differ diff --git a/public/img2/444.png b/public/img2/444.png new file mode 100644 index 0000000..cd53804 Binary files /dev/null and b/public/img2/444.png differ diff --git a/public/img2/4_1.jpg b/public/img2/4_1.jpg new file mode 100644 index 0000000..69f2236 Binary files /dev/null and b/public/img2/4_1.jpg differ diff --git a/public/img2/5.jpg b/public/img2/5.jpg new file mode 100644 index 0000000..456cec1 Binary files /dev/null and b/public/img2/5.jpg differ diff --git a/public/img2/5_5.jpg b/public/img2/5_5.jpg new file mode 100644 index 0000000..9ced54f Binary files /dev/null and b/public/img2/5_5.jpg differ diff --git a/public/img2/5_5.png b/public/img2/5_5.png new file mode 100644 index 0000000..369b63b Binary files /dev/null and b/public/img2/5_5.png differ diff --git a/public/img2/6.jpg b/public/img2/6.jpg new file mode 100644 index 0000000..550e17d Binary files /dev/null and b/public/img2/6.jpg differ diff --git a/public/img2/6_6.jpg b/public/img2/6_6.jpg new file mode 100644 index 0000000..0aa5c4b Binary files /dev/null and b/public/img2/6_6.jpg differ diff --git a/public/img2/6_6.png b/public/img2/6_6.png new file mode 100644 index 0000000..af949f9 Binary files /dev/null and b/public/img2/6_6.png differ diff --git a/public/img2/7.jpg b/public/img2/7.jpg new file mode 100644 index 0000000..17150c7 Binary files /dev/null and b/public/img2/7.jpg differ diff --git a/public/img2/77.jpg b/public/img2/77.jpg new file mode 100644 index 0000000..d9bf304 Binary files /dev/null and b/public/img2/77.jpg differ diff --git a/public/img2/777 (1).png b/public/img2/777 (1).png new file mode 100644 index 0000000..6b01d3f Binary files /dev/null and b/public/img2/777 (1).png differ diff --git a/public/img2/777.jpg b/public/img2/777.jpg new file mode 100644 index 0000000..908d164 Binary files /dev/null and b/public/img2/777.jpg differ diff --git a/public/img2/777.png b/public/img2/777.png new file mode 100644 index 0000000..41d57c8 Binary files /dev/null and b/public/img2/777.png differ diff --git a/public/img2/7_7.jpg b/public/img2/7_7.jpg new file mode 100644 index 0000000..5ee1524 Binary files /dev/null and b/public/img2/7_7.jpg differ diff --git a/public/img2/7_7.png b/public/img2/7_7.png new file mode 100644 index 0000000..d50782e Binary files /dev/null and b/public/img2/7_7.png differ diff --git a/public/img2/8.jpg b/public/img2/8.jpg new file mode 100644 index 0000000..8b41c63 Binary files /dev/null and b/public/img2/8.jpg differ diff --git a/public/img2/88.jpg b/public/img2/88.jpg new file mode 100644 index 0000000..713eeec Binary files /dev/null and b/public/img2/88.jpg differ diff --git a/public/img2/888 (1).png b/public/img2/888 (1).png new file mode 100644 index 0000000..127e80d Binary files /dev/null and b/public/img2/888 (1).png differ diff --git a/public/img2/888.jpg b/public/img2/888.jpg new file mode 100644 index 0000000..e8f33f7 Binary files /dev/null and b/public/img2/888.jpg differ diff --git a/public/img2/888.png b/public/img2/888.png new file mode 100644 index 0000000..35e3403 Binary files /dev/null and b/public/img2/888.png differ diff --git a/public/img2/8_8.png b/public/img2/8_8.png new file mode 100644 index 0000000..9f86b2c Binary files /dev/null and b/public/img2/8_8.png differ diff --git a/public/img2/9.jpg b/public/img2/9.jpg new file mode 100644 index 0000000..0b772e3 Binary files /dev/null and b/public/img2/9.jpg differ diff --git a/public/img2/99.jpg b/public/img2/99.jpg new file mode 100644 index 0000000..ed309ba Binary files /dev/null and b/public/img2/99.jpg differ diff --git a/public/img2/99.png b/public/img2/99.png new file mode 100644 index 0000000..8fd29b2 Binary files /dev/null and b/public/img2/99.png differ diff --git a/public/img2/99_1.jpg b/public/img2/99_1.jpg new file mode 100644 index 0000000..0247358 Binary files /dev/null and b/public/img2/99_1.jpg differ diff --git a/public/img2/99_2.jpg b/public/img2/99_2.jpg new file mode 100644 index 0000000..468fd51 Binary files /dev/null and b/public/img2/99_2.jpg differ diff --git a/public/img2/99_3.png b/public/img2/99_3.png new file mode 100644 index 0000000..ae2cd8e Binary files /dev/null and b/public/img2/99_3.png differ diff --git a/public/img2/9_9.jpg b/public/img2/9_9.jpg new file mode 100644 index 0000000..a77f2ae Binary files /dev/null and b/public/img2/9_9.jpg differ diff --git a/public/img2/9_9.png b/public/img2/9_9.png new file mode 100644 index 0000000..733a453 Binary files /dev/null and b/public/img2/9_9.png differ diff --git a/public/img2/black.png b/public/img2/black.png new file mode 100644 index 0000000..a10af51 Binary files /dev/null and b/public/img2/black.png differ diff --git a/public/img2/black1.png b/public/img2/black1.png new file mode 100644 index 0000000..f529bd3 Binary files /dev/null and b/public/img2/black1.png differ diff --git a/public/img2/black2.png b/public/img2/black2.png new file mode 100644 index 0000000..75758a2 Binary files /dev/null and b/public/img2/black2.png differ diff --git a/public/img2/brown.png b/public/img2/brown.png new file mode 100644 index 0000000..9ef4db4 Binary files /dev/null and b/public/img2/brown.png differ diff --git a/public/img2/brown1.png b/public/img2/brown1.png new file mode 100644 index 0000000..0b178f1 Binary files /dev/null and b/public/img2/brown1.png differ diff --git a/public/img2/brown2.png b/public/img2/brown2.png new file mode 100644 index 0000000..346b22f Binary files /dev/null and b/public/img2/brown2.png differ diff --git a/public/img2/chair.PNG b/public/img2/chair.PNG new file mode 100644 index 0000000..12af9db Binary files /dev/null and b/public/img2/chair.PNG differ diff --git a/public/img2/gray.png b/public/img2/gray.png new file mode 100644 index 0000000..55ab5bb Binary files /dev/null and b/public/img2/gray.png differ diff --git a/public/img2/gray1.png b/public/img2/gray1.png new file mode 100644 index 0000000..3137e25 Binary files /dev/null and b/public/img2/gray1.png differ diff --git a/public/img2/gray2.png b/public/img2/gray2.png new file mode 100644 index 0000000..95b8781 Binary files /dev/null and b/public/img2/gray2.png differ diff --git a/public/img2/диван.jpg b/public/img2/диван.jpg new file mode 100644 index 0000000..578d1a3 Binary files /dev/null and b/public/img2/диван.jpg differ diff --git a/public/img2/диван_1.jpg b/public/img2/диван_1.jpg new file mode 100644 index 0000000..3618723 Binary files /dev/null and b/public/img2/диван_1.jpg differ diff --git a/public/img2/кресло.jpg b/public/img2/кресло.jpg new file mode 100644 index 0000000..6d1c12b Binary files /dev/null and b/public/img2/кресло.jpg differ diff --git a/public/img2/кресло_1.jpg b/public/img2/кресло_1.jpg new file mode 100644 index 0000000..e59b3b1 Binary files /dev/null and b/public/img2/кресло_1.jpg differ diff --git a/public/img2/слайдер_1.jpg b/public/img2/слайдер_1.jpg new file mode 100644 index 0000000..255440e Binary files /dev/null and b/public/img2/слайдер_1.jpg differ diff --git a/public/img2/слайдер_2.jpg b/public/img2/слайдер_2.jpg new file mode 100644 index 0000000..9cb6403 Binary files /dev/null and b/public/img2/слайдер_2.jpg differ diff --git a/public/img2/слайдер_3.jpg b/public/img2/слайдер_3.jpg new file mode 100644 index 0000000..dc65d0b Binary files /dev/null and b/public/img2/слайдер_3.jpg differ diff --git a/public/img2/слайдер_4.jpg b/public/img2/слайдер_4.jpg new file mode 100644 index 0000000..f6d8d38 Binary files /dev/null and b/public/img2/слайдер_4.jpg differ diff --git a/public/img2/слайдер_5.jpg b/public/img2/слайдер_5.jpg new file mode 100644 index 0000000..a9cfbb6 Binary files /dev/null and b/public/img2/слайдер_5.jpg differ diff --git a/public/img2/слайдер_6.jpg b/public/img2/слайдер_6.jpg new file mode 100644 index 0000000..0dc28a0 Binary files /dev/null and b/public/img2/слайдер_6.jpg differ diff --git a/public/img2/спальня.jpg b/public/img2/спальня.jpg new file mode 100644 index 0000000..f403324 Binary files /dev/null and b/public/img2/спальня.jpg differ diff --git a/public/img2/стили_оформления.css b/public/img2/стили_оформления.css new file mode 100644 index 0000000..2e1d09c --- /dev/null +++ b/public/img2/стили_оформления.css @@ -0,0 +1,61 @@ + +.error-message { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; +} + +.form__input.error { + border-color: #ff0000; +} + +.form__group { + position: relative; + margin-bottom: 15px; +} + +.page-messages { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + width: 90%; + max-width: 500px; +} + +.message { + padding: 15px; + margin: 10px 0; + border-radius: 5px; + text-align: center; + font-weight: bold; + display: none; +} + +.message.error { + background-color: #ffebee; + color: #c62828; + border: 1px solid #ffcdd2; +} + +.message.success { + background-color: #e8f5e9; + color: #453227; + border: 1px solid #c8e6c9; +} + +.message.warning { + background-color: #fff3e0; + color: #ef6c00; + border: 1px solid #ffe0b2; +} + +.privacy-error { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; + text-align: center; +} diff --git a/public/includes/auth.php b/public/includes/auth.php new file mode 100644 index 0000000..5326642 --- /dev/null +++ b/public/includes/auth.php @@ -0,0 +1,127 @@ +getConnection(); + + try { + $stmt = $db->prepare(" + SELECT user_id, email, password_hash, full_name, phone, city, is_admin, is_active + FROM users WHERE email = ? + "); + $stmt->execute([$email]); + $user = $stmt->fetch(); + + if (!$user) { + return ['success' => false, 'message' => 'Пользователь не найден']; + } + + if (!$user['is_active']) { + return ['success' => false, 'message' => 'Аккаунт заблокирован']; + } + + if (!password_verify($password, $user['password_hash'])) { + return ['success' => false, 'message' => 'Неверный пароль']; + } + + $_SESSION['user_id'] = $user['user_id']; + $_SESSION['user_email'] = $user['email']; + $_SESSION['full_name'] = $user['full_name']; + $_SESSION['user_phone'] = $user['phone'] ?? ''; + $_SESSION['user_city'] = $user['city'] ?? ''; + $_SESSION['isLoggedIn'] = true; + $_SESSION['isAdmin'] = (bool)$user['is_admin']; + $_SESSION['login_time'] = time(); + + $updateStmt = $db->prepare("UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE user_id = ?"); + $updateStmt->execute([$user['user_id']]); + + return ['success' => true, 'user' => $user]; + + } catch (PDOException $e) { + return ['success' => false, 'message' => 'Ошибка базы данных']; + } +} + +function registerUser(array $data): array { + $db = Database::getInstance()->getConnection(); + + $email = trim($data['email'] ?? ''); + $password = $data['password'] ?? ''; + $fullName = trim($data['full_name'] ?? ''); + $phone = trim($data['phone'] ?? ''); + $city = trim($data['city'] ?? ''); + + if (empty($email) || empty($password) || empty($fullName)) { + return ['success' => false, 'message' => 'Заполните все обязательные поля']; + } + + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + return ['success' => false, 'message' => 'Некорректный email']; + } + + if (strlen($password) < 6) { + return ['success' => false, 'message' => 'Пароль должен содержать минимум 6 символов']; + } + + try { + + $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?"); + $checkStmt->execute([$email]); + + if ($checkStmt->fetch()) { + return ['success' => false, 'message' => 'Пользователь с таким email уже существует']; + } + + $passwordHash = password_hash($password, PASSWORD_DEFAULT); + + $stmt = $db->prepare(" + INSERT INTO users (email, password_hash, full_name, phone, city, is_active) + VALUES (?, ?, ?, ?, ?, TRUE) + RETURNING user_id + "); + $stmt->execute([$email, $passwordHash, $fullName, $phone, $city]); + $userId = $stmt->fetchColumn(); + + $_SESSION['user_id'] = $userId; + $_SESSION['user_email'] = $email; + $_SESSION['full_name'] = $fullName; + $_SESSION['user_phone'] = $phone; + $_SESSION['user_city'] = $city; + $_SESSION['isLoggedIn'] = true; + $_SESSION['isAdmin'] = false; + $_SESSION['login_time'] = time(); + + return ['success' => true, 'user_id' => $userId]; + + } catch (PDOException $e) { + return ['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]; + } +} + +function logoutUser(): void { + $_SESSION = []; + + if (ini_get("session.use_cookies")) { + $params = session_get_cookie_params(); + setcookie(session_name(), '', time() - 42000, + $params["path"], $params["domain"], + $params["secure"], $params["httponly"] + ); + } + + session_destroy(); +} + +function checkAdminAccess(): bool { + if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) { + return false; + } + + if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) { + return false; + } + + return true; +} diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..308ddc7 --- /dev/null +++ b/public/index.php @@ -0,0 +1,760 @@ + + + + + + + AETERNA - Мебель и Интерьер + + + + + + + + +
+
+
+ + +
+
+ Все категории + +
+ +
+ + +
+
+ +
+ +
+
+ +
+
+
+
+
+ Кресло и торшер +
+
+

ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР

+

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

+ + + ПЕРЕЙТИ В КАТАЛОГ + + ПЕРЕЙТИ В КАТАЛОГ + +
+
+
+ +
+
+
+

ПОЧЕМУ
ВЫБИРАЮТ НАС?

+
+
+ 1 +

ГАРАНТИЯ ВЫСОЧАЙШЕГО КАЧЕСТВА

+

Собственное производство и строгий контроль на всех этапах.

+
+
+ 2 +

ИСПОЛЬЗОВАНИЕ НАДЕЖНЫХ МАТЕРИАЛОВ

+

Гарантия безопасности и долговечности.

+
+
+ 3 +

ИНДИВИДУАЛЬНЫЙ ПОДХОД И ГИБКОСТЬ УСЛОВИЙ

+

Реализуем проекты любой сложности по вашим техническим заданиям.

+
+
+
+ +
+
+ Кровать и тумба +
+

НОВИНКИ В КАТЕГОРИЯХ
МЯГКАЯ МЕБЕЛЬ

+ ПЕРЕЙТИ +
+
+
+ Диван в гостиной +
+

РАСПРОДАЖА
ПРЕДМЕТЫ ДЕКОРА

+ ПЕРЕЙТИ +
+
+
+
+
+ +
+
+
+
+

О НАС

+

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

+
+ Фиолетовое кресло +
+ +
+ Белый диван с подушками +

Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.

+
+
+
+ +
+
+
+
+
+ Готовое решение для гостиной +
+

ГОТОВОЕ РЕШЕНИЕ
ДЛЯ ВАШЕЙ ГОСТИНОЙ


+

УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС

+
+ Подробнее +
+ +
+ Готовое решение для спальни +
+

ГОТОВОЕ РЕШЕНИЕ
ДЛЯ ВАШЕЙ СПАЛЬНИ


+

УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС

+
+ Подробнее +
+
+
+
+
+ +
+
+
+
+
10+
+
Лет работы
+
+
+
30 000+
+
Довольных покупателей
+
+
+
4500+
+
Реализованных заказов
+
+
+
+
+ +
+
+

ОТВЕТЫ НА ВОПРОСЫ

+
+
+ 1 +
+

Сколько времени занимает доставка?

+

Доставка готовых позиций занимает 1-3 дня. Мебель на заказ изготавливается от 14 до 45 рабочих дней, в зависимости от сложности. Точные сроки озвучит ваш менеджер при оформлении заказа.

+
+
+
+ 2 +
+

Нужно ли вносить предоплату?

+

Да, для запуска заказа в производство необходима предоплата в размере 50-70% от стоимости, в зависимости от изделия. Оставшаяся сумма оплачивается при доставке и приемке мебели.

+
+
+
+ 3 +
+

Предоставляется ли рассрочка или кредит?

+

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

+
+
+
+ 4 +
+

Что делать, если мебель пришла с дефектом?

+

В этом случае необходимо в течение 7 дней со дня доставки сообщить нам о проблеме, прислать фото/видео дефекта. Мы оперативно решим вопрос о бесплатной замене или ремонте изделия.

+
+
+
+ +
+
+
+ + + + + + + + + + + + + +
+

Быстрый вход:

+ +
+ + + + \ No newline at end of file diff --git a/вход.php b/public/login.php similarity index 73% rename from вход.php rename to public/login.php index bb358b8..fb62884 100644 --- a/вход.php +++ b/public/login.php @@ -1,305 +1,295 @@ - - - - - - AETERNA - Вход - - - - - - - - - - - - -
-
-
- - -
-
- Все категории -
-
    -
  • Диваны
  • -
  • Кровати
  • -
  • Шкафы
  • -
  • Стулья
  • -
  • Столы
  • -
  • Комоды
  • -
  • Тумбы
  • -
  • Полки
  • -
  • Стенки
  • -
  • Аксессуары
  • -
-
-
- -
- -
- - -
-
-
- -
- -
-
- -
-
-
- -
- -
-
-

ВХОД В АККАУНТ

-
-
- - -
- Введите корректный email адрес -
-
- -
- - -
Неверный пароль
-
- -
- - Забыли пароль? -
- - - -
- Нет аккаунта? - -
-
-
-
-
-
- - -
-
-
-
-
- - - -// В файле вход.html добавьте: - - - - + + + + + + + AETERNA - Вход + + + + + + + + + + + + +
+
+
+ + +
+
+ Все категории +
+
    +
  • Диваны
  • +
  • Кровати
  • +
  • Шкафы
  • +
  • Стулья
  • +
  • Столы
  • +
  • Комоды
  • +
  • Тумбы
  • +
  • Полки
  • +
  • Стенки
  • +
  • Аксессуары
  • +
+
+
+ +
+ +
+ + +
+
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+

ВХОД В АККАУНТ

+
+
+ + +
+ Введите корректный email адрес +
+
+ +
+ + +
Неверный пароль
+
+ +
+ + Забыли пароль? +
+ + + +
+ Нет аккаунта? + +
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + diff --git a/public/logout.php b/public/logout.php new file mode 100644 index 0000000..02ef1c7 --- /dev/null +++ b/public/logout.php @@ -0,0 +1,17 @@ +getConnection(); - -try { - // Получаем информацию о товаре - $productStmt = $db->prepare(" - SELECT - p.*, - c.name as category_name, - c.slug as category_slug - FROM products p - LEFT JOIN categories c ON p.category_id = c.category_id - WHERE p.product_id = ? AND p.is_available = TRUE - "); - $productStmt->execute([$product_id]); - $product = $productStmt->fetch(); - - if (!$product) { - header('Location: catalog.php?error=product_not_found'); - exit(); - } - - // Получаем похожие товары - $similarStmt = $db->prepare(" - SELECT * FROM products - WHERE category_id = ? - AND product_id != ? - AND is_available = TRUE - ORDER BY RANDOM() - LIMIT 3 - "); - $similarStmt->execute([$product['category_id'], $product_id]); - $similarProducts = $similarStmt->fetchAll(); - - // Получаем отзывы (если есть отдельная таблица reviews) - $reviewsStmt = $db->prepare(" - SELECT rating, comment, created_at - FROM reviews - WHERE product_id = ? - ORDER BY created_at DESC - LIMIT 5 - "); - $reviewsStmt->execute([$product_id]); - $reviews = $reviewsStmt->fetchAll(); - -} catch (PDOException $e) { - die("Ошибка базы данных: " . $e->getMessage()); -} - -// HTML код страницы товара -?> - - - - - - AETERNA - <?= htmlspecialchars($product['name']) ?> - - - - - - - -
-
-
- - -
-
- Все категории - -
- -
- -
- - - -
-
-
- -
- -
-
- -
- - -
- - -
-

- -
-
- = 0.5; - - for ($i = 1; $i <= 5; $i++) { - if ($i <= $fullStars) { - echo ''; - } elseif ($i == $fullStars + 1 && $hasHalfStar) { - echo ''; - } else { - echo ''; - } - } - ?> -
- - ( отзывов) -
- -
- - ₽ - - $product['price']): ?> - - ₽ - - - -% - - -
- -
- 10) { - echo ' В наличии'; - } elseif ($product['stock_quantity'] > 0) { - echo ' Осталось мало: ' . $product['stock_quantity'] . ' шт.'; - } else { - echo ' Нет в наличии'; - } - ?> -
- -
-
- Артикул: - -
-
- Категория: - -
-
- На складе: - шт. -
-
- -

- -

- - 0): ?> -
-
- - - -
- -
- - -
-
- -
- -
- - - -
- - Редактировать - - -
- -
-
- - -
-

Похожие товары

-
- -
-
- <?= htmlspecialchars($similar['name']) ?> -
-
-

-

- ₽ -

- - Подробнее - -
-
- -
-
- -
- - - - - +getConnection(); + +try { + + $productStmt = $db->prepare(" + SELECT + p.*, + c.name as category_name, + c.slug as category_slug + FROM products p + LEFT JOIN categories c ON p.category_id = c.category_id + WHERE p.product_id = ? AND p.is_available = TRUE + "); + $productStmt->execute([$product_id]); + $product = $productStmt->fetch(); + + if (!$product) { + header('Location: catalog.php?error=product_not_found'); + exit(); + } + + $similarStmt = $db->prepare(" + SELECT * FROM products + WHERE category_id = ? + AND product_id != ? + AND is_available = TRUE + ORDER BY RANDOM() + LIMIT 3 + "); + $similarStmt->execute([$product['category_id'], $product_id]); + $similarProducts = $similarStmt->fetchAll(); + + $reviews = []; + +} catch (PDOException $e) { + die("Ошибка базы данных: " . $e->getMessage()); +} + +?> + + + + + + + AETERNA - <?= htmlspecialchars($product['name']) ?> + + + + + + + +
+
+
+ + +
+
+ Все категории + +
+ +
+ +
+ + + +
+
+
+ +
+ +
+
+ +
+ + +
+ + +
+

+ +
+
+ = 0.5; + + for ($i = 1; $i <= 5; $i++) { + if ($i <= $fullStars) { + echo ''; + } elseif ($i == $fullStars + 1 && $hasHalfStar) { + echo ''; + } else { + echo ''; + } + } + ?> +
+ + ( отзывов) +
+ +
+ + ₽ + + $product['price']): ?> + + ₽ + + + -% + + +
+ +
+ 10) { + echo ' В наличии'; + } elseif ($product['stock_quantity'] > 0) { + echo ' Осталось мало: ' . $product['stock_quantity'] . ' шт.'; + } else { + echo ' Нет в наличии'; + } + ?> +
+ +
+
+ Артикул: + +
+
+ Категория: + +
+
+ На складе: + шт. +
+
+ +

+ +

+ + 0): ?> +
+
+ + + +
+ +
+ + +
+
+ +
+ +
+ + + +
+ + Редактировать + + +
+ +
+
+ + +
+

Похожие товары

+
+ +
+
+ <?= htmlspecialchars($similar['name']) ?> +
+
+

+

+ ₽ +

+ + Подробнее + +
+
+ +
+
+ +
+ + + + + \ No newline at end of file diff --git a/public/profile.php b/public/profile.php new file mode 100644 index 0000000..065f18c --- /dev/null +++ b/public/profile.php @@ -0,0 +1,261 @@ +getConnection(); + +$user = null; +try { + $stmt = $db->prepare("SELECT user_id, email, full_name, phone, city FROM users WHERE user_id = ?"); + $stmt->execute([$user_id]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); +} catch (PDOException $e) { + $error = "Ошибка загрузки данных: " . $e->getMessage(); +} + +if (!$user) { + header('Location: login.php?error=user_not_found'); + exit(); +} + +$update_errors = $_SESSION['update_errors'] ?? []; +$update_success = $_SESSION['update_success'] ?? ''; +unset($_SESSION['update_errors']); +unset($_SESSION['update_success']); + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $full_name = trim($_POST['fio'] ?? ''); + $city = trim($_POST['city'] ?? ''); + $phone = trim($_POST['phone'] ?? ''); + $password = $_POST['password'] ?? ''; + $confirm_password = $_POST['confirm-password'] ?? ''; + + $errors = []; + + if (empty($full_name) || strlen($full_name) < 3) { + $errors[] = 'ФИО должно содержать минимум 3 символа'; + } + + if (empty($city) || strlen($city) < 2) { + $errors[] = 'Введите корректное название города'; + } + + if (empty($phone) || !preg_match('/^(\+7|8)[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/', $phone)) { + $errors[] = 'Введите корректный номер телефона'; + } + + if (!empty($password)) { + if (strlen($password) < 6) { + $errors[] = 'Пароль должен содержать минимум 6 символов'; + } + if ($password !== $confirm_password) { + $errors[] = 'Пароли не совпадают'; + } + } + + if (empty($errors)) { + try { + if (!empty($password)) { + $password_hash = password_hash($password, PASSWORD_DEFAULT); + $stmt = $db->prepare(" + UPDATE users + SET full_name = ?, phone = ?, city = ?, password_hash = ?, updated_at = CURRENT_TIMESTAMP + WHERE user_id = ? + "); + $stmt->execute([$full_name, $phone, $city, $password_hash, $user_id]); + } else { + $stmt = $db->prepare(" + UPDATE users + SET full_name = ?, phone = ?, city = ?, updated_at = CURRENT_TIMESTAMP + WHERE user_id = ? + "); + $stmt->execute([$full_name, $phone, $city, $user_id]); + } + + $_SESSION['full_name'] = $full_name; + $_SESSION['user_phone'] = $phone; + $_SESSION['user_city'] = $city; + + $_SESSION['update_success'] = 'Профиль успешно обновлен!'; + header('Location: profile.php'); + exit(); + } catch (PDOException $e) { + $errors[] = 'Ошибка обновления: ' . $e->getMessage(); + } + } + + if (!empty($errors)) { + $_SESSION['update_errors'] = $errors; + header('Location: profile.php'); + exit(); + } +} +?> + + + + + + + AETERNA - Мой профиль + + + + + + + + + +
+ +
+

Ошибки:

+ +
+ + + +
+ +
+ + +
+
+ +
+

Мой профиль

+

+ Управляйте своими данными и настройками аккаунта +

+
+
+ +
+
+

РЕДАКТИРОВАНИЕ ПРОФИЛЯ

+
+
+ + + E-mail нельзя изменить +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+
+
+ + + + + + + + diff --git a/профиль.php b/public/register.php similarity index 84% rename from профиль.php rename to public/register.php index ba3aab2..e69f5ba 100644 --- a/профиль.php +++ b/public/register.php @@ -1,778 +1,742 @@ -getConnection(); - - try { - // Проверяем, существует ли пользователь - $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?"); - $checkStmt->execute([$email]); - - if (!$checkStmt->fetch()) { - // Создаем пользователя - $password_hash = password_hash($password, PASSWORD_DEFAULT); - $is_admin = ($_GET['quick_register'] == 'admin'); - - $stmt = $db->prepare(" - INSERT INTO users (email, password_hash, full_name, phone, city, is_admin) - VALUES (?, ?, ?, ?, ?, ?) - RETURNING user_id - "); - - $stmt->execute([$email, $password_hash, $full_name, $phone, $city, $is_admin]); - $user_id = $stmt->fetchColumn(); - } else { - // Получаем существующего пользователя - $stmt = $db->prepare("SELECT user_id, is_admin FROM users WHERE email = ?"); - $stmt->execute([$email]); - $user = $stmt->fetch(); - $user_id = $user['user_id']; - $is_admin = $user['is_admin']; - } - - // Авторизуем - $_SESSION['user_id'] = $user_id; - $_SESSION['user_email'] = $email; - $_SESSION['full_name'] = $full_name; - $_SESSION['user_phone'] = $phone; - $_SESSION['user_city'] = $city; - $_SESSION['isLoggedIn'] = true; - $_SESSION['isAdmin'] = $is_admin; - $_SESSION['login_time'] = time(); - - header('Location: catalog.php'); - exit(); - - } catch (Exception $e) { - $registration_errors[] = 'Ошибка быстрой регистрации: ' . $e->getMessage(); - } -} -?> - - - - - - AETERNA - Регистрация - - - - - - - - - -
-
-
- - -
-
- Все категории - -
- -
- - -
-
- -
- -
-
- -
- -
-

Ошибки регистрации:

- -
- - - -
- -
- - -
- Для доступа к каталогу и оформления заказов необходимо зарегистрироваться -
- -
-
- -
-

Присоединяйтесь к нам

-

- Создайте аккаунт чтобы получить доступ ко всем функциям: -

-
    -
  • Доступ к каталогу товаров
  • -
  • Добавление товаров в корзину
  • -
  • Оформление заказов
  • -
  • История покупок
  • -
  • Специальные предложения
  • -
-
-
- -
-
-

РЕГИСТРАЦИЯ

-
-
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
- - - -
- -
- - -
- - - Уже есть аккаунт? Войти - - - - -
- После регистрации вы будете перенаправлены в каталог -
-
-
-
-
-
- - -
- - - -
- - - - -
- - -
- - - - +getConnection(); + + try { + + $checkStmt = $db->prepare("SELECT user_id FROM users WHERE email = ?"); + $checkStmt->execute([$email]); + + if (!$checkStmt->fetch()) { + + $password_hash = password_hash($password, PASSWORD_DEFAULT); + $is_admin = ($_GET['quick_register'] == 'admin'); + + $stmt = $db->prepare(" + INSERT INTO users (email, password_hash, full_name, phone, city, is_admin) + VALUES (?, ?, ?, ?, ?, ?) + RETURNING user_id + "); + + $stmt->execute([$email, $password_hash, $full_name, $phone, $city, $is_admin]); + $user_id = $stmt->fetchColumn(); + } else { + + $stmt = $db->prepare("SELECT user_id, is_admin FROM users WHERE email = ?"); + $stmt->execute([$email]); + $user = $stmt->fetch(); + $user_id = $user['user_id']; + $is_admin = $user['is_admin']; + } + + $_SESSION['user_id'] = $user_id; + $_SESSION['user_email'] = $email; + $_SESSION['full_name'] = $full_name; + $_SESSION['user_phone'] = $phone; + $_SESSION['user_city'] = $city; + $_SESSION['isLoggedIn'] = true; + $_SESSION['isAdmin'] = $is_admin; + $_SESSION['login_time'] = time(); + + header('Location: catalog.php'); + exit(); + + } catch (Exception $e) { + $registration_errors[] = 'Ошибка быстрой регистрации: ' . $e->getMessage(); + } +} +?> + + + + + + + AETERNA - Регистрация + + + + + + + + + +
+
+
+ + +
+
+ Все категории + +
+ +
+ + +
+
+ +
+ +
+
+ +
+ +
+

Ошибки регистрации:

+ +
+ + + +
+ +
+ + +
+ Для доступа к каталогу и оформления заказов необходимо зарегистрироваться +
+ +
+
+ +
+

Присоединяйтесь к нам

+

+ Создайте аккаунт чтобы получить доступ ко всем функциям: +

+
    +
  • Доступ к каталогу товаров
  • +
  • Добавление товаров в корзину
  • +
  • Оформление заказов
  • +
  • История покупок
  • +
  • Специальные предложения
  • +
+
+
+ +
+
+

РЕГИСТРАЦИЯ

+
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + +
+ + + Уже есть аккаунт? Войти + + + + +
+ После регистрации вы будете перенаправлены в каталог +
+
+
+
+
+
+ +
+ + + +
+ + + +
+ + +
+ + + + \ No newline at end of file diff --git a/услуги.php b/public/services.php similarity index 84% rename from услуги.php rename to public/services.php index 9c05bc6..d71317c 100644 --- a/услуги.php +++ b/public/services.php @@ -1,113 +1,109 @@ - - - - - - - AETERNA - Услуги - - - - - - - - - - - -
-
-
-
-
-

ДОСТАВКА

-

Стоимость доставки зависит от таких факторов, как: вес, адрес, удаленность от города, дата

-
-
-

СБОРКА

-

Стоимость сборки рассчитывается индивидуально, так как на цену влияет несколько факторов

-
-
- -
-

ДИЗАЙН‑ПРОЕКТ

-

Предоставляем услугу по составлению дизайн‑проекта. Учитываем индивидуальные пожелания каждого клиента. Работаем с интерьерами различной сложности.

-
-
-
-
-
- - - - + + + + + + + + AETERNA - Услуги + + + + + + + + + +
+
+
+
+
+

ДОСТАВКА

+

Стоимость доставки зависит от таких факторов, как: вес, адрес, удаленность от города, дата

+
+
+

СБОРКА

+

Стоимость сборки рассчитывается индивидуально, так как на цену влияет несколько факторов

+
+
+ +
+

ДИЗАЙН‑ПРОЕКТ

+

Предоставляем услугу по составлению дизайн‑проекта. Учитываем индивидуальные пожелания каждого клиента. Работаем с интерьерами различной сложности.

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/style_for_cite.less b/public/style_for_cite.less similarity index 91% rename from style_for_cite.less rename to public/style_for_cite.less index 3a52d85..289aae7 100644 --- a/style_for_cite.less +++ b/public/style_for_cite.less @@ -1,3178 +1,3344 @@ -@import "mixins.less"; -@import "стили_оформления.less"; -// ======================= -// === БАЗОВЫЕ СТИЛИ === -// ======================= -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, body { - height: 100%; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: @font-main; - background-color: @color-secondary; - color: @color-text-dark; - line-height: 1.6; - display: flex; - flex-direction: column; - min-height: 100vh; -} - -.container { - max-width: 1210px; - margin: 0 auto; - padding: 0 20px; -} - -ul { - list-style: none; -} - -a { - text-decoration: none; - color: inherit; - transition: all 0.3s ease; -} - -h1, h2, h3, h4, h5, h6 { - font-family: @font-heading; - margin: 0; -} - -p, li, span { - font-family: @font-main; -} - -// ======================= -// === КОМПОНЕНТЫ === -// ======================= - -.logo, .footer-logo { - font: bold 32px/1 @font-logo; - letter-spacing: 2px; - text-shadow: @shadow-dark; - flex-shrink: 0; -} - -.btn { - padding: 12px 30px; - border: none; - cursor: pointer; - font-size: 14px; - text-transform: uppercase; - transition: all 0.3s ease; - font-family: @font-main; - - &.primary-btn { - background-color: @color-button; - color: @color-text-light; - - &:hover { - background-color: lighten(@color-button, 10%); - transform: translateY(-2px); - box-shadow: @shadow-light; - } - } -} - -.number-circle { - .flex-center(); - width: 28px; - height: 28px; - border-radius: 50%; - background-color: @color-button; - color: @color-text-light; - font-size: 16px; - font-weight: bold; - flex-shrink: 0; -} - -.breadcrumbs { - font-size: 14px; - margin-bottom: 20px; - color: #666; - - a { - color: #666; - opacity: 0.7; - &:hover { opacity: 1; } - } - - .current-page { - font-weight: bold; - color: @color-text-dark; - } -} - -// ======================= -// === ШАПКА САЙТА === -// ======================= -.header { - background-color: @color-secondary; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); - z-index: 1000; - - &__top, &__bottom { - padding: 15px 0; - .container { - .flex-between(); - gap: 20px; - } - } - - &__bottom { - padding: 10px 0; - border-top: 1px solid rgba(0, 0, 0, 0.05); - - .catalog-link.active-catalog { - background-color: rgba(0, 0, 0, 0.08); - pointer-events: none; - } - } - - .search-catalog { - .flex-center(); - border: 2px solid @color-text-dark; - background-color: #fff; - max-width: 600px; - width: 100%; - margin: 0 auto; - overflow: hidden; - - .catalog-dropdown { - position: relative; - background-color: #f8f8f8; - padding: 10px 15px 10px 25px; - font-size: 18px; - cursor: pointer; - border-right: 1px solid @color-text-dark; - .flex-center(10px); - width: 200px; - flex-shrink: 0; - - &__menu { - .menu-base(); - li { - padding: 8px 0; - cursor: pointer; - transition: color 0.3s; - border-bottom: 1px solid #f0f0f0; - &:last-child { border-bottom: none; } - &:hover { color: @color-accent; } - } - } - &:hover &__menu { display: block; } - } - - .search-box { - .flex-center(); - padding: 0 15px; - flex-grow: 1; - position: relative; - font-size: 15px; - - input { - border: none; - padding: 10px 30px 10px 0; - outline: none; - font-size: 16px; - width: 100%; - text-align: left; - } - - .search-icon { - font-size: 20px; - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - } - }} - - &__icons--top { - .flex-center(15px); - flex-shrink: 0; - .icon { .icon-base(); font-size: 20px;} - } - - .nav-list { - .flex-center(30px); - font-size: 18px; - a { - text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); - &:hover { text-shadow: @shadow-dark; } - &.active { - border-bottom: 2px solid @color-button; - padding-bottom: 5px; - text-shadow: @shadow-dark; - } - &[href="#footer"] { - cursor: pointer; - } - } - } - - .catalog-link { - .flex-center(10px); - border-radius: 4px; - white-space: nowrap; - font-size: 18px; - padding: 10px 18px; - &:hover { background-color: rgba(0, 0, 0, 0.05); } - } - - .header-phone { - font-weight: bold; - color: @color-button; - flex-shrink: 0; - } -} - -// ======================= -// === ОСНОВНЫЕ СЕКЦИИ === -// ======================= -.hero { - padding: 15px 0; - - &__content { - .flex-center(50px); - min-height: 60vh; - align-items: center; - } - - &__image-block { - position: relative; - flex: 0 0 40%; - max-width: 600px; - height: 600px; - .flex-center(); - - .hero__circle { - position: absolute; - width: 450px; - height: 450px; - background-color: @color-primary; - border-radius: 50%; - z-index: 1; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - } - - .hero__img { - position: relative; - width: 100%; - height: 100%; - object-fit: contain; - z-index: 2; - } - } - - &__text-block { - flex: 0 0 60%; - padding-left: 50px; - - h1 { - font-size: 42px; - font-weight: normal; - margin-bottom: 25px; - line-height: 1.3; - } - - .hero__usp-text { - position: relative; - padding-left: 50px; - margin-bottom: 35px; - line-height: 1.7; - .flex-center(); - justify-content: flex-start; - min-height: 40px; - font-size: 16px; - - &::before { - content: "✓"; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); - width: 32px; - height: 32px; - border: 2px solid @color-button; - background-color: transparent; - color: @color-button; - border-radius: 50%; - .flex-center(); - font-size: 16px; - font-weight: bold; - } - } - - .btn.primary-btn { - margin: 25px 0 0 50px; - padding: 14px 35px; - font-size: 15px; - } - } -} - -.advantages { - padding: 30px 0 40px; - - &__header { - display: flex; - align-items: center; - gap: 50px; - margin-bottom: 40px; - h2 { - font-size: 32px; - font-weight: normal; - flex: 0 0 30%; - } - } - - &__items { - flex: 0 0 70%; - display: flex; - gap: 30px; - } - - .advantage-item { - flex: 1; - text-align: left; - position: relative; - padding-top: 30px; - - &__number { - .number-circle(); - position: absolute; - top: 0; - left: 0; - } - - h4 { - font-weight: 600; - margin-bottom: 10px; - } - } -} - -.promo-images { - display: flex; - gap: 20px; - margin-top: 50px; - - .promo-image-col { - position: relative; - overflow: hidden; - border-radius: 8px; - flex: 1; - transition: all 0.3s ease; - - &:hover { - transform: translateY(-5px); - box-shadow: @shadow-light; - .image-overlay-text { background-color: rgba(0, 0, 0, 0.6); } - img { transform: scale(1.05); } - .image-overlay-text h4, - .image-overlay-text .overlay-link { transform: translateY(0); } - } - - img { - width: 100%; - height: 350px; - object-fit: cover; - display: block; - transition: transform 0.5s ease; - } - - .image-overlay-text { - .image-overlay(); - h4 { - font-size: 24px; - text-transform: uppercase; - line-height: 1.2; - margin-bottom: 15px; - transform: translateY(20px); - transition: transform 0.3s ease; - } - } - - .overlay-link { - display: inline-block; - text-transform: uppercase; - font-weight: bold; - border-radius: 3px; - margin-top: 15px; - padding: 10px 25px; - background-color: @color-button; - color: @color-text-light; - font-size: 12px; - transform: translateY(20px); - transition: all 0.3s ease; - - &:hover { - background-color: lighten(@color-button, 10%); - transform: translateY(-2px); - box-shadow: @shadow-light; - } - } - } -} - -.about { - padding: 40px 0 80px; - - &__content { - display: flex; - align-items: flex-start; - gap: 50px; - } - - &__column { - display: flex; - flex-direction: column; - gap: 20px; - &--left { flex: 0 0 40%; margin-bottom: 30px; } - &--right { - flex: 0 0 60%; - .about__caption { - padding-right: 50px; - } - } - } - - &__text-block { - margin-bottom: 30px; - h2 { margin-bottom: 15px; } - } - - &__img { - width: 93%; - object-fit: cover; - display: block; - &--small { height: 300px; } - &--large { height: 450px; } - } - - .text-justified { - text-align: justify; - color: #555; - } -} - -.solutions { - padding: 0; - background-color: @color-secondary; - - &-slider { - position: relative; - width: 100%; - max-width: 1200px; - margin: 40px auto; - border-radius: 8px; - overflow: hidden; - - &__slides { - display: flex; - width: 200%; - height: 100%; - animation: slideLeftRight 10s infinite ease-in-out; - } - - &__slide { - width: 50%; - flex-shrink: 0; - position: relative; - overflow: hidden; - transition: transform 0.5s ease, box-shadow 0.5s ease; - - &:hover { - transform: scale(1.02); - box-shadow: 0 10px 25px rgba(0,0,0,0.3); - .solution-img { - transform: scale(1.05); - filter: brightness(0.8); - } - .solution-text-overlay { - opacity: 1; - transform: translateY(-5px); - } - .solution-image-link { - transform: translateX(-50%) translateY(-6px); - background-color: rgba(255,255,255,0.9); - color: @color-text-dark; - } - } - } - - .solution-img { - width: 100%; - height: auto; - object-fit: cover; - display: block; - } - - .solution-text-overlay { - position: absolute; - top: 15%; - left: 8%; - color: #493131; - text-shadow: 2px 2px 4px rgba(0,0,0,0.6); - z-index: 2; - opacity: 0.9; - transition: opacity 0.5s ease, transform 0.5s ease; - h2 { - font-size: 35px; - text-transform: uppercase; - margin-bottom: 10px; - } - p { - font-size: 25px; - text-transform: uppercase; - } - } - - .solution-image-link { - position: absolute; - bottom: 40px; - left: 50%; - transform: translateX(-50%); - padding: 12px 30px; - border: 2px solid @color-text-light; - color: #493131; - text-transform: uppercase; - font-size: 16px; - font-weight: bold; - background: transparent; - transition: 0.4s ease; - z-index: 2; - &:hover { - background: @color-text-light; - color: @color-text-dark; - transform: translateX(-50%) translateY(-2px); - } - } - } -} - -@keyframes slideLeftRight { - 0%, 40% { transform: translateX(0); } - 50%, 90% { transform: translateX(-50%); } - 100% { transform: translateX(0); } -} - -.stats { - padding: 0; - margin-top: 20px; - - .container { - display: flex; - justify-content: flex-end; - } - - &__items { - display: flex; - gap: 20px; - .stat-item { - text-align: left; - .stat-number { - font-size: 36px; - font-weight: bold; - color: @color-text-dark; - margin-bottom: 5px; - } - .stat-label { color: @color-text-dark; } - } - } -} - -.faq { - padding: 50px 0; - - h2 { - text-align: left; - font-size: 32px; - font-weight: normal; - margin-bottom: 40px; - } - - &__items { - display: flex; - flex-wrap: wrap; - gap: 40px 60px; - margin-bottom: 40px; - } - - .faq-item { - flex: 0 0 calc(50% - 30px); - .flex-center(15px); - align-items: flex-start; - &__content h4 { - font-weight: 600; - margin-bottom: 10px; - } - } - - .btn.primary-btn { - display: block; - width: 100%; - margin: 20px auto 80px; - } -} - -// ======================= -// === СТИЛИ КАТАЛОГА === -// ======================= -.catalog-main { - padding: 30px 0 60px; - background-color: lighten(@color-secondary, 5%); -} - -.catalog-wrapper { - display: flex; - gap: 20px; -} - -.catalog-sidebar { - flex: 0 0 250px; - background-color: #fff; - padding: 20px; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); - height: fit-content; -} - -.filter-group { - margin-bottom: 30px; -} - -.filter-title { - font-size: 16px; - font-weight: bold; - margin-bottom: 15px; - text-transform: uppercase; -} - -.filter-list li { - padding: 5px 0; - font-size: 16px; - a { - color: #555; - transition: color 0.2s; - &:hover { color: @color-accent; } - &.active-category { - font-weight: bold; - color: @color-primary; - } - } -} - -.price-range { - display: flex; - flex-direction: column; - gap: 15px; - width: 100%; - - .range-slider { - width: 100%; - - input[type="range"] { - -webkit-appearance: none; - appearance: none; - width: 100%; - height: 5px; - background: @color-primary; - border-radius: 5px; - outline: none; - margin: 0; - - &::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 20px; - height: 20px; - background: @color-accent; - border: 2px solid #fff; - border-radius: 50%; - cursor: pointer; - box-shadow: 0 2px 4px rgba(0,0,0,0.2); - transition: all 0.3s ease; - - &:hover { - transform: scale(1.1); - background: lighten(@color-accent, 10%); - } - } - - &::-moz-range-thumb { - width: 20px; - height: 20px; - background: @color-accent; - border: 2px solid #fff; - border-radius: 50%; - cursor: pointer; - box-shadow: 0 2px 4px rgba(0,0,0,0.2); - transition: all 0.3s ease; - - &:hover { - transform: scale(1.1); - background: lighten(@color-accent, 10%); - } - } - } - } - - .price-display { - font-size: 14px; - font-weight: bold; - text-align: center; - color: @color-text-dark; - padding: 10px; - background: #f8f8f8; - border-radius: 4px; - } -} - -.filter-options { - list-style: none; - li { - display: flex; - align-items: center; - padding: 4px 0; - font-size: 14px; - } - label { - margin-left: 10px; - cursor: pointer; - color: #555; - } - input[type="checkbox"] { - width: 15px; - height: 15px; - cursor: pointer; - accent-color: @color-primary; - &:checked + label { - font-weight: bold; - color: @color-primary; - } - } -} - -.filter-apply-btn { - width: 100%; - margin-top: 20px; -} - -.catalog-products { - flex-grow: 1; -} - -.products-container { - display: flex; - flex-wrap: wrap; - gap: 20px; -} - -.product-card { - background-color: #fff; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); - display: flex; - flex-direction: column; - position: relative; - transition: transform 0.3s ease; - box-sizing: border-box; - - &:hover { - transform: translateY(-5px); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); - .product-img { transform: scale(1.05); } - } -} - -.product-image-container { - position: relative; - overflow: hidden; - margin-bottom: 0; - padding: 0; - height: 250px; - .flex-center(); -} - -.product-img { - width: 100%; - height: 100%; - object-fit: contain; - display: block; - transition: transform 0.3s ease; - margin: 0; -} - -.product-img1 { - width: 100%; - height: 100%; - object-fit: cover; - display: block; - transition: transform 0.3s ease; - margin: 0; -} - -.product-discount { - position: absolute; - top: 10px; - right: 10px; - background-color: @color-button; - color: @color-text-light; - padding: 3px 8px; - font-size: 12px; - font-weight: bold; - z-index: 10; -} - -.product-wishlist-icon { - position: absolute; - top: 10px; - left: 10px; - color: #333; - font-size: 18px; - cursor: pointer; - transition: color 0.3s ease; - z-index: 10; - &:hover { color: @color-accent; } -} - -.product-name { - font-size: 16px; - font-weight: bold; - margin-bottom: 5px; -} - -.product-details { - font-size: 13px; - color: #777; - margin-bottom: 10px; - flex-grow: 1; -} - -.product-price { - font-size: 18px; - font-weight: bold; - color: @color-button; -} - -.product-card.small { flex: 0 0 300px; max-width: 300px; height: 200px; } -.product-card.small1 { flex: 0 0 320px; max-width: 320px; height: 250px;width: 320px; } -.product-card.large { flex: 0 0 580px; max-width: 580px; height: 380px; } -.product-card.wide { flex: 0 0 240px; max-width: 240px; height: 250px; } -.product-card.wide1 { flex: 0 0 350px; max-width: 350px; height: 250px; } -.product-card.wide2 { flex: 0 0 560px; max-width: 560px; height: 260px; } -.product-card.wide2_1 { flex: 0 0 560px; max-width: 560px; height: 260px; margin: -280px 0 0; } -.product-card.wide3 { - flex: 0 0 320px; max-width: 320px; height: 540px; - .product-image-container { height: 580px; } -} -.product-card.wide4 { - flex: 0 0 545px; max-width: 545px; margin: -270px 0 0; height: 250px; - .product-image-container { padding: 0; justify-content: flex-start; } - .product-img { margin-left: 0; align-self: flex-start; object-position: left center; } -} -.product-card.tall { flex: 0 0 300px; max-width: 300px; margin: -180px 0 0; height: 430px; } -.product-card.full-width { flex: 0 0 100%; margin: -20px 0 0; max-width: 900px; height: 300px;} - -.product-card.full-width { - flex: 0 0 100%; - max-width: 100%; - height: 300px; - - .product-image-container { - height: 100%; - padding: 0; - margin: 0; - - .product-img1 { - width: 100%; - height: 100%; - object-fit: cover; - margin: 0; - padding: 0; - } - } -} - -.product-card.tall .product-image-container, -.product-card.large .product-image-container { height: 430px; } - -// ======================= -// === СТРАНИЦА ТОВАРА === -// ======================= -.product__section { - display: flex; - gap: 0; - margin: 30px 0; -} - -.product__gallery, .product__info { - flex: 1; -} - -.product__main-image { - margin-bottom: 15px; -} - -.product__image { - width: 500px; - height: 300px; - border-radius: 4px; -} - -.product__thumbnails { - display: flex; - gap: 10px; -} - -.product__thumbnail { - border: none; - background: none; - cursor: pointer; - padding: 0; -} - -.product__thumbnail img { - width: 245px; - height: 150px; - object-fit: cover; - border-radius: 4px; -} - -.product__title { - font-size: 30px; - margin-bottom: 35px; -} - -.product__rating { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 30px; -} - -.product__color-selector { - display: flex; - gap: 10px; - margin-bottom: 40px; -} - -.product__color-option { - width: 45px; - height: 45px; - border-radius: 50%; - border: 2px solid transparent; - cursor: pointer; - transition: transform 0.3s ease; - - &:hover{ - transform: translateY(-2px); - } -} - -.product__color-option.active { - border-color: @color-primary; -} - -.product__description { - margin-bottom: 65px; - line-height: 1.5; -} - -.product__details-link { - display: inline-block; - margin-bottom: 20px; - color: @color-primary; - font-weight: bold; -} - -.product__purchase { - display: flex; - justify-content: space-between; - margin-bottom: 35px; -} - -.product__price { - font-size: 24px; - font-weight: bold; -} - -.product__quantity { - display: flex; - align-items: center; - gap: 10px; -} - -.product__qty-btn { - width: 30px; - height: 30px; - background: @color-button; - color: @color-text-light; - border: none; - border-radius: 50%; - cursor: pointer; - font-weight: bold; - transition: all 0.3s ease; - - &:hover { - background: lighten(@color-button, 10%); - transform: scale(1.1); - } -} - -.product__qty-value { - font-weight: bold; - min-width: 30px; - text-align: center; -} - -.product__actions { - display: flex; - gap: 15px; -} - -.product__btn { - flex: 1; - padding: 12px 20px; - border: none; - border-radius: 4px; - font-weight: bold; - cursor: pointer; - transition: all 0.3s ease; - - &:hover { - transform: translateY(-2px); - box-shadow: @shadow-light; - } -} - -.product__btn.primary { - background: @color-button; - color: @color-text-light; - - &:hover { - background: lighten(@color-button, 10%); - } -} - -.product__btn.secondary { - background: transparent; - border: 1px solid @color-button; - color: @color-button; - - &:hover { - background: @color-button; - color: @color-text-light; - } -} - -.similar { - margin: 60px 0; -} - -.similar__title { - margin-bottom: 30px; - font-size: 28px; - font-weight: bold; -} - -.similar__grid { - display: flex; - gap: 25px; - flex-wrap: wrap; - justify-content: space-between; -} - -.similar__card { - flex: 0 0 calc(33.333% - 17px); - min-width: 320px; - background: @color-secondary; - border-radius: 12px; - overflow: hidden; - transition: all 0.3s ease; - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); - - &:hover { - transform: translateY(-8px); - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); - } -} - -.similar__card-image { - height: 300px; - overflow: hidden; - background: white; - - img { - width: 100%; - height: 100%; - object-fit: contain; - transition: transform 0.3s ease; - - &:hover { - transform: scale(1.05); - } - } -} - -.similar__card-content { - padding: 25px; -} - -.similar__card-title { - font-weight: bold; - margin-bottom: 10px; - font-size: 20px; - color: @color-text-dark; -} - -.similar__card-description { - font-size: 15px; - margin-bottom: 15px; - color: #666; - line-height: 1.5; -} - -.similar__card-price { - font-weight: bold; - font-size: 22px; - color: @color-button; -} - -@media (max-width: 1024px) { - .similar { - &__card { - flex: 0 0 calc(50% - 13px); - min-width: 280px; - } - } -} - -@media (max-width: 768px) { - .similar { - &__grid { - justify-content: center; - } - - &__card { - flex: 0 0 100%; - max-width: 400px; - } - } -} - -// ======================= -// === КОРЗИНА И ЗАКАЗ === -// ======================= -.main__content { - display: flex; - gap: 40px; - margin: 30px 0; - - .products { - flex: 1; - } - - .order { - flex: 0 0 65%; - padding: 40px; - - &__header { - .flex-between(); - margin-bottom: 20px; - } - - &__title { - font-family: @font-logo; - font-size: 28px; - color: @color-text-dark; - margin: 0; - } - - &__total { - font-weight: bold; - color: @color-text-dark; - } - - &__section { - margin-bottom: 25px; - } - - &__section-title { - font-family: @font-logo; - margin-bottom: 15px; - font-size: 18px; - color: @color-text-dark; - } - } -} - -.products { - &__title { - font-family: @font-logo; - margin-bottom: 20px; - font-size: 24px; - color: @color-text-dark; - } - - &__list { - .flex-column(); - gap: 20px; - } - - &__item { - background-color: @color-secondary; - border-radius: 8px; - padding: 20px; - display: flex; - gap: 15px; - border: 1px solid @color-secondary; - transition: transform 0.3s ease; - align-items: flex-start; - position: relative; - - &:hover { - transform: translateY(-2px); - } - } - - &__image { - width: 300px; - height: 200px; - border-radius: 4px; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: flex-start; - } - - .product-img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; - transition: transform 0.3s ease; - margin: 0; - } - - &__details { - flex: 1; - .flex-column(); - justify-content: space-between; - align-items: flex-start; - min-height: 200px; - } - - &__name { - font-weight: bold; - margin-bottom: 5px; - color: @color-accent; - font-size: 18px; - font-family: @font-main; - } - - &__price { - font-weight: bold; - font-size: 18px; - margin-bottom: 15px; - color: @color-text-dark; - } - - &__controls { - display: flex; - align-items: center; - gap: 15px; - margin-top: auto; - width: 100%; - justify-content: space-between; - } - - &__quantity { - display: flex; - align-items: center; - gap: 10px; - } - - &__qty-btn { - width: 30px; - height: 30px; - background-color: @color-text-dark; - color: @color-text-light; - border: none; - border-radius: 50%; - cursor: pointer; - .flex-center(); - font-family: @font-main; - font-weight: bold; - transition: all 0.3s ease; - - &:hover { - transform: scale(1.1); - } - } - - &__qty-value { - font-weight: bold; - min-width: 30px; - text-align: center; - font-size: 16px; - } - - &__cart-icon { - background-color: transparent; - color: @color-text-dark; - width: 40px; - height: 40px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.3s ease; - border: 2px solid @color-text-dark; - margin-left: 20px; - - &:hover { - transform: scale(1.1); - } - - i { font-size: 18px; } - } -} - -.form { - &__group { margin-bottom: 15px; } - &__label { - display: block; - margin-bottom: 5px; - font-weight: bold; - color: #000000; - } - &__input { - width: 100%; - padding: 14px 16px; - border: 2px solid #ccc; - font-family: @font-main; - font-size: 15px; - transition: border-color 0.3s ease; - - &:focus { - border-color: @color-primary; - } - - &:hover { - border-color: darken(#ccc, 10%); - } - &::placeholder { - font-style: italic; - color: #999; - } - } - &__row { - display: flex; - gap: 20px; - justify-content: space-between; - } - &__input--half { - width: 100%; - } - &__radio-group { - display: flex; - justify-content: space-between; - margin-bottom: 20px; - margin-top: 20px; - } - &__radio-label { - display: flex; - align-items: center; - cursor: pointer; - color: @color-text-dark; - position: relative; - padding-left: 30px; - flex: 1; - - &:hover { - .form__custom-radio { - border-color: lighten(@color-accent, 10%); - } - } - } - &__radio-input { - position: absolute; - opacity: 0; - cursor: pointer; - } - &__custom-radio { - position: absolute; - left: 0; - height: 20px; - width: 20px; - background-color: @color-secondary; - border: 2px solid @color-accent; - border-radius: 50%; - transition: border-color 0.3s ease; - } - &__radio-input:checked ~ &__custom-radio { - background-color: @color-accent; - - &:after { - content: ""; - position: absolute; - display: block; - top: 4px; - left: 4px; - width: 8px; - height: 8px; - border-radius: 50%; - background: white; - } - } -} - -.divider { - height: 1px; - background-color: #999; - margin: 20px 0; -} - -.promo { - display: flex; - margin-bottom: 20px; - - &__input { - flex: 1; - padding: 10px; - border: 1px solid #000; - background-color: @color-secondary; - font-family: @font-main; - height: auto; - min-height: 48px; - - &:hover { - border-color: @color-primary; - } - - &::placeholder { - font-style: italic; - color: #999; - } - } - - &__btn { - background-color: @color-accent; - color: @color-secondary; - border: none; - padding: 10px 60px; - cursor: pointer; - font-family: @font-main; - font-size: 18px; - transition: all 0.3s ease; - - &:hover { - background-color: lighten(@color-accent, 10%); - transform: translateY(-2px); - } - } -} - -.summary { - margin-bottom: 20px; - - &__item { - display: flex; - justify-content: space-between; - margin-bottom: 10px; - color: @color-text-dark; - - &.total { - font-weight: bold; - font-size: 18px; - padding-top: 10px; - margin-top: 10px; - } - } -} - -.order-btn { - width: 100%; - background-color: @color-accent; - color: @color-secondary; - border: none; - padding: 15px; - border-radius: 4px; - font-size: 18px; - cursor: pointer; - margin-bottom: 10px; - font-family: @font-main; - transition: all 0.3s ease; - - &:hover { - background-color: lighten(@color-accent, 10%); - transform: translateY(-2px); - } -} - -.privacy { - display: flex; - gap: 8px; - font-size: 16px; - color: #666; - margin-bottom: 20px; - - input[type="checkbox"] { - width: 18px; - height: 18px; - cursor: pointer; - } -} - -.services { - display: flex; - flex-direction: row; - justify-content: center; - flex-wrap: wrap; - gap: 24px; - margin-bottom: 24px; - - &__title { - font-family: @font-logo; - margin-bottom: 10px; - font-size: 18px; - color: @color-text-dark; - display: block; - width: 100%; - } - - &__item { - display: flex; - justify-content: space-between; - margin-bottom: 8px; - color: @color-text-dark; - width: 100%; - } -} - -.cart-icon { - position: relative; -} - -.cart-count { - position: absolute; - top: -8px; - right: -8px; - background: @color-accent; - color: @color-text-light; - border-radius: 50%; - width: 18px; - height: 18px; - font-size: 12px; - display: flex; - align-items: center; - justify-content: center; -} - -.form__input.error { - border-color: #ff4444; - box-shadow: 0 0 5px rgba(255, 68, 68, 0.3); -} - -.empty-cart { - text-align: center; - padding: 40px; - color: #666; - font-size: 18px; -} - -// ======================= -// === АВТОРИЗАЦИЯ === -// ======================= -.profile-page-main { - .flex-center(); - min-height: 80vh; - padding: 40px 0; - background-color: lighten(@color-secondary, 5%); - z-index: 1; - - .profile-container { - display: flex; - width: 100%; - max-width: 1000px; - min-height: 600px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); - background-color: @color-text-light; - } - - .profile-left-col { - flex: 0 0 35%; - background-color: @color-primary; - color: @color-text-light; - display: flex; - justify-content: flex-start; - align-items: flex-start; - padding: 40px; - .logo { - font-size: 32px; - font-weight: normal; - text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4); - color: @color-text-light; - } - } - - .profile-right-col { - flex: 0 0 65%; - .flex-center(); - padding: 40px; - .profile-form-block { - width: 100%; - max-width: 400px; - h2 { - font-size: 28px; - font-weight: normal; - margin-bottom: 40px; - text-align: left; - color: @color-text-dark; - } - } - } - - .profile-form { - .input-group { - margin-bottom: 20px; - label { - display: block; - font-size: 12px; - font-weight: bold; - color: @color-text-dark; - margin-bottom: 5px; - text-transform: uppercase; - } - } - - input[type="text"], - input[type="email"], - input[type="tel"] { - .input-base(); - } - - .password-link { - display: block; - text-align: left; - font-size: 13px; - color: @color-text-dark; - text-decoration: underline; - margin: 10px 0 20px; - &:hover { - color: @color-accent; - text-decoration: none; - } - } - - .save-btn { - padding: 15px 30px; - border: none; - cursor: pointer; - font-size: 15px; - text-transform: uppercase; - transition: all 0.3s ease; - font-family: @font-main; - width: 100%; - margin-top: 20px; - background-color: @color-primary; - color: @color-text-light; - - &:hover { - background-color: lighten(@color-primary, 10%); - transform: translateY(-2px); - box-shadow: @shadow-light; - } - } - - .auth-actions { - display: flex; - justify-content: space-between; - align-items: center; - margin-top: 25px; - padding-top: 20px; - border-top: 1px solid #eee; - - .auth-text { - font-size: 13px; - color: @color-text-dark; - } - - .login-btn { - background-color: transparent; - color: @color-accent; - border: 1px solid @color-accent; - padding: 10px 25px; - font-size: 13px; - cursor: pointer; - transition: all 0.3s ease; - - &:hover { - background-color: @color-primary; - color: @color-text-light; - } - } - } - } -} - -// ======================= -// === СЕКЦИЯ УСЛУГ === -// ======================= -.services-section { - padding: 60px 0; - background-color: @color-secondary; -} - -.services__wrapper { - display: flex; - flex-direction: column; - gap: 30px; -} - -.services__top-row { - display: flex; - gap: 30px; - justify-content: center; - - @media (max-width: 768px) { - flex-direction: column; - align-items: center; - } -} - -.service-card { - border-radius: 8px; - padding: 40px; - min-height: 200px; - text-align: center; - transition: all 0.3s ease; - - &:hover { - transform: translateY(-5px); - box-shadow: @shadow-light; - } - - &--green { - background: @color-primary; - color: @color-text-light; - flex: 1; - max-width: 450px; - } - - &--beige { - background: @color-beige; - color: @color-text-light; - width: 100%; - max-width: 930px; - margin: 0 auto; - } - - &__title { - font-family: @font-logo; - font-size: 24px; - font-weight: bold; - margin-bottom: 15px; - text-transform: uppercase; - } - - &__text { - font-family: @font-main; - font-size: 16px; - line-height: 1.6; - margin: 0; - } -} - -// ======================= -// === ФУТЕР === -// ======================= -.footer { - background-color: @color-primary; - color: black; - padding: 40px 0 10px; - position: relative; - z-index: 1000; - - &::before { - content: ''; - display: block; - position: absolute; - top: -80px; - left: 0; - width: 100%; - height: 1px; - visibility: hidden; - } - - &__content { - display: flex; - gap: 20px; - padding-bottom: 30px; - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - } - - &__col { - flex: 1; - &--logo { flex: 1.5; } - h5 { - margin-bottom: 15px; - font-size: 14px; - text-transform: uppercase; - } - ul li { - margin-bottom: 8px; - a:hover { text-decoration: underline; } - } - .social-icons, - .payment-icons { - .flex-center(15px); - justify-content: flex-start; - margin-top: 10px; - } - .social-icons .icon { - .icon-base(20px, 1.1); - color: black; - &:hover { color: @color-accent; } - } - .payment-icons .pay-icon { - .icon-base(24px, 1.05); - color: black; - } - } - - .copyright { - text-align: center; - font-size: 12px; - padding-top: 20px; - color: rgba(255, 255, 255, 0.6); - } -} - -// ======================= -// === ДОСТАВКА === -// ======================= -.delivery-content { - max-width: 1200px; - margin: 0 auto; - padding: 40px 20px; -} - -.delivery-content h1 { - font-family: @font-logo; - font-size: 42px; - text-align: center; - margin-bottom: 50px; - color: #453227; -} - -.delivery-section { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 30px; - margin-bottom: 60px; -} - -.delivery-card { - background: white; - padding: 30px; - border-radius: 12px; - box-shadow: 0 5px 15px rgba(0,0,0,0.1); - text-align: center; - transition: transform 0.3s ease; - flex: 1; - min-width: 350px; - max-width: 400px; -} - -.delivery-card:hover { - transform: translateY(-5px); -} - -.delivery-icon { - font-size: 48px; - color: #617365; - margin-bottom: 20px; -} - -.delivery-card h3 { - font-family: @font-logo; - font-size: 24px; - margin-bottom: 20px; - color: #453227; -} - -.delivery-details { - text-align: left; -} - -.detail-item { - display: flex; - justify-content: space-between; - margin-bottom: 12px; - padding-bottom: 12px; - border-bottom: 1px solid #f0f0f0; -} - -.detail-label { - font-weight: bold; - color: #333; -} - -.detail-value { - color: #617365; - text-align: right; -} - -// ======================= -// === ГАРАНТИЯ === -// ======================= -.warranty-content { - max-width: 1200px; - margin: 0 auto; - padding: 40px 20px; -} - -.warranty-content h1 { - font-family: 'Anek Kannada', sans-serif; - font-size: 42px; - text-align: center; - margin-bottom: 50px; - color: #453227; -} - -.warranty-overview { - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: 25px; - margin-bottom: 60px; -} - -.warranty-card { - background: white; - padding: 30px; - border-radius: 12px; - text-align: center; - box-shadow: 0 5px 15px rgba(0,0,0,0.1); - transition: transform 0.3s ease; - flex: 1; - min-width: 250px; - max-width: 280px; - display: flex; - flex-direction: column; - align-items: center; -} - -.warranty-card:hover { - transform: translateY(-5px); -} - -.warranty-icon { - font-size: 48px; - color: #617365; - margin-bottom: 20px; -} - -.warranty-card h3 { - font-family: 'Anek Kannada', sans-serif; - font-size: 20px; - margin-bottom: 15px; - color: #453227; -} - -.warranty-period { - font-size: 24px; - font-weight: bold; - color: #617365; - margin-top: auto; -} - -.coverage-section { - display: flex; - flex-wrap: wrap; - gap: 40px; - margin-bottom: 60px; -} - -.coverage-covered, -.coverage-not-covered { - flex: 1; - min-width: 300px; -} - -.coverage-section h2 { - font-family: 'Anek Kannada', sans-serif; - font-size: 24px; - margin-bottom: 25px; - color: #453227; -} - -.coverage-list { - display: flex; - flex-direction: column; - gap: 20px; -} - -.coverage-item { - display: flex; - align-items: flex-start; - gap: 15px; - padding: 20px; - border-radius: 8px; - background: white; - box-shadow: 0 3px 10px rgba(0,0,0,0.1); -} - -.coverage-item.covered i { - color: #28a745; - font-size: 20px; - margin-top: 2px; - flex-shrink: 0; -} - -.coverage-item.not-covered i { - color: #dc3545; - font-size: 20px; - margin-top: 2px; - flex-shrink: 0; -} - -.coverage-text { - flex: 1; -} - -.coverage-item h4 { - font-family: 'Anek Kannada', sans-serif; - font-size: 16px; - margin-bottom: 5px; - color: #333; -} - -.card { - min-height: 250px; - display: flex; - align-items: center; - justify-content: center; - color: @color-text-light; - text-align: center; - - &--green { - background: @color-primary; - flex: 0 1 450px; - max-width: 450px; - } - - &--beige { - background: @color-beige; - color: @color-text-dark; - flex: 0 1 925px; - max-width: 925px; - } -} - -.design-section { - display: flex; - justify-content: center; - margin-bottom: 40px; - .card { width: 100%; } -} - -// ======================= -// === АДАПТИВНОСТЬ === -// ======================= -@media (max-width: 1240px) { - .catalog-wrapper { gap: 20px; } - .catalog-sidebar { flex: 0 0 200px; } - .products-container { - gap: 15px; - display: flex; - flex-wrap: wrap; - } - - .product-card.small1 { - margin-top: 100px; - } - - .product-card.small, - .product-card.small1, - .product-card.large, - .product-card.wide, - .product-card.wide1, - .product-card.wide2, - .product-card.wide2_1, - .product-card.wide4 { - flex: 0 0 calc(33.333% - 10px); - max-width: calc(33.333% - 10px); - height: 180px; - margin: 0; - - .product-image-container { - height: 180px; - } - } - - .product-card.wide3 { - flex: 0 0 calc(25% - 10px); - max-width: calc(25% - 10px); - height: 300px; - margin: 0; - - .product-image-container { - height: 350px; - } - } - - .product-card.tall { - flex: 0 0 calc(25% - 10px); - max-width: calc(25% - 10px); - height: 300px; - margin: 0; - - .product-image-container { - height: 300px; - } - } - - .product-card.full-width { - flex: 0 0 100%; - max-width: 100%; - height: 300px; - margin: 0; - - .product-image-container { - height: 300px; - } - } - - .product-card.small { order: 1; } - .product-card.large { order: 2; } - .product-card.wide { order: 3; } - - .product-card.small1 { order: 11; } - .product-card.wide2 { order: 12; } - .product-card.wide2_1 { order: 13; } - - .product-card.wide3 { order: 21; } - .product-card.tall { order: 22; } - - .product-card.wide3 { order: 31; } - - .product-card.full-width { order: 41; flex-basis: 100%; } - - .main__content { - gap: 20px; - .products { - flex: 0 0 35%; - .products__image { - width: 250px; - height: 180px; - } - } - .order { - flex: 0 0 60%; - padding: 30px; - - .order__title { - font-size: 24px; - } - - .order__section-title { - font-size: 16px; - } - } - } - - .solutions-slider { - &__slide { - .solution-text-overlay { - top: 10%; - left: 5%; - h2 { - font-size: 26px; - margin-bottom: 5px; - line-height: 1.2; - } - p { - font-size: 18px; - line-height: 1.2; - } - } - .solution-image-link { - bottom: 70px; - padding: 10px 25px; - font-size: 14px; - } - } - } - - .product__image { - width: 350px; - height: 250px; - } - - .product__thumbnail img { - width: 170px; - height: 120px; - } -} - -@media (max-width: 1024px) { - .main__content { - gap: 25px; - .products { - flex: 0 0 30%; - .products__image { - width: 200px; - height: 150px; - } - - .products__name { - font-size: 16px; - } - - .products__price { - font-size: 16px; - } - } - .order { - flex: 0 0 60%; - padding: 25px; - - .order__title { - font-size: 22px; - } - - .form__input { - padding: 12px 14px; - font-size: 14px; - } - - .promo__btn { - padding: 10px 40px; - font-size: 16px; - } - } - } -} - -@media (max-width: 768px) { - .container { padding: 0 15px; } - - .delivery-section { - flex-direction: column; - align-items: center; - } - - .delivery-card { - min-width: 100%; - max-width: 100%; - } - - .delivery-content h1 { - font-size: 32px; - } - - .warranty-overview { - flex-direction: column; - align-items: center; - } - - .warranty-card { - max-width: 100%; - width: 100%; - } - - .coverage-section { - flex-direction: column; - } - - .warranty-content h1 { - font-size: 32px; - } - - .header__top .container, - .header__bottom .container, - .hero__content, - .advantages__header, - .about__content, - .advantages__items, - .promo-images, - .stats__items, - .faq__items, - .catalog-wrapper, - .main__content { - flex-direction: column; - gap: 30px; - } - - .search-catalog { - order: 3; - width: 100%; - max-width: 100%; - } - - .nav-list { - flex-wrap: wrap; - justify-content: center; - gap: 15px; - } - - .hero { - &__image-block { - flex: none; - max-width: 400px; - height: 400px; - } - &__circle { - width: 380px; - height: 380px; - } - &__text-block { - flex: none; - padding-left: 0; - text-align: center; - h1 { font-size: 32px; } - .hero__usp-text { - padding-left: 0; - justify-content: center; - &::before { display: none; } - } - .btn.primary-btn { margin-left: 0; } - } - } - - .advantages__header h2, - .faq h2 { font-size: 28px; } - - .faq-item, - .stat-item { - flex: none; - .flex-center(); - text-align: center; - } - - .stats .container { justify-content: center; } - .catalog-dropdown__menu { width: 200px; } - - .catalog-sidebar { width: 100%; flex: none; } - .products-container { gap: 15px; } - - .product-card.small, - .product-card.small1, - .product-card.large, - .product-card.wide, - .product-card.wide1, - .product-card.wide2, - .product-card.wide2_1, - .product-card.wide3, - .product-card.wide4, - .product-card.tall, - .product-card.full-width { - flex: 0 0 100%; - max-width: 100%; - height: 250px; - margin: 0; - - .product-image-container { - height: 200px; - } - } - - .main__content { - flex-direction: column; - gap: 20px; - - .products, - .order { - flex: 0 0 100%; - width: 100%; - } - - .products { - .products__item { - flex-direction: column; - text-align: center; - gap: 15px; - } - - .products__image { - width: 100%; - height: 200px; - justify-content: center; - } - - .products__details { - min-height: auto; - align-items: center; - } - - .products__controls { - justify-content: center; - margin-top: 15px; - } - - .products__cart-icon { - margin-left: 0; - } - } - - .order { - padding: 20px; - - .order__title { - font-size: 20px; - text-align: center; - } - - .order__total { - text-align: center; - } - - .form__radio-group { - flex-direction: column; - gap: 15px; - } - - .form__radio-label { - flex: none; - justify-content: flex-start; - } - - .promo { - flex-direction: column; - gap: 10px; - - &__btn { - width: 100%; - padding: 12px; - } - } - - .order-btn { - padding: 12px; - font-size: 16px; - } - - .services { - flex-direction: column; - align-items: center; - } - } - } - - .product-image-container { height: 200px; } - .product-card.tall .product-image-container, - .product-card.large .product-image-container { height: 250px; } - - .profile-page-main { - .profile-container { - flex-direction: column; - min-height: auto; - max-width: 100%; - box-shadow: none; - } - .profile-left-col { - flex: none; - width: 100%; - height: 100px; - .flex-center(); - padding: 0; - } - .profile-right-col { - flex: none; - width: 100%; - padding: 30px 20px; - } - .profile-form-block { max-width: 100%; } - } - - .form__row { flex-direction: column; } - .form__input--half { flex: 0 0 100%; max-width: 100%; } - .services { flex-direction: column; align-items: center; } - - .services-section { - padding: 40px 0; - } - - .service-card { - padding: 30px 20px; - min-height: 180px; - - &--green, - &--beige { - max-width: 100%; - } - - &__title { - font-size: 20px; - } - - &__text { - font-size: 14px; - } - } - .solutions-slider { - margin: 20px auto; - &__slide { - .solution-text-overlay { - top: 8%; - left: 4%; - h2 { - font-size: 20px; - margin-bottom: 3px; - line-height: 1.1; - } - p { - font-size: 15px; - line-height: 1.1; - } - } - .solution-image-link { - bottom: 90px; - padding: 8px 20px; - font-size: 13px; - } - } - } -} - -// Стили для ошибок полей -.error-input { - border-color: #ff4444 !important; - box-shadow: 0 0 0 1px #ff4444; -} - -.field-error { - color: #ff4444; - font-size: 12px; - margin-top: 5px; - margin-bottom: 10px; -} - -// Стили для сообщений -.message { - padding: 15px; - margin: 20px 0; - border-radius: 5px; - display: none; -} - -.message.error { - background-color: #ffebee; - color: #c62828; - border: 1px solid #ffcdd2; -} - -.message.success { - background-color: #e8f5e9; - color: #2e7d32; - border: 1px solid #c8e6c9; -} - -// Добавьте в конец файла -.access-denied { - text-align: center; - padding: 80px 20px; - background: white; - border-radius: 10px; - box-shadow: 0 5px 15px rgba(0,0,0,0.1); - margin: 50px 0; - - h2 { - color: #dc3545; - margin-bottom: 30px; - font-size: 28px; - } - - p { - color: #666; - margin-bottom: 40px; - font-size: 18px; - line-height: 1.6; - } - - .btn { - margin: 5px; - min-width: 200px; - } -} -// ======================= -// === ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ === -// ======================= - -.user-profile-dropdown { - position: relative; - display: inline-block; - - &__toggle { - display: flex; - align-items: center; - gap: 8px; - cursor: pointer; - padding: 8px 12px; - border-radius: 4px; - transition: all 0.3s ease; - - &:hover { - background-color: rgba(0, 0, 0, 0.05); - } - - .user-avatar { - width: 32px; - height: 32px; - border-radius: 50%; - background-color: @color-primary; - color: @color-text-light; - .flex-center(); - font-weight: bold; - } - - .user-info { - display: flex; - flex-direction: column; - - .user-email { - font-size: 12px; - color: #666; - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .user-status { - font-size: 10px; - padding: 2px 6px; - border-radius: 10px; - text-transform: uppercase; - - &.admin { - background-color: #617365; - color: white; - } - - &.user { - background-color: #28a745; - color: white; - } - } - } - } - - &__menu { - .menu-base(); - width: 220px; - top: 100%; - right: 0; - - .user-details { - padding: 15px; - border-bottom: 1px solid #eee; - - .user-name { - font-weight: bold; - margin-bottom: 5px; - } - - .user-registered { - font-size: 11px; - color: #999; - } - } - - ul { - padding: 10px 0; - - li { - padding: 8px 15px; - cursor: pointer; - display: flex; - align-items: center; - gap: 10px; - transition: background-color 0.3s ease; - - &:hover { - background-color: #f5f5f5; - } - - &.logout { - color: #dc3545; - border-top: 1px solid #eee; - margin-top: 5px; - padding-top: 12px; - - &:hover { - background-color: #ffe6e6; - } - } - } - } - } - - &:hover &__menu { - display: block; - } -} - -// ======================= -// === КАРТОЧКА ТОВАРА === -// ======================= - -.product-image-container { - position: relative; - overflow: hidden; - margin-bottom: 0; - padding: 0; - height: 250px; - .flex-center(); - - &:hover { - .product-overlay-info { - opacity: 1; - transform: translateY(0); - } - } -} - -.product-overlay-info { - position: absolute; - bottom: 0; - left: 0; - right: 0; - background: linear-gradient(to top, rgba(0,0,0,0.8), transparent); - color: white; - padding: 15px; - opacity: 0; - transform: translateY(10px); - transition: all 0.3s ease; - - .product-overlay-name { - font-weight: bold; - font-size: 14px; - margin-bottom: 5px; - } - - .product-overlay-price { - font-size: 16px; - font-weight: bold; - - .old-price { - text-decoration: line-through; - font-size: 12px; - color: #ccc; - margin-right: 5px; - } - - .current-price { - color: #ffd700; - } - } - - .product-overlay-category { - font-size: 11px; - opacity: 0.8; - margin-top: 3px; - } - - .product-overlay-stock { - font-size: 11px; - margin-top: 5px; - - &.out-of-stock { - color: #ff6b6b; - } - - i { - margin-right: 5px; - } - } -} - -.product-card-details { - padding: 15px; - background: white; - flex-grow: 1; - display: flex; - flex-direction: column; - - .product-card-name { - font-weight: bold; - font-size: 16px; - margin-bottom: 8px; - color: @color-text-dark; - } - - .product-card-description { - font-size: 13px; - color: #777; - margin-bottom: 10px; - flex-grow: 1; - } - - .product-card-attributes { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-bottom: 10px; - - .attribute { - font-size: 11px; - background: #f5f5f5; - padding: 3px 8px; - border-radius: 12px; - color: #666; - - i { - margin-right: 3px; - } - } - } - - .product-card-price { - margin-bottom: 10px; - - .old-price { - text-decoration: line-through; - font-size: 14px; - color: #999; - margin-right: 8px; - } - - .current-price { - font-size: 18px; - font-weight: bold; - color: @color-button; - } - } - - .add-to-cart-btn { - width: 100%; - padding: 8px; - font-size: 14px; - } - - .admin-actions { - display: flex; - gap: 5px; - margin-top: 10px; - - .admin-btn { - flex: 1; - font-size: 12px; - padding: 6px; - - &.delete-btn { - background: #dc3545; - - &:hover { - background: #c82333; - } - } - } - } -} - -// ======================= -// === ПРОФИЛЬ ПОЛЬЗОВАТЕЛЯ === -// ======================= - -.user-profile-dropdown { - position: relative; - display: inline-block; - - .user-profile-toggle { - display: flex; - align-items: center; - gap: 10px; - cursor: pointer; - padding: 8px 12px; - border-radius: 4px; - transition: all 0.3s ease; - - &:hover { - background-color: rgba(0, 0, 0, 0.05); - - .dropdown-arrow { - transform: rotate(180deg); - } - } - - .user-avatar { - width: 36px; - height: 36px; - border-radius: 50%; - background: linear-gradient(135deg, #617365 0%, #453227 100%); - color: @color-text-light; - .flex-center(); - font-weight: bold; - font-size: 16px; - box-shadow: 0 2px 5px rgba(0,0,0,0.2); - } - - .user-info { - display: flex; - flex-direction: column; - - .user-email { - font-size: 12px; - color: #666; - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .user-status { - font-size: 10px; - padding: 2px 8px; - border-radius: 12px; - text-transform: uppercase; - font-weight: bold; - text-align: center; - margin-top: 2px; - - &.admin { - background-color: #617365; - color: white; - border: 1px solid #617365; - } - - &.user { - background-color: #28a745; - color: white; - border: 1px solid #28a745; - } - } - } - - .dropdown-arrow { - font-size: 10px; - color: #666; - transition: transform 0.3s ease; - } - } - - .user-profile-menu { - .menu-base(); - width: 280px; - top: 100%; - right: 0; - margin-top: 10px; - padding: 0; - overflow: hidden; - - .user-profile-header { - padding: 20px; - background: linear-gradient(135deg, #617365 0%, #453227 100%); - color: white; - - .user-profile-name { - font-weight: bold; - margin-bottom: 8px; - display: flex; - align-items: center; - gap: 10px; - font-size: 16px; - } - - .user-profile-details { - small { - display: block; - opacity: 0.8; - margin-bottom: 5px; - font-size: 11px; - - i { - margin-right: 5px; - width: 14px; - text-align: center; - } - } - } - } - - .user-profile-links { - list-style: none; - padding: 10px 0; - - li { - border-bottom: 1px solid #f0f0f0; - - &:last-child { - border-bottom: none; - } - - a { - display: flex; - align-items: center; - gap: 12px; - padding: 12px 20px; - color: #333; - transition: all 0.3s ease; - - &:hover { - background-color: #f8f9fa; - color: @color-primary; - text-decoration: none; - - i { - transform: scale(1.1); - } - } - - i { - width: 20px; - text-align: center; - font-size: 14px; - color: #617365; - transition: transform 0.3s ease; - } - - span { - flex-grow: 1; - } - } - } - - .logout-item { - border-top: 2px solid #f0f0f0; - margin-top: 5px; - - a { - color: #dc3545; - - &:hover { - background-color: #ffe6e6; - color: #c82333; - - i { - color: #dc3545; - } - } - } - } - } - } - - &:hover .user-profile-menu { - display: block; - animation: fadeIn 0.3s ease; - } -} - -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -// Для мобильных устройств -@media (max-width: 768px) { - .user-profile-dropdown { - .user-profile-toggle { - .user-info { - display: none; - } - - .dropdown-arrow { - display: none; - } - } - - .user-profile-menu { - width: 250px; - right: -50px; - } - } -} -// Добавьте в конец файла -.unavailable-product { - position: relative; - opacity: 0.6; - filter: grayscale(0.7); - - &::before { - content: "ТОВАР ЗАКОНЧИЛСЯ"; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: rgba(0, 0, 0, 0.85); - color: white; - padding: 15px 25px; - border-radius: 5px; - font-weight: bold; - font-size: 16px; - text-align: center; - z-index: 100; - white-space: nowrap; - pointer-events: none; - box-shadow: 0 4px 12px rgba(0,0,0,0.3); - } - - .product-name-overlay { - .name, .price { - color: #999 !important; - text-shadow: none !important; - } - } - - .add-to-cart-btn { - display: none !important; - } - - &:hover { - transform: none !important; - cursor: not-allowed; - } -} - -.out-of-stock-badge { - position: absolute; - top: 10px; - left: 10px; - background: #6c757d; - color: white; - padding: 5px 10px; - border-radius: 4px; - font-size: 12px; - font-weight: bold; - z-index: 10; -} - -// Для админ-таблицы -.admin-table tr.unavailable { - background-color: #f8f9fa !important; - opacity: 0.7; - - td { - color: #999; - } -} - -.status-unavailable { - background-color: #6c757d !important; - color: white !important; +@import "mixins.less"; +@import "стили_оформления.less"; +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body { + height: 100%; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: @font-main; + background-color: @color-secondary; + color: @color-text-dark; + line-height: 1.6; + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.container { + max-width: 1210px; + margin: 0 auto; + padding: 0 20px; +} + +ul { + list-style: none; +} + +a { + text-decoration: none; + color: inherit; + transition: all 0.3s ease; +} + +h1, h2, h3, h4, h5, h6 { + font-family: @font-heading; + margin: 0; +} + +p, li, span { + font-family: @font-main; +} + +.logo, .footer-logo { + font: bold 32px/1 @font-logo; + letter-spacing: 2px; + text-shadow: @shadow-dark; + flex-shrink: 0; +} + +.btn { + padding: 12px 30px; + border: none; + cursor: pointer; + font-size: 14px; + text-transform: uppercase; + transition: all 0.3s ease; + font-family: @font-main; + + &.primary-btn { + background-color: @color-button; + color: @color-text-light; + + &:hover { + background-color: lighten(@color-button, 10%); + transform: translateY(-2px); + box-shadow: @shadow-light; + } + } +} + +.number-circle { + .flex-center(); + width: 28px; + height: 28px; + border-radius: 50%; + background-color: @color-button; + color: @color-text-light; + font-size: 16px; + font-weight: bold; + flex-shrink: 0; +} + +.breadcrumbs { + font-size: 14px; + margin-bottom: 20px; + color: #666; + + a { + color: #666; + opacity: 0.7; + &:hover { opacity: 1; } + } + + .current-page { + font-weight: bold; + color: @color-text-dark; + } +} + +.header { + background-color: @color-secondary; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + z-index: 1000; + + &__top, &__bottom { + padding: 15px 0; + .container { + .flex-between(); + gap: 20px; + } + } + + &__bottom { + padding: 10px 0; + border-top: 1px solid rgba(0, 0, 0, 0.05); + + .catalog-link.active-catalog { + background-color: rgba(0, 0, 0, 0.08); + pointer-events: none; + } + } + + .search-catalog { + .flex-center(); + border: 2px solid @color-text-dark; + background-color: #fff; + max-width: 600px; + width: 100%; + margin: 0 auto; + overflow: hidden; + + .catalog-dropdown { + position: relative; + background-color: #f8f8f8; + padding: 10px 15px 10px 25px; + font-size: 18px; + cursor: pointer; + border-right: 1px solid @color-text-dark; + .flex-center(10px); + width: 200px; + flex-shrink: 0; + + &__menu { + .menu-base(); + li { + padding: 8px 0; + cursor: pointer; + transition: color 0.3s; + border-bottom: 1px solid #f0f0f0; + &:last-child { border-bottom: none; } + &:hover { color: @color-accent; } + } + } + &:hover &__menu { display: block; } + } + + .search-box { + .flex-center(); + padding: 0 15px; + flex-grow: 1; + position: relative; + font-size: 15px; + + input { + border: none; + padding: 10px 30px 10px 0; + outline: none; + font-size: 16px; + width: 100%; + text-align: left; + } + + .search-icon { + font-size: 20px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + } + }} + + &__icons--top { + .flex-center(15px); + flex-shrink: 0; + .icon { .icon-base(); font-size: 20px;} + } + + .nav-list { + .flex-center(30px); + font-size: 18px; + a { + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2); + &:hover { text-shadow: @shadow-dark; } + &.active { + border-bottom: 2px solid @color-button; + padding-bottom: 5px; + text-shadow: @shadow-dark; + } + &[href="#footer"] { + cursor: pointer; + } + } + } + + .catalog-link { + .flex-center(10px); + border-radius: 4px; + white-space: nowrap; + font-size: 18px; + padding: 10px 18px; + &:hover { background-color: rgba(0, 0, 0, 0.05); } + } + + .header-phone { + font-weight: bold; + color: @color-button; + flex-shrink: 0; + } +} + +.hero { + padding: 15px 0; + + &__content { + .flex-center(50px); + min-height: 60vh; + align-items: center; + } + + &__image-block { + position: relative; + flex: 0 0 40%; + max-width: 600px; + height: 600px; + .flex-center(); + + .hero__circle { + position: absolute; + width: 450px; + height: 450px; + background-color: @color-primary; + border-radius: 50%; + z-index: 1; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + .hero__img { + position: relative; + width: 100%; + height: 100%; + object-fit: contain; + z-index: 2; + } + } + + &__text-block { + flex: 0 0 60%; + padding-left: 50px; + + h1 { + font-size: 42px; + font-weight: normal; + margin-bottom: 25px; + line-height: 1.3; + } + + .hero__usp-text { + position: relative; + padding-left: 50px; + margin-bottom: 35px; + line-height: 1.7; + .flex-center(); + justify-content: flex-start; + min-height: 40px; + font-size: 16px; + + &::before { + content: "✓"; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 32px; + height: 32px; + border: 2px solid @color-button; + background-color: transparent; + color: @color-button; + border-radius: 50%; + .flex-center(); + font-size: 16px; + font-weight: bold; + } + } + + .btn.primary-btn { + margin: 25px 0 0 50px; + padding: 14px 35px; + font-size: 15px; + } + } +} + +.advantages { + padding: 30px 0 40px; + + &__header { + display: flex; + align-items: center; + gap: 50px; + margin-bottom: 40px; + h2 { + font-size: 32px; + font-weight: normal; + flex: 0 0 30%; + } + } + + &__items { + flex: 0 0 70%; + display: flex; + gap: 30px; + } + + .advantage-item { + flex: 1; + text-align: left; + position: relative; + padding-top: 30px; + + &__number { + .number-circle(); + position: absolute; + top: 0; + left: 0; + } + + h4 { + font-weight: 600; + margin-bottom: 10px; + } + } +} + +.promo-images { + display: flex; + gap: 20px; + margin-top: 50px; + + .promo-image-col { + position: relative; + overflow: hidden; + border-radius: 8px; + flex: 1; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-5px); + box-shadow: @shadow-light; + .image-overlay-text { background-color: rgba(0, 0, 0, 0.6); } + img { transform: scale(1.05); } + .image-overlay-text h4, + .image-overlay-text .overlay-link { transform: translateY(0); } + } + + img { + width: 100%; + height: 350px; + object-fit: cover; + display: block; + transition: transform 0.5s ease; + } + + .image-overlay-text { + .image-overlay(); + h4 { + font-size: 24px; + text-transform: uppercase; + line-height: 1.2; + margin-bottom: 15px; + transform: translateY(20px); + transition: transform 0.3s ease; + } + } + + .overlay-link { + display: inline-block; + text-transform: uppercase; + font-weight: bold; + border-radius: 3px; + margin-top: 15px; + padding: 10px 25px; + background-color: @color-button; + color: @color-text-light; + font-size: 12px; + transform: translateY(20px); + transition: all 0.3s ease; + + &:hover { + background-color: lighten(@color-button, 10%); + transform: translateY(-2px); + box-shadow: @shadow-light; + } + } + } +} + +.about { + padding: 40px 0 80px; + + &__content { + display: flex; + align-items: flex-start; + gap: 50px; + } + + &__column { + display: flex; + flex-direction: column; + gap: 20px; + &--left { flex: 0 0 40%; margin-bottom: 30px; } + &--right { + flex: 0 0 60%; + .about__caption { + padding-right: 50px; + } + } + } + + &__text-block { + margin-bottom: 30px; + h2 { margin-bottom: 15px; } + } + + &__img { + width: 93%; + object-fit: cover; + display: block; + &--small { height: 300px; } + &--large { height: 450px; } + } + + .text-justified { + text-align: justify; + color: #555; + } +} + +.solutions { + padding: 0; + background-color: @color-secondary; + + &-slider { + position: relative; + width: 100%; + max-width: 1200px; + margin: 40px auto; + border-radius: 8px; + overflow: hidden; + + &__slides { + display: flex; + width: 200%; + height: 100%; + animation: slideLeftRight 10s infinite ease-in-out; + } + + &__slide { + width: 50%; + flex-shrink: 0; + position: relative; + overflow: hidden; + transition: transform 0.5s ease, box-shadow 0.5s ease; + + &:hover { + transform: scale(1.02); + box-shadow: 0 10px 25px rgba(0,0,0,0.3); + .solution-img { + transform: scale(1.05); + filter: brightness(0.8); + } + .solution-text-overlay { + opacity: 1; + transform: translateY(-5px); + } + .solution-image-link { + transform: translateX(-50%) translateY(-6px); + background-color: rgba(255,255,255,0.9); + color: @color-text-dark; + } + } + } + + .solution-img { + width: 100%; + height: auto; + object-fit: cover; + display: block; + } + + .solution-text-overlay { + position: absolute; + top: 15%; + left: 8%; + color: #493131; + text-shadow: 2px 2px 4px rgba(0,0,0,0.6); + z-index: 2; + opacity: 0.9; + transition: opacity 0.5s ease, transform 0.5s ease; + h2 { + font-size: 35px; + text-transform: uppercase; + margin-bottom: 10px; + } + p { + font-size: 25px; + text-transform: uppercase; + } + } + + .solution-image-link { + position: absolute; + bottom: 40px; + left: 50%; + transform: translateX(-50%); + padding: 12px 30px; + border: 2px solid @color-text-light; + color: #493131; + text-transform: uppercase; + font-size: 16px; + font-weight: bold; + background: transparent; + transition: 0.4s ease; + z-index: 2; + &:hover { + background: @color-text-light; + color: @color-text-dark; + transform: translateX(-50%) translateY(-2px); + } + } + } +} + +@keyframes slideLeftRight { + 0%, 40% { transform: translateX(0); } + 50%, 90% { transform: translateX(-50%); } + 100% { transform: translateX(0); } +} + +.stats { + padding: 0; + margin-top: 20px; + + .container { + display: flex; + justify-content: flex-end; + } + + &__items { + display: flex; + gap: 20px; + .stat-item { + text-align: left; + .stat-number { + font-size: 36px; + font-weight: bold; + color: @color-text-dark; + margin-bottom: 5px; + } + .stat-label { color: @color-text-dark; } + } + } +} + +.faq { + padding: 50px 0; + + h2 { + text-align: left; + font-size: 32px; + font-weight: normal; + margin-bottom: 40px; + } + + &__items { + display: flex; + flex-wrap: wrap; + gap: 40px 60px; + margin-bottom: 40px; + } + + .faq-item { + flex: 0 0 calc(50% - 30px); + .flex-center(15px); + align-items: flex-start; + &__content h4 { + font-weight: 600; + margin-bottom: 10px; + } + } + + .btn.primary-btn { + display: block; + width: 100%; + margin: 20px auto 80px; + } +} + +.catalog-main { + padding: 30px 0 60px; + background-color: lighten(@color-secondary, 5%); +} + +.catalog-wrapper { + display: flex; + gap: 20px; +} + +.catalog-sidebar { + flex: 0 0 250px; + background-color: #fff; + padding: 20px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); + height: fit-content; +} + +.filter-group { + margin-bottom: 30px; +} + +.filter-title { + font-size: 16px; + font-weight: bold; + margin-bottom: 15px; + text-transform: uppercase; +} + +.filter-list li { + padding: 5px 0; + font-size: 16px; + a { + color: #555; + transition: color 0.2s; + &:hover { color: @color-accent; } + &.active-category { + font-weight: bold; + color: @color-primary; + } + } +} + +.price-range { + display: flex; + flex-direction: column; + gap: 15px; + width: 100%; + + .range-slider { + width: 100%; + + input[type="range"] { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 5px; + background: @color-primary; + border-radius: 5px; + outline: none; + margin: 0; + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 20px; + height: 20px; + background: @color-accent; + border: 2px solid #fff; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + transition: all 0.3s ease; + + &:hover { + transform: scale(1.1); + background: lighten(@color-accent, 10%); + } + } + + &::-moz-range-thumb { + width: 20px; + height: 20px; + background: @color-accent; + border: 2px solid #fff; + border-radius: 50%; + cursor: pointer; + box-shadow: 0 2px 4px rgba(0,0,0,0.2); + transition: all 0.3s ease; + + &:hover { + transform: scale(1.1); + background: lighten(@color-accent, 10%); + } + } + } + } + + .price-display { + font-size: 14px; + font-weight: bold; + text-align: center; + color: @color-text-dark; + padding: 10px; + background: #f8f8f8; + border-radius: 4px; + } +} + +.filter-options { + list-style: none; + li { + display: flex; + align-items: center; + padding: 4px 0; + font-size: 14px; + } + label { + margin-left: 10px; + cursor: pointer; + color: #555; + } + input[type="checkbox"] { + width: 15px; + height: 15px; + cursor: pointer; + accent-color: @color-primary; + &:checked + label { + font-weight: bold; + color: @color-primary; + } + } +} + +.filter-apply-btn { + width: 100%; + margin-top: 20px; +} + +.catalog-products { + flex-grow: 1; +} + +.products-container { + display: flex; + flex-wrap: wrap; + gap: 20px; +} + +.product-card { + background-color: #fff; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: column; + position: relative; + transition: transform 0.3s ease; + box-sizing: border-box; + + &:hover { + transform: translateY(-5px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); + .product-img { transform: scale(1.05); } + } +} + +.product-image-container { + position: relative; + overflow: hidden; + margin-bottom: 0; + padding: 0; + height: 250px; + .flex-center(); +} + +.product-img { + width: 100%; + height: 100%; + object-fit: contain; + display: block; + transition: transform 0.3s ease; + margin: 0; +} + +.product-img1 { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: transform 0.3s ease; + margin: 0; +} + +.product-discount { + position: absolute; + top: 10px; + right: 10px; + background-color: @color-button; + color: @color-text-light; + padding: 3px 8px; + font-size: 12px; + font-weight: bold; + z-index: 10; +} + +.product-wishlist-icon { + position: absolute; + top: 10px; + left: 10px; + color: #333; + font-size: 18px; + cursor: pointer; + transition: color 0.3s ease; + z-index: 10; + &:hover { color: @color-accent; } +} + +.product-name { + font-size: 16px; + font-weight: bold; + margin-bottom: 5px; +} + +.product-details { + font-size: 13px; + color: #777; + margin-bottom: 10px; + flex-grow: 1; +} + +.product-price { + font-size: 18px; + font-weight: bold; + color: @color-button; +} + +.product-card.small { flex: 0 0 300px; max-width: 300px; height: 200px; } +.product-card.small1 { flex: 0 0 320px; max-width: 320px; height: 250px;width: 320px; } +.product-card.large { flex: 0 0 580px; max-width: 580px; height: 380px; } +.product-card.wide { flex: 0 0 240px; max-width: 240px; height: 250px; } +.product-card.wide1 { flex: 0 0 350px; max-width: 350px; height: 250px; } +.product-card.wide2 { flex: 0 0 560px; max-width: 560px; height: 260px; } +.product-card.wide2_1 { flex: 0 0 560px; max-width: 560px; height: 260px; margin: -280px 0 0; } +.product-card.wide3 { + flex: 0 0 320px; max-width: 320px; height: 540px; + .product-image-container { height: 580px; } +} +.product-card.wide4 { + flex: 0 0 545px; max-width: 545px; margin: -270px 0 0; height: 250px; + .product-image-container { padding: 0; justify-content: flex-start; } + .product-img { margin-left: 0; align-self: flex-start; object-position: left center; } +} +.product-card.tall { flex: 0 0 300px; max-width: 300px; margin: -180px 0 0; height: 430px; } +.product-card.full-width { flex: 0 0 100%; margin: -20px 0 0; max-width: 900px; height: 300px;} + +.product-card.full-width { + flex: 0 0 100%; + max-width: 100%; + height: 300px; + + .product-image-container { + height: 100%; + padding: 0; + margin: 0; + + .product-img1 { + width: 100%; + height: 100%; + object-fit: cover; + margin: 0; + padding: 0; + } + } +} + +.product-card.tall .product-image-container, +.product-card.large .product-image-container { height: 430px; } + +.product__section { + display: grid; + grid-template-columns: 350px 1fr; + gap: 30px; + margin: 40px 0; + padding: 20px 0; +} + +.product__gallery { + position: relative; + width: 350px !important; + max-width: 350px !important; +} + +.product__main-image { + width: 350px !important; + height: 350px !important; + min-width: 350px !important; + min-height: 350px !important; + max-width: 350px !important; + max-height: 350px !important; + background: #f8f9fa; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 15px; + border: 1px solid #e9ecef; + flex-shrink: 0; + + img { + width: 100% !important; + height: 100% !important; + max-width: 350px !important; + max-height: 350px !important; + object-fit: contain !important; + transition: transform 0.3s ease; + + &:hover { + transform: scale(1.05); + } + } +} + +.product__thumbnails { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; +} + +.product__thumbnail { + height: 70px; + background: #f8f9fa; + border: 2px solid #e9ecef; + border-radius: 6px; + overflow: hidden; + cursor: pointer; + padding: 0; + transition: all 0.3s ease; + + &:hover { + border-color: #453227; + box-shadow: 0 2px 8px rgba(69, 50, 39, 0.2); + } + + &.active { + border-color: #453227; + box-shadow: 0 0 0 2px rgba(69, 50, 39, 0.1); + } + + img { + width: 100%; + height: 100%; + object-fit: cover; + } +} + +.product__info { + padding: 0; + + h1 { + font-size: 32px; + font-weight: 600; + color: #212529; + margin-bottom: 20px; + line-height: 1.3; + } +} + +.product__rating { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 25px; + padding-bottom: 25px; + border-bottom: 1px solid #e9ecef; + + .stars { + display: flex; + gap: 4px; + + .star { + font-size: 18px; + color: #ffc107; + + &.filled { + color: #ffc107; + } + } + } + + .rating-value { + font-weight: 600; + font-size: 16px; + color: #212529; + } + + .reviews-count { + color: #6c757d; + font-size: 14px; + } +} + +.product__price { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 25px; + flex-wrap: wrap; + + .current-price { + font-size: 36px; + font-weight: 700; + color: #453227; + } + + .old-price { + font-size: 24px; + color: #6c757d; + text-decoration: line-through; + } + + .discount-badge { + background: #dc3545; + color: white; + padding: 6px 12px; + border-radius: 6px; + font-size: 14px; + font-weight: 600; + } +} + +.product__color-selector { + display: flex; + gap: 10px; + margin-bottom: 40px; +} + +.product__color-option { + width: 45px; + height: 45px; + border-radius: 50%; + border: 2px solid transparent; + cursor: pointer; + transition: transform 0.3s ease; + + &:hover{ + transform: translateY(-2px); + } +} + +.product__color-option.active { + border-color: @color-primary; +} + +.product__description { + margin-bottom: 65px; + line-height: 1.5; +} + +.product__details-link { + display: inline-block; + margin-bottom: 20px; + color: @color-primary; + font-weight: bold; +} + +.product__purchase { + display: flex; + justify-content: space-between; + margin-bottom: 35px; +} + +.product__price { + font-size: 24px; + font-weight: bold; +} + +.product__quantity { + display: flex; + align-items: center; + gap: 10px; +} + +.product__qty-btn { + width: 30px; + height: 30px; + background: @color-button; + color: @color-text-light; + border: none; + border-radius: 50%; + cursor: pointer; + font-weight: bold; + transition: all 0.3s ease; + + &:hover { + background: lighten(@color-button, 10%); + transform: scale(1.1); + } +} + +.product__qty-value { + font-weight: bold; + min-width: 30px; + text-align: center; +} + +.product__actions { + display: flex; + gap: 15px; +} + +.product__btn { + flex: 1; + padding: 12px 20px; + border: none; + border-radius: 4px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-2px); + box-shadow: @shadow-light; + } +} + +.product__btn.primary { + background: @color-button; + color: @color-text-light; + + &:hover { + background: lighten(@color-button, 10%); + } +} + +.product__btn.secondary { + background: transparent; + border: 1px solid @color-button; + color: @color-button; + + &:hover { + background: @color-button; + color: @color-text-light; + } +} + +.similar { + margin: 60px 0; +} + +.similar__title { + margin-bottom: 30px; + font-size: 28px; + font-weight: bold; +} + +.similar__grid { + display: flex; + gap: 25px; + flex-wrap: wrap; + justify-content: space-between; +} + +.similar__card { + flex: 0 0 calc(33.333% - 17px); + min-width: 320px; + background: @color-secondary; + border-radius: 12px; + overflow: hidden; + transition: all 0.3s ease; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + + &:hover { + transform: translateY(-8px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); + } +} + +.similar__card-image { + height: 300px; + overflow: hidden; + background: white; + + img { + width: 100%; + height: 100%; + object-fit: contain; + transition: transform 0.3s ease; + + &:hover { + transform: scale(1.05); + } + } +} + +.similar__card-content { + padding: 25px; +} + +.similar__card-title { + font-weight: bold; + margin-bottom: 10px; + font-size: 20px; + color: @color-text-dark; +} + +.similar__card-description { + font-size: 15px; + margin-bottom: 15px; + color: #666; + line-height: 1.5; +} + +.similar__card-price { + font-weight: bold; + font-size: 22px; + color: @color-button; +} + +@media (max-width: 1024px) { + .similar { + &__card { + flex: 0 0 calc(50% - 13px); + min-width: 280px; + } + } +} + +@media (max-width: 768px) { + .similar { + &__grid { + justify-content: center; + } + + &__card { + flex: 0 0 100%; + max-width: 400px; + } + } +} + +.main__content { + display: flex; + gap: 40px; + margin: 30px 0; + + .products { + flex: 1; + } + + .order { + flex: 0 0 65%; + padding: 40px; + + &__header { + .flex-between(); + margin-bottom: 20px; + } + + &__title { + font-family: @font-logo; + font-size: 28px; + color: @color-text-dark; + margin: 0; + } + + &__total { + font-weight: bold; + color: @color-text-dark; + } + + &__section { + margin-bottom: 25px; + } + + &__section-title { + font-family: @font-logo; + margin-bottom: 15px; + font-size: 18px; + color: @color-text-dark; + } + } +} + +.products { + &__title { + font-family: @font-logo; + margin-bottom: 20px; + font-size: 24px; + color: @color-text-dark; + } + + &__list { + .flex-column(); + gap: 20px; + } + + &__item { + background-color: @color-secondary; + border-radius: 8px; + padding: 20px; + display: flex; + gap: 15px; + border: 1px solid @color-secondary; + transition: transform 0.3s ease; + align-items: flex-start; + position: relative; + + &:hover { + transform: translateY(-2px); + } + } + + &__image { + width: 300px; + height: 200px; + border-radius: 4px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: flex-start; + } + + .product-img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + transition: transform 0.3s ease; + margin: 0; + } + + &__details { + flex: 1; + .flex-column(); + justify-content: space-between; + align-items: flex-start; + min-height: 200px; + } + + &__name { + font-weight: bold; + margin-bottom: 5px; + color: @color-accent; + font-size: 18px; + font-family: @font-main; + } + + &__price { + font-weight: bold; + font-size: 18px; + margin-bottom: 15px; + color: @color-text-dark; + } + + &__controls { + display: flex; + align-items: center; + gap: 15px; + margin-top: auto; + width: 100%; + justify-content: space-between; + } + + &__quantity { + display: flex; + align-items: center; + gap: 10px; + } + + &__qty-btn { + width: 30px; + height: 30px; + background-color: @color-text-dark; + color: @color-text-light; + border: none; + border-radius: 50%; + cursor: pointer; + .flex-center(); + font-family: @font-main; + font-weight: bold; + transition: all 0.3s ease; + + &:hover { + transform: scale(1.1); + } + } + + &__qty-value { + font-weight: bold; + min-width: 30px; + text-align: center; + font-size: 16px; + } + + &__cart-icon { + background-color: transparent; + color: @color-text-dark; + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid @color-text-dark; + margin-left: 20px; + + &:hover { + transform: scale(1.1); + } + + i { font-size: 18px; } + } +} + +.form { + &__group { margin-bottom: 15px; } + &__label { + display: block; + margin-bottom: 5px; + font-weight: bold; + color: #000000; + } + &__input { + width: 100%; + padding: 14px 16px; + border: 2px solid #ccc; + font-family: @font-main; + font-size: 15px; + transition: border-color 0.3s ease; + + &:focus { + border-color: @color-primary; + } + + &:hover { + border-color: darken(#ccc, 10%); + } + &::placeholder { + font-style: italic; + color: #999; + } + } + &__row { + display: flex; + gap: 20px; + justify-content: space-between; + } + &__input--half { + width: 100%; + } + &__radio-group { + display: flex; + justify-content: space-between; + margin-bottom: 20px; + margin-top: 20px; + } + &__radio-label { + display: flex; + align-items: center; + cursor: pointer; + color: @color-text-dark; + position: relative; + padding-left: 30px; + flex: 1; + + &:hover { + .form__custom-radio { + border-color: lighten(@color-accent, 10%); + } + } + } + &__radio-input { + position: absolute; + opacity: 0; + cursor: pointer; + } + &__custom-radio { + position: absolute; + left: 0; + height: 20px; + width: 20px; + background-color: @color-secondary; + border: 2px solid @color-accent; + border-radius: 50%; + transition: border-color 0.3s ease; + } + &__radio-input:checked ~ &__custom-radio { + background-color: @color-accent; + + &:after { + content: ""; + position: absolute; + display: block; + top: 4px; + left: 4px; + width: 8px; + height: 8px; + border-radius: 50%; + background: white; + } + } +} + +.divider { + height: 1px; + background-color: #999; + margin: 20px 0; +} + +.promo { + display: flex; + margin-bottom: 20px; + + &__input { + flex: 1; + padding: 10px; + border: 1px solid #000; + background-color: @color-secondary; + font-family: @font-main; + height: auto; + min-height: 48px; + + &:hover { + border-color: @color-primary; + } + + &::placeholder { + font-style: italic; + color: #999; + } + } + + &__btn { + background-color: @color-accent; + color: @color-secondary; + border: none; + padding: 10px 60px; + cursor: pointer; + font-family: @font-main; + font-size: 18px; + transition: all 0.3s ease; + + &:hover { + background-color: lighten(@color-accent, 10%); + transform: translateY(-2px); + } + } +} + +.summary { + margin-bottom: 20px; + + &__item { + display: flex; + justify-content: space-between; + margin-bottom: 10px; + color: @color-text-dark; + + &.total { + font-weight: bold; + font-size: 18px; + padding-top: 10px; + margin-top: 10px; + } + } +} + +.order-btn { + width: 100%; + background-color: @color-accent; + color: @color-secondary; + border: none; + padding: 15px; + border-radius: 4px; + font-size: 18px; + cursor: pointer; + margin-bottom: 10px; + font-family: @font-main; + transition: all 0.3s ease; + + &:hover { + background-color: lighten(@color-accent, 10%); + transform: translateY(-2px); + } +} + +.privacy { + display: flex; + gap: 8px; + font-size: 16px; + color: #666; + margin-bottom: 20px; + + input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; + } +} + +.services { + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; + gap: 24px; + margin-bottom: 24px; + + &__title { + font-family: @font-logo; + margin-bottom: 10px; + font-size: 18px; + color: @color-text-dark; + display: block; + width: 100%; + } + + &__item { + display: flex; + justify-content: space-between; + margin-bottom: 8px; + color: @color-text-dark; + width: 100%; + } +} + +.cart-icon { + position: relative; +} + +.cart-count { + position: absolute; + top: -8px; + right: -8px; + background: @color-accent; + color: @color-text-light; + border-radius: 50%; + width: 18px; + height: 18px; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; +} + +.form__input.error { + border-color: #ff4444; + box-shadow: 0 0 5px rgba(255, 68, 68, 0.3); +} + +.empty-cart { + text-align: center; + padding: 40px; + color: #666; + font-size: 18px; +} + +.profile-page-main { + .flex-center(); + min-height: 80vh; + padding: 40px 0; + background-color: lighten(@color-secondary, 5%); + z-index: 1; + + .profile-container { + display: flex; + width: 100%; + max-width: 1000px; + min-height: 600px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + background-color: @color-text-light; + } + + .profile-left-col { + flex: 0 0 35%; + background-color: @color-primary; + color: @color-text-light; + display: flex; + justify-content: flex-start; + align-items: flex-start; + padding: 40px; + .logo { + font-size: 32px; + font-weight: normal; + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.4); + color: @color-text-light; + } + } + + .profile-right-col { + flex: 0 0 65%; + .flex-center(); + padding: 40px; + .profile-form-block { + width: 100%; + max-width: 400px; + h2 { + font-size: 28px; + font-weight: normal; + margin-bottom: 40px; + text-align: left; + color: @color-text-dark; + } + } + } + + .profile-form { + .input-group { + margin-bottom: 20px; + label { + display: block; + font-size: 12px; + font-weight: bold; + color: @color-text-dark; + margin-bottom: 5px; + text-transform: uppercase; + } + } + + input[type="text"], + input[type="email"], + input[type="tel"] { + .input-base(); + } + + .password-link { + display: block; + text-align: left; + font-size: 13px; + color: @color-text-dark; + text-decoration: underline; + margin: 10px 0 20px; + &:hover { + color: @color-accent; + text-decoration: none; + } + } + + .save-btn { + padding: 15px 30px; + border: none; + cursor: pointer; + font-size: 15px; + text-transform: uppercase; + transition: all 0.3s ease; + font-family: @font-main; + width: 100%; + margin-top: 20px; + background-color: @color-primary; + color: @color-text-light; + + &:hover { + background-color: lighten(@color-primary, 10%); + transform: translateY(-2px); + box-shadow: @shadow-light; + } + } + + .auth-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 25px; + padding-top: 20px; + border-top: 1px solid #eee; + + .auth-text { + font-size: 13px; + color: @color-text-dark; + } + + .login-btn { + background-color: transparent; + color: @color-accent; + border: 1px solid @color-accent; + padding: 10px 25px; + font-size: 13px; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + background-color: @color-primary; + color: @color-text-light; + } + } + } + } +} + +.services-section { + padding: 60px 0; + background-color: @color-secondary; +} + +.services__wrapper { + display: flex; + flex-direction: column; + gap: 30px; +} + +.services__top-row { + display: flex; + gap: 30px; + justify-content: center; + + @media (max-width: 768px) { + flex-direction: column; + align-items: center; + } +} + +.service-card { + border-radius: 8px; + padding: 40px; + min-height: 200px; + text-align: center; + transition: all 0.3s ease; + + &:hover { + transform: translateY(-5px); + box-shadow: @shadow-light; + } + + &--green { + background: @color-primary; + color: @color-text-light; + flex: 1; + max-width: 450px; + } + + &--beige { + background: @color-beige; + color: @color-text-light; + width: 100%; + max-width: 930px; + margin: 0 auto; + } + + &__title { + font-family: @font-logo; + font-size: 24px; + font-weight: bold; + margin-bottom: 15px; + text-transform: uppercase; + } + + &__text { + font-family: @font-main; + font-size: 16px; + line-height: 1.6; + margin: 0; + } +} + +.footer { + background-color: @color-primary; + color: black; + padding: 40px 0 10px; + position: relative; + z-index: 1000; + + &::before { + content: ''; + display: block; + position: absolute; + top: -80px; + left: 0; + width: 100%; + height: 1px; + visibility: hidden; + } + + &__content { + display: flex; + gap: 20px; + padding-bottom: 30px; + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + } + + &__col { + flex: 1; + &--logo { flex: 1.5; } + h5 { + margin-bottom: 15px; + font-size: 14px; + text-transform: uppercase; + } + ul li { + margin-bottom: 8px; + a:hover { text-decoration: underline; } + } + .social-icons, + .payment-icons { + .flex-center(15px); + justify-content: flex-start; + margin-top: 10px; + } + .social-icons .icon { + .icon-base(20px, 1.1); + color: black; + &:hover { color: @color-accent; } + } + .payment-icons .pay-icon { + .icon-base(24px, 1.05); + color: black; + } + } + + .copyright { + text-align: center; + font-size: 12px; + padding-top: 20px; + color: rgba(255, 255, 255, 0.6); + } +} + +.delivery-content { + max-width: 1200px; + margin: 0 auto; + padding: 40px 20px; +} + +.delivery-content h1 { + font-family: @font-logo; + font-size: 42px; + text-align: center; + margin-bottom: 50px; + color: #453227; +} + +.delivery-section { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 30px; + margin-bottom: 60px; +} + +.delivery-card { + background: white; + padding: 30px; + border-radius: 12px; + box-shadow: 0 5px 15px rgba(0,0,0,0.1); + text-align: center; + transition: transform 0.3s ease; + flex: 1; + min-width: 350px; + max-width: 400px; +} + +.delivery-card:hover { + transform: translateY(-5px); +} + +.delivery-icon { + font-size: 48px; + color: #617365; + margin-bottom: 20px; +} + +.delivery-card h3 { + font-family: @font-logo; + font-size: 24px; + margin-bottom: 20px; + color: #453227; +} + +.delivery-details { + text-align: left; +} + +.detail-item { + display: flex; + justify-content: space-between; + margin-bottom: 12px; + padding-bottom: 12px; + border-bottom: 1px solid #f0f0f0; +} + +.detail-label { + font-weight: bold; + color: #333; +} + +.detail-value { + color: #617365; + text-align: right; +} + +.warranty-content { + max-width: 1200px; + margin: 0 auto; + padding: 40px 20px; +} + +.warranty-content h1 { + font-family: 'Anek Kannada', sans-serif; + font-size: 42px; + text-align: center; + margin-bottom: 50px; + color: #453227; +} + +.warranty-overview { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 25px; + margin-bottom: 60px; +} + +.warranty-card { + background: white; + padding: 30px; + border-radius: 12px; + text-align: center; + box-shadow: 0 5px 15px rgba(0,0,0,0.1); + transition: transform 0.3s ease; + flex: 1; + min-width: 250px; + max-width: 280px; + display: flex; + flex-direction: column; + align-items: center; +} + +.warranty-card:hover { + transform: translateY(-5px); +} + +.warranty-icon { + font-size: 48px; + color: #617365; + margin-bottom: 20px; +} + +.warranty-card h3 { + font-family: 'Anek Kannada', sans-serif; + font-size: 20px; + margin-bottom: 15px; + color: #453227; +} + +.warranty-period { + font-size: 24px; + font-weight: bold; + color: #617365; + margin-top: auto; +} + +.coverage-section { + display: flex; + flex-wrap: wrap; + gap: 40px; + margin-bottom: 60px; +} + +.coverage-covered, +.coverage-not-covered { + flex: 1; + min-width: 300px; +} + +.coverage-section h2 { + font-family: 'Anek Kannada', sans-serif; + font-size: 24px; + margin-bottom: 25px; + color: #453227; +} + +.coverage-list { + display: flex; + flex-direction: column; + gap: 20px; +} + +.coverage-item { + display: flex; + align-items: flex-start; + gap: 15px; + padding: 20px; + border-radius: 8px; + background: white; + box-shadow: 0 3px 10px rgba(0,0,0,0.1); +} + +.coverage-item.covered i { + color: #28a745; + font-size: 20px; + margin-top: 2px; + flex-shrink: 0; +} + +.coverage-item.not-covered i { + color: #dc3545; + font-size: 20px; + margin-top: 2px; + flex-shrink: 0; +} + +.coverage-text { + flex: 1; +} + +.coverage-item h4 { + font-family: 'Anek Kannada', sans-serif; + font-size: 16px; + margin-bottom: 5px; + color: #333; +} + +.card { + min-height: 250px; + display: flex; + align-items: center; + justify-content: center; + color: @color-text-light; + text-align: center; + + &--green { + background: @color-primary; + flex: 0 1 450px; + max-width: 450px; + } + + &--beige { + background: @color-beige; + color: @color-text-dark; + flex: 0 1 925px; + max-width: 925px; + } +} + +.design-section { + display: flex; + justify-content: center; + margin-bottom: 40px; + .card { width: 100%; } +} + +@media (max-width: 1240px) { + .catalog-wrapper { gap: 20px; } + .catalog-sidebar { flex: 0 0 200px; } + .products-container { + gap: 15px; + display: flex; + flex-wrap: wrap; + } + + .product-card.small1 { + margin-top: 100px; + } + + .product-card.small, + .product-card.small1, + .product-card.large, + .product-card.wide, + .product-card.wide1, + .product-card.wide2, + .product-card.wide2_1, + .product-card.wide4 { + flex: 0 0 calc(33.333% - 10px); + max-width: calc(33.333% - 10px); + height: 180px; + margin: 0; + + .product-image-container { + height: 180px; + } + } + + .product-card.wide3 { + flex: 0 0 calc(25% - 10px); + max-width: calc(25% - 10px); + height: 300px; + margin: 0; + + .product-image-container { + height: 350px; + } + } + + .product-card.tall { + flex: 0 0 calc(25% - 10px); + max-width: calc(25% - 10px); + height: 300px; + margin: 0; + + .product-image-container { + height: 300px; + } + } + + .product-card.full-width { + flex: 0 0 100%; + max-width: 100%; + height: 300px; + margin: 0; + + .product-image-container { + height: 300px; + } + } + + .product-card.small { order: 1; } + .product-card.large { order: 2; } + .product-card.wide { order: 3; } + + .product-card.small1 { order: 11; } + .product-card.wide2 { order: 12; } + .product-card.wide2_1 { order: 13; } + + .product-card.wide3 { order: 21; } + .product-card.tall { order: 22; } + + .product-card.wide3 { order: 31; } + + .product-card.full-width { order: 41; flex-basis: 100%; } + + .main__content { + gap: 20px; + .products { + flex: 0 0 35%; + .products__image { + width: 250px; + height: 180px; + } + } + .order { + flex: 0 0 60%; + padding: 30px; + + .order__title { + font-size: 24px; + } + + .order__section-title { + font-size: 16px; + } + } + } + + .solutions-slider { + &__slide { + .solution-text-overlay { + top: 10%; + left: 5%; + h2 { + font-size: 26px; + margin-bottom: 5px; + line-height: 1.2; + } + p { + font-size: 18px; + line-height: 1.2; + } + } + .solution-image-link { + bottom: 70px; + padding: 10px 25px; + font-size: 14px; + } + } + } + + .product__main-image { + width: 350px; + height: 350px; + } + + .product__thumbnail { + height: 80px; + } +} + +@media (max-width: 1024px) { + .main__content { + gap: 25px; + .products { + flex: 0 0 30%; + .products__image { + width: 200px; + height: 150px; + } + + .products__name { + font-size: 16px; + } + + .products__price { + font-size: 16px; + } + } + .order { + flex: 0 0 60%; + padding: 25px; + + .order__title { + font-size: 22px; + } + + .form__input { + padding: 12px 14px; + font-size: 14px; + } + + .promo__btn { + padding: 10px 40px; + font-size: 16px; + } + } + } +} + +@media (max-width: 768px) { + .container { padding: 0 15px; } + + .delivery-section { + flex-direction: column; + align-items: center; + } + + .delivery-card { + min-width: 100%; + max-width: 100%; + } + + .delivery-content h1 { + font-size: 32px; + } + + .warranty-overview { + flex-direction: column; + align-items: center; + } + + .warranty-card { + max-width: 100%; + width: 100%; + } + + .coverage-section { + flex-direction: column; + } + + .warranty-content h1 { + font-size: 32px; + } + + .header__top .container, + .header__bottom .container, + .hero__content, + .advantages__header, + .about__content, + .advantages__items, + .promo-images, + .stats__items, + .faq__items, + .catalog-wrapper, + .main__content { + flex-direction: column; + gap: 30px; + } + + .search-catalog { + order: 3; + width: 100%; + max-width: 100%; + } + + .nav-list { + flex-wrap: wrap; + justify-content: center; + gap: 15px; + } + + .hero { + &__image-block { + flex: none; + max-width: 400px; + height: 400px; + } + &__circle { + width: 380px; + height: 380px; + } + &__text-block { + flex: none; + padding-left: 0; + text-align: center; + h1 { font-size: 32px; } + .hero__usp-text { + padding-left: 0; + justify-content: center; + &::before { display: none; } + } + .btn.primary-btn { margin-left: 0; } + } + } + + .advantages__header h2, + .faq h2 { font-size: 28px; } + + .faq-item, + .stat-item { + flex: none; + .flex-center(); + text-align: center; + } + + .stats .container { justify-content: center; } + .catalog-dropdown__menu { width: 200px; } + + .catalog-sidebar { width: 100%; flex: none; } + .products-container { gap: 15px; } + + .product-card.small, + .product-card.small1, + .product-card.large, + .product-card.wide, + .product-card.wide1, + .product-card.wide2, + .product-card.wide2_1, + .product-card.wide3, + .product-card.wide4, + .product-card.tall, + .product-card.full-width { + flex: 0 0 100%; + max-width: 100%; + height: 250px; + margin: 0; + + .product-image-container { + height: 200px; + } + } + + .main__content { + flex-direction: column; + gap: 20px; + + .products, + .order { + flex: 0 0 100%; + width: 100%; + } + + .products { + .products__item { + flex-direction: column; + text-align: center; + gap: 15px; + } + + .products__image { + width: 100%; + height: 200px; + justify-content: center; + } + + .products__details { + min-height: auto; + align-items: center; + } + + .products__controls { + justify-content: center; + margin-top: 15px; + } + + .products__cart-icon { + margin-left: 0; + } + } + + .order { + padding: 20px; + + .order__title { + font-size: 20px; + text-align: center; + } + + .order__total { + text-align: center; + } + + .form__radio-group { + flex-direction: column; + gap: 15px; + } + + .form__radio-label { + flex: none; + justify-content: flex-start; + } + + .promo { + flex-direction: column; + gap: 10px; + + &__btn { + width: 100%; + padding: 12px; + } + } + + .order-btn { + padding: 12px; + font-size: 16px; + } + + .services { + flex-direction: column; + align-items: center; + } + } + } + + .product-image-container { height: 200px; } + .product-card.tall .product-image-container, + .product-card.large .product-image-container { height: 250px; } + + .profile-page-main { + .profile-container { + flex-direction: column; + min-height: auto; + max-width: 100%; + box-shadow: none; + } + .profile-left-col { + flex: none; + width: 100%; + height: 100px; + .flex-center(); + padding: 0; + } + .profile-right-col { + flex: none; + width: 100%; + padding: 30px 20px; + } + .profile-form-block { max-width: 100%; } + } + + .form__row { flex-direction: column; } + .form__input--half { flex: 0 0 100%; max-width: 100%; } + .services { flex-direction: column; align-items: center; } + + .services-section { + padding: 40px 0; + } + + .service-card { + padding: 30px 20px; + min-height: 180px; + + &--green, + &--beige { + max-width: 100%; + } + + &__title { + font-size: 20px; + } + + &__text { + font-size: 14px; + } + } + .solutions-slider { + margin: 20px auto; + &__slide { + .solution-text-overlay { + top: 8%; + left: 4%; + h2 { + font-size: 20px; + margin-bottom: 3px; + line-height: 1.1; + } + p { + font-size: 15px; + line-height: 1.1; + } + } + .solution-image-link { + bottom: 90px; + padding: 8px 20px; + font-size: 13px; + } + } + } +} + +.error-input { + border-color: #ff4444 !important; + box-shadow: 0 0 0 1px #ff4444; +} + +.field-error { + color: #ff4444; + font-size: 12px; + margin-top: 5px; + margin-bottom: 10px; +} + +.message { + padding: 15px; + margin: 20px 0; + border-radius: 5px; + display: none; +} + +.message.error { + background-color: #ffebee; + color: #c62828; + border: 1px solid #ffcdd2; +} + +.message.success { + background-color: #e8f5e9; + color: #2e7d32; + border: 1px solid #c8e6c9; +} + +.access-denied { + text-align: center; + padding: 80px 20px; + background: white; + border-radius: 10px; + box-shadow: 0 5px 15px rgba(0,0,0,0.1); + margin: 50px 0; + + h2 { + color: #dc3545; + margin-bottom: 30px; + font-size: 28px; + } + + p { + color: #666; + margin-bottom: 40px; + font-size: 18px; + line-height: 1.6; + } + + .btn { + margin: 5px; + min-width: 200px; + } +} + +.user-profile-dropdown { + position: relative; + display: inline-block; + + &__toggle { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 8px 12px; + border-radius: 4px; + transition: all 0.3s ease; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + } + + .user-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + background-color: @color-primary; + color: @color-text-light; + .flex-center(); + font-weight: bold; + } + + .user-info { + display: flex; + flex-direction: column; + + .user-email { + font-size: 12px; + color: #666; + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .user-status { + font-size: 10px; + padding: 2px 6px; + border-radius: 10px; + text-transform: uppercase; + + &.admin { + background-color: #617365; + color: white; + } + + &.user { + background-color: #28a745; + color: white; + } + } + } + } + + &__menu { + .menu-base(); + width: 220px; + top: 100%; + right: 0; + + .user-details { + padding: 15px; + border-bottom: 1px solid #eee; + + .user-name { + font-weight: bold; + margin-bottom: 5px; + } + + .user-registered { + font-size: 11px; + color: #999; + } + } + + ul { + padding: 10px 0; + + li { + padding: 8px 15px; + cursor: pointer; + display: flex; + align-items: center; + gap: 10px; + transition: background-color 0.3s ease; + + &:hover { + background-color: #f5f5f5; + } + + &.logout { + color: #dc3545; + border-top: 1px solid #eee; + margin-top: 5px; + padding-top: 12px; + + &:hover { + background-color: #ffe6e6; + } + } + } + } + } + + &:hover &__menu { + display: block; + } +} + +.product-image-container { + position: relative; + overflow: hidden; + margin-bottom: 0; + padding: 0; + height: 250px; + .flex-center(); + + &:hover { + .product-overlay-info { + opacity: 1; + transform: translateY(0); + } + } +} + +.product-overlay-info { + position: absolute; + bottom: 0; + left: 0; + right: 0; + background: linear-gradient(to top, rgba(0,0,0,0.8), transparent); + color: white; + padding: 15px; + opacity: 0; + transform: translateY(10px); + transition: all 0.3s ease; + + .product-overlay-name { + font-weight: bold; + font-size: 14px; + margin-bottom: 5px; + } + + .product-overlay-price { + font-size: 16px; + font-weight: bold; + + .old-price { + text-decoration: line-through; + font-size: 12px; + color: #ccc; + margin-right: 5px; + } + + .current-price { + color: #ffd700; + } + } + + .product-overlay-category { + font-size: 11px; + opacity: 0.8; + margin-top: 3px; + } + + .product-overlay-stock { + font-size: 11px; + margin-top: 5px; + + &.out-of-stock { + color: #ff6b6b; + } + + i { + margin-right: 5px; + } + } +} + +.product-card-details { + padding: 15px; + background: white; + flex-grow: 1; + display: flex; + flex-direction: column; + + .product-card-name { + font-weight: bold; + font-size: 16px; + margin-bottom: 8px; + color: @color-text-dark; + } + + .product-card-description { + font-size: 13px; + color: #777; + margin-bottom: 10px; + flex-grow: 1; + } + + .product-card-attributes { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 10px; + + .attribute { + font-size: 11px; + background: #f5f5f5; + padding: 3px 8px; + border-radius: 12px; + color: #666; + + i { + margin-right: 3px; + } + } + } + + .product-card-price { + margin-bottom: 10px; + + .old-price { + text-decoration: line-through; + font-size: 14px; + color: #999; + margin-right: 8px; + } + + .current-price { + font-size: 18px; + font-weight: bold; + color: @color-button; + } + } + + .add-to-cart-btn { + width: 100%; + padding: 8px; + font-size: 14px; + } + + .admin-actions { + display: flex; + gap: 5px; + margin-top: 10px; + + .admin-btn { + flex: 1; + font-size: 12px; + padding: 6px; + + &.delete-btn { + background: #dc3545; + + &:hover { + background: #c82333; + } + } + } + } +} + +.user-profile-dropdown { + position: relative; + display: inline-block; + + .user-profile-toggle { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + padding: 8px 12px; + border-radius: 4px; + transition: all 0.3s ease; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + + .dropdown-arrow { + transform: rotate(180deg); + } + } + + .user-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + background: linear-gradient(135deg, #617365 0%, #453227 100%); + color: @color-text-light; + .flex-center(); + font-weight: bold; + font-size: 16px; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + } + + .user-info { + display: flex; + flex-direction: column; + + .user-email { + font-size: 12px; + color: #666; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .user-status { + font-size: 10px; + padding: 2px 8px; + border-radius: 12px; + text-transform: uppercase; + font-weight: bold; + text-align: center; + margin-top: 2px; + + &.admin { + background-color: #617365; + color: white; + border: 1px solid #617365; + } + + &.user { + background-color: #28a745; + color: white; + border: 1px solid #28a745; + } + } + } + + .dropdown-arrow { + font-size: 10px; + color: #666; + transition: transform 0.3s ease; + } + } + + .user-profile-menu { + .menu-base(); + width: 280px; + top: 100%; + right: 0; + margin-top: 10px; + padding: 0; + overflow: hidden; + + .user-profile-header { + padding: 20px; + background: linear-gradient(135deg, #617365 0%, #453227 100%); + color: white; + + .user-profile-name { + font-weight: bold; + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 10px; + font-size: 16px; + } + + .user-profile-details { + small { + display: block; + opacity: 0.8; + margin-bottom: 5px; + font-size: 11px; + + i { + margin-right: 5px; + width: 14px; + text-align: center; + } + } + } + } + + .user-profile-links { + list-style: none; + padding: 10px 0; + + li { + border-bottom: 1px solid #f0f0f0; + + &:last-child { + border-bottom: none; + } + + a { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 20px; + color: #333; + transition: all 0.3s ease; + + &:hover { + background-color: #f8f9fa; + color: @color-primary; + text-decoration: none; + + i { + transform: scale(1.1); + } + } + + i { + width: 20px; + text-align: center; + font-size: 14px; + color: #617365; + transition: transform 0.3s ease; + } + + span { + flex-grow: 1; + } + } + } + + .logout-item { + border-top: 2px solid #f0f0f0; + margin-top: 5px; + + a { + color: #dc3545; + + &:hover { + background-color: #ffe6e6; + color: #c82333; + + i { + color: #dc3545; + } + } + } + } + } + } + + &:hover .user-profile-menu { + display: block; + animation: fadeIn 0.3s ease; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 768px) { + .user-profile-dropdown { + .user-profile-toggle { + .user-info { + display: none; + } + + .dropdown-arrow { + display: none; + } + } + + .user-profile-menu { + width: 250px; + right: -50px; + } + } +} +.unavailable-product { + position: relative; + opacity: 0.6; + filter: grayscale(0.7); + + &::before { + content: "ТОВАР ЗАКОНЧИЛСЯ"; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.85); + color: white; + padding: 15px 25px; + border-radius: 5px; + font-weight: bold; + font-size: 16px; + text-align: center; + z-index: 100; + white-space: nowrap; + pointer-events: none; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + } + + .product-name-overlay { + .name, .price { + color: #999 !important; + text-shadow: none !important; + } + } + + .add-to-cart-btn { + display: none !important; + } + + &:hover { + transform: none !important; + cursor: not-allowed; + } +} + +.out-of-stock-badge { + position: absolute; + top: 10px; + left: 10px; + background: #6c757d; + color: white; + padding: 5px 10px; + border-radius: 4px; + font-size: 12px; + font-weight: bold; + z-index: 10; +} + +.admin-table tr.unavailable { + background-color: #f8f9fa !important; + opacity: 0.7; + + td { + color: #999; + } +} + +.status-unavailable { + background-color: #6c757d !important; + color: white !important; +} + +.similar-products { + margin: 40px 0; + + h2 { + font-size: 24px; + margin-bottom: 20px; + color: #453227; + } +} + +.products-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 20px; +} + +.product-card { + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; + + &:hover { + transform: translateY(-5px); + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15); + } +} + +.product-image { + width: 100%; + height: 250px; + overflow: hidden; + background: #f5f5f5; + + img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; + } + + &:hover img { + transform: scale(1.05); + } +} + +.product-info { + padding: 15px; + + h3 { + font-size: 16px; + margin: 0 0 10px; + color: #333; + min-height: 40px; + } + + .product-price { + font-size: 20px; + font-weight: bold; + color: #453227; + margin: 10px 0; + } + + .btn { + width: 100%; + padding: 10px; + text-align: center; + margin-top: 10px; + } +} + +@media (max-width: 1200px) { + .product__section { + grid-template-columns: 330px 1fr; + gap: 25px; + } + + .product__main-image { + width: 330px; + height: 330px; + } +} + +@media (max-width: 992px) { + .product__section { + grid-template-columns: 1fr; + gap: 25px; + } + + .product__main-image { + width: 100%; + max-width: 400px; + height: 350px; + margin: 0 auto; + } + + .product__thumbnails { + grid-template-columns: repeat(4, 1fr); + } +} + +@media (max-width: 576px) { + .product__main-image { + width: 100%; + height: 300px; + } + + .product__thumbnails { + grid-template-columns: repeat(3, 1fr); + } + + .product__info h1 { + font-size: 24px; + } + + .product__price .current-price { + font-size: 28px; + } } \ No newline at end of file diff --git a/uploads/products/.gitkeep b/public/uploads/products/.gitkeep similarity index 100% rename from uploads/products/.gitkeep rename to public/uploads/products/.gitkeep diff --git a/public/uploads/products/product_1765745390_2854.png b/public/uploads/products/product_1765745390_2854.png new file mode 100644 index 0000000..55ab5bb Binary files /dev/null and b/public/uploads/products/product_1765745390_2854.png differ diff --git a/public/uploads/products/product_1765750758_4966.jpg b/public/uploads/products/product_1765750758_4966.jpg new file mode 100644 index 0000000..e59b3b1 Binary files /dev/null and b/public/uploads/products/product_1765750758_4966.jpg differ diff --git a/Гарантия.php b/public/warranty.php similarity index 91% rename from Гарантия.php rename to public/warranty.php index 4730164..95464a5 100644 --- a/Гарантия.php +++ b/public/warranty.php @@ -1,206 +1,202 @@ - - - - - - AETERNA - Гарантия - - - - - - - - - - - - -
-
- - -
-

ГАРАНТИЙНЫЕ ОБЯЗАТЕЛЬСТВА

- -
-
-
- -
-

Мягкая мебель

-
18 месяцев
-
- -
-
- -
-

Корпусная мебель

-
24 месяца
-
- -
-
- -
-

Элементы освещения

-
12 месяцев
-
- -
-
- -
-

Фурнитура и механизмы

-
36 месяцев
-
-
- -
-
-

Что покрывается гарантией

-
-
- -
-

Производственные дефекты

-

Трещины, сколы, брак материалов

-
-
-
- -
-

Неисправности механизмов

-

Трансформации, выдвижные системы

-
-
-
- -
-

Проблемы с фурнитурой

-

Ручки, петли, направляющие

-
-
-
- -
-

Дефекты покрытия

-

Отслоение шпона, краски, ламинации

-
-
-
-
- -
-

Что не покрывается гарантией

-
-
- -
-

Механические повреждения

-

Царапины, вмятины от неправильной эксплуатации

-
-
-
- -
-

Следы износа

-

Естественное старение материалов

-
-
-
- -
-

Неправильная сборка

-

Последствия самостоятельного ремонта

-
-
-
- -
-

Внешние воздействия

-

Повреждения от жидкостей, солнечных лучей

-
-
-
-
-
-
-
-
- - - - + + + + + + + AETERNA - Гарантия + + + + + + + + + + +
+
+ + +
+

ГАРАНТИЙНЫЕ ОБЯЗАТЕЛЬСТВА

+ +
+
+
+ +
+

Мягкая мебель

+
18 месяцев
+
+ +
+
+ +
+

Корпусная мебель

+
24 месяца
+
+ +
+
+ +
+

Элементы освещения

+
12 месяцев
+
+ +
+
+ +
+

Фурнитура и механизмы

+
36 месяцев
+
+
+ +
+
+

Что покрывается гарантией

+
+
+ +
+

Производственные дефекты

+

Трещины, сколы, брак материалов

+
+
+
+ +
+

Неисправности механизмов

+

Трансформации, выдвижные системы

+
+
+
+ +
+

Проблемы с фурнитурой

+

Ручки, петли, направляющие

+
+
+
+ +
+

Дефекты покрытия

+

Отслоение шпона, краски, ламинации

+
+
+
+
+ +
+

Что не покрывается гарантией

+
+
+ +
+

Механические повреждения

+

Царапины, вмятины от неправильной эксплуатации

+
+
+
+ +
+

Следы износа

+

Естественное старение материалов

+
+
+
+ +
+

Неправильная сборка

+

Последствия самостоятельного ремонта

+
+
+
+ +
+

Внешние воздействия

+

Повреждения от жидкостей, солнечных лучей

+
+
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/стили_оформления.less b/public/стили_оформления.less similarity index 78% rename from стили_оформления.less rename to public/стили_оформления.less index fb07c54..a6fe073 100644 --- a/стили_оформления.less +++ b/public/стили_оформления.less @@ -1,142 +1,137 @@ -.error-message { - color: #ff0000; - font-size: 12px; - margin-top: 5px; - display: none; -} - -.form__input.error { - border-color: #ff0000; -} - -.form__group { - position: relative; - margin-bottom: 15px; -} - -/* Стили для сообщений внизу страницы */ -.page-messages { - position: fixed; - bottom: 20px; - left: 50%; - transform: translateX(-50%); - z-index: 1000; - width: 90%; - max-width: 500px; -} - -.message { - padding: 15px; - margin: 10px 0; - border-radius: 5px; - text-align: center; - font-weight: bold; - display: none; -} - -.message.error { - background-color: #ffebee; - color: #c62828; - border: 1px solid #ffcdd2; -} - -.message.success { - background-color: #e8f5e9; - color: #453227; - border: 1px solid #c8e6c9; -} - -.message.warning { - background-color: #fff3e0; - color: #ef6c00; - border: 1px solid #ffe0b2; -} - -.privacy-error { - color: #ff0000; - font-size: 12px; - margin-top: 5px; - display: none; - text-align: center; -} - -/* Дополнительные стили для формы регистрации */ -.input-group { - position: relative; - margin-bottom: 20px; -} - -.profile-form input.error { - border-color: #ff0000; - background-color: #fff5f5; -} - -.privacy-checkbox { - margin: 20px 0; - text-align: center; -} - -.privacy-checkbox label { - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - cursor: pointer; - font-size: 14px; -} - -.privacy-checkbox input[type="checkbox"] { - margin: 0; -} - -/* Исправление отступов для страницы регистрации */ -.profile-page-main { - padding: 40px 0; - min-height: calc(100vh - 200px); -} - -/* Убедимся, что контейнер не перекрывает шапку и футер */ -.profile-container { - margin: 0 auto; - position: relative; - z-index: 1; -} - -.input-hint { - font-size: 12px; - color: #666; - margin-top: 5px; -} - -/* Стили для страницы входа */ -.form-options { - display: flex; - justify-content: space-between; - align-items: center; - margin: 20px 0; -} - -.remember-me { - display: flex; - align-items: center; - gap: 8px; - font-size: 14px; - color: #453227; -} - -.remember-me input[type="checkbox"] { - width: 16px; - height: 16px; - cursor: pointer; -} - -.forgot-password { - font-size: 14px; - color: #453227; - text-decoration: underline; -} - -.forgot-password:hover { - color: #617365; - text-decoration: none; +.error-message { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; +} + +.form__input.error { + border-color: #ff0000; +} + +.form__group { + position: relative; + margin-bottom: 15px; +} + +.page-messages { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + width: 90%; + max-width: 500px; +} + +.message { + padding: 15px; + margin: 10px 0; + border-radius: 5px; + text-align: center; + font-weight: bold; + display: none; +} + +.message.error { + background-color: #ffebee; + color: #c62828; + border: 1px solid #ffcdd2; +} + +.message.success { + background-color: #e8f5e9; + color: #453227; + border: 1px solid #c8e6c9; +} + +.message.warning { + background-color: #fff3e0; + color: #ef6c00; + border: 1px solid #ffe0b2; +} + +.privacy-error { + color: #ff0000; + font-size: 12px; + margin-top: 5px; + display: none; + text-align: center; +} + +.input-group { + position: relative; + margin-bottom: 20px; +} + +.profile-form input.error { + border-color: #ff0000; + background-color: #fff5f5; +} + +.privacy-checkbox { + margin: 20px 0; + text-align: center; +} + +.privacy-checkbox label { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; + font-size: 14px; +} + +.privacy-checkbox input[type="checkbox"] { + margin: 0; +} + +.profile-page-main { + padding: 40px 0; + min-height: calc(100vh - 200px); +} + +.profile-container { + margin: 0 auto; + position: relative; + z-index: 1; +} + +.input-hint { + font-size: 12px; + color: #666; + margin-top: 5px; +} + +.form-options { + display: flex; + justify-content: space-between; + align-items: center; + margin: 20px 0; +} + +.remember-me { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + color: #453227; +} + +.remember-me input[type="checkbox"] { + width: 16px; + height: 16px; + cursor: pointer; +} + +.forgot-password { + font-size: 14px; + color: #453227; + text-decoration: underline; +} + +.forgot-password:hover { + color: #617365; + text-decoration: none; } \ No newline at end of file diff --git a/register.php b/register.php deleted file mode 100644 index a5d967b..0000000 --- a/register.php +++ /dev/null @@ -1,87 +0,0 @@ -getConnection(); - - try { - // Проверяем существование email - $check_stmt = $db->prepare("SELECT user_id FROM users WHERE email = ?"); - $check_stmt->execute([$email]); - - if ($check_stmt->fetch()) { - $errors[] = 'Пользователь с таким email уже существует'; - } else { - // Хэшируем пароль - $password_hash = password_hash($password, PASSWORD_DEFAULT); - - // Создаем пользователя - $stmt = $db->prepare(" - INSERT INTO users (email, password_hash, full_name, phone, is_active) - VALUES (?, ?, ?, ?, TRUE) - RETURNING user_id - "); - - $stmt->execute([$email, $password_hash, $full_name, $phone]); - $user_id = $stmt->fetchColumn(); - - if ($user_id) { - // Автоматически входим - $_SESSION['user_id'] = $user_id; - $_SESSION['user_email'] = $email; - $_SESSION['full_name'] = $full_name; - $_SESSION['isLoggedIn'] = true; - $_SESSION['isAdmin'] = false; // Обычный пользователь - - // Успешная регистрация - $_SESSION['registration_success'] = true; - header('Location: catalog.php'); - exit(); - } else { - $errors[] = 'Ошибка при создании пользователя'; - } - } - } catch (PDOException $e) { - error_log("Ошибка регистрации: " . $e->getMessage()); - $errors[] = 'Ошибка базы данных. Попробуйте позже.'; - } - } - - // Если есть ошибки, сохраняем их - if (!empty($errors)) { - $_SESSION['registration_errors'] = $errors; - $_SESSION['old_input'] = [ - 'email' => $email, - 'full_name' => $full_name, - 'phone' => $phone - ]; - header('Location: профиль.php'); - exit(); - } -} else { - header('Location: профиль.php'); - exit(); -} -?> \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..03cd61a --- /dev/null +++ b/setup.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# +# AETERNA - Скрипт настройки проекта +# Запуск: ./setup.sh +# + +echo "===========================================" +echo " AETERNA - Настройка проекта" +echo "===========================================" +echo "" + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$PROJECT_DIR" + +# Проверяем наличие PHP +if ! command -v php &> /dev/null; then + echo "[ERROR] PHP не установлен!" + exit 1 +fi + +echo "[1/3] Проверка структуры проекта..." +REQUIRED_DIRS=("public" "admin" "api" "includes" "config" "migrations" "assets" "uploads") +for dir in "${REQUIRED_DIRS[@]}"; do + if [ ! -d "$dir" ]; then + mkdir -p "$dir" + echo " Создана папка: $dir" + fi +done +echo " ✓ Структура OK" + +echo "" +echo "[2/3] Запуск миграций базы данных..." +if [ -f "migrations/migrate.php" ]; then + php migrations/migrate.php +else + echo " ⚠ Файл миграций не найден" +fi + +echo "" +echo "[3/3] Загрузка начальных данных..." +echo " Загрузить тестовые данные (админ, категории, товары)? (y/n)" +read -r answer +if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then + php migrations/migrate.php --seed + echo " ✓ Данные загружены" +else + echo " Пропущено" +fi + +echo "" +echo "===========================================" +echo " Готово!" +echo "===========================================" +echo "" +echo "Тестовые аккаунты:" +echo " Админ: admin@aeterna.ru / admin123" +echo " Пользователь: user@test.com / user123" +echo "" +echo "Запуск сервера:" +echo " php -S localhost:8000 -t public" +echo "" diff --git a/test_add_simple.php b/test_add_simple.php deleted file mode 100644 index b68e64d..0000000 --- a/test_add_simple.php +++ /dev/null @@ -1,131 +0,0 @@ -getConnection(); - -// Обработка формы -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $action = $_POST['action'] ?? ''; - - if ($action === 'add_category') { - $name = $_POST['name'] ?? ''; - $slug = preg_replace('/[^a-z0-9]/i', '-', strtolower($name)); - - try { - $stmt = $db->prepare("INSERT INTO categories (name, slug) VALUES (?, ?)"); - $result = $stmt->execute([$name, $slug]); - - if ($result) { - $message = "✅ Категория '$name' успешно добавлена!"; - $message_class = "success"; - } else { - $message = "❌ Ошибка при добавлении категории"; - $message_class = "error"; - } - } catch (PDOException $e) { - $message = "❌ Ошибка БД: " . $e->getMessage(); - $message_class = "error"; - } - } - - if ($action === 'add_product') { - $name = $_POST['product_name'] ?? ''; - $price = floatval($_POST['price'] ?? 0); - $category_id = intval($_POST['category_id'] ?? 1); - $slug = preg_replace('/[^a-z0-9]/i', '-', strtolower($name)); - - try { - $stmt = $db->prepare("INSERT INTO products (category_id, name, slug, price, stock_quantity, is_available) VALUES (?, ?, ?, ?, ?, ?)"); - $result = $stmt->execute([$category_id, $name, $slug, $price, 10, true]); - - if ($result) { - $message = "✅ Товар '$name' успешно добавлен!"; - $message_class = "success"; - } else { - $message = "❌ Ошибка при добавлении товара"; - $message_class = "error"; - } - } catch (PDOException $e) { - $message = "❌ Ошибка БД: " . $e->getMessage(); - $message_class = "error"; - } - } -} - -// Получить категории для выпадающего списка -$categories = []; -try { - $categories = $db->query("SELECT * FROM categories")->fetchAll(); -} catch (Exception $e) { - $categories_error = "Ошибка получения категорий: " . $e->getMessage(); -} - -?> - - - - - Тестовое добавление - - - -

Тестовое добавление

- - -
- - -
-

Добавить категорию

- - - -
- -
-

Добавить товар

- -
-
- -
- - -
- -
- -

Существующие категории ():

- - - -
- - - \ No newline at end of file diff --git a/страница_товара.php b/страница_товара.php deleted file mode 100644 index 21058b5..0000000 --- a/страница_товара.php +++ /dev/null @@ -1,237 +0,0 @@ - - - - - - AETERNA - Стул Ender - - - - - - - - -
-
-
- - -
-
- Все категории - -
- -
- -
- - - -
-
-
- -
- -
-
- -
-
- - -
- - -
-

Стул ENDER

- -
- - 9/10 - 54 отзывов -
- -
-
-
-
-
-
- -

- Стулья с закрытой обивкой созданы в стилистике практичности и комфортабельности, - придает помещению, эффектно выделяющимися средствами. -

- - -
-
16 999 ₽
-
- - 1 - -
-
- -
- - -
-
-
- -
-

Похожие

-
-
-
- Кресло ASTER -
- -
- -
-
- Кресло GETSBY -
- -
- -
-
- Кресло PASTEL -
- -
-
-
-
-
- - - - - - \ No newline at end of file