commit 2c516362df4c2c7dc55ede14d7b8aa5c92bdf527 Author: Fedor Date: Thu Jan 15 15:40:13 2026 +0300 feat: Secure SMS verification with Redis (Predis) - Added Predis library for Redis connection (no PHP extension required) - Server-side SMS code generation and storage in Redis - Rate limiting and brute-force protection - Integration with n8n webhook for SMS sending - Environment variables moved to .env file - Fixed policy verification endpoint - Added file-based fallback if Redis unavailable diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58963af --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Логи +logs/ +*.log + +# Временные файлы +*.tmp +*.temp +*.swp +*.swo +*~ + +# Системные файлы +.DS_Store +Thumbs.db +.idea/ +.vscode/ + +# Файлы загрузок (если есть временная папка) +uploads/temp/ +tmp/ + +# Конфигурационные файлы с секретами +config.local.php +.env.local +.env + +# Composer dependencies +vendor/ + +# Storage (SMS коды и прочее) +storage/ + diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..d7b65f9 --- /dev/null +++ b/.htaccess @@ -0,0 +1,21 @@ + + Header set Access-Control-Allow-Origin "*" + + +RewriteEngine On +RewriteCond %{HTTPS} off +RewriteCond %{HTTP:X-Forwarded-Proto} !https +RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] +Options -Indexes +php_value error_reporting "E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT" +php_flag short_open_tag off +php_flag display_errors on +php_flag log_errors on +php_value max_execution_time 0 +php_value max_input_time 6000 +php_value max_input_vars 150000 +php_value max_file_uploads 100 +php_value upload_max_filesize 400M +php_value post_max_size 410M +php_value default_socket_timeout 600 +php_value memory_limit -1 diff --git a/CHANGELOG_SMS.md b/CHANGELOG_SMS.md new file mode 100644 index 0000000..48f6517 --- /dev/null +++ b/CHANGELOG_SMS.md @@ -0,0 +1,96 @@ +# Изменения в SMS верификации + +## Дата: 2025-01-14 + +### ✅ Выполнено + +1. **Создан новый безопасный API** (`sms-verify.php`) + - Генерация кода на сервере (не на клиенте) + - Хранение кодов в Redis с TTL 10 минут + - Серверная проверка кода + - Rate limiting: 5 SMS в час на номер + - Защита от брутфорса: блокировка после 10 попыток на 15 минут + - Использование кредов из `.env` + +2. **Создана утилита для загрузки .env** (`env_loader.php`) + - Автоматическая загрузка переменных из `.env` + - Функция `env()` для получения значений + +3. **Обновлен JavaScript** (`js/common.js`) + - Функция `send_sms()` теперь использует новый API + - Проверка кода выполняется на сервере + - Добавлен обработчик для повторной отправки SMS + +4. **Обновлен старый API** (`sms-test.php`) + - Теперь работает как прокси к новому API + - Обеспечивает обратную совместимость + +### 📁 Новые файлы + +- `env_loader.php` - утилита для загрузки .env +- `sms-verify.php` - новый безопасный API +- `SMS_VERIFICATION_README.md` - документация + +### 🔄 Измененные файлы + +- `sms-test.php` - переписан как прокси +- `js/common.js` - обновлен для использования нового API + +### 🔐 Безопасность + +**До:** +- ❌ Код генерировался на клиенте (JavaScript) +- ❌ Код хранился в переменной JavaScript +- ❌ Проверка кода только на клиенте +- ❌ Креды в открытом виде в коде +- ❌ Нет защиты от брутфорса +- ❌ Нет rate limiting + +**После:** +- ✅ Код генерируется на сервере +- ✅ Код хранится в Redis +- ✅ Проверка кода на сервере +- ✅ Креды в `.env` файле +- ✅ Защита от брутфорса (10 попыток → блокировка) +- ✅ Rate limiting (5 SMS/час) + +### 📊 Используемые переменные из .env + +``` +SMS_API_URL=https://online.sigmasms.ru/api/ +SMS_LOGIN=kfv.advokat@gmail.com +SMS_PASSWORD=s7NRIb +SMS_TOKEN=27f89492e00973263ff746a655663678fae7203bac8b62919700e489e33b3902 +SMS_SENDER=Clientright +REDIS_HOST=crm.clientright.ru +REDIS_PORT=6379 +REDIS_PASSWORD=CRM_Redis_Pass_2025_Secure! +``` + +### 🚀 Endpoints + +**Новый API (рекомендуется):** +- `POST /sms-verify.php?action=send` - Отправка SMS +- `POST /sms-verify.php?action=verify` - Проверка кода +- `POST /sms-verify.php?action=check_verified` - Проверка статуса + +**Старый API (для обратной совместимости):** +- `POST /sms-test.php` - Прокси к новому API + +### 📝 Логи + +Все операции логируются в: +- `logs/sms_verify.log` + +### ✅ Проверка + +Все файлы проверены на синтаксические ошибки: +- ✅ `sms-verify.php` - OK +- ✅ `sms-test.php` - OK +- ✅ `env_loader.php` - OK + +### 🎯 Статус + +**Готово к использованию!** + +JavaScript уже использует новый API (`sms-verify.php`), старый API (`sms-test.php`) работает как прокси для обратной совместимости. diff --git a/POLICY_CHECK_FIX.md b/POLICY_CHECK_FIX.md new file mode 100644 index 0000000..b347f12 --- /dev/null +++ b/POLICY_CHECK_FIX.md @@ -0,0 +1,53 @@ +# Исправление проверки полиса + +## Проблемы, которые были исправлены: + +1. ✅ **Хардкод кредов MySQL** - теперь используются переменные из `.env` +2. ✅ **Отсутствие логирования** - добавлено логирование в `logs/policy_check.log` +3. ✅ **Улучшена обработка ошибок** - более детальные сообщения об ошибках +4. ✅ **Нормализация номера полиса** - добавлена замена кириллической "А" на латинскую "A" +5. ✅ **Заголовки JSON** - добавлены правильные заголовки для JSON ответов + +## Что нужно добавить в .env: + +Добавьте следующие переменные в `.env` файл (если их еще нет): + +```env +# MySQL ERV (для проверки полисов) +MYSQL_ERV_HOST=localhost +MYSQL_ERV_USER=ci20465_erv +MYSQL_ERV_PASSWORD=c7vOXbmG +MYSQL_ERV_DB=ci20465_erv +``` + +Если переменные не указаны, используются значения по умолчанию (для обратной совместимости). + +## Как работает проверка полиса: + +1. Пользователь вводит номер полиса +2. Нажимает кнопку "Проверить наличие полиса" +3. JavaScript отправляет AJAX запрос на `database.php?action=user_verify` +4. `database.php`: + - Подключается к БД MySQL (ci20465_erv) + - Ищет полис в таблице `lexrpiority` по полю `voucher` + - Проверяет тарифы (должны быть в списке валидных) + - Возвращает результат + +## Логирование: + +Все операции логируются в `logs/policy_check.log`: +- Попытки подключения к БД +- Номера полисов (частично, для безопасности) +- Результаты проверки +- Ошибки + +## Проверка работы: + +1. Откройте форму +2. Введите номер полиса +3. Нажмите "Проверить наличие полиса" +4. Проверьте логи: `tail -f logs/policy_check.log` + +## Статус: + +✅ **Исправлено и готово к использованию!** diff --git a/SMS_VERIFICATION_README.md b/SMS_VERIFICATION_README.md new file mode 100644 index 0000000..cb7a3dd --- /dev/null +++ b/SMS_VERIFICATION_README.md @@ -0,0 +1,118 @@ +# Безопасная SMS верификация + +## Что было исправлено + +### Проблемы безопасности (исправлены): + +1. ✅ **Код генерируется на сервере** (раньше на клиенте в JavaScript) +2. ✅ **Код хранится в Redis** (раньше в переменной JavaScript) +3. ✅ **Проверка кода на сервере** (раньше только на клиенте) +4. ✅ **Креды вынесены в .env** (раньше хардкод в коде) +5. ✅ **Rate limiting** - ограничение на количество отправок SMS (5 в час) +6. ✅ **Защита от брутфорса** - блокировка после 10 неудачных попыток (15 минут) + +## Новые файлы + +### `env_loader.php` +Утилита для загрузки переменных из `.env` файла. + +### `sms-verify.php` +Новый безопасный API для SMS верификации с тремя endpoints: + +- **`POST /sms-verify.php?action=send`** - Отправка SMS кода + - Параметры: `phonenumber` + - Возвращает: `{success: true, message: "..."}` + +- **`POST /sms-verify.php?action=verify`** - Проверка кода + - Параметры: `phonenumber`, `code` + - Возвращает: `{success: true, token: "..."}` - токен для последующей проверки + +- **`POST /sms-verify.php?action=check_verified`** - Проверка статуса верификации + - Параметры: `phonenumber`, `token` + - Возвращает: `{success: true, verified: true/false}` + +## Изменения в существующих файлах + +### `js/common.js` +- Функция `send_sms()` теперь отправляет запрос на сервер +- Проверка кода теперь выполняется на сервере через AJAX +- Добавлен обработчик для повторной отправки SMS из модального окна + +### `.env` +Используются следующие переменные: +- `SMS_API_URL` - URL API SigmaSMS +- `SMS_LOGIN` - Логин для SigmaSMS +- `SMS_PASSWORD` - Пароль для SigmaSMS +- `SMS_TOKEN` - Токен (опционально, если есть) +- `SMS_SENDER` - Имя отправителя +- `REDIS_HOST` - Хост Redis +- `REDIS_PORT` - Порт Redis +- `REDIS_PASSWORD` - Пароль Redis + +## Логирование + +Все операции логируются в файл: +- `logs/sms_verify.log` + +## Безопасность + +### Rate Limiting +- Максимум **5 отправок SMS** на номер в час +- Максимум **10 попыток проверки кода** за 15 минут + +### Хранение кодов +- Коды хранятся в Redis с TTL **10 минут** +- После успешной проверки код удаляется из Redis +- Токен верификации действует **1 час** + +### Защита от брутфорса +- После 10 неудачных попыток номер блокируется на 15 минут +- Счетчик попыток сбрасывается после успешной проверки + +## Миграция со старого API + +Старый файл `sms-test.php` оставлен для обратной совместимости, но рекомендуется использовать новый `sms-verify.php`. + +### Изменения в JavaScript: +```javascript +// Старый код (небезопасный): +var sended_code = Math.floor(Math.random()*(999999-100000+1)+100000); +if($('.sms-checking input[type="text"]').val() == sended_code) { + // проверка на клиенте +} + +// Новый код (безопасный): +$.ajax({ + url: 'sms-verify.php?action=send', + // отправка на сервер +}); +$.ajax({ + url: 'sms-verify.php?action=verify', + // проверка на сервере +}); +``` + +## Требования + +- PHP с расширением Redis +- Доступ к Redis серверу +- Настроенные креды в `.env` файле + +## Тестирование + +1. Проверьте подключение к Redis: +```bash +php -r "require 'env_loader.php'; require 'sms-verify.php';" +``` + +2. Проверьте отправку SMS: +```bash +curl -X POST "http://your-domain/sms-verify.php?action=send" \ + -d "phonenumber=79991234567" +``` + +3. Проверьте проверку кода: +```bash +curl -X POST "http://your-domain/sms-verify.php?action=verify" \ + -d "phonenumber=79991234567&code=123456" +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d3e8e56 --- /dev/null +++ b/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "predis/predis": "^3.3" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..f968df8 --- /dev/null +++ b/composer.lock @@ -0,0 +1,135 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c3902467a00c42aac080128ac52fd7b9", + "packages": [ + { + "name": "predis/predis", + "version": "v3.3.0", + "source": { + "type": "git", + "url": "https://github.com/predis/predis.git", + "reference": "153097374b39a2f737fe700ebcd725642526cdec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/predis/predis/zipball/153097374b39a2f737fe700ebcd725642526cdec", + "reference": "153097374b39a2f737fe700ebcd725642526cdec", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.0|^2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.3", + "phpstan/phpstan": "^1.9", + "phpunit/phpcov": "^6.0 || ^8.0", + "phpunit/phpunit": "^8.0 || ~9.4.4" + }, + "suggest": { + "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "Predis\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Till Krüss", + "homepage": "https://till.im", + "role": "Maintainer" + } + ], + "description": "A flexible and feature-complete Redis/Valkey client for PHP.", + "homepage": "http://github.com/predis/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "support": { + "issues": "https://github.com/predis/predis/issues", + "source": "https://github.com/predis/predis/tree/v3.3.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/tillkruss", + "type": "github" + } + ], + "time": "2025-11-24T17:48:50+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/css/custom.css b/css/custom.css new file mode 100644 index 0000000..bd74124 --- /dev/null +++ b/css/custom.css @@ -0,0 +1,67 @@ +form { + width: 700px; + margin: 0 auto; + padding: 40px 0; +} + +fieldset { + border: 1px solid #d1d1d1; + padding: 20px; + border-radius: 3px; +} + +[haserror="yes"] { + border: 2px solid tomato !important; +} + +fieldset.constant { + display: none; +} + +fieldset.hidden { + display: none; +} + +.sum_removing { + display: none; +} + +.error-message { + color: tomato; +} + +.claim_additional { + display: none; +} + +#tour-product, +#tour-accomodation, +#tour-transportation, +#tour-other { + display: none; +} + +.autocomplete { + padding: 10px 10px 10px 10px; + border: 1px solid #f3f3f3; + display: none; +} + +.autocomplete.active { + display: block; +} + +.autocomplete__item { + padding: 2px; + font-weight: 400; +} + +.autocomplete__item:hover { + cursor: pointer; + background-color: #f3f3f3; +} + +.country-select{ + width: 100% !important; +} + diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..8f3e633 --- /dev/null +++ b/css/main.css @@ -0,0 +1,610 @@ +@font-face { + font-family: "r-regular"; + font-weight: normal; + font-style: normal; + src: url("../fonts/Roboto/Roboto-Regular.eot"); + src: url("../fonts/Roboto/Roboto-Regular.eot?#iefix") format("embedded-opentype"), url("../fonts/Roboto/Roboto-Regular.woff") format("woff"), url("../fonts/Roboto/Roboto-Regular.ttf") format("truetype"); +} +@font-face { + font-family: "r-medium"; + font-weight: normal; + font-style: normal; + src: url("../fonts/Roboto/Roboto-Medium.eot"); + src: url("../fonts/Roboto/Roboto-Medium.eot?#iefix") format("embedded-opentype"), url("../fonts/Roboto/Roboto-Medium.woff") format("woff"), url("../fonts/Roboto/Roboto-Medium.ttf") format("truetype"); +} +@font-face { + font-family: "r-bold"; + font-weight: normal; + font-style: normal; + src: url("../fonts/Roboto/Roboto-Bold.eot"); + src: url("../fonts/Roboto/Roboto-Bold.eot?#iefix") format("embedded-opentype"), url("../fonts/Roboto/Roboto-Bold.woff") format("woff"), url("../fonts/Roboto/Roboto-Bold.ttf") format("truetype"); +} +@font-face { + font-family: "r-light"; + font-weight: normal; + font-style: normal; + src: url("../fonts/Roboto/Roboto-Light.eot"); + src: url("../fonts/Roboto/Roboto-Light.eot?#iefix") format("embedded-opentype"), url("../fonts/Roboto/Roboto-Light.woff") format("woff"), url("../fonts/Roboto/Roboto-Light.ttf") format("truetype"); +} +@font-face { + font-family: "r-semibold"; + font-weight: normal; + font-style: normal; + src: url("../fonts/Roboto/Roboto-SemiBold.eot"); + src: url("../fonts/Roboto/Roboto-SemiBold.eot?#iefix") format("embedded-opentype"), url("../fonts/Roboto/Roboto-SemiBold.woff") format("woff"), url("../fonts/Roboto/Roboto-SemiBold.ttf") format("truetype"); +} +/*! + * Bootstrap Reboot v4.0.0 (https://getbootstrap.com) + * Copyright 2011-2018 The Bootstrap Authors + * Copyright 2011-2018 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) + */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-family: 'r-regular',Arial,sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; + -ms-overflow-style: scrollbar; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +@-ms-viewport { + width: device-width; +} +article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; +} + +[tabindex="-1"]:focus { + outline: 0 !important; +} + +.container { + max-width: 900px; + margin: 0 auto; + padding-left: 10px; + padding-right: 10px; +} + +.form{ + padding-top: 100px; + max-width: 760px; + margin: 0 auto; +} +.form__title{ + font-weight: normal; + text-align: center; + font-size: 24px; + line-height: 1.5; + max-width: 560px; + margin: 0 auto; + margin-bottom: 50px; +} +.form__title strong{ + font-weight: bold; +} + +.form-item { + margin-bottom: 20px; +} +.form-item .form-item__label { + font-size: 20px; + line-height: 1.55; + display: block; + padding-bottom: 5px; +} +.form-item .form-item__sublabel { + /* font-family: r-light; */ + margin-bottom: 25px; + font-size: 16px; + line-height: 1.55; + display: block; +} +.form-item .form-item__sublabel a{ + color: #ff8562; + text-decoration: none; +} +.form-item .form-input, .form-item .t-datepicker{ + margin: 0; + font-size: 100%; + height: 60px; + padding: 0 20px; + font-size: 16px; + line-height: 1.33; + width: 100%; + border: 0 none; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + outline: none; + -webkit-appearance: none; + border-radius: 0; + color: #000000; + border: 1px solid #000000; + font-family: 'r-regular',Arial,sans-serif; +} +input::placeholder{ + color: #ff000083; +} +.select-wrap{ + position: relative; +} +.select-wrap:after{ + content: ' '; + width: 0; + height: 0; + border-style: solid; + border-width: 6px 5px 0 5px; + border-color: #000 transparent transparent transparent; + position: absolute; + right: 20px; + top: 0; + bottom: 0; + margin: auto; + pointer-events: none; +} + +.form-item .form-input--date{ + background: url('../img/date.svg') no-repeat right 14px center; + background-size: 27px; + width: 245px; +} + +.form-item .form-input::placeholder{ + color:#7f7f7f4d; +} +.form-item .form-item__warning {} + + +.form-item .form-input--textarea{ + height: 102px; + padding-top: 17px; +} + +.form-step{ + display: none; +} +.form-step.active +{ + display: block; +} + +.form__warning{ + background: #F95D51; + padding: 10px; + height: 70px; + display: flex; + justify-content: center; + align-items: center; + text-align: center; + margin-bottom: 20px; + color:#fff; + text-align: center; + font-size: 20px; + line-height: 1.55; +} +.t-check-in, .t-check-out, .t-datepicker{ + float: none !important; +} + +.form__action{ + position: relative; + display: flex; + justify-content: space-between; +} +.progress-row{ + position: absolute; + left: 0; + top:-25px; + width: 100%; + display: flex; + justify-content: center; +} +.progress-row .span-progress{ + transform: translateY(40px); +} + +.btn{ + height: 45px; + border: none; + outline: none; + font-size: 14px; + padding-left: 30px; + padding-right: 30px; + background: #000; + text-decoration: none; + display: flex; + justify-content: center; + align-items: center; + color:#fff; + +} + + +.form-note { + font-size: 15px; + line-height: 1.55; + text-align: center; + margin-top: 20px; +} +.form-note a{ + color: #ff8562; + text-decoration: none; +} +.btn span.icon{ + width: 18px; + height: 16px; + position: relative; + margin-left: 5px; +} +.btn--next{ + margin-left: auto; +} +.btn--next span.icon{ + margin-left: 5px; +} +.btn--prev span.icon{ + margin-left: 5px; +} +.btn span.icon:after{ + color:#fff; + position: absolute; + left: 0; + top: 0; + height: 100%; + line-height: 100%; + font-size: 14px; + display: inline-block; + font-family: Arial,Helvetica,sans-serif; + } +.btn--next span.icon:after{ + content: '→'; +} +.btn--prev span.icon:after{ + content: '←'; + } + + +.form-step__info{ + font-family: 'r-regular',Arial,sans-serif; + display: block; + margin-bottom: 20px; +} +.form-item input[type="file"]{ + display: none; +} +.form-item input[type="file"] +label { + height: 45px; + border: none; + outline: none; + font-size: 14px; + padding-left: 30px; + padding-right: 30px; + background: #000; + text-decoration: none; + display: inline-flex; + justify-content: center; + align-items: center; + color:#fff; + font-family: r-bold; +} + +.iti{ + width: 100%; +} + +.span-progress { + font-size: 12px; + opacity: 0.6; +} +.span-progress .current {} +.span-progress .total {} + + +.datepicker__header{ + background: #efefef !important; +} + +.form-item__warning{ + color: red; + font-size: 13px; + display: block; + margin-top: 5px; +} +.form-item__warning--success{ + color: #28a745 !important; /* Зеленый цвет для успешных сообщений */ +} +.datepicker__day.is-today,.qs-current{ + background: #bdbdbd !important; + color:#fff !important; + border-radius: 50% !important; +} + +.checkbox-item {} +.checkbox-item .form-checkbox { + display: none; +} +.checkbox-item .form-checkbox + label{ + padding-left: 30px; + position: relative; +} +.checkbox-item .form-checkbox + label:after{ + content: ''; + position: absolute; + display: inline-block; + vertical-align: middle; + height: 20px; + top: 0; + width: 20px; + border: 2px solid #000; + box-sizing: border-box; + margin-right: 10px; + -webkit-transition: all 0.2s; + transition: all 0.2s; + opacity: .6; + left: 0 +} +.checkbox-item .form-checkbox + label:before{ + content: ''; + position: absolute; + display: inline-block; + vertical-align: middle; + height: 20px; + top: 0; + width: 20px; + box-sizing: border-box; + margin-right: 10px; + -webkit-transition: all 0.2s; + transition: all 0.2s; + opacity: .6; + left: 0; + opacity: 0; + background: url('../img/check.svg') no-repeat center; + background-size: 13px; +} +.checkbox-item .form-checkbox + label:before{ + +} + +.checkbox-item .form-checkbox:checked + label:before{ + opacity: 1; + background: url('../img/check.svg') no-repeat center; + background-size: 13px; +} +.w-100{ + width: 100% !important; +} +.sms-action{ + /* display: flex; + justify-content: space-between; + flex-wrap: wrap; + align-items: center; */ + margin-top: 20px; + margin-bottom: 20px; +} + +@media screen and (max-width: 768px) { + .form-item .form-input--date{ + width: 100%; + } + .form__title { + font-size: 16px; + } + .form-item .form-input, .form-item .t-datepicker { + height: 50px; + } +} + +.disabled{ + opacity: 0.3; + pointer-events: none; +} +.disabled+label{ + opacity: 0.3; + pointer-events: none; +} +button[disabled=disabled], button:disabled { + opacity: 0.4; +} + +.js-code-warning{ + color: #88b56d; + text-align: center; + font-size: 15px; + display: block; +} +.modal{ + max-width: 400px !important; + +} +.modal h4.title{ + text-align: center; +} +.modal p{ + text-align: center; +} + + +.modal{ + position: relative; +} +.loader-wrap{ + width: 100%; + height: 100%; + background: rgba(255,255,255,0.5); + position: absolute; + z-index: 1000; + backdrop-filter: blur(8px); + left: 0; + top:0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + pointer-events: none; +} + +.loader { + width: 48px; + height: 48px; + display: inline-block; + position: relative; +} +.loader::after, +.loader::before { + content: ''; + box-sizing: border-box; + width: 48px; + height: 48px; + border: 2px solid rgb(182, 179, 179); + position: absolute; + left: 0; + top: 0; + animation: rotationBreak 3s ease-in-out infinite alternate; +} +.loader::after { + border-color: #36353e; + animation-direction: alternate-reverse; +} +.loader-info{ + display: block; + width: 100%; + text-align: center; + font-size: 18px; + padding-left: 20px; + padding-right: 20px; + color: #3d2626; + font-weight: bold; + margin-bottom: 30px; +} + +@keyframes rotationBreak { + 0% { + transform: rotate(0); + } + 25% { + transform: rotate(90deg); + } + 50% { + transform: rotate(180deg); + } + 75% { + transform: rotate(270deg); + } + 100% { + transform: rotate(360deg); + } +} + +.d-none{ + display: none; +} +.form-item{ + position: relative; +} +.form-item__dropdown{ + position: absolute; + width: 100%; + background: #fff; + font-size: 13px; + box-shadow: 0 0 15px rgba(0,0,0,.05); + z-index: 123; +} + +.form-item input[type="file"] +label{ + background: none; + color:#999999; + text-decoration: underline; + padding-left: 0; + margin-left: 0; + font-weight: normal; +} +.fileList{ + list-style: none; + padding-left: 0; + margin-left: 0; +} +.fileList li{ + display: flex; + justify-content: space-between; + padding-top: 3px; + padding-bottom: 3px; + border-bottom: 1px solid #f5f2f2; +} +.fileList li strong{ + width: 70%; + font-weight: normal; + font-size: 14px; +} +.fileList li span{ + width: 20%; + font-size: 14px; +} +.fileList li .removefile{ + width: 20px; + height: 20px; + background: url('../img/close.svg') no-repeat center; + background-size: 10px; +} +.upload-action{ + display: flex; + justify-content: flex-end; +} + +.disabled{ + pointer-events: none; + opacity: 0.5; +} +.country-select{ + width: 100% !important; +} + +.form-row{ + display: flex; + justify-content: space-between; +} +.form-col{ + width: 48%; +} + +.js-result{ + color:#30cc11c2; + margin-top: 10px; + margin-bottom: 10px; +} +.js-result.danger{ + color:#F95D51; +} +.js-result.form-item__warning--success{ + color: #28a745 !important; /* Зеленый цвет для успешных сообщений */ +} + +.suсcess-upload{ + margin-bottom: 2px; + margin-top: 2px; +} + +.form-text{ + margin-bottom: 30px; + margin-top: 30px; + text-align: center; + display: block; +} \ No newline at end of file diff --git a/database.php b/database.php new file mode 100644 index 0000000..c546aa6 --- /dev/null +++ b/database.php @@ -0,0 +1,167 @@ + "false", + "message" => "Ошибка подключения к базе данных: " . $error, + "result" => "" + ], JSON_UNESCAPED_UNICODE); + return; + } + + mysqli_set_charset($link, "utf8"); + log_message("Подключение к БД успешно"); + + $inn = isset($_POST['inn']) ? trim($_POST['inn']) : ''; + + if (empty($inn)) { + log_message("Номер полиса не указан"); + echo json_encode([ + "success" => "false", + "message" => "Номер полиса не указан", + "result" => "" + ], JSON_UNESCAPED_UNICODE); + mysqli_close($link); + return; + } + + log_message("Проверка полиса: " . substr($inn, 0, 5) . "***"); + + // Нормализуем номер полиса: заменяем кириллическую "Е" на латинскую "E" + // Это нужно, т.к. в базе данных используются только латинские буквы + $inn_original = $inn; + $inn = str_replace('Е', 'E', $inn); // Кириллическая Е -> латинская E + $inn = str_replace('е', 'e', $inn); // Кириллическая е -> латинская e (на всякий случай) + $inn = str_replace('А', 'A', $inn); // Кириллическая А -> латинская A + $inn = str_replace('а', 'a', $inn); // Кириллическая а -> латинская a + + if ($inn_original !== $inn) { + log_message("Номер полиса нормализован: $inn_original -> $inn"); + } + + // Экранируем для безопасности + $inn_escaped = mysqli_real_escape_string($link, $inn); + + // Ищем полис по номеру voucher + $sql = "SELECT * FROM lexrpiority WHERE voucher = '$inn_escaped' LIMIT 1"; + log_message("SQL запрос: SELECT * FROM lexrpiority WHERE voucher = '***'"); + + $result = mysqli_query($link, $sql); + + if (!$result) { + $error = mysqli_error($link); + log_message("Ошибка SQL запроса: $error"); + echo json_encode([ + "success" => "false", + "message" => "Ошибка запроса к базе данных: " . $error, + "result" => "" + ], JSON_UNESCAPED_UNICODE); + mysqli_close($link); + return; + } + + $row = mysqli_fetch_assoc($result); + + if (!$row) { + // Полис не найден в базе + log_message("Полис не найден в базе данных"); + echo json_encode([ + "success" => "false", + "message" => "Полис не найден", + "result" => "" + ], JSON_UNESCAPED_UNICODE); + mysqli_close($link); + return; + } + + log_message("Полис найден в БД. Тарифы: basic=" . ($row['tariff_code_basic'] ?? 'нет') . ", other=" . ($row['tariff_code_other'] ?? 'нет')); + + // Полис найден, проверяем тариф + $tariff_basic = isset($row['tariff_code_basic']) ? trim($row['tariff_code_basic']) : ''; + $tariff_other = isset($row['tariff_code_other']) ? trim($row['tariff_code_other']) : ''; + + $has_valid_tariff = in_array($tariff_basic, $valid_tariffs_basic) || + in_array($tariff_other, $valid_tariffs_other); + + if ($has_valid_tariff) { + // Полис найден и тариф подходит + log_message("Полис валиден. Тариф подходит для задержки рейса"); + echo json_encode([ + "success" => "true", + "message" => "Полис найден", + "result" => $row + ], JSON_UNESCAPED_UNICODE); + } else { + // Полис найден, но тариф не включает покрытие задержки рейса + log_message("Полис найден, но тариф не подходит для задержки рейса"); + echo json_encode([ + "success" => "false", + "message" => "Ваш полис не включает покрытие задержки рейса", + "result" => "" + ], JSON_UNESCAPED_UNICODE); + } + + mysqli_close($link); +} + +?> diff --git a/database_old.php b/database_old.php new file mode 100644 index 0000000..bd8e9b1 --- /dev/null +++ b/database_old.php @@ -0,0 +1,59 @@ +"false","message"=>"Полис не найден", "result" => ""); + + if($inn) { + while ($row = mysqli_fetch_assoc($result)) { + if($inn==$row['voucher']) { + $finded_row['success']="true"; + $finded_row['message']="Полис найден"; + $finded_row['result']=$row; + } + } +} + echo json_encode($finded_row); +} + +?> \ No newline at end of file diff --git a/env_loader.php b/env_loader.php new file mode 100644 index 0000000..325b299 --- /dev/null +++ b/env_loader.php @@ -0,0 +1,65 @@ +Файл «' . $name . '» успешно загружен.

