Исправление багов авторизации, корзины и админки
- Исправлено выпадающее меню профиля (hover-баг с margin-top) - Исправлена авторизация: правильные пути к API (api/auth.php) - Исправлены ссылки на админку (admin/index.php вместо admin_panel.php) - Исправлены пути API корзины в catalog.php и checkout.php - Добавлена форма добавления/редактирования товаров в админке - Исправлены кнопки +/- в корзине (улучшена обработка AJAX) - Исправлена регистрация: правильные пути и обработка boolean в PostgreSQL - Добавлена миграция для назначения прав админа пользователю admin@mail.ru - Удален тестовый блок 'Быстрый вход' для неавторизованных пользователей - Улучшена обработка ошибок во всех API-эндпоинтах
BIN
assets/img/1 — копия.jpg
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
assets/img/1.jpg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
assets/img/100.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
assets/img/11.jpg
Normal file
|
After Width: | Height: | Size: 183 KiB |
BIN
assets/img/111.jpg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
assets/img/11_1.png
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
assets/img/1_1.jpg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/img/1_2.jpg
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
assets/img/1_2.png
Normal file
|
After Width: | Height: | Size: 190 KiB |
BIN
assets/img/2.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
assets/img/22.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
assets/img/25.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/img/2_2.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
assets/img/2_2.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
assets/img/3.jpg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
assets/img/3_3.jpg
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
assets/img/3_3.png
Normal file
|
After Width: | Height: | Size: 414 KiB |
BIN
assets/img/4.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/img/44.jpg
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
assets/img/444
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
assets/img/444 (1).png
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
assets/img/444.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
assets/img/444.png
Normal file
|
After Width: | Height: | Size: 291 KiB |
BIN
assets/img/4_1.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
assets/img/5.jpg
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
assets/img/5_5.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
assets/img/5_5.png
Normal file
|
After Width: | Height: | Size: 198 KiB |
BIN
assets/img/6.jpg
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
assets/img/6_6.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
assets/img/6_6.png
Normal file
|
After Width: | Height: | Size: 372 KiB |
BIN
assets/img/7.jpg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
assets/img/77.jpg
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
assets/img/777 (1).png
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
assets/img/777.jpg
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
assets/img/777.png
Normal file
|
After Width: | Height: | Size: 555 KiB |
BIN
assets/img/7_7.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
assets/img/7_7.png
Normal file
|
After Width: | Height: | Size: 479 KiB |
BIN
assets/img/8.jpg
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
assets/img/88.jpg
Normal file
|
After Width: | Height: | Size: 124 KiB |
BIN
assets/img/888 (1).png
Normal file
|
After Width: | Height: | Size: 176 KiB |
BIN
assets/img/888.jpg
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
assets/img/888.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
assets/img/8_8.png
Normal file
|
After Width: | Height: | Size: 430 KiB |
BIN
assets/img/9.jpg
Normal file
|
After Width: | Height: | Size: 167 KiB |
BIN
assets/img/99.jpg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
assets/img/99.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
assets/img/99_1.jpg
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
assets/img/99_2.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
assets/img/99_3.png
Normal file
|
After Width: | Height: | Size: 497 KiB |
BIN
assets/img/9_9.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
assets/img/9_9.png
Normal file
|
After Width: | Height: | Size: 759 KiB |
BIN
assets/img/black.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/img/black1.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/img/black2.png
Normal file
|
After Width: | Height: | Size: 654 KiB |
BIN
assets/img/brown.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/img/brown1.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
assets/img/brown2.png
Normal file
|
After Width: | Height: | Size: 739 KiB |
BIN
assets/img/chair.PNG
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
assets/img/gray.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
assets/img/gray1.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
assets/img/gray2.png
Normal file
|
After Width: | Height: | Size: 655 KiB |
BIN
assets/img/диван.jpg
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
assets/img/диван_1.jpg
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
assets/img/кресло.jpg
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
assets/img/кресло_1.jpg
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
assets/img/слайдер_1.jpg
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
assets/img/слайдер_2.jpg
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
assets/img/слайдер_3.jpg
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
assets/img/слайдер_4.jpg
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
assets/img/слайдер_5.jpg
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
assets/img/слайдер_6.jpg
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
assets/img/спальня.jpg
Normal file
|
After Width: | Height: | Size: 151 KiB |
62
assets/img/стили_оформления.css
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
.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;
|
||||
}
|
||||
346
assets/js/checkout.js
Normal file
@@ -0,0 +1,346 @@
|
||||
// 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('<div class="field-error" id="' + fieldId.replace('#', '') + '-error">' + message + '</div>');
|
||||
}
|
||||
}
|
||||
|
||||
// Валидация поля при потере фокуса с указанием конкретной ошибки
|
||||
$('#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('<div class="empty-cart">Корзина пуста</div>');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Обработчик применения промокода
|
||||
$('.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();
|
||||
});
|
||||
384
assets/js/profile.js
Normal file
@@ -0,0 +1,384 @@
|
||||
$(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', 'Для восстановления пароля обратитесь к администратору');
|
||||
});
|
||||
});
|
||||
142
assets/less/checkout.less
Normal file
@@ -0,0 +1,142 @@
|
||||
.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;
|
||||
}
|
||||
86
assets/less/mixins.less
Normal file
@@ -0,0 +1,86 @@
|
||||
// ===================================
|
||||
// === ПЕРЕМЕННЫЕ И МИКСИНЫ AETERNA ===
|
||||
// ===================================
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||