Скачать'; + } else { + $error = 'Не удалось загрузить файл.'; + } + } + } +} + +if (!empty($error)) { + $error = '

' . $error . '

'; +} + +$data = array( + 'error' => $error, + 'success' => $success, +); + +header('Content-Type: application/json'); +echo json_encode($data, JSON_UNESCAPED_UNICODE); +exit(); + +//exec("convert banner.png banner.pdf"); + diff --git a/fileupload.php b/fileupload.php new file mode 100644 index 0000000..2053b99 --- /dev/null +++ b/fileupload.php @@ -0,0 +1,114 @@ +"false","message"=>"asdasd", "result" => ""); + + +$lastname = str_replace(' ', '_',$_POST['lastname']); +$inputsArray = $_POST['files_names']; +$inputLabel = $_POST['docs_names']; +$pdf_page_counts=array(); +$img_page_counts=0; +if($inputsArray) { + +foreach($inputsArray as $index => $inputsArray_item) { + for($i=0;$i<10;$i++) { + if (!isset($_FILES[$inputsArray_item.'-'.$i])) { + $error = 'Файл не загружен.'; + break; + } else { + $file = $_FILES[$inputsArray_item.'-'.$i]; + $allow = array(); + $deny = array( + 'phtml', 'php', 'php3', 'php4', 'php5', 'php6', 'php7', 'phps', 'cgi', 'pl', 'asp', + 'aspx', 'shtml', 'shtm', 'htaccess', 'htpasswd', 'ini', 'log', 'sh', 'js', 'html', + 'htm', 'css', 'sql', 'spl', 'scgi', 'fcgi', 'exe' + ); + $path = __DIR__ . '/uploads/'; + $error = $success = ''; + if (!empty($file['error']) || empty($file['tmp_name'])) { + $error = 'Не удалось загрузить файл.'; + } elseif ($file['tmp_name'] == 'none' || !is_uploaded_file($file['tmp_name'])) { + $error = 'Не удалось загрузить файл.'; + } else { + $pattern = "[^a-zа-яё0-9,~!@#%^-_\$\?\(\)\{\}\[\]\.]"; + $name = mb_eregi_replace($pattern, '-', $file['name']); + $name = mb_ereg_replace('[-]+', '-', $name); + $parts = pathinfo($name); + if (empty($name) || empty($parts['extension'])) { + $error = 'Недопустимый тип файла'; + } elseif (!empty($allow) && !in_array(strtolower($parts['extension']), $allow)) { + $error = 'Недопустимый тип файла'; + } elseif (!empty($deny) && in_array(strtolower($parts['extension']), $deny)) { + $error = 'Недопустимый тип файла'; + } else { + if (move_uploaded_file($file['tmp_name'], $path . $name)) { + $fullpath = $_SERVER['HTTP_REFERER']. '/uploads/' . $name; + if(strtolower($parts['extension']) != 'pdf') { + $oldfile = 'uploads/'.$name; + $name = trim(preg_replace('/\s*\([^)]*\)/', '', $name)); + $newfile = 'uploads/'.$name.'_'.date('m-d-Y-H-i-s').'.pdf'; + exec("convert ".$oldfile." ".$newfile." "); + $pdfFiles[] = $newfile; + $img_page_counts++; + } else { + $pdfFiles[] = 'uploads/' . $name; // 'uploads/' + $pdf_page_counts[]=get_pdf_count('uploads/'.$name); + } + //exec("convert uploads/".$name." uploads/".$name.'_'.date('m-d-Y-H-i-s').".pdf"); + //$success = '

Файл «' . $name . '» успешно загружен.

Скачать'; + } else { + $error = 'Не удалось загрузить файл.'; + } + } + } + } + } + $pages_count=array_sum($pdf_page_counts)+$img_page_counts; + $new = 'uploads/'.translit($inputLabel[$index]).'_'.date('d-m-Y').'_'.translit($lastname).'_'.$pages_count.'_CTP.pdf'; + $cmd = "gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=".$new." ".implode(" ", $pdfFiles); + shell_exec($cmd); + + } +} + +function get_pdf_count($target_pdf){ + $cmd = sprintf("identify %s", $target_pdf); + exec($cmd, $output); + $pages = count($output); + return $pages; +} + +if($new) { + $result['success']="true"; + $result['message']=$new; +} + + +function translit($value) +{ + $converter = array( + 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', + 'е' => 'e', 'ё' => 'e', 'ж' => 'zh', 'з' => 'z', 'и' => 'i', + 'й' => 'y', 'к' => 'k', 'л' => 'l', 'м' => 'm', 'н' => 'n', + 'о' => 'o', 'п' => 'p', 'р' => 'r', 'с' => 's', 'т' => 't', + 'у' => 'u', 'ф' => 'f', 'х' => 'h', 'ц' => 'c', 'ч' => 'ch', + 'ш' => 'sh', 'щ' => 'sch', 'ь' => '', 'ы' => 'y', 'ъ' => '', + 'э' => 'e', 'ю' => 'yu', 'я' => 'ya', + + 'А' => 'A', 'Б' => 'B', 'В' => 'V', 'Г' => 'G', 'Д' => 'D', + 'Е' => 'E', 'Ё' => 'E', 'Ж' => 'Zh', 'З' => 'Z', 'И' => 'I', + 'Й' => 'Y', 'К' => 'K', 'Л' => 'L', 'М' => 'M', 'Н' => 'N', + 'О' => 'O', 'П' => 'P', 'Р' => 'R', 'С' => 'S', 'Т' => 'T', + 'У' => 'U', 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C', 'Ч' => 'Ch', + 'Ш' => 'Sh', 'Щ' => 'Sch', 'Ь' => '', 'Ы' => 'Y', 'Ъ' => '', + 'Э' => 'E', 'Ю' => 'Yu', 'Я' => 'Ya', + ); + + $value = strtr($value, $converter); + return preg_replace('/\s+/', '', $value); +} + + +echo json_encode($result); + +?> \ No newline at end of file diff --git a/fonts/Roboto/Roboto-Black.eot b/fonts/Roboto/Roboto-Black.eot new file mode 100644 index 0000000..cd571fd Binary files /dev/null and b/fonts/Roboto/Roboto-Black.eot differ diff --git a/fonts/Roboto/Roboto-Black.ttf b/fonts/Roboto/Roboto-Black.ttf new file mode 100644 index 0000000..144abcd Binary files /dev/null and b/fonts/Roboto/Roboto-Black.ttf differ diff --git a/fonts/Roboto/Roboto-Black.woff b/fonts/Roboto/Roboto-Black.woff new file mode 100644 index 0000000..d77f5a1 Binary files /dev/null and b/fonts/Roboto/Roboto-Black.woff differ diff --git a/fonts/Roboto/Roboto-BlackItalic.eot b/fonts/Roboto/Roboto-BlackItalic.eot new file mode 100644 index 0000000..4bf5ffb Binary files /dev/null and b/fonts/Roboto/Roboto-BlackItalic.eot differ diff --git a/fonts/Roboto/Roboto-BlackItalic.ttf b/fonts/Roboto/Roboto-BlackItalic.ttf new file mode 100644 index 0000000..af5c0d9 Binary files /dev/null and b/fonts/Roboto/Roboto-BlackItalic.ttf differ diff --git a/fonts/Roboto/Roboto-BlackItalic.woff b/fonts/Roboto/Roboto-BlackItalic.woff new file mode 100644 index 0000000..943e97e Binary files /dev/null and b/fonts/Roboto/Roboto-BlackItalic.woff differ diff --git a/fonts/Roboto/Roboto-Bold.eot b/fonts/Roboto/Roboto-Bold.eot new file mode 100644 index 0000000..14ad9ae Binary files /dev/null and b/fonts/Roboto/Roboto-Bold.eot differ diff --git a/fonts/Roboto/Roboto-Bold.ttf b/fonts/Roboto/Roboto-Bold.ttf new file mode 100644 index 0000000..0388c50 Binary files /dev/null and b/fonts/Roboto/Roboto-Bold.ttf differ diff --git a/fonts/Roboto/Roboto-Bold.woff b/fonts/Roboto/Roboto-Bold.woff new file mode 100644 index 0000000..51f1e89 Binary files /dev/null and b/fonts/Roboto/Roboto-Bold.woff differ diff --git a/fonts/Roboto/Roboto-BoldItalic.eot b/fonts/Roboto/Roboto-BoldItalic.eot new file mode 100644 index 0000000..5b40508 Binary files /dev/null and b/fonts/Roboto/Roboto-BoldItalic.eot differ diff --git a/fonts/Roboto/Roboto-BoldItalic.ttf b/fonts/Roboto/Roboto-BoldItalic.ttf new file mode 100644 index 0000000..0ac4cd4 Binary files /dev/null and b/fonts/Roboto/Roboto-BoldItalic.ttf differ diff --git a/fonts/Roboto/Roboto-BoldItalic.woff b/fonts/Roboto/Roboto-BoldItalic.woff new file mode 100644 index 0000000..d916f94 Binary files /dev/null and b/fonts/Roboto/Roboto-BoldItalic.woff differ diff --git a/fonts/Roboto/Roboto-Italic.eot b/fonts/Roboto/Roboto-Italic.eot new file mode 100644 index 0000000..baa8a94 Binary files /dev/null and b/fonts/Roboto/Roboto-Italic.eot differ diff --git a/fonts/Roboto/Roboto-Italic.ttf b/fonts/Roboto/Roboto-Italic.ttf new file mode 100644 index 0000000..d632831 Binary files /dev/null and b/fonts/Roboto/Roboto-Italic.ttf differ diff --git a/fonts/Roboto/Roboto-Italic.woff b/fonts/Roboto/Roboto-Italic.woff new file mode 100644 index 0000000..8e72e8d Binary files /dev/null and b/fonts/Roboto/Roboto-Italic.woff differ diff --git a/fonts/Roboto/Roboto-Light.eot b/fonts/Roboto/Roboto-Light.eot new file mode 100644 index 0000000..3d50d57 Binary files /dev/null and b/fonts/Roboto/Roboto-Light.eot differ diff --git a/fonts/Roboto/Roboto-Light.ttf b/fonts/Roboto/Roboto-Light.ttf new file mode 100644 index 0000000..3a9bdc6 Binary files /dev/null and b/fonts/Roboto/Roboto-Light.ttf differ diff --git a/fonts/Roboto/Roboto-Light.woff b/fonts/Roboto/Roboto-Light.woff new file mode 100644 index 0000000..eec3617 Binary files /dev/null and b/fonts/Roboto/Roboto-Light.woff differ diff --git a/fonts/Roboto/Roboto-LightItalic.eot b/fonts/Roboto/Roboto-LightItalic.eot new file mode 100644 index 0000000..a78bfe9 Binary files /dev/null and b/fonts/Roboto/Roboto-LightItalic.eot differ diff --git a/fonts/Roboto/Roboto-LightItalic.ttf b/fonts/Roboto/Roboto-LightItalic.ttf new file mode 100644 index 0000000..82e9221 Binary files /dev/null and b/fonts/Roboto/Roboto-LightItalic.ttf differ diff --git a/fonts/Roboto/Roboto-LightItalic.woff b/fonts/Roboto/Roboto-LightItalic.woff new file mode 100644 index 0000000..b6b2525 Binary files /dev/null and b/fonts/Roboto/Roboto-LightItalic.woff differ diff --git a/fonts/Roboto/Roboto-Medium.eot b/fonts/Roboto/Roboto-Medium.eot new file mode 100644 index 0000000..0f60b5e Binary files /dev/null and b/fonts/Roboto/Roboto-Medium.eot differ diff --git a/fonts/Roboto/Roboto-Medium.ttf b/fonts/Roboto/Roboto-Medium.ttf new file mode 100644 index 0000000..09f51e2 Binary files /dev/null and b/fonts/Roboto/Roboto-Medium.ttf differ diff --git a/fonts/Roboto/Roboto-Medium.woff b/fonts/Roboto/Roboto-Medium.woff new file mode 100644 index 0000000..b4f0629 Binary files /dev/null and b/fonts/Roboto/Roboto-Medium.woff differ diff --git a/fonts/Roboto/Roboto-MediumItalic.eot b/fonts/Roboto/Roboto-MediumItalic.eot new file mode 100644 index 0000000..141543a Binary files /dev/null and b/fonts/Roboto/Roboto-MediumItalic.eot differ diff --git a/fonts/Roboto/Roboto-MediumItalic.ttf b/fonts/Roboto/Roboto-MediumItalic.ttf new file mode 100644 index 0000000..9943d85 Binary files /dev/null and b/fonts/Roboto/Roboto-MediumItalic.ttf differ diff --git a/fonts/Roboto/Roboto-MediumItalic.woff b/fonts/Roboto/Roboto-MediumItalic.woff new file mode 100644 index 0000000..ca56ca3 Binary files /dev/null and b/fonts/Roboto/Roboto-MediumItalic.woff differ diff --git a/fonts/Roboto/Roboto-Regular.eot b/fonts/Roboto/Roboto-Regular.eot new file mode 100644 index 0000000..2f61547 Binary files /dev/null and b/fonts/Roboto/Roboto-Regular.eot differ diff --git a/fonts/Roboto/Roboto-Regular.ttf b/fonts/Roboto/Roboto-Regular.ttf new file mode 100644 index 0000000..28e2c02 Binary files /dev/null and b/fonts/Roboto/Roboto-Regular.ttf differ diff --git a/fonts/Roboto/Roboto-Regular.woff b/fonts/Roboto/Roboto-Regular.woff new file mode 100644 index 0000000..b070d8e Binary files /dev/null and b/fonts/Roboto/Roboto-Regular.woff differ diff --git a/fonts/Roboto/Roboto-Thin.eot b/fonts/Roboto/Roboto-Thin.eot new file mode 100644 index 0000000..65eaafa Binary files /dev/null and b/fonts/Roboto/Roboto-Thin.eot differ diff --git a/fonts/Roboto/Roboto-Thin.ttf b/fonts/Roboto/Roboto-Thin.ttf new file mode 100644 index 0000000..301842a Binary files /dev/null and b/fonts/Roboto/Roboto-Thin.ttf differ diff --git a/fonts/Roboto/Roboto-Thin.woff b/fonts/Roboto/Roboto-Thin.woff new file mode 100644 index 0000000..bc18032 Binary files /dev/null and b/fonts/Roboto/Roboto-Thin.woff differ diff --git a/fonts/Roboto/Roboto-ThinItalic.eot b/fonts/Roboto/Roboto-ThinItalic.eot new file mode 100644 index 0000000..e9c3118 Binary files /dev/null and b/fonts/Roboto/Roboto-ThinItalic.eot differ diff --git a/fonts/Roboto/Roboto-ThinItalic.ttf b/fonts/Roboto/Roboto-ThinItalic.ttf new file mode 100644 index 0000000..301fbe1 Binary files /dev/null and b/fonts/Roboto/Roboto-ThinItalic.ttf differ diff --git a/fonts/Roboto/Roboto-ThinItalic.woff b/fonts/Roboto/Roboto-ThinItalic.woff new file mode 100644 index 0000000..8863946 Binary files /dev/null and b/fonts/Roboto/Roboto-ThinItalic.woff differ diff --git a/fonts/Roboto/RobotoBold.eot b/fonts/Roboto/RobotoBold.eot new file mode 100644 index 0000000..f3ea16c Binary files /dev/null and b/fonts/Roboto/RobotoBold.eot differ diff --git a/fonts/Roboto/RobotoBold.ttf b/fonts/Roboto/RobotoBold.ttf new file mode 100644 index 0000000..5a156f5 Binary files /dev/null and b/fonts/Roboto/RobotoBold.ttf differ diff --git a/fonts/Roboto/RobotoBold.woff b/fonts/Roboto/RobotoBold.woff new file mode 100644 index 0000000..ca95d4c Binary files /dev/null and b/fonts/Roboto/RobotoBold.woff differ diff --git a/fonts/Roboto/RobotoRegular.eot b/fonts/Roboto/RobotoRegular.eot new file mode 100644 index 0000000..466f3a7 Binary files /dev/null and b/fonts/Roboto/RobotoRegular.eot differ diff --git a/fonts/Roboto/RobotoRegular.ttf b/fonts/Roboto/RobotoRegular.ttf new file mode 100644 index 0000000..a4ebaf7 Binary files /dev/null and b/fonts/Roboto/RobotoRegular.ttf differ diff --git a/fonts/Roboto/RobotoRegular.woff b/fonts/Roboto/RobotoRegular.woff new file mode 100644 index 0000000..0871062 Binary files /dev/null and b/fonts/Roboto/RobotoRegular.woff differ diff --git a/fonts/Roboto/stylesheet.css b/fonts/Roboto/stylesheet.css new file mode 100644 index 0000000..a92e86d --- /dev/null +++ b/fonts/Roboto/stylesheet.css @@ -0,0 +1,133 @@ +/* This stylesheet generated by Transfonter (https://transfonter.org) on February 25, 2018 4:00 PM */ + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-MediumItalic.eot'); + src: local('Roboto Medium Italic'), local('Roboto-MediumItalic'), + url('Roboto-MediumItalic.eot?#iefix') format('embedded-opentype'), + url('Roboto-MediumItalic.woff') format('woff'), + url('Roboto-MediumItalic.ttf') format('truetype'); + font-weight: 500; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-Italic.eot'); + src: local('Roboto Italic'), local('Roboto-Italic'), + url('Roboto-Italic.eot?#iefix') format('embedded-opentype'), + url('Roboto-Italic.woff') format('woff'), + url('Roboto-Italic.ttf') format('truetype'); + font-weight: normal; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-Bold.eot'); + src: local('Roboto Bold'), local('Roboto-Bold'), + url('Roboto-Bold.eot?#iefix') format('embedded-opentype'), + url('Roboto-Bold.woff') format('woff'), + url('Roboto-Bold.ttf') format('truetype'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-Regular.eot'); + src: local('Roboto'), local('Roboto-Regular'), + url('Roboto-Regular.eot?#iefix') format('embedded-opentype'), + url('Roboto-Regular.woff') format('woff'), + url('Roboto-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-Medium.eot'); + src: local('Roboto Medium'), local('Roboto-Medium'), + url('Roboto-Medium.eot?#iefix') format('embedded-opentype'), + url('Roboto-Medium.woff') format('woff'), + url('Roboto-Medium.ttf') format('truetype'); + font-weight: 500; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-BoldItalic.eot'); + src: local('Roboto Bold Italic'), local('Roboto-BoldItalic'), + url('Roboto-BoldItalic.eot?#iefix') format('embedded-opentype'), + url('Roboto-BoldItalic.woff') format('woff'), + url('Roboto-BoldItalic.ttf') format('truetype'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-ThinItalic.eot'); + src: local('Roboto Thin Italic'), local('Roboto-ThinItalic'), + url('Roboto-ThinItalic.eot?#iefix') format('embedded-opentype'), + url('Roboto-ThinItalic.woff') format('woff'), + url('Roboto-ThinItalic.ttf') format('truetype'); + font-weight: 100; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-Black.eot'); + src: local('Roboto Black'), local('Roboto-Black'), + url('Roboto-Black.eot?#iefix') format('embedded-opentype'), + url('Roboto-Black.woff') format('woff'), + url('Roboto-Black.ttf') format('truetype'); + font-weight: 900; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-Light.eot'); + src: local('Roboto Light'), local('Roboto-Light'), + url('Roboto-Light.eot?#iefix') format('embedded-opentype'), + url('Roboto-Light.woff') format('woff'), + url('Roboto-Light.ttf') format('truetype'); + font-weight: 300; + font-style: normal; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-LightItalic.eot'); + src: local('Roboto Light Italic'), local('Roboto-LightItalic'), + url('Roboto-LightItalic.eot?#iefix') format('embedded-opentype'), + url('Roboto-LightItalic.woff') format('woff'), + url('Roboto-LightItalic.ttf') format('truetype'); + font-weight: 300; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-BlackItalic.eot'); + src: local('Roboto Black Italic'), local('Roboto-BlackItalic'), + url('Roboto-BlackItalic.eot?#iefix') format('embedded-opentype'), + url('Roboto-BlackItalic.woff') format('woff'), + url('Roboto-BlackItalic.ttf') format('truetype'); + font-weight: 900; + font-style: italic; +} + +@font-face { + font-family: 'Roboto'; + src: url('Roboto-Thin.eot'); + src: local('Roboto Thin'), local('Roboto-Thin'), + url('Roboto-Thin.eot?#iefix') format('embedded-opentype'), + url('Roboto-Thin.woff') format('woff'), + url('Roboto-Thin.ttf') format('truetype'); + font-weight: 100; + font-style: normal; +} diff --git a/img/check.svg b/img/check.svg new file mode 100644 index 0000000..6bc4c06 --- /dev/null +++ b/img/check.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/img/close.svg b/img/close.svg new file mode 100644 index 0000000..7fb5610 --- /dev/null +++ b/img/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/date.svg b/img/date.svg new file mode 100644 index 0000000..cbe2648 --- /dev/null +++ b/img/date.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/img/favicon/apple-touch-icon-180x180.png b/img/favicon/apple-touch-icon-180x180.png new file mode 100644 index 0000000..80b441b Binary files /dev/null and b/img/favicon/apple-touch-icon-180x180.png differ diff --git a/img/favicon/favicon.ico b/img/favicon/favicon.ico new file mode 100644 index 0000000..8f399b0 Binary files /dev/null and b/img/favicon/favicon.ico differ diff --git a/index.php b/index.php new file mode 100644 index 0000000..3bf66f6 --- /dev/null +++ b/index.php @@ -0,0 +1,1094 @@ + + + + + + + + Site name + + + + + + + + + + + + + + + + 'http://ip-api.com/json/' . $_SERVER['REMOTE_ADDR'] . '?lang=ru', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'GET', + )); + $response = curl_exec($curl); + $response_array = json_decode($response, true); + curl_close($curl); + ?> + + +
+ +
+ +
+ Обращение за выплатой заполняется на каждое лицо, указанное в полисе, и по каждому страховому случаю.
При этом за несовершеннолетнего обращение заполняет законный представитель (родитель/усыновитель/опекун/попечитель). +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Номер полиса + + +
+
+ +
+
+ + +
+
+ Полис + (скан или фотография полиса) +
+ +
    +
    +
    + +
    +
    + +
    +
    + Ваш номер телефона + + +
    +
    + Банк для получения выплаты + + Выплата страхового возмещения производится через Сервис Быстрых Платежей (СБП)
    + Выберите ваш банк для получения выплаты.
    + Начните вводить название для поиска по всем банкам +
    + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + Фамилия + + +
    + +
    + Имя + + +
    + +
    + Отчество (при наличии) + + +
    + +
    + Дата рождения + + +
    + +
    + ФИО Получателя/законного представителя несовершеннолетнего + Заполняется только для несовершеннолетних (до 18 лет) + + +
    + +
    + Документы, подтверждающие статус законного представителя + Свидетельство о рождении несовершеннолетнего (до 18 лет) / Решение органа опеки и попечительства о назначении опекуна или попечителя / Решение суда об усыновлении (удочерении) +
    + +
      +
      +
      + +
      + +
      + +
      + +
      + Выберите тип события +
      + + +
      + +
      + +
      + Дата наступления страхового случая + + +?> + +
      + +
      + Номер рейса/поезда/парома + + +
      + + + + + + +
      + Подтверждающие документы. Посадочный талон, билет и т.д. +
      + + +
        +
        +
        + +
        + + + + +
        + Описание ситуации + + +
        + + + +
        + +
        + +
        + Адрес регистрации, включая индекс + + +
        + + + Ваш ИНН. + Узнать свой ИНН вы можете здесь + + +
        + */ + ?> + + + +
        + Документ, удостоверяющий личность +
        + +
        + + +
        + + + Cерия документа + + +
        + */ + ?> + +
        + Серия и номер документа + + +
        + + Гражданство (код страны) +
        + + + +
        + + + + +
        + */ + ?> +
        + Страна события + +
        + +
        + +
        + +
        + Email + + +
        + + + + Описание проблемы + + +
        + */ + ?> + +
        + Документ, удостоверяющий личность (страница с фото) +
        + +
          +
          +
          + +
          + +
          +
          + + +
          +
          + +
          + +
          Пожалуйста, заполните все обязательные поля
          + +
          +
          +
          + 1 + / + 3 +
          +
          + + вперед + + + + + +
          +
          + Нажимая на кнопку, вы даете согласие на обработку персональных данных. +
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/common.js b/js/common.js new file mode 100644 index 0000000..3198e52 --- /dev/null +++ b/js/common.js @@ -0,0 +1,1721 @@ +// Функция инициализации автодополнения адресов через DaData +function initAddressSuggestions() { + if (typeof $.fn.suggestions !== 'undefined') { + // Инициализируем автодополнение адресов для поля с классом .js-adres + $('.js-adres').each(function() { + // Проверяем, не инициализировано ли уже + if (!$(this).data('suggestions-initialized')) { + $(this).suggestions({ + token: "d2fa8613e186d54c6c62fc321414552353ffdfa8", + type: "ADDRESS", + onSelect: function(suggestion) { + // При выборе адреса заполняем поле + $(this).val(suggestion.value); + } + }); + $(this).data('suggestions-initialized', true); + } + }); + } +} + +$(function() { + $(document).ready(function(){ + + // Блокируем кнопку "Отправить смс" по умолчанию, пока полис не проверен + $('.js-send-sms').addClass('disabled'); + $('.js-send-sms').prop('disabled', true); + + // Поля телефона и банка скрыты по умолчанию (через класс d-none в HTML) + + // Блокируем поле "ФИО Получателя" по умолчанию (доступно только для несовершеннолетних) + $("input[data-enableby=birthday]").addClass('disabled'); + $("input[data-enableby=birthday]").prop('disabled', true); + $("input[data-enableby=birthday]").prop('readonly', true); + + $(".js-progress-mask").inputmask("99"); + $(".js-inn-mask").inputmask("999999999999"); + $(".js-inn-mask2").inputmask("9{10,12}"); + $(".js-bank-mask").inputmask({ mask: ["9999 9999 9999 9999", "9999 9999 9999 9999", "9999 9999 9999 9999", "9999 999999 99999"], greedy: false, "placeholder": "*", "clearIncomplete": true }); + + $(".js-code-mask").inputmask("999999"); + $(".js-date-mask").inputmask("99-99-9999",{ "placeholder": "дд-мм-гггг" }); + + $(".js-doccode-mask").inputmask("99"); + $(".js-countrycode-mask").inputmask("999"); + + // $("#country").countrySelect(); + Inputmask.extendDefinitions({ + '*': { //masksymbol + "validator": "[0-9\(\)\.\+/ ]" + }, + }); + + $(".js-inndb-mask").inputmask("A9{3,5}-*{6,10}"); + + $(".js-inndb-mask").keyup(function(){ + //if($this) + }); + + + document.querySelector('input').addEventListener('keydown', function (e) { + if (e.which == 9) { + e.preventDefault(); + } + }); + + + let month =['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь']; + let days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']; + + + if($('input[name="birthday"]').length) { + + var birthday = datepicker('input[name="birthday"]', + { + customOverlayMonths: month, + customMonths: month, + customDays: days, + maxDate: new Date(), + formatter: (input, date, instance) => { + const value = date.toLocaleDateString() + input.value = value // => '1/1/2099' + }, + onSelect: function(dateText, inst) { + let birthday=$('input[name="birthday"]').val(); + + if(getAge(birthday)<18) { + // Если возраст меньше 18 - разблокируем поле + $("input[data-enableby=birthday]").removeClass('disabled'); + $("input[data-disabledby=birthday]").removeClass('disabled'); + $("input[data-enableby=birthday]").prop('disabled', false); + $("input[data-enableby=birthday]").prop('readonly', false); + } else { + // Если возраст 18 и больше - блокируем поле + $("input[data-enableby=birthday]").addClass('disabled'); + $("input[data-disabledby=birthday]").addClass('disabled'); + $("input[data-enableby=birthday]").prop('disabled', true); + $("input[data-enableby=birthday]").prop('readonly', true); + $("input[data-enableby=birthday]").val(''); // Очищаем значение при блокировке + } + + } + }); + + } + + // Инициализация datepicker для поля даты рейса отправления + if($('input[name="departure_date"]').length) { + var departure_date = datepicker('input[name="departure_date"]',{ + customOverlayMonths: month, + customMonths: month, + customDays: days, + maxDate: new Date(), + formatter: (input, date, instance) => { + const value = date.toLocaleDateString() + input.value = value // => '1/1/2099' + } + }); + } + + // Инициализация datepicker для поля даты наступления страхового случая + if($('input[name="insurence_date"]').length) { + var insurence_date = datepicker('input[name="insurence_date"]',{ + customOverlayMonths: month, + customMonths: month, + customDays: days, + maxDate: new Date(), + formatter: (input, date, instance) => { + const value = date.toLocaleDateString() + input.value = value // => '1/1/2099' + } + }); + } + + var phone = document.querySelectorAll('.js-phone-mask'); + $('.js-phone-mask').inputmask('999 999-99-99'); + phone.forEach(el => { + const iti = window.intlTelInput(el, { + initialCountry: 'ru', + onlyCountries : ['ru'], + separateDialCode: true, + customContainer: ".form-item", + autoPlaceholder: 'off', // Отключаем автоматический placeholder + allowDropdown: false, // Убираем выпадающий список + utilsScript: "libs/intl-tel-input-master/build/js/utils.js", + }); + // Устанавливаем кастомный placeholder без "8", соответствует маске 999 999-99-99 + el.setAttribute('placeholder', '912 345-67-89'); + }); + + // Защита от изменения подтвержденного номера телефона (в т.ч. через автозаполнение) + $('.js-phone-mask').on('input change', function() { + var $this = $(this); + var confirmedPhone = $this.attr('data-confirmed-phone'); + + if (confirmedPhone && $this.prop('readonly')) { + var currentValue = $this.val(); + if (currentValue !== confirmedPhone) { + $this.val(confirmedPhone); + } + } + }); + + // Обработчик смены страны отключен, так как выпадающий список убран + // $('.js-phone-mask').on('countrychange', e => { + // let $this = $(e.currentTarget), + // placeholder = $this.attr('placeholder'), + // mask = placeholder.replace(/[0-9]/g, 9); + // $this.val('').inputmask(mask); + // let inputCode = $(".code"), + // flag = document.querySelector(".iti__selected-flag"), + // codeTitle = flag.getAttribute("title"); + // inputCode.val(codeTitle); + // }); + + + + let index=1; + + $('.js-btn-next').on("click",function(e){ + e.preventDefault(); + + // Проверяем, не заблокирована ли кнопка из-за невалидного полиса + if($(this).hasClass('disabled') || $(this).prop('disabled')) { + return false; + } + + let isvalid=validate_step(index); + + if(isvalid) { + index++; + $('.span-progress .current').text(index); + if(index==3) { + $(this).hide(); + $('.btn--submit').show(); + } else { + $(this).show(); + $('.js-btn-prev').show(); + } + $('.form-step').removeClass('active'); + $('.form-step[data-step='+index+']').addClass('active'); + } + + + }); + + $('.js-btn-prev').on("click",function(e){ + e.preventDefault(); + index--; + if(index==1) {$(this).hide();} else $(this).show(); + if(index<1) index=1; + if(index<4) { + $('.btn--submit').hide(); + $('.js-btn-next').show(); + } + $('.span-progress .current').text(index); + $('.form-step').removeClass('active'); + $('.form-step[data-step='+index+']').addClass('active'); + + }); + + $('select[name=claim]').on("change",function(e){ + if($(this).val()==0) { + $(this).closest('.form-step').find('input[type=text]').addClass('disabled'); + $(this).closest('.form-step').find('input[type=file]').addClass('disabled'); + $(this).closest('.form-step').find('input[type=file]').parent().addClass('disabled'); + + } else { + $(this).closest('.form-step').find('input[type=text]').removeClass('disabled'); + $(this).closest('.form-step').find('input[type=file]').removeClass('disabled'); + $(this).closest('.form-step').find('input[type=file]').parent().removeClass('disabled'); + } + + }); + + $('select[name=countryevent]').on("change",function(e){ + $('.countryevent').val($(this).find(":selected").text()); + }); + + // Обработка изменения типа события + $('select[name="event_type"]').on('change', function() { + const selectedValue = $(this).val(); + + // Скрываем все дополнительные поля + $('.connection-fields, .connection-date-fields, .cancel-flight-docs').hide(); + + // Меняем лейблы в зависимости от выбора + if (selectedValue === 'miss_connection') { + $('#transport_number_label').text('Укажите номер рейса прибытия'); + $('#insurence_date_label').text('Дата рейса прибытия'); + $('.connection-fields, .connection-date-fields').show(); + } else if (selectedValue === 'cancel_flight') { + $('#transport_number_label').text('Номер рейса/поезда/парома'); + $('#insurence_date_label').text('Дата наступления страхового случая'); + $('.cancel-flight-docs').show(); + } else { + $('#transport_number_label').text('Номер рейса/поезда/парома'); + $('#insurence_date_label').text('Дата наступления страхового случая'); + } + }); + + function validate_step(step_index){ + let inputs=$('.form-step.active').find('input[type="text"], input[type="file"],input[type="email"], textarea.form-input, input[type="checkbox"]'); + let all_filled=false; + let res_array=[]; + inputs.each(function(e){ + let field_fill=false; + let $this = $(this); + + // Пропускаем поля, которые помечены как не требующие валидации + if($this.hasClass('notvalidate')) { + field_fill=true; // Считаем невалидируемые поля валидными + res_array.push(field_fill); + return; + } + + // Пропускаем поля, которые заблокированы (disabled или readonly) + if($this.prop('disabled') || $this.prop('readonly') || $this.hasClass('disabled')) { + field_fill=true; // Считаем заблокированные поля валидными + res_array.push(field_fill); // Добавляем в массив результатов + return; // Пропускаем дальнейшую проверку + } + + // Пропускаем поля, которые находятся в скрытых блоках (d-none или display: none) + if($this.closest('.d-none').length > 0) { + field_fill=true; // Считаем скрытые поля валидными + res_array.push(field_fill); + return; + } + + // Проверяем, скрыт ли родительский блок через inline стиль display: none + let $parentContainer = $this.closest('.form-item'); + if($parentContainer.length > 0 && $parentContainer.is(':hidden')) { + field_fill=true; // Считаем скрытые поля валидными + res_array.push(field_fill); + return; + } + + if(($this.val()=='' && !$this.hasClass('disabled') && !$this.hasClass('notvalidate') && !$this.hasClass('error') )) { + $this.closest('.form-item').find('.form-item__warning').text('Пожалуйста, заполните все обязательные поля'); + field_fill=false; + + } else { + $this.closest('.form-item').find('.form-item__warning').text(''); + + if($this.attr('type')=='email'){ + + if(validateEmail($this.val())) { + field_fill=true; + } else { + field_fill=false; + $this.closest('.form-item').find('.form-item__warning').text($this.data('warmes')); + } + } else if($this.attr('type')=='file') { + // Специальная валидация для файлов + var fileInput = this; + var filesCount = fileInput.files ? fileInput.files.length : 0; + // Проверяем также через fileList, если файлы уже обработаны + var fileListItems = $this.closest('.form-item').find('.fileList li').length; + + // Если поле находится в скрытом блоке, но есть другое видимое поле с таким же name - пропускаем + var fieldName = $this.attr('name'); + var isHidden = $this.closest('.d-none').length > 0; + + if(isHidden && fieldName) { + // Проверяем, есть ли видимое поле с таким же name + var visibleField = $('.form-step.active').find('input[name="' + fieldName + '"]').not($this).filter(function() { + return $(this).closest('.d-none').length === 0; + }); + if(visibleField.length > 0) { + // Есть видимое поле с таким же name - пропускаем скрытое + field_fill=true; + res_array.push(field_fill); + return; + } + } + + // Для видимых полей проверяем наличие файлов + if(!isHidden) { + if(filesCount > 0 || fileListItems > 0) { + field_fill=true; + } else { + field_fill=false; + $this.closest('.form-item').find('.form-item__warning').text('Пожалуйста, загрузите файлы'); + } + } else { + // Скрытое поле без видимого дубликата - считаем валидным + field_fill=true; + } + } else { + // Специальная валидация для поля банка + if($this.hasClass('js-bank-autocomplete')) { + var bankId = $this.closest('.form-item').find('input[name="bank_id"]').val(); + if(bankId && bankId !== '') { + field_fill=true; + } else { + field_fill=false; + $this.closest('.form-item').find('.form-item__warning').text('Пожалуйста, выберите банк из списка'); + } + } else { + field_fill=true; + } + } + if($this.attr('type')=='checkbox'){ + + if($this.is(':checked')){ + field_fill=true; + $this.parent().parent().find('.form-item__warning').text(); + } else { + field_fill=false; + $this.parent().parent().find('.form-item__warning').text('Пожалуйста, заполните все обязательные поля'); + } + if(($this.parent().hasClass('js-enable-inputs'))){ + field_fill=true; + } + } + + + } + res_array.push(field_fill); + + // Отладочный вывод для незаполненных полей отключен + }); + + // Проверка валидности полиса теперь выполняется перед отправкой SMS, а не здесь + + if((step_index==3) && $('.form-step[data-step='+step_index+']').find('input[type="checkbox"]:checked').length<1) { + $('.form__warning').show(); + $('.form__warning').text('Необходимо согласие с политикой обработки персональных данных'); + return false; + } else { + $('.form__warning').text('Пожалуйста, заполните все обязательные поля'); + } + + if(!res_array.includes(false)){ + $('.form__warning').hide(); + return true; + } else { + $('.form__warning').show(); + return false; + } + } + + const validateEmail = (email) => { + return email.match( + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ); + }; + + $('.js-enable-inputs input[type=checkbox]').on("change",function(e){ + e.preventDefault(); + $(this).closest('.form-item').find('input[type=file]').toggleClass('disabled'); + $(this).closest('.form-item').find('input[type=file]+label').toggleClass('disabled'); + }); + + // start sms + + // Глобальная переменная для хранения токена верификации + var sms_verify_token = null; + + function send_sms(){ + var $phoneInput = $('input[name="phonenumber"]'); + var phone = $phoneInput.val(); + + // Получаем номер через intlTelInput API (если доступен) + if ($phoneInput.length > 0 && window.intlTelInput) { + var iti = window.intlTelInputGlobals.getInstance($phoneInput[0]); + if (iti) { + // Получаем полный номер с кодом страны (например, +79991234567) + var fullNumber = iti.getNumber(); + if (fullNumber) { + phone = fullNumber; + } else { + // Если getNumber не работает, очищаем от маски вручную + phone = phone.replace(/[() -]/g, ''); + // Добавляем +7 если номер начинается с 8 или 9 + if (phone.match(/^[89]/)) { + phone = '+7' + phone.replace(/^[89]/, ''); + } else if (!phone.match(/^\+/)) { + phone = '+7' + phone; + } + } + } else { + // Если intlTelInput не инициализирован, очищаем от маски + phone = phone.replace(/[() -]/g, ''); + if (phone.match(/^[89]/)) { + phone = '+7' + phone.replace(/^[89]/, ''); + } else if (!phone.match(/^\+/)) { + phone = '+7' + phone; + } + } + } + + console.log('Отправка SMS на номер:', phone); + + if (!phone) { + $('.form-item__warning').text('Пожалуйста, укажите номер телефона'); + return false; + } + + // Показываем загрузку + $('.js-send-sms').prop('disabled', true).text('Отправка...'); + + $.ajax({ + url: 'sms-verify.php?action=send', + method: 'post', + data: { + phonenumber: phone + }, + dataType: 'json', + success: function(data) { + if (data.success) { + $('.js-code-warning').text('Код отправлен на ваш номер телефона'); + $('.js-send-sms').prop('disabled', false).text('Отправить смс'); + $.fancybox.open({ + src: '#confirm_sms', + type: 'inline' + }); + } else { + $('.form-item__warning').text(data.message || 'Ошибка отправки SMS'); + $('.js-send-sms').prop('disabled', false).text('Отправить смс'); + } + }, + error: function (jqXHR, exception) { + var errorMsg = 'Ошибка отправки SMS'; + if (jqXHR.responseJSON && jqXHR.responseJSON.message) { + errorMsg = jqXHR.responseJSON.message; + } + $('.form-item__warning').text(errorMsg); + $('.js-send-sms').prop('disabled', false).text('Отправить смс'); + } + }); + + return null; // Код больше не генерируется на клиенте + } + + function countDown(elm, duration, fn){ + var countDownDate = new Date().getTime() + (1000 * duration); + var x = setInterval(function() { + var now = new Date().getTime(); + var distance = countDownDate - now; + var days = Math.floor(distance / (1000 * 60 * 60 * 24)); + var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); + var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); + var seconds = Math.floor((distance % (1000 * 60)) / 1000); + elm.innerHTML = minutes + "м " + seconds + "с "; + if (distance < 0) { + clearInterval(x); + fn(); + elm.innerHTML = ""; + $('.sms-countdown').hide(); + } + }, 1000); + } + + $('.js-send-sms').on('click', function(e) { + e.preventDefault(); + + // Проверка валидности полиса перед отправкой SMS + var indatabase = $('.js-indatabase').val(); + // Проверяем, был ли полис загружен вручную (через policy-upload-section) + var polisInput = $('#polis_upload')[0]; + var polisUploaded = polisInput && polisInput.files && polisInput.files.length > 0; + + // Если полис невалидный И не загружен вручную - блокируем отправку + if((indatabase == '0' || indatabase == '' || indatabase == undefined) && !polisUploaded) { + $('.form__warning').show(); + $('.form__warning').text('Пожалуйста, проверьте полис. Ваш полис не покрывает данный вид обращения.'); + return false; + } + + // Валидация поля банка перед отправкой SMS + var bankInput = $('.js-bank-autocomplete'); + var bankId = $('input[name="bank_id"]').val(); + + if (!bankInput.val() || !bankId || bankId === '') { + bankInput.closest('.form-item').find('.form-item__warning').text('Пожалуйста, выберите банк из списка'); + bankInput.focus(); + return false; + } else { + bankInput.closest('.form-item').find('.form-item__warning').text(''); + } + + // Отправляем SMS через новый безопасный API + send_sms(); + $('.sms-countdown').show(); + $('.modal .js-accept-sms').show(); + $('.modal .js-send-sms').hide(); + $('.modal .form-item__warning').text(""); + countDown(document.querySelector('.sms-countdown .time'), 30, function(){ + $('.modal .js-send-sms').show(); + $('.sms-checking button.js-accept-sms').hide(); + $('.js-code-warning').hide(); + }) + }); + + // Обработчик для повторной отправки SMS из модального окна + $(document).on('click', '.modal .js-send-sms', function(e) { + e.preventDefault(); + var $phoneInput = $('input[name="phonenumber"]'); + var phone = $phoneInput.val(); + + // Получаем номер через intlTelInput API (если доступен) - ТОЧНО ТАК ЖЕ как при первой отправке + if ($phoneInput.length > 0 && window.intlTelInput) { + var iti = window.intlTelInputGlobals.getInstance($phoneInput[0]); + if (iti) { + var fullNumber = iti.getNumber(); + if (fullNumber) { + phone = fullNumber; + } else { + phone = phone.replace(/[() -]/g, ''); + if (phone.match(/^[89]/)) { + phone = '+7' + phone.replace(/^[89]/, ''); + } else if (!phone.match(/^\+/)) { + phone = '+7' + phone; + } + } + } else { + phone = phone.replace(/[() -]/g, ''); + if (phone.match(/^[89]/)) { + phone = '+7' + phone.replace(/^[89]/, ''); + } else if (!phone.match(/^\+/)) { + phone = '+7' + phone; + } + } + } + + console.log('Повторная отправка SMS на номер:', phone); + + if (!phone) { + $('.modal .form-item__warning').text('Номер телефона не указан'); + return false; + } + + // Отправляем SMS + $('.modal .js-send-sms').prop('disabled', true).text('Отправка...'); + $('.modal .form-item__warning').text(''); + + $.ajax({ + url: 'sms-verify.php?action=send', + method: 'post', + data: { + phonenumber: phone + }, + dataType: 'json', + success: function(data) { + if (data.success) { + $('.modal .form-item__warning').text('').removeClass('form-item__warning--error'); + $('.js-code-warning').text('Код отправлен на ваш номер телефона').show(); + $('.modal .js-send-sms').hide(); + $('.modal .js-accept-sms').show(); + $('.sms-countdown').show(); + + // Запускаем таймер снова + countDown(document.querySelector('.sms-countdown .time'), 30, function(){ + $('.modal .js-send-sms').show(); + $('.sms-checking button.js-accept-sms').hide(); + $('.js-code-warning').hide(); + }); + } else { + $('.modal .form-item__warning').text(data.message || 'Ошибка отправки SMS'); + $('.modal .js-send-sms').prop('disabled', false).text('Отправить повторно'); + } + }, + error: function (jqXHR, exception) { + var errorMsg = 'Ошибка отправки SMS'; + if (jqXHR.responseJSON && jqXHR.responseJSON.message) { + errorMsg = jqXHR.responseJSON.message; + } + $('.modal .form-item__warning').text(errorMsg); + $('.modal .js-send-sms').prop('disabled', false).text('Отправить повторно'); + } + }); + }); + + $('.sms-checking .js-accept-sms').on('click', function(e) { + e.preventDefault(); + + var code = $('.sms-checking input[type="text"]').val(); + var $phoneInput = $('input[name="phonenumber"]'); + var phone = $phoneInput.val(); + + // Получаем номер через intlTelInput API (если доступен) - ТОЧНО ТАК ЖЕ как при отправке + if ($phoneInput.length > 0 && window.intlTelInput) { + var iti = window.intlTelInputGlobals.getInstance($phoneInput[0]); + if (iti) { + var fullNumber = iti.getNumber(); + if (fullNumber) { + phone = fullNumber; + } else { + phone = phone.replace(/[() -]/g, ''); + if (phone.match(/^[89]/)) { + phone = '+7' + phone.replace(/^[89]/, ''); + } else if (!phone.match(/^\+/)) { + phone = '+7' + phone; + } + } + } else { + phone = phone.replace(/[() -]/g, ''); + if (phone.match(/^[89]/)) { + phone = '+7' + phone.replace(/^[89]/, ''); + } else if (!phone.match(/^\+/)) { + phone = '+7' + phone; + } + } + } + + console.log('Проверка кода для номера:', phone); + + if (!code || !phone) { + $('.modal .form-item__warning').text("Введите код подтверждения"); + return false; + } + + // Показываем загрузку + $('.js-accept-sms').prop('disabled', true).text('Проверка...'); + $('.modal .form-item__warning').text(''); + + // Проверяем код на сервере + $.ajax({ + url: 'sms-verify.php?action=verify', + method: 'post', + data: { + phonenumber: phone, + code: code + }, + dataType: 'json', + success: function(data) { + if (data.success) { + // Сохраняем токен верификации + sms_verify_token = data.token; + + $('.sms-success').removeClass('d-none'); + + // Проверяем, был ли полис уже проверен + var indatabase = $('.js-indatabase').val(); + if(indatabase == '1') { + // Если полис валидный, сразу показываем форму для заполнения + $('.db-success').removeClass('d-none'); + $('.form-step[data-step=1]').removeClass('disabled'); + // Инициализируем автодополнение адресов после показа формы + setTimeout(function() { + initAddressSuggestions(); + }, 500); + } else { + // Если полис невалидный, но был загружен через policy-upload-section, показываем форму + var polisInput = $('#polis_upload')[0]; + var polisUploaded = polisInput && polisInput.files && polisInput.files.length > 0; + if(polisUploaded) { + $('.db-success').removeClass('d-none'); + $('.form-step[data-step=1]').removeClass('disabled'); + // Инициализируем автодополнение адресов + setTimeout(function() { + initAddressSuggestions(); + }, 500); + } + } + + $('.modal .js-send-sms').show(); + $('.sms-check .form-item > .js-send-sms').hide(); + $.fancybox.close(); + $.fancybox.close(); + $('.sms-check').addClass("disabled"); + $('.sms-check .form-item__warning') + .addClass('form-item__warning--success') + .text("Вы успешно подтвердили номер"); + + // Блокируем поле телефона после подтверждения + var $phoneField = $('.js-phone-mask'); + var confirmedPhone = $phoneField.val(); + $phoneField.attr('data-confirmed-phone', confirmedPhone) + .attr('data-verify-token', sms_verify_token) + .prop('readonly', true) + .attr('autocomplete', 'off'); + + $('.js-accept-sms').prop('disabled', false).text('Подтвердить'); + } else { + $('.modal .form-item__warning').text(data.message || "Неверный код"); + $('.sms-success').addClass('d-none'); + $('.js-accept-sms').prop('disabled', false).text('Подтвердить'); + } + }, + error: function (jqXHR, exception) { + var errorMsg = "Ошибка проверки кода"; + if (jqXHR.responseJSON && jqXHR.responseJSON.message) { + errorMsg = jqXHR.responseJSON.message; + } + $('.modal .form-item__warning').text(errorMsg); + $('.sms-success').addClass('d-none'); + $('.js-accept-sms').prop('disabled', false).text('Подтвердить'); + } + }); + }); + + $('.form.active form').submit(function(e){ + + if(!validate_step(index)){ e.preventDefault(); } else { + e.preventDefault(); + + $('button[type="submit"]').attr("disabled", true); + + if(1) { + $('.js-code-warning').text(''); + $('.js-code-warning').hide(); + $('.js-send-sms').hide(); + + + $.fancybox.open({ + src: '#confirm_sms', + type: 'inline' + }); + + $('.loader-wrap').removeClass('d-none'); + + $('button[type="submit"]').attr("disabled", false); + + $('button[type="submit"]').text("Данные отправляются..."); + + var formData = new FormData(); + + let fileIndex = 0; // Счетчик для правильной индексации файлов + // Отправляем файлы напрямую как бинарные данные (без предварительной загрузки через fileupload_v2.php) + jQuery.each(jQuery('input[type=file].js-attach').not('.disabled'), function(i, file) { + + if(!$(this).hasClass('disabled')) { + // Используем field-type или name для группировки файлов + let field_name=jQuery(this).data('field-type') || jQuery(this).attr('name'); + let docname=jQuery(this).data('docname'); + + // Отправляем файлы напрямую как бинарные данные в submit.php + jQuery.each(jQuery(this)[0].files, function(i, file) { + formData.append(field_name+'-'+i, file); + }); + + // УБРАНО: старая система загрузки через fileupload_v2.php + // Теперь файлы отправляются напрямую в submit.php как бинарные данные + // n8n будет обрабатывать файлы сам + } + }); + + jQuery.each(jQuery('.js-append'), function(i, file) { + + let ws_name=jQuery(this).data('ws_name'); + let ws_type=jQuery(this).data('ws_type'); + + let val=""; + if(jQuery(this).attr('type') == 'checkbox'){ + if(jQuery(this).is(':checked')){ + val=jQuery(this).val(); + } + } else { + val=jQuery(this).val(); + } + let array={ + ws_name : ws_name, + ws_type: ws_type, + field_val : val + }; + formData.append('appends[]',JSON.stringify(array)); + + }); + + formData.append('lastname',jQuery('[name="lastname"]').val()); + + formData.append('getservice',jQuery('[name="getservice"]').val()); //Если есть агент посредник + + let sub_dir=jQuery('input[name=sub_dir]').val(); + formData.append('sub_dir',sub_dir); + + // Отладочный вывод отключен + // for (var pair of formData.entries()) { + // console.log(pair[0]+ ', ' + pair[1]); + // } + + $.ajax({ + url: 'submit.php', + method: 'post', + cache: false, + contentType: false, + processData: false, + data: formData, + dataType: 'json', + success: function(data) { + // Отладочный вывод отключен + // console.log(data); + + $('.loader-wrap').addClass('d-none'); + $.fancybox.close(); + $.fancybox.open({ + src: '#success_modal', + type: 'inline' + }); + setTimeout(function(){ + $.fancybox.close(); + },30) + setTimeout(function(){ + window.location.href = "https://lexpriority.ru/ok"; + },30) + + $('button[type="submit"]').text("Отправить"); + }, + error: function (jqXHR, exception) { + // Отладочный вывод отключен + // console.log(jqXHR); + if (jqXHR.status === 0) { + alert('Not connect. Verify Network.'); + } else if (jqXHR.status == 404) { + alert('Requested page not found (404).'); + } else if (jqXHR.status == 500) { + alert('Internal Server Error (500).'); + } else if (exception === 'parsererror') { + } else if (exception === 'timeout') { + alert('Time out error.'); + } else if (exception === 'abort') { + alert('Ajax request aborted.'); + } else { + alert('Uncaught Error. ' + jqXHR.responseText); + } + } + }); + + + return false; + } else { + $('.js-code-warning').text('Неверный код'); + return false; + } + } + + }); + + }); + + $('input[name=place],input[name=place_adres],input[name=place_inn]').keyup(function(e){ + var sourceInput = $(this); + var query = $(this).val(); + var settings = { + "url": "https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/party", + "method": "POST", + "timeout": 0, + "headers": { + "Authorization": "Token d2fa8613e186d54c6c62fc321414552353ffdfa8", + "Content-Type": "application/json", + "Cookie": "__ddg1_=BoLd7l5yOCjL9tr6qUth" + }, + "data": JSON.stringify({ + "query": query + }), + }; + $('.autocomplete__item').remove(); + var address_array = []; + $.ajax(settings).done(function (response) { + for(let i=0; i'+response.suggestions[i].value+'').appendTo(sourceInput.closest('.form-item').find('.form-item__dropdown')); + } + }); + }); + + $(document).on('click', '.autocomplete__item', function() { + let currentAutocompleteItem=$(this); + let prefix = $(this).closest('.autocomplete').data('groupename'); + $('.'+prefix+'_name').val(currentAutocompleteItem.text()); + $('.'+prefix+'_adres').val(currentAutocompleteItem.attr('data-address')); + $('.'+prefix+'_inn').val(currentAutocompleteItem.attr('data-inn')); + currentAutocompleteItem.closest('.form-item').find('.form-item__dropdown').empty(); + }); + + // Автоподстановка банков из API + let banksList = []; // Кэш списка банков + let banksLoading = false; // Флаг загрузки + + // Загрузка списка банков через прокси на сервере (обход CORS) + function loadBanksList() { + if (banksLoading) return; // Предотвращаем множественные запросы + banksLoading = true; + + $.ajax({ + url: 'load_banks.php', // PHP скрипт на сервере для обхода CORS + method: 'GET', + dataType: 'json', + timeout: 15000, // 15 секунд таймаут + success: function(data) { + banksLoading = false; + if (Array.isArray(data)) { + banksList = data; + // Отладочный вывод отключен + // console.log('Загружено банков:', banksList.length); + // Убираем предупреждение об ошибке, если оно было + $('.js-bank-autocomplete').closest('.form-item').find('.form-item__warning').text(''); + } else if (data && data.error) { + // Отладочный вывод отключен + // console.error('Ошибка от сервера:', data.error); + showBanksLoadError('Ошибка сервера: ' + data.error); + } else { + // Отладочный вывод отключен + // console.error('Неверный формат данных:', data); + showBanksLoadError('Неверный формат данных от сервера'); + } + }, + error: function(jqXHR, exception) { + banksLoading = false; + // Отладочный вывод отключен + // console.error('Ошибка загрузки списка банков:', exception, jqXHR); + var errorMsg = 'Не удалось загрузить список банков'; + if (jqXHR.status === 0) { + errorMsg += ' (нет соединения)'; + } else if (jqXHR.status === 404) { + errorMsg += ' (файл прокси не найден)'; + } else if (jqXHR.status === 500) { + errorMsg += ' (ошибка сервера)'; + } else if (exception === 'timeout') { + errorMsg += ' (превышено время ожидания)'; + } + showBanksLoadError(errorMsg); + } + }); + } + + // Показать ошибку загрузки банков + function showBanksLoadError(message) { + var bankInput = $('.js-bank-autocomplete'); + if (bankInput.length) { + var warningText = message || 'Не удалось загрузить список банков. Пожалуйста, обновите страницу или введите название банка вручную.'; + bankInput.closest('.form-item').find('.form-item__warning') + .text(warningText) + .css('color', '#dc3545'); + } + } + + // Загружаем банки при загрузке страницы + $(document).ready(function() { + // Небольшая задержка, чтобы убедиться, что DOM готов + setTimeout(function() { + loadBanksList(); + }, 100); + }); + + // Популярные банки (показываются первыми) + var popularBanks = ['Сбербанк', 'ВТБ', 'Альфа-Банк', 'Тинькофф', 'Райффайзен Банк', 'Газпромбанк', 'Россельхозбанк', 'ПСБ', 'МКБ', 'ЮМани']; + + // Функция для сортировки банков (популярные вверху) + function sortBanks(banks) { + return banks.sort(function(a, b) { + var aIndex = popularBanks.findIndex(function(name) { + return a.bankName.indexOf(name) !== -1; + }); + var bIndex = popularBanks.findIndex(function(name) { + return b.bankName.indexOf(name) !== -1; + }); + + if (aIndex === -1 && bIndex === -1) return 0; + if (aIndex === -1) return 1; + if (bIndex === -1) return -1; + return aIndex - bIndex; + }); + } + + // Автоподстановка банков + $('.js-bank-autocomplete').on('input keyup', function(e) { + var sourceInput = $(this); + var query = $(this).val().toLowerCase().trim(); + var dropdown = sourceInput.closest('.form-item').find('.form-item__dropdown'); + + // Очищаем предыдущие результаты + dropdown.empty(); + + // Если запрос пустой, показываем больше банков (30) с популярными вверху + if (query.length === 0) { + var sortedBanks = sortBanks(banksList.slice()); + showBanksDropdown(sortedBanks.slice(0, 30), dropdown, sourceInput, banksList.length, true); + return; + } + + // Фильтруем банки по запросу + var filteredBanks = banksList.filter(function(bank) { + return bank.bankName.toLowerCase().indexOf(query) !== -1; + }); + + // Показываем результаты (максимум 50 при поиске) + showBanksDropdown(filteredBanks.slice(0, 50), dropdown, sourceInput, filteredBanks.length, false); + }); + + // Функция для отображения выпадающего списка банков + function showBanksDropdown(banks, dropdown, sourceInput, totalCount, showHint) { + dropdown.empty(); + + if (banks.length === 0) { + dropdown.append('
          Банки не найдены
          '); + return; + } + + banks.forEach(function(bank) { + var item = $('
          ' + bank.bankName + '
          '); + dropdown.append(item); + }); + + // Добавляем подсказку внизу списка + if (showHint && totalCount > banks.length) { + var hint = $('
          ' + + 'Показано ' + banks.length + ' из ' + totalCount + ' банков
          ' + + 'Начните вводить название банка для поиска по всем ' + totalCount + ' банкам' + + '
          '); + dropdown.append(hint); + } else if (!showHint && totalCount > banks.length) { + var hint = $('
          ' + + 'Найдено: ' + totalCount + ' банков. Показано ' + banks.length + '. Уточните запрос для более точного поиска.' + + '
          '); + dropdown.append(hint); + } + } + + // Обработчик выбора банка - используем mousedown для надежности + $(document).on('mousedown', '.form-item__dropdown .autocomplete__item', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var selectedItem = $(this); + var bankId = selectedItem.attr('data-bank-id'); + var bankName = selectedItem.attr('data-bank-name'); + var formItem = selectedItem.closest('.form-item'); + + // Проверяем, что это не подсказка + if (selectedItem.hasClass('autocomplete__hint')) { + return false; + } + + // Заполняем поля + var bankInput = formItem.find('.js-bank-autocomplete'); + var bankIdInput = formItem.find('input[name="bank_id"]'); + + bankInput.val(bankName); + bankIdInput.val(bankId); + + // Очищаем выпадающий список + formItem.find('.form-item__dropdown').empty(); + + // Убираем предупреждение + formItem.find('.form-item__warning').text(''); + + // Убираем фокус с input + bankInput.blur(); + + // Отладочный вывод отключен + // console.log('Банк выбран:', bankName, 'ID:', bankId); + + return false; + }); + + // Дублируем обработчик на click для совместимости + $(document).on('click', '.form-item__dropdown .autocomplete__item', function(e) { + e.preventDefault(); + e.stopPropagation(); + + var selectedItem = $(this); + + // Проверяем, что это не подсказка + if (selectedItem.hasClass('autocomplete__hint')) { + return false; + } + + // Если mousedown не сработал, обрабатываем здесь + var bankId = selectedItem.attr('data-bank-id'); + var bankName = selectedItem.attr('data-bank-name'); + var formItem = selectedItem.closest('.form-item'); + + if (bankId && bankName) { + formItem.find('.js-bank-autocomplete').val(bankName); + formItem.find('input[name="bank_id"]').val(bankId); + formItem.find('.form-item__dropdown').empty(); + formItem.find('.form-item__warning').text(''); + } + + return false; + }); + + // При фокусе показываем список банков + $('.js-bank-autocomplete').on('focus', function() { + var sourceInput = $(this); + var dropdown = sourceInput.closest('.form-item').find('.form-item__dropdown'); + var query = $(this).val().toLowerCase().trim(); + + if (banksList.length === 0) { + // Если банки еще не загружены, пробуем загрузить + loadBanksList(); + return; + } + + if (query.length === 0) { + var sortedBanks = sortBanks(banksList.slice()); + showBanksDropdown(sortedBanks.slice(0, 30), dropdown, sourceInput, banksList.length, true); + } else { + // Триггерим событие input для фильтрации + $(this).trigger('input'); + } + }); + + // Скрываем выпадающий список при потере фокуса (с задержкой для клика) + $('.js-bank-autocomplete').on('blur', function(e) { + var sourceInput = $(this); + var dropdown = sourceInput.closest('.form-item').find('.form-item__dropdown'); + + // Увеличиваем задержку, чтобы клик успел обработаться + setTimeout(function() { + // Проверяем, не кликнули ли мы на элемент списка или внутри dropdown + var activeElement = document.activeElement; + var relatedTarget = e.relatedTarget; + + // Если фокус перешел на элемент из dropdown, не скрываем + if (relatedTarget && $(relatedTarget).closest('.form-item__dropdown').length) { + return; + } + + if (!$(activeElement).hasClass('autocomplete__item') && + !$(activeElement).closest('.form-item__dropdown').length) { + dropdown.empty(); + } + }, 200); + }); + + // Предотвращаем blur при клике на dropdown + $(document).on('mousedown', '.form-item__dropdown', function(e) { + // Останавливаем всплытие, чтобы blur не сработал на input + e.stopPropagation(); + }); + + + // Инициализация автодополнения адресов при загрузке страницы + setTimeout(function() { + initAddressSuggestions(); + }, 1000); + + // $('input[name=db_birthday],input[name=db_inn]').keyup(function(e){ + + $(document).on('click', '.js-check-in', function(e) { + + e.preventDefault(); + let birthday=$('input[name=birthday]').val(); + let police_number=$('input[name=police_number]').val(); + + if(police_number.slice(0,1)=="Е"){ + police_number=police_number.replace(/Е/g, 'E'); + } + + if(police_number.slice(0,1)=="А"){ + police_number=police_number.replace(/А/g, 'A'); + } + + let dbdata={ + "action" : "user_verify", + 'birthday' : birthday.replace(/-/g, '.'), + 'inn' : police_number + } + + // Показываем загрузку + $('.js-check-in').prop('disabled', true).text('Проверка...'); + $('.js-result').html('').removeClass('danger form-item__warning--success'); + + $.ajax({ + url: 'database.php', + method: 'post', + data: dbdata, + dataType: 'json', + success: function(data) { + $('.js-check-in').prop('disabled', false).text('Проверить наличие полиса'); + + // Если data уже объект, не парсим + let res = typeof data === 'string' ? JSON.parse(data) : data; + if(res.success=="true"){ + // Полис валидный - показываем успешное сообщение + $('.js-result').html(res.message); + $('.js-result').removeClass("danger"); + $('.js-result').addClass("form-item__warning--success"); + + $('input[name=insured_from]').val(res.result.insured_from.replace(/\./g, '-')); + $('input[name=insured_to]').val(res.result.insured_to.replace(/\./g, '-')); + + + $('.js-indatabase').val('1'); + $('.policy-upload-section').addClass('d-none'); + + // Показываем поля телефона и банка после успешной проверки полиса + $('.policy-validated-fields').removeClass('d-none'); + + // Разблокируем кнопку "Отправить смс" если полис валидный + $('.js-send-sms').removeClass('disabled'); + $('.js-send-sms').prop('disabled', false); + $('.form__warning').hide(); + + // Если SMS уже подтверждено, сразу показываем форму для заполнения + if($('.sms-success').is(':visible') || !$('.sms-success').hasClass('d-none')) { + $('.db-success').removeClass('d-none'); + $('.form-step[data-step=1]').removeClass('disabled'); + } + // Инициализируем автодополнение адресов после проверки полиса + setTimeout(function() { + initAddressSuggestions(); + }, 500); + + } else { + // Полис не найден - показываем ТОЛЬКО поле для загрузки скана полиса + $('.js-indatabase').val('0'); + + // Убираем сообщение об ошибке из .js-result + $('.js-result').html(''); + $('.js-result').removeClass("danger form-item__warning--success"); + + // НЕ показываем форму (.sms-success), только блок загрузки полиса + $('.sms-success').addClass('d-none'); + $('.db-success').addClass('d-none'); + + // Показываем ТОЛЬКО блок для загрузки полиса + $('.policy-upload-section').removeClass('d-none'); + $('.policy-upload-section').find('input[type=file]').removeClass("notvalidate"); + $('.policy-upload-section').find('input[type=file]').removeClass("disabled"); + + // Показываем уведомление о необходимости загрузить полис + $('.policy-upload-section').find('.form-item__warning') + .text('Загрузите скан полиса для продолжения') + .css('color', '#856404') + .show(); + + // Скрываем поля телефона и банка + $('.policy-validated-fields').addClass('d-none'); + + // Блокируем кнопку "Отправить смс" + $('.js-send-sms').addClass('disabled'); + $('.js-send-sms').prop('disabled', true); + $('.form__warning').hide(); + + + } + return false; + }, + error: function (jqXHR, exception) { + $('.js-check-in').prop('disabled', false).text('Проверить наличие полиса'); + + var errorMsg = 'Ошибка при проверке полиса'; + if (jqXHR.responseText) { + try { + var errorData = JSON.parse(jqXHR.responseText); + if (errorData.message) { + errorMsg = errorData.message; + } + } catch(e) { + errorMsg = jqXHR.responseText.substring(0, 100); + } + } + + $('.js-result').html(errorMsg) + .removeClass('form-item__warning--success') + .addClass('danger'); + + console.error('Ошибка проверки полиса:', jqXHR, exception); + return false; + } + }); + + }); + + + $('input[name=birthday]').on("change input", function (e) { + // Отладочный вывод отключен + // console.log("Date changed: ", e.target.value); + + let birthday=$(this).val(); + + + if(getAge(birthday)<18) { + // Если возраст меньше 18 - разблокируем поле + $("input[data-enableby=birthday]").removeClass('disabled'); + $("input[data-disabledby=birthday]").removeClass('disabled'); + $("input[data-enableby=birthday]").prop('disabled', false); + $("input[data-enableby=birthday]").prop('readonly', false); + } else { + // Если возраст 18 и больше - блокируем поле + $("input[data-enableby=birthday]").addClass('disabled'); + $("input[data-disabledby=birthday]").addClass('disabled'); + $("input[data-enableby=birthday]").prop('disabled', true); + $("input[data-enableby=birthday]").prop('readonly', true); + $("input[data-enableby=birthday]").val(''); // Очищаем значение при блокировке + } + + }); + + + + $('input[name=lastname],input[name=firstname],input[name=patronymic]').keyup(function(e){ + let full_name=$('input[name=lastname]').val()+" "+$('input[name=firstname]').val()+" "+$('input[name=patronymic]').val(); + $("input[data-enableby=birthday]").val(full_name); + }); + + + + function getAge(dateString) { + var today = new Date(); + var birthDate = new Date(dateString.replace( /(\d{2})-(\d{2})-(\d{4})/, "$2/$1/$3")); + var age = today.getFullYear() - birthDate.getFullYear(); + var m = today.getMonth() - birthDate.getMonth(); + if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) { + age--; + } + return age; + } + + + + +// Загрузка файлов + + function declOfNum(number, words) { + return words[(number % 100 > 4 && number % 100 < 20) ? 2 : [2, 0, 1, 1, 1, 2][(number % 10 < 5) ? Math.abs(number) % 10 : 5]]; + } + + function updateSize(elem) { + var filesQty = elem[0].files.length; + if(filesQty>10) { + elem.closest('.form-item').find('.form-item__warning').text("Разрешено не более 10 файлов"); + return; + } + + elem.closest('.form-item').find('.form-item__warning').text(''); + let file_status=[]; + var formats = ['pdf','jpg','png','gif','jpeg']; + for(var i=0; i 1) ext = parts.pop(); + if(!formats.includes(ext)) { + elem.closest('.form-item').find('.form-item__warning').append('
          Файл '+file.name+' не подходит по формату. Доступные форматы: .pdf, .jpg, .png, .gif
          '); + elem.addClass('error'); + file_status.push(false); + } else { + // elem.closest('.form-item').find('.form-item__warning').text(); + if((file.size/1024/1024) > 5){ + file_status.push(false); + elem.closest('.form-item').find('.form-item__warning').append('
          Размер файла '+file.name+' превышает 5 Мб.
          '); + } else { + elem.removeClass('error'); + file_status.push(true); + + + + } + } + + + + } + + // ОТКЛЮЧЕНО: старая система загрузки файлов через fileupload_v2.php + // Файлы теперь отправляются напрямую в submit.php как бинарные данные + if(file_status.every(val => val === true)) + { + // upload_file(elem); // ОТКЛЮЧЕНО - файлы отправляются напрямую при submit формы + $('.form__action').find('.js-btn-next').removeClass('disabled'); + } else { + $('.form__action').find('.js-btn-next').addClass('disabled'); + } + } + + function upload_file(thisfile) { + + let formData = new FormData(); + + let field_name=thisfile.data('crmname') || thisfile.data('field-type'); + let docname=thisfile.data('docname'); + + // Отладочный вывод отключен + // console.log('=== UPLOAD_FILE FUNCTION ==='); + // console.log('Uploading for field:', field_name, 'docname:', docname); + // console.log('File input element:', thisfile[0]); + // console.log('Files in input:', thisfile[0].files); + // console.log('Field type:', thisfile.data('field-type')); + // console.log('Form item:', thisfile.closest('.form-item')[0]); + let sub_dir=jQuery('input[name=sub_dir]').val(); + + jQuery.each(thisfile[0].files, function(i, file) { + formData.append(field_name+'-'+i, file); + }); + thisfile.closest('.form-item').find('.suсcess-upload').remove(); + + + formData.append('lastname',jQuery('input[name=lastname]').val()); + formData.append('files_names[]',field_name); + formData.append('docs_names[]',docname); + + + formData.append('sub_dir',sub_dir); + + thisfile.closest('.form-item').append("

          "); + + + // for (var pair of formData.entries()) { + // console.log(pair[0]+ ', ' + pair[1]); + // } + $.ajax({ + xhr: function() { + var xhr = new window.XMLHttpRequest(); + // Upload progress + xhr.upload.addEventListener("progress", function(evt){ + if (evt.lengthComputable) { + var percentComplete = evt.loaded / evt.total; + //Do something with upload progress + + let complete=Math.round(percentComplete * 100); + // Отладочный вывод отключен + // console.log(complete); + thisfile.closest('.form-item').find(".info-upload").text("Загружено "+complete+" %"); + + } + }, false); + // Download progress + xhr.addEventListener("progress", function(evt){ + if (evt.lengthComputable) { + var percentComplete = evt.loaded / evt.total; + // Do something with download progress + // Отладочный вывод отключен + // console.log(percentComplete); + } + }, false); + return xhr; + }, + url: 'https://form.clientright.ru/fileupload_v2.php', + method: 'post', + cache: false, + contentType: false, + processData: false, + data: formData, + // dataType : 'json', + beforeSend : function (){ + $('.form__action').find('.js-btn-next').addClass('disabled'); + $('.form__action').find('.btn--submit').addClass('disabled'); + }, + success: function(data) { + + let res=JSON.parse(data); + if(res.success=="true"){ + // Отладочный вывод отключен + // console.log(res); + // thisfile.closest('.form-item').append("

          Файл успешно загружен на сервер.

          "); + thisfile.attr('data-uploadurl',res.empty_file); + thisfile.attr('data-uploadurl_real',res.real_file); + // thisfile.closest('.form-item').append("

          "+res.message+"

          "); + thisfile.closest('.form-item').find('.info-upload').remove(); + + + $('.form__action').find('.js-btn-next').removeClass('disabled'); + $('.form__action').find('.btn--submit').removeClass('disabled'); + + + } else { + } + return false; + }, + error: function (jqXHR, exception) { + return false; + } + }); + + } + + function formatBytes(bytes, decimals = 2) { + if (!+bytes) return '0 Bytes' + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}` + } + + var fileIdCounter = 0; + + jQuery('.js-attach').each(function() { + var fieldType = $(this).data('field-type'); // Получаем тип поля + let filethis = $(this); + + // Создаем изолированное хранилище файлов для каждого поля + var fieldFiles = { + filesToUpload: [], + fieldType: fieldType + }; + + filethis.change(function (evt) { + var output = []; + let elem= $(this); + let currentFieldFiles = fieldFiles; // Используем изолированное хранилище + let currentFormItem = elem.closest('.form-item'); + + // Очищаем предупреждения только для текущего поля + currentFormItem.find('.form-item__warning').text(''); + let file_status=[]; + var formats = ['pdf','jpg','png','gif','jpeg']; + + // Отладочный вывод отключен + // console.log('=== FILE UPLOAD START ==='); + // console.log('Processing files for field type:', currentFieldFiles.fieldType); + // console.log('Field element:', elem[0]); + // console.log('Form item:', currentFormItem[0]); + // console.log('Files to process:', evt.target.files); + // console.log('Current fieldFiles state:', currentFieldFiles); + + if(evt.target.files.length>10) { + elem.closest('.form-item').find('.form-item__warning').text("Разрешено не более 10 файлов"); + return; + } + + // Очищаем предыдущий список файлов для этого поля + currentFormItem.find('.fileList').empty(); + currentFieldFiles.filesToUpload = []; + + for (var i = 0; i < evt.target.files.length; i++) { + fileIdCounter++; + var file = evt.target.files[i]; + var fileId = "fileid_" + fileIdCounter; + + // Отладочный вывод отключен + // console.log(file); + + let ext = "не определилось"; + let parts = file.name.split('.'); + + if (parts.length > 1) ext = parts.pop(); + if(!formats.includes(ext)) { + elem.closest('.form-item').find('.form-item__warning').append('
          Файл '+file.name+' не подходит по формату. Доступные форматы: .pdf, .jpg, .png, .gif
          '); + elem.addClass('error'); + file_status.push(false); + } else { + // elem.closest('.form-item').find('.form-item__warning').text(); + if((file.size/1024/1024) > 5){ + file_status.push(false); + elem.closest('.form-item').find('.form-item__warning').append('
          Размер файла '+file.name+' превышает 5 Мб.
          '); + } else { + elem.removeClass('error'); + file_status.push(true); + + + var removeLink = ""; + output.push("
        • ", file.name, " ", formatBytes(file.size) , " ", removeLink, "
        • "); + + currentFieldFiles.filesToUpload.push({ + id: fileId, + file: file + }); + + + + + } + } + + //evt.target.value = null; + + + + // elem.closest('.form-item').find('.upload-action').removeClass('d-none'); + + + + + + + + }; + + currentFormItem.find(".fileList").append(output.join("")); + + let container = new DataTransfer(); + for (var i = 0, len = currentFieldFiles.filesToUpload.length; i < len; i++) { + container.items.add(currentFieldFiles.filesToUpload[i].file); + } + + elem[0].files = container.files; + + if(file_status.every(val => val === true)) + { + // Отладочный вывод отключен + // console.log('=== FILE UPLOAD SUCCESS ==='); + // console.log('Uploading files for field type:', currentFieldFiles.fieldType); + // console.log('Final fieldFiles state:', currentFieldFiles); + // console.log('Files in DataTransfer:', elem[0].files); + // upload_file(elem); // ОТКЛЮЧЕНО - файлы отправляются напрямую при submit формы + $('.form__action').find('.js-btn-next').removeClass('disabled'); + + // Если загружен полис из блока policy-upload-section, показываем телефон и банк + if(currentFieldFiles.fieldType === 'polis' && elem.closest('.policy-upload-section').length > 0) { + // Скрываем блок загрузки полиса + $('.policy-upload-section').addClass('d-none'); + // Показываем поля телефона и банка + $('.policy-validated-fields').removeClass('d-none'); + // Разблокируем кнопку "Отправить смс" + $('.js-send-sms').removeClass('disabled'); + $('.js-send-sms').prop('disabled', false); + // Убираем уведомление + $('.policy-upload-section').find('.form-item__warning').text('').hide(); + } + } else { + // Отладочный вывод отключен + // console.log('=== FILE UPLOAD FAILED ==='); + // console.log('File status:', file_status); + $('.form__action').find('.js-btn-next').addClass('disabled'); + } + }); + + // Обработчик удаления файлов с доступом к fieldFiles через замыкание + $(this).closest('.form-item').on("click", ".removefile", function (e) { + let elem = $(this); + e.preventDefault(); + var fileId = elem.data("fileid"); + + // Находим соответствующий input файла для этого .form-item + let fileInput = elem.closest('.form-item').find('input[type="file"].js-attach'); + let fieldType = fileInput.data('field-type'); + + // Отладочный вывод отключен + // console.log('=== REMOVE FILE ==='); + // console.log('Removing file with ID:', fileId); + // console.log('From field type:', fieldType); + // console.log('Current fieldFiles:', fieldFiles); + + // Используем fieldFiles из замыкания + for (var i = 0; i < fieldFiles.filesToUpload.length; ++i) { + if (fieldFiles.filesToUpload[i].id === fileId) { + fieldFiles.filesToUpload.splice(i, 1); + // Отладочный вывод отключен + // console.log('File removed from fieldFiles'); + break; + } + } + + elem.parent().remove(); + + if(fieldFiles.filesToUpload.length == 0) { + elem.closest('.form-item').find('.upload-action').addClass('d-none'); + elem.closest('.form-item').find('.suсcess-upload').text(''); + } else { + let container = new DataTransfer(); + for (var i = 0, len = fieldFiles.filesToUpload.length; i < len; i++) { + container.items.add(fieldFiles.filesToUpload[i].file); + } + fileInput[0].files = container.files; + updateSize(fileInput); + } + }); + + + + }); + +// End Загрузка файлов + + + + + }); + + + \ No newline at end of file diff --git a/libs/bootstrap/scss/_alert.scss b/libs/bootstrap/scss/_alert.scss new file mode 100644 index 0000000..dd43e23 --- /dev/null +++ b/libs/bootstrap/scss/_alert.scss @@ -0,0 +1,51 @@ +// +// Base styles +// + +.alert { + position: relative; + padding: $alert-padding-y $alert-padding-x; + margin-bottom: $alert-margin-bottom; + border: $alert-border-width solid transparent; + @include border-radius($alert-border-radius); +} + +// Headings for larger alerts +.alert-heading { + // Specified to prevent conflicts of changing $headings-color + color: inherit; +} + +// Provide class for links that match alerts +.alert-link { + font-weight: $alert-link-font-weight; +} + + +// Dismissible alerts +// +// Expand the right padding and account for the close button's positioning. + +.alert-dismissible { + padding-right: ($close-font-size + $alert-padding-x * 2); + + // Adjust close link position + .close { + position: absolute; + top: 0; + right: 0; + padding: $alert-padding-y $alert-padding-x; + color: inherit; + } +} + + +// Alternate styles +// +// Generate contextual modifier classes for colorizing the alert. + +@each $color, $value in $theme-colors { + .alert-#{$color} { + @include alert-variant(theme-color-level($color, $alert-bg-level), theme-color-level($color, $alert-border-level), theme-color-level($color, $alert-color-level)); + } +} diff --git a/libs/bootstrap/scss/_badge.scss b/libs/bootstrap/scss/_badge.scss new file mode 100644 index 0000000..b87a1b0 --- /dev/null +++ b/libs/bootstrap/scss/_badge.scss @@ -0,0 +1,47 @@ +// Base class +// +// Requires one of the contextual, color modifier classes for `color` and +// `background-color`. + +.badge { + display: inline-block; + padding: $badge-padding-y $badge-padding-x; + font-size: $badge-font-size; + font-weight: $badge-font-weight; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + @include border-radius($badge-border-radius); + + // Empty badges collapse automatically + &:empty { + display: none; + } +} + +// Quick fix for badges in buttons +.btn .badge { + position: relative; + top: -1px; +} + +// Pill badges +// +// Make them extra rounded with a modifier to replace v3's badges. + +.badge-pill { + padding-right: $badge-pill-padding-x; + padding-left: $badge-pill-padding-x; + @include border-radius($badge-pill-border-radius); +} + +// Colors +// +// Contextual variations (linked badges get darker on :hover). + +@each $color, $value in $theme-colors { + .badge-#{$color} { + @include badge-variant($value); + } +} diff --git a/libs/bootstrap/scss/_breadcrumb.scss b/libs/bootstrap/scss/_breadcrumb.scss new file mode 100644 index 0000000..25b9d85 --- /dev/null +++ b/libs/bootstrap/scss/_breadcrumb.scss @@ -0,0 +1,38 @@ +.breadcrumb { + display: flex; + flex-wrap: wrap; + padding: $breadcrumb-padding-y $breadcrumb-padding-x; + margin-bottom: $breadcrumb-margin-bottom; + list-style: none; + background-color: $breadcrumb-bg; + @include border-radius($border-radius); +} + +.breadcrumb-item { + // The separator between breadcrumbs (by default, a forward-slash: "/") + + .breadcrumb-item::before { + display: inline-block; // Suppress underlining of the separator in modern browsers + padding-right: $breadcrumb-item-padding; + padding-left: $breadcrumb-item-padding; + color: $breadcrumb-divider-color; + content: "#{$breadcrumb-divider}"; + } + + // IE9-11 hack to properly handle hyperlink underlines for breadcrumbs built + // without `