This commit is contained in:
Fedor
2026-03-13 10:42:01 +03:00
commit 9708638e0a
4050 changed files with 1028632 additions and 0 deletions

28
.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
# Секреты и конфиги с ключами
.env
.env.*
!.env.example
# Логи
logs/
*.log
# Кэш и временные файлы
cache/
tmp/
temp/
*.tmp
# IDE
.idea/
.vscode/
*.swp
*.swo
# Системные
.DS_Store
Thumbs.db
# Зависимости (если появятся)
node_modules/
vendor/

21
.htaccess Normal file
View File

@@ -0,0 +1,21 @@
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
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

486
API_REFERENCE.md Normal file
View File

@@ -0,0 +1,486 @@
# Справочник API
## CRM WebService API
### Базовый URL
```
https://crm.clientright.ru/webservice.php
```
### Авторизация
#### 1. Получение токена (getchallenge)
```php
GET ?operation=getchallenge&username=api
```
**Ответ:**
```json
{
"success": true,
"result": {
"token": ременный_токен"
}
}
```
#### 2. Получение sessionId (login)
```php
POST
operation=login
username=api
accessKey=md5(challengeToken + userAccessKey)
```
**Где:**
- `userAccessKey = '4r9ANex8PT2IuRV'`
- `accessKey = md5(challengeToken + userAccessKey)`
**Ответ:**
```json
{
"success": true,
"result": {
"sessionName": "session_id_для_дальнейшихапросов"
}
}
```
### Операции с клиентами
#### CreateContact - Создание/обновление клиента
**Параметры:**
```php
operation = CreateContact
sessionName = полученный_sessionId
firstname = Имя (обязательно)
lastname = Фамилия (обязательно)
secondname = Отчество
mobile = Телефон (обязательно)
email = Email
tgid = Telegram ID (число)
birthday = Дата рождения в формате ГГГГ-ММ-ДД (обязательно)
birthplace = Место рождения
mailingstreet = Адрес проживания
inn = ИНН (обязательно)
requisites = Реквизиты для перечисления средств
code = СМС код
```
**Пример запроса:**
```php
$params = array(
'operation' => 'CreateContact',
'sessionName' => $sessionId,
'firstname' => 'Василий',
'lastname' => 'Пупкинидзе',
'secondname' => 'Алибабаевич',
'mobile' => '+7(949) 123-45-11',
'email' => 'rrrr@mail.ru',
'birthday' => '1986-11-15',
'mailingstreet' => 'г. Калининград',
'inn' => '321654987654',
'code' => '4568'
);
```
**Ответ:**
```json
{
"success": true,
"result": "83859"
}
```
Где `result` - это ID созданного или обновленного клиента.
### Операции с контрагентами
#### CreateAccount - Создание/обновление контрагента
**Параметры:**
```php
operation = CreateAccount
sessionName = полученный_sessionId
accountname = Наименование юрлица (обязательно)
address = Юридический адрес
email = Email
website = Сайт
phone = Телефон
inn = ИНН (обязательно)
ogrn = ОГРН (обязательно)
```
**Пример запроса:**
```php
$params = array(
'operation' => 'CreateAccount',
'sessionName' => $sessionId,
'accountname' => 'ООО "Три бобра"',
'address' => 'г. Москва, Кремль',
'email' => 'qqqqq@ya.ru',
'website' => 'https://pikabu.ru',
'phone' => '+7 928 664-66-11',
'inn' => '1234567899',
'ogrn' => '32165498711'
);
```
**Ответ:**
```json
{
"success": true,
"result": "83837"
}
```
Где `result` - это ID созданного или обновленного контрагента.
### Операции с проектами
#### CreateProject - Создание проекта
**Параметры:**
```php
operation = CreateProject
sessionName = полученный_sessionId
contactid = ID клиента (обязательно)
offenderid = ID контрагента-обидчика (обязательно)
agentid = ID контрагента-посредника (опционально)
sms = Код из СМС
ip = IP пользователя
source = Откуда пришли
region = Регион пользователя
formid = ID формы
category = Категория обращения
direction = Направление обращения
agrprice = Цена договора (число)
subject = Предмет договора
agrdate = Дата заключения договора (ГГГГ-ММ-ДД)
startdate = Срок начальный (ГГГГ-ММ-ДД)
finishdate = Срок конечный (ГГГГ-ММ-ДД)
loss = Убыток (число)
servicecost = Стоимость услуги (число)
progress = Прогресс в % (число)
country = Страна путешествия
hotel = Средство размещения
transport = Транспортные услуги ("да" или "нет")
insurance = Страховка ("да" или "нет")
other = Прочее
description = Описание
independently = Самостоятельно соблюден претензионный порядок ("да" или "нет")
claimdate = Дата направления претензии (ГГГГ-ММ-ДД)
returned = Вернули в претензионном порядке (число)
```
**Пример запроса:**
```php
$params = array(
'operation' => 'CreateProject',
'sessionName' => $sessionId,
'contactid' => 83834,
'offenderid' => 84675,
'sms' => '1234',
'ip' => '192.168.0.1',
'source' => 'с улицы',
'region' => 'Владивосток',
'formid' => 1376,
'category' => 'Абидили!!',
'direction' => 'Путину',
'description' => 'какое-то описание'
);
```
**Ответ:**
```json
{
"success": true,
"result": "83839"
}
```
Где `result` - это ID созданного проекта.
### Получение списка файлов
#### GetFilesList - Получение списка файлов клиента
**Параметры:**
```php
operation = GetFilesList
sessionName = полученный_sessionId
inn = ИНН клиента (обязательно)
sms = СМС-код (обязательно)
```
**Пример запроса:**
```php
$params = array(
'operation' => 'GetFilesList',
'sessionName' => $sessionId,
'inn' => '643922466250',
'sms' => '795372'
);
```
**Ответ при успехе:**
```json
{
"success": true,
"result": {
"status": "ok",
"files": [
{
"title": "договор",
"path": "storage/2023/December/week4/9e84c3f86209302799f46d0136e93ab6.pdf"
},
{
"title": "подтверждение оплаты",
"path": "storage/2023/December/week4/cf65c58a01e7db0b19c8e1fe119c0e32.pdf"
}
]
}
}
```
**Ответ при ошибке:**
```json
{
"success": true,
"result": {
"status": "failed",
"message": "Клиент с указанным СМС-кодом и ИНН не найден"
}
}
```
## WebForms Capture API
### Базовый URL
```
https://crm.clientright.ru/modules/Webforms/capture.php
```
### Параметры формы
**Обязательные системные параметры:**
```php
__vtrftk = 'sid:ec649134ad232e44c3ad71bbd321cee986f05545,1688385374'
publicid = '3ddc71c2d79ef101c09b0d4e9c6bd08b'
urlencodeenable = '1'
name = 'websiteticket'
```
**Кастомные поля:**
Все поля с атрибутом `data-crmname` отправляются как отдельные параметры.
## File Upload API
### Базовый URL
```
https://form.clientright.ru/fileupload_v2.php
```
### Параметры
```php
POST multipart/form-data
lastname = Фамилия клиента
sub_dir = ID сессии
files_names[] = Массив имен полей для файлов
docs_names[] = Массив названий документов
field_name-0 = Файл 1
field_name-1 = Файл 2
...
```
**Ответ:**
```json
{
"success": "true",
"empty_file": "путь_к_пустышке",
"real_file": "путь_к_реальномуайлу",
"message": "Файл успешно загружен"
}
```
## DaData API
### Базовый URL
```
https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/party
```
### Авторизация
```
Authorization: Token f5d6928d7490cd44124ccae11a08c7fa5625d48c
Content-Type: application/json
```
### Запрос
```json
POST
{
"query": "название организации или ИНН"
}
```
### Ответ
```json
{
"suggestions": [
{
"value": "ООО Рога и Копыта",
"data": {
"inn": "1234567890",
"address": {
"value": "г. Москва, ул. Ленина, д. 1"
}
}
}
]
}
```
## IP Geolocation API
### Базовый URL
```
http://ip-api.com/json/{IP}?lang=ru
```
### Пример запроса
```
GET http://ip-api.com/json/192.168.1.1?lang=ru
```
### Ответ
```json
{
"status": "success",
"country": "Россия",
"regionName": "Москва",
"city": "Москва",
"lat": 55.7558,
"lon": 37.6173
}
```
## Обработка ошибок
### Стандартный формат ошибки
```json
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Описание ошибки"
}
}
```
### HTTP коды статуса
- `200` - Успешный запрос
- `400` - Неверные параметры
- `401` - Ошибка авторизации
- `404` - Ресурс не найден
- `500` - Внутренняя ошибка сервера
### Важные замечания
1. **SessionId** имеет ограниченное время жизни (15 минут)
2. **Поле success** в ответе означает только техническую успешность запроса
3. Для проверки бизнес-логики используйте поле **status** внутри **result**
4. Все даты должны быть в формате **ГГГГ-ММ-ДД**
5. При работе с файлами используйте **multipart/form-data**
## Примеры использования
### Полный цикл создания обращения
```php
// 1. Получение токена
$ch = curl_init();
$url = "https://crm.clientright.ru/webservice.php?operation=getchallenge&username=api";
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
$response = curl_exec($ch);
$jsonResponse = json_decode($response, true);
$challengeToken = $jsonResponse['result']['token'];
// 2. Получение sessionId
$userAccessKey = '4r9ANex8PT2IuRV';
$generatedKey = md5($challengeToken . $userAccessKey);
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_URL => 'https://crm.clientright.ru/webservice.php',
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => array(
'operation' => 'login',
'username' => 'api',
'accessKey' => $generatedKey
)
));
$response = curl_exec($ch);
$jsonResponse = json_decode($response, true);
$sessionId = $jsonResponse['result']['sessionName'];
// 3. Создание клиента
$clientParams = array(
'operation' => 'CreateContact',
'sessionName' => $sessionId,
'firstname' => 'Иван',
'lastname' => 'Иванов',
'mobile' => '+79991234567',
'email' => 'ivan@example.com',
'birthday' => '1990-01-01',
'inn' => '123456789012',
'code' => '123456'
);
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_POST => 1,
CURLOPT_URL => 'https://crm.clientright.ru/webservice.php',
CURLOPT_POSTFIELDS => $clientParams
));
$response = curl_exec($ch);
$jsonResponse = json_decode($response, true);
$contactId = $jsonResponse['result'];
// 4. Создание контрагента
$accountParams = array(
'operation' => 'CreateAccount',
'sessionName' => $sessionId,
'accountname' => 'ООО "Пример"',
'inn' => '9876543210',
'ogrn' => '1234567890123'
);
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_POST => 1,
CURLOPT_URL => 'https://crm.clientright.ru/webservice.php',
CURLOPT_POSTFIELDS => $accountParams
));
$response = curl_exec($ch);
$jsonResponse = json_decode($response, true);
$accountId = $jsonResponse['result'];
// 5. Создание проекта
$projectParams = array(
'operation' => 'CreateProject',
'sessionName' => $sessionId,
'contactid' => $contactId,
'offenderid' => $accountId,
'description' => 'Описание обращения'
);
curl_setopt_array($ch, array(
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_POST => 1,
CURLOPT_URL => 'https://crm.clientright.ru/webservice.php',
CURLOPT_POSTFIELDS => $projectParams
));
$response = curl_exec($ch);
$jsonResponse = json_decode($response, true);
$projectId = $jsonResponse['result'];
curl_close($ch);
```

279
CONFIG.md Normal file
View File

@@ -0,0 +1,279 @@
# Конфигурация проекта
## Переменные окружения и настройки
### База данных
**Файл:** `ticket/database.php`
```php
$host = "localhost";
$username = "ci20465_erv";
$password = "c7vOXbmG";
$database = "ci20465_erv";
$table = "lexrpiority";
```
### CRM API
**Endpoint:**
```
https://crm.clientright.ru/webservice.php
```
**Учетные данные:**
```php
$username = "api";
$userAccessKey = "4r9ANex8PT2IuRV";
```
**WebForms Capture:**
```
https://crm.clientright.ru/modules/Webforms/capture.php
```
**Параметры формы:**
```php
$formToken = "sid:ec649134ad232e44c3ad71bbd321cee986f05545,1688385374";
$publicId = "3ddc71c2d79ef101c09b0d4e9c6bd08b";
$formName = "websiteticket";
```
### Внешние сервисы
#### DaData API
```php
$dadataToken = "f5d6928d7490cd44124ccae11a08c7fa5625d48c";
$dadataUrl = "https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/party";
```
#### IP Geolocation
```php
$ipApiUrl = "http://ip-api.com/json/{IP}?lang=ru";
```
#### File Upload
```php
$fileUploadUrl = "https://form.clientright.ru/fileupload_v2.php";
```
#### Form Processing
```php
$formProcessingUrl = "https://form.clientright.ru/server_new.php";
```
### Контрагент по умолчанию (ЕРВ)
**Файл:** `ticket/index.php` (строки 65-72)
```php
$contractor = array(
'inn' => '7714312079',
'ogrn' => '1037714037426',
'accountname' => 'Филиал ООО РСО ЕВРОИНС Туристическое',
'address' => '119049 Москва, 4-й Добрынинский пер., д.8, помещ. С 14-I, ком. 21-26',
'email' => 'info@erv.ru',
'phone' => '84956265800',
'website' => 'https://www.erv.ru/'
);
```
### Валидные тарифы полисов
**Базовые тарифы:**
```php
$valid_tariffs_basic = [
'STB0048', 'STB1099', 'STB1100', 'STB2099',
'AVS21500', 'AVS22500'
];
```
**Дополнительные тарифы:**
```php
$valid_tariffs_other = [
'SPV0001', 'SPV0002', 'SPV0003', 'SPV0004', 'SPV0005',
'STV0090',
'SPV1001', 'SPV1002', 'SPV1003', 'SPV1004', 'SPV1005',
'SPV2001', 'SPV2002', 'SPV2004', 'SPV2005',
'OPV1001', 'OPV1002', 'OPV1003', 'OPV1004', 'OPV1005',
'OPV2001', 'OPV2002', 'OPV2004', 'OPV2005'
];
```
### Ограничения файлов
```php
$maxFileSize = 5 * 1024 * 1024; // 5 МБ
$maxFilesPerField = 10;
$allowedFormats = ['pdf', 'jpg', 'png', 'gif', 'jpeg', 'heic', 'HEIC'];
```
### Настройки формы
**ID формы:**
```php
$formId = "00001";
```
**Направление обращения:**
```php
$direction = "ЕРВ Средства размещения";
```
### Маппинг полей формы
#### Поля клиента (data-ws_type="client")
| Поле формы | CRM поле | Описание |
|------------|----------|----------|
| `lastname` | `lastname` | Фамилия |
| `firstname` | `firstname` | Имя |
| `patronymic` | `secondname` | Отчество |
| `phonenumber` | `mobile` | Телефон |
| `email` | `email` | Email |
| `birthday` | `birthday` | Дата рождения |
| `reg_adres` | `mailingstreet` | Адрес регистрации |
| `inn` | `inn` | ИНН |
| `code` | `code` | SMS-код |
#### Поля проекта (data-ws_type="project")
| Поле формы | CRM поле | Описание |
|------------|----------|----------|
| `insured_from` | `cf_1887` | Дата начала страхования |
| `insured_to` | `cf_1889` | Дата окончания страхования |
| `claim` | `cf_1899` | Код документа |
| `doc` | `cf_1804` | Серия и номер документа |
| `countryevent` | `cf_1909` | Страна события |
| `agree` | `cf_2502` | Согласие на обработку данных |
#### Поля контрагента (data-ws_type="contractor")
| Поле формы | CRM поле | Описание |
|------------|----------|----------|
| `place_inn` | `inn` | ИНН |
| - | `ogrn` | ОГРН |
| `place` | `accountname` | Наименование |
| `place_adres` | `address` | Адрес |
| `place_email` | `email` | Email |
| `place_phone` | `phone` | Телефон |
| `place_website` | `website` | Сайт |
#### Другие поля (data-ws_type="other")
| Поле формы | CRM поле | Описание |
|------------|----------|----------|
| - | `cf_2446` | Флаг наличия в базе |
| `police_number` | `cf_1885` | Номер полиса |
| `event_type` | `cf_1726` | Тип события |
| `insurence_date` | `cf_2566` | Дата страхового случая |
| `transport_number` | `cf_2568` | Номер рейса/поезда |
| `description` | `description` | Описание ситуации |
| - | `cf_1945` | ФИО получателя |
| - | `cf_1265` | Банк |
| - | `cf_1267` | БИК |
| - | `cf_1269` | Расчетный счет |
| - | `cf_1271` | Корр. счет |
| - | `cf_1273` | Иные реквизиты |
### Типы страховых случаев
```php
$eventTypes = [
'delay_flight' => 'Задержка авиарейса (более 3 часов)',
'cancel_flight' => 'Отмена авиарейса',
'miss_connection' => 'Пропуск (задержка прибытия) стыковочного рейса',
'emergency_landing' => 'Посадка воздушного судна на запасной аэродром',
'delay_train' => 'Задержка отправки поезда',
'cancel_train' => 'Отмена поезда',
'delay_ferry' => 'Задержка/отмена отправки парома/круизного судна'
];
```
### Коды документов
```php
$documentCodes = [
'21' => 'Паспорт гражданина Российской Федерации',
'07' => 'Военный билет',
'24' => 'Удостоверение личности военнослужащего Российской Федерации',
'91' => 'Иные документы'
];
```
### Настройки логирования
**Путь к логам:**
```php
$logFile = "logs/logfile.log";
```
**Уровни логирования:**
- Информационные сообщения
- Ошибки
- Отладочная информация
### Настройки сессии
```php
session_start();
$subDir = session_id(); // Используется для изоляции файлов пользователей
```
### URL для редиректа после успешной отправки
```php
$successRedirectUrl = "https://lexpriority.ru/ok";
```
### Политика обработки персональных данных
```php
$privacyPolicyUrl = "https://lexpriority.ru/politics";
```
## Рекомендации по безопасности
1. **Храните секретные ключи в переменных окружения**
2. **Не коммитьте пароли и токены в репозиторий**
3. **Используйте HTTPS для всех внешних запросов**
4. **Валидируйте все входные данные**
5. **Используйте prepared statements для SQL**
6. **Ограничьте права доступа к файлам**
7. **Регулярно обновляйте зависимости**
## Переменные для разработки
### Тестовый режим
```php
// Добавьте ?demodata=1 к URL для автоматического заполнения формы
$demoMode = isset($_GET["demodata"]) ? $_GET["demodata"] : false;
```
### Тестовые данные для API
```php
$testInn = "643922466250";
$testSmsCode = "795372";
```
## Настройки сервера
### PHP требования
- PHP 7.4 или выше
- Расширения:
- curl
- mysqli
- gd (для работы с изображениями)
- mbstring
### Права доступа
```bash
# Для директории загрузки файлов
chmod 755 uploads/
chmod 755 logs/
```
### Настройки PHP.ini
```ini
upload_max_filesize = 10M
post_max_size = 10M
max_execution_time = 300
memory_limit = 256M
```

31
MEMORY.md Normal file
View File

@@ -0,0 +1,31 @@
# Ключевые моменты по проекту ERV (формa Багаж, ticket_dev, miniapp)
## Структура проектов
- **miniapp** — основная форма (авиа + отель), эталон рабочей логики СМС и модалок.
- **hotels** — форма только по риску «Неполучение услуг размещения (отель)», класс `form-hotels-only`.
- **ticket_dev** — копия ticket с проверкой полиса через N8N, ios-оформление, тестовый СМС.
- **luggage** — форма «Багаж» (задержка/утрата/повреждение), копия ticket_dev по структуре.
## Модалка СМС (подтверждение кода)
- **Чтобы кнопка «Подтвердить» кликалась:** кнопка должна быть **внутри** элемента с классом `sms-checking` (как в miniapp). Разметка: внутри `.form-item.sms-checking` — инпут кода, затем `<div class="sms-checking sms-action">` с кнопками. Иначе селектор `$('.sms-checking .js-accept-sms')` не находит кнопку.
- Открытие через **Fancybox**: `$.fancybox.open({ src: '#confirm_sms', type: 'inline' })`, закрытие `$.fancybox.close()`.
- Обработчик — прямой: `$('.sms-checking .js-accept-sms').on('click', ...)` и `var code = $('.sms-checking input[type="text"]').val();`.
- **Где правили:** luggage/index.php + luggage/js/common.js; ticket_dev/index.php (разметка по аналогии с miniapp).
## Куда уходит submit
- **miniapp, hotels** → `https://n8n.clientright.pro/webhook/oldform_combine`
- **ticket_dev, luggage, ticket** → URL берётся из **.env**: переменная **N8N_SEND_WEBHOOK**. В luggage/ticket_dev в submit.php подключается env_loader.php и `$webhook_url = trim(env('N8N_SEND_WEBHOOK', '...'));`. Пример в luggage/.env: `N8N_SEND_WEBHOOK=https://n8n.clientright.pro/webhook/oldform_combine`.
## Валидация и форма (luggage)
- Форма с пошаговыми шагами: **novalidate** на `<form>`, чтобы браузер не ругался «invalid form control is not focusable» для полей с minlength на скрытых шагах.
- Описание: минимум 20 символов, атрибут `data-minlen="20"`, проверка в JS при submit; при ошибке — показ сообщения и прокрутка к полю описания.
- Поле «Задержка выдачи багажа (часов)»: обязательно при выборе типа «Задержка», только цифры — класс `js-delay-hours-mask`, inputmask `9{1,4}`, без `notvalidate`.
## Список файлов (длинные имена)
- В .fileList длинное имя не должно наезжать на размер: у `.fileList li strong``flex: 1 1 auto`, `min-width: 0`, `overflow: hidden`, `text-overflow: ellipsis`, `white-space: nowrap`. Размер и крестик — `flex-shrink: 0`. Правили luggage, miniapp, ticket_dev, hotels в css/main.css.
## Тестовый СМС (без реальной отправки)
- В .env: `SMS_ENABLED=false`. В sms-verify.php при action=send возвращается `demo_code`, при action=verify проверка локально (Redis/файл). В common.js при успешной отправке показывается код в модалке (`data.demo_code`) и подстановка в поле.
## Окружение
- Ubuntu 22.04. Пользователь — Фёдор. Отвечать на русском.

245
PROJECT_STRUCTURE.md Normal file
View File

@@ -0,0 +1,245 @@
# Структура проекта - Детальное описание
## Обзор архитектуры
Проект состоит из нескольких версий формы, каждая из которых может иметь свои особенности. Основная рабочая версия находится в директории `/ticket/`.
## Основные компоненты
### 1. Frontend (Клиентская часть)
#### `index.php`
Главный файл формы, содержит:
- HTML-разметку многошаговой формы
- PHP-код для получения IP и региона пользователя
- Интеграцию с внешними API (ip-api.com)
- Структуру из 3 шагов:
- Шаг 1: SMS-подтверждение и проверка полиса
- Шаг 2: Информация о страховом случае
- Шаг 3: Персональные данные и документы
#### `js/common.js`
Основной JavaScript файл, реализует:
- Валидацию форм на каждом шаге
- Маски ввода (телефон, ИНН, даты, банковские реквизиты)
- Логику переключения между шагами
- Отправку SMS-кодов
- Загрузку и валидацию файлов
- Конвертацию HEIC в JPEG
- AJAX-запросы к серверу
- Интеграцию с DaData API для автодополнения
#### `css/main.css` и `css/custom.css`
Стили для формы, включая:
- Адаптивный дизайн
- Стили для шагов формы
- Стили для загрузки файлов
- Модальные окна
### 2. Backend (Серверная часть)
#### `database.php`
Проверка полисов в базе данных:
- Подключение к MySQL
- Проверка номера полиса
- Валидация тарифов
- Проверка сроков действия полиса
- Возврат JSON-ответа с результатами
#### `sms-test.php`
Тестирование отправки SMS:
- Генерация кода подтверждения
- Отправка SMS на указанный номер
- Логирование операций
#### `file-server.php` (если существует)
Обработка загрузки файлов на сервер
### 3. Интеграция с CRM
#### Основной endpoint
`https://form.clientright.ru/server_new.php`
Обрабатывает:
- Данные формы из POST-запроса
- Загруженные файлы
- Отправку данных в CRM через `capture.php`
- Логирование операций
#### Веб-сервисы CRM
`https://crm.clientright.ru/webservice.php`
Операции:
1. **getchallenge** - получение токена
2. **login** - авторизация и получение sessionId
3. **CreateContact** - создание/обновление клиента
4. **CreateAccount** - создание/обновление контрагента
5. **CreateProject** - создание проекта
6. **GetFilesList** - получение списка файлов клиента
### 4. База данных
#### Структура таблицы `lexrpiority`
Поля (предположительно):
- `inn` или `police_number` - номер полиса
- `birthday` - дата рождения
- `insured_from` - дата начала действия
- `insured_to` - дата окончания действия
- `tariff` - тариф полиса
#### Валидные тарифы
**Базовые тарифы:**
- STB0048, STB1099, STB1100, STB2099
- AVS21500, AVS22500
**Дополнительные тарифы:**
- SPV0001-SPV0005, STV0090
- SPV1001-SPV1005, SPV2001-SPV2005
- OPV1001-OPV1005, OPV2001-OPV2005
### 5. Внешние библиотеки
#### JavaScript библиотеки
- **jQuery 3.6.3** - основной фреймворк
- **Inputmask** - маски ввода
- **Datepicker** - выбор дат
- **intl-tel-input** - ввод телефонных номеров
- **Fancybox** - модальные окна
- **heic2any** - конвертация HEIC в JPEG
- **suggestions-jquery** - автодополнение адресов (DaData)
#### PHP библиотеки (Composer)
- **PHPMailer** - отправка email
- **FPDF/FPDI** - работа с PDF
- **PDF Merger** - объединение PDF
### 6. Поток данных
```
Пользователь заполняет форму
Валидация на клиенте (JavaScript)
Отправка данных на server_new.php
Обработка файлов и данных
Отправка в CRM через capture.php
Получение sessionId через веб-сервисы
Создание/обновление клиента (CreateContact)
Создание/обновление контрагента (CreateAccount)
Создание проекта (CreateProject)
Загрузка файлов в CRM
Отправка подтверждающего email
Возврат результата пользователю
```
## Версии проекта
### `/ticket/` - Основная рабочая версия
Используется в продакшене.
### `/ticket3/` - Альтернативная версия
Возможно, содержит изменения или эксперименты.
### `/ervws/` - Версия для веб-сервисов
Версия с акцентом на веб-сервисы.
### `/test/` - Тестовая версия
Для тестирования новых функций.
### `/public_html/` - Публичная версия
Публичная версия для клиентов.
## Конфигурация полей формы
### Поля клиента (data-ws_type="client")
- `firstname` - Имя
- `lastname` - Фамилия
- `secondname` - Отчество
- `mobile` - Телефон
- `email` - Email
- `birthday` - Дата рождения
- `mailingstreet` - Адрес регистрации
- `inn` - ИНН
- `code` - SMS-код
### Поля проекта (data-ws_type="project")
- `cf_1887` - Дата начала страхования (insured_from)
- `cf_1889` - Дата окончания страхования (insured_to)
- `cf_1899` - Код документа
- `cf_1804` - Серия и номер документа
- `cf_2502` - Согласие на обработку данных
### Поля контрагента (data-ws_type="contractor")
- `inn` - ИНН
- `ogrn` - ОГРН
- `accountname` - Наименование
- `address` - Адрес
- `email` - Email
- `phone` - Телефон
- `website` - Сайт
### Другие поля (data-ws_type="other")
- `cf_2446` - Флаг наличия в базе
- `cf_1885` - Номер полиса
- `cf_1726` - Тип события
- `cf_2566` - Дата страхового случая
- `cf_2568` - Номер рейса/поезда
- `description` - Описание ситуации
- И другие кастомные поля
## Безопасность
1. **Валидация файлов:**
- Проверка расширений
- Проверка MIME-типов
- Ограничение размера (5 МБ)
- Ограничение количества (10 файлов)
2. **Валидация данных:**
- Проверка обязательных полей
- Валидация email
- Валидация телефона
- Проверка форматов дат
3. **Защита от атак:**
- Экранирование данных
- Использование prepared statements (рекомендуется)
- Валидация на сервере
- CSRF-токены (если реализованы)
## Логирование
Логи сохраняются в:
- `logs/logfile.log` - общие логи
- `formlog/` - логи форм
- JSON-файлы с префиксом `resend_log_` - логи повторной отправки
## Тестирование
### Тестовый режим формы
Добавьте `?demodata=1` к URL для автоматического заполнения всех полей.
### Тестовые данные для API
- ИНН: `643922466250`
- СМС-код: `795372`
## Рекомендации по разработке
1. Всегда тестируйте изменения в `/test/` перед применением в продакшене
2. Используйте версионирование для отслеживания изменений
3. Логируйте важные операции
4. Валидируйте данные на сервере, не только на клиенте
5. Используйте prepared statements для SQL-запросов
6. Регулярно проверяйте логи на ошибки
7. Делайте резервные копии перед крупными изменениями

159
README.md Normal file
View File

@@ -0,0 +1,159 @@
# Проект ЕРВ - Форма обращений за выплатой страховки
## Описание проекта
Веб-приложение для обработки обращений за выплатой страхового возмещения по полисам ЕРВ (Евроинс). Проект представляет собой многошаговую форму с интеграцией в CRM-систему ClientRight.
## Технологический стек
- **Backend**: PHP 7.4+
- **Frontend**: HTML5, CSS3, JavaScript (jQuery)
- **База данных**: MySQL
- **Интеграции**:
- CRM ClientRight (веб-сервисы)
- DaData API (автодополнение адресов и организаций)
- SMS-сервис для подтверждения телефона
- IP-геолокация (ip-api.com)
## Структура проекта
```
erv.clientright.ru/
├── ticket/ # Основная рабочая версия формы
│ ├── index.php # Главная страница формы
│ ├── database.php # Проверка полисов в БД
│ ├── sms-test.php # Тестирование SMS
│ ├── css/ # Стили
│ ├── js/ # JavaScript файлы
│ └── libs/ # Внешние библиотеки
├── ticket3/ # Альтернативная версия
├── ervws/ # Версия для веб-сервисов
├── test/ # Тестовая версия
├── public_html/ # Публичная версия
├── vendor/ # PHP зависимости (Composer)
├── libs/ # Общие библиотеки
└── Документация API.txt # Документация по API CRM
```
## Основные функции
1. **Многошаговая форма** (3 шага):
- Шаг 1: Подтверждение телефона через SMS
- Шаг 2: Информация о страховом случае
- Шаг 3: Персональные данные и документы
2. **Проверка полиса**:
- Валидация номера полиса в базе данных
- Проверка тарифов и сроков действия
3. **Загрузка файлов**:
- Поддержка форматов: PDF, JPG, PNG, GIF, JPEG, HEIC
- Конвертация HEIC в JPEG
- Максимальный размер файла: 5 МБ
- Максимум 10 файлов на поле
4. **Интеграция с CRM**:
- Создание/обновление клиента (CreateContact)
- Создание/обновление контрагента (CreateAccount)
- Создание проекта (CreateProject)
- Загрузка файлов в CRM
## Конфигурация
### База данных
- Хост: `localhost`
- База: `ci20465_erv`
- Таблица для проверки полисов: `lexrpiority`
### CRM API
- Endpoint: `https://crm.clientright.ru/webservice.php`
- Username: `api`
- Access Key: `4r9ANex8PT2IuRV`
### Внешние сервисы
- **DaData API Token**: `f5d6928d7490cd44124ccae11a08c7fa5625d48c`
- **Форма обработки**: `https://form.clientright.ru/server_new.php`
- **Загрузка файлов**: `https://form.clientright.ru/fileupload_v2.php`
## Установка и запуск
1. Убедитесь, что установлены:
- PHP 7.4 или выше
- MySQL
- Composer (для зависимостей)
2. Установите зависимости:
```bash
composer install
```
3. Настройте подключение к базе данных в `database.php`
4. Настройте права доступа для загрузки файлов
## Рабочие версии
- **Основная рабочая версия**: `/ticket/`
- **Тестовая версия**: `/test/`
- **Публичная версия**: `/public_html/`
## API CRM - Основные операции
### 1. Авторизация
```php
operation = getchallenge
operation = login
```
### 2. Работа с клиентами
```php
operation = CreateContact
```
### 3. Работа с контрагентами
```php
operation = CreateAccount
```
### 4. Работа с проектами
```php
operation = CreateProject
```
### 5. Получение списка файлов
```php
operation = GetFilesList
```
Подробная документация в файле `Документация API.txt`
## Особенности реализации
- **Валидация форм**: Проверка обязательных полей на каждом шаге
- **Маски ввода**: Телефон, ИНН, даты, банковские реквизиты
- **Адаптивность**: Поддержка мобильных устройств
- **Логирование**: Логи операций в `logs/logfile.log`
- **Обработка ошибок**: Try-catch блоки и валидация ответов
## Безопасность
- Проверка типов файлов при загрузке
- Валидация размеров файлов
- Защита от XSS через экранирование данных
- Использование сессий для изоляции данных пользователей
## Разработка
Для разработки используйте версию в директории `/ticket/` или `/test/`.
### Тестовый режим
Добавьте параметр `?demodata=1` к URL для автоматического заполнения формы тестовыми данными.
## Контакты
- Разработчик: [TG: @wwdev](https://t.me/wwdev)
## Лицензия
Проект разработан для ЕРВ (Евроинс).

960
common.js Normal file
View File

@@ -0,0 +1,960 @@
$(function() {
$(document).ready(function(){
$(".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)
});
$(".js-bik-mask").inputmask("999999999");
$(".js-rs-mask").inputmask("99999999999999999999");
$(".js-ks-mask").inputmask("99999999999999999999");
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) {
$("input[data-enableby=birthday]").removeClass('disabled');
$("input[data-disabledby=birthday]").removeClass('disabled');
$("input[data-enableby=birthday]").removeAttr("disabled");
} else {
$("input[data-enableby=birthday]").addClass('disabled');
$("input[data-disabledby=birthday]").addClass('disabled');
$("input[data-enableby=birthday]").attr("disabled","disabled");
}
}
});
}
if($('input[name="insurence_date"]').length) {
var contract_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'
}
});
}
// Инициализация 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'
}
});
}
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: 'aggressive',
utilsScript: "libs/intl-tel-input-master/build/js/utils.js",
});
});
$('.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();
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('Укажите номер рейса прибытия');
$('.connection-fields, .connection-date-fields').show();
} else if (selectedValue === 'cancel_flight') {
$('#transport_number_label').text('Номер рейса/поезда/парома');
$('.cancel-flight-docs').show();
} else {
$('#transport_number_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;
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 {
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);
});
if((step_index==3) && $('.form-step[data-step='+step_index+']').find('input[type="checkbox"]:checked').length<1) {
$('.form__warning').show();
$('.form__warning').text('Выберите хотя 1 страховой случай');
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
function send_sms(){
var sended_code = Math.floor(Math.random()*(999999-100000+1)+100000);
var smsFormData = new FormData();
smsFormData.append('smscode',sended_code);
smsFormData.append('phonenumber',$('input[name="phonenumber"]').val());
// $.ajax({
// url: 'sms-test.php',
// method: 'post',
// cache: false,
// contentType: false,
// processData: false,
// data: smsFormData,
// dataType : 'json',
// success: function(data) {
// console.log(data);
// return false;
// },
// error: function (jqXHR, exception) {
// return false;
// }
// });
alert(sended_code);
$('.js-code-warning').text('Код отправлен на ваш номер телефона');
$.fancybox.open({
src: '#confirm_sms',
type: 'inline'
});
$('.fancybox-close-small').click(function(e) {
$('button[type="submit"]').attr("disabled", false);
$('button[type="submit"]').attr("disabled", false);
$('button[type="submit"]').text("Подать обращение");
});
return sended_code;
}
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);
}
let sended_code;
$('.js-send-sms').on('click', function(e) {
// $('.js-send-sms').hide();
sended_code=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-checking .js-accept-sms').on('click', function(e) {
e.preventDefault();
if($('.sms-checking input[type="text"]').val() == sended_code) {
$('.sms-success').removeClass('d-none');
$('.form-step[data-step=1]').removeClass('disabled');
$('.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').text("Вы успешно подтвердили номер");
} else {
$('.modal .form-item__warning').text("Неверный код");
$('.sms-success').addClass('d-none');
}
});
$('.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();
jQuery.each(jQuery('input[type=file].js-attach').not('.disabled'), function(i, file) {
if(!$(this).hasClass('disabled')) {
let field_name=jQuery(this).data('crmname');
let docname=jQuery(this).data('docname');
let upload_url=jQuery(this).attr('data-uploadurl');
let upload_url_real=jQuery(this).attr('data-uploadurl_real');
jQuery.each(jQuery(this)[0].files, function(i, file) {
formData.append(field_name+'-'+i, file);
});
if(upload_url) { // Если файл загрузился и получили ответ от upload тогда добавляем в форму
formData.append('upload_urls[]',upload_url);
formData.append('upload_urls_real[]',upload_url_real);
formData.append('files_names[]',field_name);
formData.append('docs_names[]',docname);
if(jQuery(this).data('doctype')=="ticket") {
formData.append('docs_ticket_files_ids[]',i);
} else {
formData.append('docs_ticket_files_ids[]','simple');
}
}
}
});
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: 'https://form.clientright.ru/server_webservice2.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 f5d6928d7490cd44124ccae11a08c7fa5625d48c",
"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.length; i++) {
$('<div class="autocomplete__item" data-address="'+response.suggestions[i].data.address.value+'" data-inn="'+response.suggestions[i].data.inn+'">'+response.suggestions[i].value+'</div>').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();
});
// $(document).ready(function(){
// setTimeout(function() {
// var $form = $(".form form");
// createSuggestions($form);
// }, 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
}
$.ajax({
url: 'database.php',
method: 'post',
data: dbdata,
// dataType : 'json',
success: function(data) {
console.log(data);
let res=JSON.parse(data);
if(res.success=="true"){
$('.db-success').removeClass("d-none");
$('.js-result').html(res.message);
$('.js-result').removeClass("danger");
$('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');
$('.form-item--polis').find('input[type=file]').addClass("notvalidate");
$('.form-item--polis').find('input[type=file]').addClass("disabled");
$('.form-item--polis').addClass('d-none');
} else {
$('.db-success').removeClass("d-none");
$('.js-result').html(res.message);
$('.js-result').addClass("danger");
$('.js-indatabase').val('0');
$('.form-item--polis').find('input[type=file]').removeClass("notvalidate");
$('.form-item--polis').find('input[type=file]').removeClass("disabled");
$('.form-item--polis').removeClass('d-none');
}
return false;
},
error: function (jqXHR, exception) {
$('.js-result').html(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) {
$("input[data-enableby=birthday]").removeClass('disabled');
$("input[data-disabledby=birthday]").removeClass('disabled');
$("input[data-enableby=birthday]").removeAttr("disabled");
} else {
$("input[data-enableby=birthday]").addClass('disabled');
$("input[data-disabledby=birthday]").addClass('disabled');
$("input[data-enableby=birthday]").attr("disabled","disabled");
}
});
$('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<filesQty; i++) {
var file = elem[0].files[i],
ext = "не определилось",
parts = file.name.split('.');
if (parts.length > 1) ext = parts.pop();
if(!formats.includes(ext)) {
elem.closest('.form-item').find('.form-item__warning').append('<div> Файл '+file.name+' не подходит по формату. Доступные форматы: .pdf, .jpg, .png, .gif </div>');
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('<div>Размер файла '+file.name+' превышает 5 Мб. </div>');
} else {
elem.removeClass('error');
file_status.push(true);
}
}
}
if(file_status.every(val => val === true))
{
upload_file(elem);
$('.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');
let docname=thisfile.data('docname');
console.log(docname);
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("<p class='info-upload'></p>");
// 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("<p class='suсcess-upload'>Файл успешно загружен на сервер.</p>");
thisfile.attr('data-uploadurl',res.empty_file);
thisfile.attr('data-uploadurl_real',res.real_file);
// thisfile.closest('.form-item').append("<p class='suсcess-upload'>"+res.message+"</p>");
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 filesToUpload = [];
let filethis=$(this);
filethis.change(function (evt) {
var output = [];
let elem= $(this);
elem.closest('.form-item').find('.form-item__warning').text('');
let file_status=[];
var formats = ['pdf','jpg','png','gif','jpeg'];
console.log(evt.target.files);
if(evt.target.files.length>10) {
elem.closest('.form-item').find('.form-item__warning').text("Разрешено не более 10 файлов");
return;
}
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('<div> Файл '+file.name+' не подходит по формату. Доступные форматы: .pdf, .jpg, .png, .gif </div>');
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('<div>Размер файла '+file.name+' превышает 5 Мб. </div>');
} else {
elem.removeClass('error');
file_status.push(true);
var removeLink = "<a class=\"removefile\" href=\"#\" data-fileid=\"" + fileId + "\"></a>";
output.push("<li><strong>", file.name, "</strong> <span class='size'> ", formatBytes(file.size) , " </span> ", removeLink, "</li> ");
filesToUpload.push({
id: fileId,
file: file
});
}
}
//evt.target.value = null;
// elem.closest('.form-item').find('.upload-action').removeClass('d-none');
};
elem.closest('.form-item').find(".fileList").append(output.join(""));
let container = new DataTransfer();
for (var i = 0, len = filesToUpload.length; i < len; i++) {
container.items.add(filesToUpload[i].file);
}
elem[0].files = container.files;
if(file_status.every(val => val === true))
{
upload_file(elem);
$('.form__action').find('.js-btn-next').removeClass('disabled');
} else {
$('.form__action').find('.js-btn-next').addClass('disabled');
}
});
$(this).closest('.form-item').on("click", ".removefile", function (e) {
let elem= $(this);
e.preventDefault();
var fileId = elem.parent().children("a").data("fileid");
for (var i = 0; i < filesToUpload.length; ++i) {
if (filesToUpload[i].id === fileId) filesToUpload.splice(i, 1);
}
elem.parent().remove();
if(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 = filesToUpload.length; i < len; i++) {
container.items.add(filesToUpload[i].file);
}
filethis[0].files = container.files;
updateSize(filethis);
}
});
});
// End Загрузка файлов
});

67
css/custom.css Normal file
View File

@@ -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;
}

604
css/main.css Normal file
View File

@@ -0,0 +1,604 @@
@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;
}
.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;
}
.suсcess-upload{
margin-bottom: 2px;
margin-top: 2px;
}
.form-text{
margin-bottom: 30px;
margin-top: 30px;
text-align: center;
display: block;
}

BIN
data/other.pdf Normal file

Binary file not shown.

BIN
data/passport.pdf Normal file

Binary file not shown.

BIN
data/polis.pdf Normal file

Binary file not shown.

BIN
data/ticket.pdf Normal file

Binary file not shown.

107
database.php Normal file
View File

@@ -0,0 +1,107 @@
<?php
// База данных: ci20465_erv
// Таблица: lexrpiority
// Форма: Средства размещения (НУ - неудовлетворительные условия)
if(isset($_POST['action']) && !empty($_POST['action'])) {
$action = $_POST['action'];
switch($action) {
case 'user_verify' : user_verify(); break;
}
}
function user_verify() {
// Тарифы, которые включают покрытие НУ (Средства размещения)
// Только в колонке tariff_code_basic
$valid_tariffs_basic = [
'STB0027', 'STB0028', 'STB0034', 'STB0037', 'STB0038', 'STB0045',
'SPP1023', 'SPP1024', 'SPP1025',
'STA1011', 'STA1012',
'STB1088', 'STB1089', 'STB1091', 'STB1092', 'STB1100',
'STB2088', 'STB2089', 'STB2091', 'STB2092',
'OPT1025', 'OPT1026', 'OPT1029', 'OPT1030', 'OPT1032', 'OPT1033', 'OPT1036', 'OPT1037',
'OPT2025', 'OPT2026', 'OPT2032', 'OPT2033',
'AQS00500', 'AQS60500', 'AQS01500', 'AQS61500', 'AQS02500', 'AQS62500'
];
$link = mysqli_connect("localhost", "ci20465_erv", "c7vOXbmG", "ci20465_erv");
if (!$link) {
echo json_encode([
"success" => "false",
"message" => "Ошибка подключения к базе данных",
"result" => ""
]);
return;
}
mysqli_set_charset($link, "utf8");
$inn = isset($_POST['inn']) ? trim($_POST['inn']) : '';
if (empty($inn)) {
echo json_encode([
"success" => "false",
"message" => "Номер полиса не указан",
"result" => ""
]);
return;
}
// Экранируем для безопасности
$inn_escaped = mysqli_real_escape_string($link, $inn);
// Ищем полис по номеру voucher
$sql = "SELECT * FROM lexrpiority WHERE voucher = '$inn_escaped' LIMIT 1";
$result = mysqli_query($link, $sql);
if (!$result) {
echo json_encode([
"success" => "false",
"message" => "Ошибка запроса к базе данных",
"result" => ""
]);
mysqli_close($link);
return;
}
$row = mysqli_fetch_assoc($result);
if (!$row) {
// Полис не найден в базе
echo json_encode([
"success" => "false",
"message" => "Полис не найден",
"result" => ""
]);
mysqli_close($link);
return;
}
// Полис найден, проверяем тариф
$tariff_basic = isset($row['tariff_code_basic']) ? trim($row['tariff_code_basic']) : '';
$has_valid_tariff = in_array($tariff_basic, $valid_tariffs_basic);
if ($has_valid_tariff) {
// Полис найден и тариф подходит
echo json_encode([
"success" => "true",
"message" => "Полис найден",
"result" => $row
]);
} else {
// Полис найден, но тариф не включает покрытие НУ
echo json_encode([
"success" => "false",
"message" => "Ваш полис не включает покрытие неудовлетворительных условий размещения",
"result" => ""
]);
}
mysqli_close($link);
}
?>

59
database_old.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
// Имя ДБ: turistpr_erv
// Пользователь: turistpr_erv
// Пароль: c7vOXbmG
// Адрес хоста: 141.8.194.131
// таблица lexrpiority
if(isset($_POST['action']) && !empty($_POST['action'])) {
$action = $_POST['action'];
switch($action) {
case 'user_verify' : user_verify(); break;
}
}
function user_verify() {
$finded_row['success']="false";
//$link = mysqli_connect("141.8.194.131", "turistpr_erv", "c7vOXbmG");
$link = mysqli_connect("localhost", "ci20465_erv", "c7vOXbmG");
if ($link == false){
}
else {
}
$birthday = $_POST['birthday'];
$inn = $_POST['inn'];
//$sql = "SELECT * FROM turistpr_erv.lexrpiority";
$sql = "SELECT * FROM ci20465_erv.lexrpiority";
$result = mysqli_query($link, $sql);
$finded_row=array("success"=>"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);
}
?>

129
database_php_returns.md Normal file
View File

@@ -0,0 +1,129 @@
# Что возвращает database.php и что используется
## SQL запрос
```sql
SELECT * FROM lexrpiority WHERE voucher = '...' LIMIT 1
```
**Возвращает ВСЕ поля строки** из таблицы `lexrpiority`.
## Что используется в PHP скрипте
После получения строки из БД (`$row = mysqli_fetch_assoc($result)`), скрипт использует:
1. **`tariff_code_basic`** - для проверки валидности тарифа
2. **`tariff_code_other`** - для проверки валидности тарифа
## Что возвращается в JSON ответе
### При успехе (полис найден и валиден):
```json
{
"success": "true",
"message": "Полис найден",
"result": {
// ВСЯ строка из БД (все поля таблицы lexrpiority)
"id": "35927",
"row_no": "893",
"agent_code": "E50208",
"voucher": "E50208-306083026",
"insured_name": "SHAROV IURII",
"insured_birth": "10.05.1974",
"insured_gender": "U",
"status": "NV",
"insured_counter": "1",
"issued_on": "17.11.2025",
"insured_from": "19.11.2025", // ⭐ ИСПОЛЬЗУЕТСЯ на фронтенде
"insured_to": "24.11.2025", // ⭐ ИСПОЛЬЗУЕТСЯ на фронтенде
"insured_days": "6",
"destination": "GEO",
"productvariant_code": "10223",
"tariff_code_basic": "STB2003",
"tariff_code_sport": "",
"tariff_code_cancel": "",
"tariff_code_other": "SPV2002", // ⭐ ИСПОЛЬЗУЕТСЯ для проверки
"premium_cur_basic": "12,48",
"premium_cur_sport": "",
"premium_cur_cancel": "",
"premium_cur_other": "13",
"premium_cur": "25,48",
"premium_loc": "2067,13",
// ... и другие поля
}
}
```
### При ошибке (полис не найден или не валиден):
```json
{
"success": "false",
"message": "Полис не найден" или "Ваш полис не включает покрытие задержки рейса",
"result": ""
}
```
## Что используется на фронтенде (JavaScript)
В `ticket_dev/js/common.js` строки 1227-1228:
```javascript
// Автозаполнение дат из результата проверки полиса
$('input[name=insured_from]').val(res.result.insured_from.replace(/\./g, '-'));
$('input[name=insured_to]').val(res.result.insured_to.replace(/\./g, '-'));
```
**Используются только 2 поля:**
- `result.insured_from` - дата начала действия полиса
- `result.insured_to` - дата окончания действия полиса
## Вывод для вебхука n8n
Для вебхука нужно возвращать:
### Минимально необходимое:
- `found: 1/0` - для IF Node в n8n
- `success: true/false` - для совместимости с PHP
- `message` - текстовое сообщение
### Желательно (для автозаполнения на фронтенде):
- `result.insured_from` - дата начала полиса
- `result.insured_to` - дата окончания полиса
### Опционально (для дополнительной информации):
- Остальные поля из таблицы `lexrpiority` (если нужны для логирования/аналитики)
## Рекомендуемый формат ответа вебхука
```json
{
"success": true,
"found": 1,
"message": "Полис найден",
"result": {
"insured_from": "19.11.2025",
"insured_to": "24.11.2025",
"voucher": "E50208-306083026",
"insured_name": "SHAROV IURII",
"tariff_code_basic": "STB2003",
"tariff_code_other": "SPV2002"
// Можно добавить другие поля, если нужны
}
}
```
Или минимальный вариант (если не нужны данные для автозаполнения):
```json
{
"success": true,
"found": 1,
"message": "Полис найден",
"result": {
"insured_from": "19.11.2025",
"insured_to": "24.11.2025"
}
}
```

4
debug.txt Normal file

File diff suppressed because one or more lines are too long

114
debug_client_data.php Normal file
View File

@@ -0,0 +1,114 @@
<?php
// Отладочный скрипт для проверки данных клиента
// Файл: debug_client_data.php
echo "<h2>Отладка данных клиента</h2>";
echo "<p><strong>Время:</strong> " . date('Y-m-d H:i:s') . "</p>";
echo "<hr>";
// Формируем данные точно как в рабочем скрипте
$appends = [
'{"ws_type":"client","ws_name":"lastname","field_val":"Козлова"}',
'{"ws_type":"client","ws_name":"firstname","field_val":"Александра"}',
'{"ws_type":"client","ws_name":"mobile","field_val":"921 862-69-44"}',
'{"ws_type":"client","ws_name":"email","field_val":"sashyliakoz@gmail.com"}',
'{"ws_type":"client","ws_name":"birthday","field_val":"04-09-1996"}',
'{"ws_type":"client","ws_name":"mailingstreet","field_val":"Индекс 188300, Ленинградская область, г. Гатчина, ул. Изотова, д.12, к.2, кв. 38"}',
'{"ws_type":"client","ws_name":"inn","field_val":"470519373754"}',
'{"ws_type":"ticket","ws_name":"ticket1","field_val":"Отсутствие вида, заявленного и оплаченного в бронировании"}',
'{"ws_type":"project","ws_name":"cf_1187","field_val":"ЕРВ Средства размещения"}',
'{"ws_type":"project","ws_name":"cf_1590","field_val":"195.175.85.146"}',
'{"ws_type":"project","ws_name":"cf_2296","field_val":"1"}',
'{"ws_type":"project","ws_name":"cf_1592","field_val":"Мугла"}',
'{"ws_type":"project","ws_name":"cf_1582","field_val":"15-09-2025"}',
'{"ws_type":"project","ws_name":"cf_1584","field_val":"25-09-2025"}',
'{"ws_type":"project","ws_name":"cf_1740","field_val":"E1000-302542604"}',
'{"ws_type":"project","ws_name":"cf_1157","field_val":"Алексеевна"}',
'{"ws_type":"project","ws_name":"cf_1869","field_val":"Козлова Александра Алексеевна"}',
'{"ws_type":"project","ws_name":"cf_1265","field_val":"СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК"}',
'{"ws_type":"project","ws_name":"cf_1267","field_val":"044030653"}',
'{"ws_type":"project","ws_name":"cf_1271","field_val":"30101810500000000653"}',
'{"ws_type":"project","ws_name":"cf_1269","field_val":"40817810755868418791"}',
'{"ws_type":"project","ws_name":"cf_1273","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1163","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1161","field_val":"Prime beach hotel"}',
'{"ws_type":"project","ws_name":"cf_1165","field_val":"Cumhuriyet Bulv. No 35 Siteler Marmaris / Muğla / Türkiye"}',
'{"ws_type":"project","ws_name":"cf_1167","field_val":"info@primebeachhotel.com"}',
'{"ws_type":"project","ws_name":"cf_1560","field_val":"0252 417 52 00"}',
'{"ws_type":"project","ws_name":"cf_1558","field_val":"https://primebeachhotel.com"}',
'{"ws_type":"project","ws_name":"cf_1173","field_val":"16-09-2025"}',
'{"ws_type":"project","ws_name":"cf_1726","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1728","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1730","field_val":"on"}',
'{"ws_type":"project","ws_name":"cf_1732","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1734","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1736","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1257","field_val":"470519373754"}',
'{"ws_type":"project","ws_name":"cf_1800","field_val":"21"}',
'{"ws_type":"project","ws_name":"cf_1802","field_val":"4116"}',
'{"ws_type":"project","ws_name":"cf_1804","field_val":"786084"}',
'{"ws_type":"project","ws_name":"cf_1798","field_val":"643"}',
'{"ws_type":"project","ws_name":"cf_2000","field_val":"ТУРЦИЯ"}',
'{"ws_type":"project","ws_name":"description","field_val":"Заселили в отель 16.09.2025. В ваучере (путевке)указан номер sea view, но при заезде разместили в номере без вида на море. \\nПункт 40.2.1 полиса "}',
'{"ws_type":"project","ws_name":"cf_1738","field_val":"on"}',
'{"ws_type":"project","ws_name":"cf_1706","field_val":"105540"}'
];
echo "<h3>1. Исходные данные appends:</h3>";
echo "<pre>";
foreach ($appends as $key => $itemjson) {
echo "[" . $key . "] " . $itemjson . "\n";
}
echo "</pre>";
echo "<h3>2. Симуляция обработки в server_webservice2.php:</h3>";
// Симулируем код из server_webservice2.php
$client_array = array(
'operation' => 'CreateContact',
'sessionName' => 'TEST_SESSION_ID',
);
echo "<p><strong>Начальный client_array:</strong></p>";
echo "<pre>" . print_r($client_array, true) . "</pre>";
echo "<p><strong>Обработка appends:</strong></p>";
foreach ($appends as $key => $itemjson) {
$item = json_decode($itemjson);
echo "<p>[" . $key . "] ws_type: '" . $item->ws_type . "', ws_name: '" . $item->ws_name . "', field_val: '" . $item->field_val . "'</p>";
if ($item->ws_type == "client") {
$client_array[$item->ws_name] = $item->field_val;
echo "<p style='color: green;'>✅ Добавлено в client_array: " . $item->ws_name . " = " . $item->field_val . "</p>";
if ($item->ws_name == "code") $sms = $item->field_val;
} else {
echo "<p style='color: gray;'>⏭️ Пропущено (ws_type != 'client')</p>";
}
}
echo "<h3>3. Итоговый client_array для отправки в CRM:</h3>";
echo "<pre>" . print_r($client_array, true) . "</pre>";
echo "<h3>4. Проверка обязательных полей:</h3>";
$required_fields = ['firstname', 'lastname', 'birthday', 'mobile', 'inn'];
$missing_fields = [];
foreach ($required_fields as $field) {
if (isset($client_array[$field]) && !empty($client_array[$field])) {
echo "<p style='color: green;'>✅ " . $field . ": '" . $client_array[$field] . "'</p>";
} else {
echo "<p style='color: red;'>❌ " . $field . ": ОТСУТСТВУЕТ</p>";
$missing_fields[] = $field;
}
}
if (empty($missing_fields)) {
echo "<p style='color: green; font-weight: bold;'>✅ Все обязательные поля присутствуют!</p>";
} else {
echo "<p style='color: red; font-weight: bold;'>❌ Отсутствуют поля: " . implode(', ', $missing_fields) . "</p>";
}
echo "<hr>";
echo "<h3>5. JSON для отправки:</h3>";
echo "<pre>" . json_encode($client_array, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
?>

193
debug_crm_responses.php Normal file
View File

@@ -0,0 +1,193 @@
<?php
// Скрипт для отладки ответов от CRM
// Файл: debug_crm_responses.php
echo "<h2>🔍 ОТЛАДКА ОТВЕТОВ ОТ CRM</h2>";
echo "<p><strong>Время:</strong> " . date('Y-m-d H:i:s') . "</p>";
echo "<hr>";
// Тестируем создание клиента напрямую
echo "<h3>1. Тест создания клиента напрямую в CRM:</h3>";
$client_data = [
'operation' => 'CreateContact',
'sessionName' => 'TEST_SESSION', // Будет заменен реальным
'firstname' => 'Александра',
'secondname' => 'Алексеевна',
'lastname' => 'Козлова',
'mobile' => '921 862-69-44',
'email' => 'sashyliakoz@gmail.com',
'birthday' => '1996-09-04',
'mailingstreet' => 'Индекс 188300, Ленинградская область, г. Гатчина, ул. Изотова, д.12, к.2, кв. 38',
'inn' => '470519373754',
'code' => '1'
];
echo "<p><strong>Данные клиента:</strong></p>";
echo "<pre>" . json_encode($client_data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
// Сначала получаем sessionName
echo "<h3>2. Получение sessionName:</h3>";
$challenge_data = [
'operation' => 'getchallenge',
'username' => 'api'
];
echo "<p><strong>Данные getchallenge:</strong></p>";
echo "<pre>" . json_encode($challenge_data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
$url = 'https://crm.clientright.ru/webservice.php?operation=getchallenge&username=api';
echo "<p><strong>URL getchallenge:</strong> " . $url . "</p>";
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_TIMEOUT => 30
]);
$challenge_response = curl_exec($curl);
curl_close($curl);
echo "<p><strong>Ответ getchallenge:</strong></p>";
echo "<pre>" . htmlspecialchars($challenge_response) . "</pre>";
$challenge_result = json_decode($challenge_response, true);
if ($challenge_result && isset($challenge_result['result']['token'])) {
$token = $challenge_result['result']['token'];
echo "<p style='color: green;'>✅ Получен token: " . substr($token, 0, 20) . "...</p>";
// Логинимся
echo "<h3>3. Авторизация:</h3>";
$generatedKey = md5($token . '4r9ANex8PT2IuRV');
$login_data = [
'operation' => 'login',
'username' => 'api',
'accessKey' => $generatedKey
];
echo "<p><strong>Данные login:</strong></p>";
echo "<pre>" . json_encode($login_data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
echo "<p><strong>Сгенерированный ключ:</strong> " . $generatedKey . "</p>";
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://crm.clientright.ru/webservice.php',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $login_data,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_TIMEOUT => 30
]);
$login_response = curl_exec($curl);
curl_close($curl);
echo "<p><strong>Ответ login:</strong></p>";
echo "<pre>" . htmlspecialchars($login_response) . "</pre>";
$login_result = json_decode($login_response, true);
if ($login_result && isset($login_result['result']['sessionName'])) {
$sessionName = $login_result['result']['sessionName'];
echo "<p style='color: green;'>✅ Получен sessionName: " . $sessionName . "</p>";
// Теперь тестируем создание клиента
echo "<h3>4. Тест создания клиента:</h3>";
$client_data['sessionName'] = $sessionName;
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://crm.clientright.ru/webservice.php',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $client_data,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_TIMEOUT => 30
]);
$client_response = curl_exec($curl);
curl_close($curl);
echo "<p><strong>Ответ CreateContact:</strong></p>";
echo "<pre>" . htmlspecialchars($client_response) . "</pre>";
$client_result = json_decode($client_response, true);
if ($client_result) {
if (isset($client_result['result'])) {
echo "<p style='color: green;'>✅ Клиент создан с ID: " . $client_result['result'] . "</p>";
} else {
echo "<p style='color: red;'>❌ Ошибка создания клиента:</p>";
echo "<pre>" . json_encode($client_result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
}
}
// Тестируем создание контрагента
echo "<h3>5. Тест создания контрагента:</h3>";
$contractor_data = [
'operation' => 'CreateAccount',
'sessionName' => $sessionName,
'inn' => '7714312079',
'ogrn' => '1037714037426',
'accountname' => 'Филиал ООО РСО ЕВРОИНС Туристическое',
'address' => '119049 Москва, 4-й Добрынинский пер., д.8, помещ. С 14-I, ком. 21-26',
'email' => 'info@erv.ru',
'phone' => '84956265800',
'website' => 'https://www.erv.ru/'
];
echo "<p><strong>Данные контрагента:</strong></p>";
echo "<pre>" . json_encode($contractor_data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://crm.clientright.ru/webservice.php',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $contractor_data,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_TIMEOUT => 30
]);
$contractor_response = curl_exec($curl);
curl_close($curl);
echo "<p><strong>Ответ CreateAccount:</strong></p>";
echo "<pre>" . htmlspecialchars($contractor_response) . "</pre>";
$contractor_result = json_decode($contractor_response, true);
if ($contractor_result) {
if (isset($contractor_result['result'])) {
echo "<p style='color: green;'>✅ Контрагент создан с ID: " . $contractor_result['result'] . "</p>";
} else {
echo "<p style='color: red;'>❌ Ошибка создания контрагента:</p>";
echo "<pre>" . json_encode($contractor_result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
}
}
} else {
echo "<p style='color: red;'>❌ Не удалось получить sessionName</p>";
}
} else {
echo "<p style='color: red;'>❌ Не удалось получить token</p>";
}
echo "<hr>";
echo "<h3>6. Сравнение с успешным логом:</h3>";
echo "<p><strong>Успешный лог (2025-09-10):</strong></p>";
echo "<pre>";
echo '{"operation":"CreateContact","sessionName":"61c825a768c1dd749ca62","mobile":"7 (953) 167-38-19","lastname":"Кулагин ","firstname":"Андрей ","secondname":"Викторович ","mailingstreet":"Ленинградская обл...","birthday":"11-05-1967","birthplace":"г Ростов-на-Дону ","inn":"780700202965","requisites":"Реквизиты...","email":"kulaginandrey110567@gmail.com","code":"438138"}';
echo "</pre>";
echo "<p><strong>Наши данные:</strong></p>";
echo "<pre>" . json_encode($client_data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
echo "<hr>";
echo "<p><strong>Отладка завершена!</strong></p>";
?>

110
debug_exact_data.php Normal file
View File

@@ -0,0 +1,110 @@
<?php
// Отладочный скрипт для точного понимания данных
// Файл: debug_exact_data.php
echo "<h2>Точная отладка данных для CRM</h2>";
echo "<p><strong>Время:</strong> " . date('Y-m-d H:i:s') . "</p>";
echo "<hr>";
// Симулируем точно как в server_webservice2.php
$appends = [
'{"ws_type":"client","ws_name":"firstname","field_val":"Александра"}',
'{"ws_type":"client","ws_name":"cf_1157","field_val":"Алексеевна"}',
'{"ws_type":"client","ws_name":"lastname","field_val":"Козлова"}',
'{"ws_type":"client","ws_name":"mobile","field_val":"921 862-69-44"}',
'{"ws_type":"client","ws_name":"email","field_val":"sashyliakoz@gmail.com"}',
'{"ws_type":"client","ws_name":"phone","field_val":""}',
'{"ws_type":"client","ws_name":"birthday","field_val":"04-09-1996"}',
'{"ws_type":"client","ws_name":"cf_1263","field_val":"г Гатчина"}',
'{"ws_type":"client","ws_name":"mailingstreet","field_val":"Индекс 188300, Ленинградская область, г. Гатчина, ул. Изотова, д.12, к.2, кв. 38"}',
'{"ws_type":"client","ws_name":"cf_1257","field_val":"470519373754"}',
'{"ws_type":"client","ws_name":"cf_1580","field_val":"105540"}',
'{"ws_type":"client","ws_name":"assigned_user_id","field_val":"19x5"}'
];
echo "<h3>1. Исходные appends:</h3>";
foreach ($appends as $key => $itemjson) {
echo "<p>[" . $key . "] " . $itemjson . "</p>";
}
echo "<h3>2. Симуляция кода из server_webservice2.php:</h3>";
// ТОЧНО как в server_webservice2.php строки 125-136
$client_array = array(
'operation' => 'CreateContact',
'sessionName' => 'TEST_SESSION_ID',
);
echo "<p><strong>Начальный client_array:</strong></p>";
echo "<pre>" . print_r($client_array, true) . "</pre>";
echo "<p><strong>Обработка foreach:</strong></p>";
foreach ($appends as $key => $itemjson) {
$item = json_decode($itemjson);
echo "<p>[" . $key . "] ws_type: '" . $item->ws_type . "', ws_name: '" . $item->ws_name . "', field_val: '" . $item->field_val . "'</p>";
if ($item->ws_type == "client") {
$client_array[$item->ws_name] = $item->field_val;
echo "<p style='color: green;'>✅ Добавлено: " . $item->ws_name . " = '" . $item->field_val . "'</p>";
if ($item->ws_name == "code") $sms = $item->field_val;
} else {
echo "<p style='color: gray;'>⏭️ Пропущено (ws_type != 'client')</p>";
}
}
echo "<h3>3. Итоговый client_array для отправки в CRM:</h3>";
echo "<pre>" . print_r($client_array, true) . "</pre>";
echo "<h3>4. JSON для отправки в CRM:</h3>";
echo "<pre>" . json_encode($client_array, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
echo "<h3>5. Проверка обязательных полей:</h3>";
$required_fields = ['firstname', 'lastname', 'birthday', 'mobile', 'inn'];
$missing_fields = [];
foreach ($required_fields as $field) {
if (isset($client_array[$field]) && !empty($client_array[$field])) {
echo "<p style='color: green;'>✅ " . $field . ": '" . $client_array[$field] . "'</p>";
} else {
echo "<p style='color: red;'>❌ " . $field . ": ОТСУТСТВУЕТ</p>";
$missing_fields[] = $field;
}
}
if (empty($missing_fields)) {
echo "<p style='color: green; font-weight: bold;'>✅ Все обязательные поля присутствуют!</p>";
} else {
echo "<p style='color: red; font-weight: bold;'>❌ Отсутствуют поля: " . implode(', ', $missing_fields) . "</p>";
}
echo "<hr>";
echo "<h3>6. Сравнение с успешным логом:</h3>";
echo "<p><strong>Успешный лог (2025-09-10):</strong></p>";
echo "<pre>";
echo '{
"firstname": "Андрей ",
"cf_1157": "Викторович ",
"lastname": "Кулагин ",
"mobile": "79531673819",
"email": "kulaginandrey110567@gmail.com",
"phone": "",
"birthday": "11-05-1967",
"cf_1263": "г Ростов-на-Дону ",
"mailingstreet": "Ленинградская обл, Выборгский р-н, поселок Пушное, ул Спортивная, д 6, кв 19",
"cf_1257": "780700202965",
"cf_1580": "438138",
"assigned_user_id": "19x5"
}';
echo "</pre>";
echo "<p><strong>Наш массив:</strong></p>";
echo "<pre>" . json_encode($client_array, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
echo "<hr>";
echo "<h3>7. Возможные проблемы:</h3>";
echo "<ul>";
echo "<li>❓ Возможно, поле ИНН должно называться не 'cf_1257', а 'inn'</li>";
echo "<li>❓ Возможно, есть проблема с кодировкой</li>";
echo "<li>❓ Возможно, CRM ожидает другие имена полей</li>";
echo "</ul>";
?>

128
debug_send.php Normal file
View File

@@ -0,0 +1,128 @@
<?php
// Отладочный скрипт для проверки данных
echo "<h2>Отладка отправки данных</h2>";
// Формируем данные точно как в оригинальном JavaScript коде
$appends = [
'{"crm_name":"lastname","field_val":"Козлова"}',
'{"crm_name":"cf_1187","field_val":"ЕРВ Средства размещения"}',
'{"crm_name":"cf_1590","field_val":"195.175.85.146"}',
'{"crm_name":"cf_2296","field_val":"1"}',
'{"crm_name":"cf_1592","field_val":"Мугла"}',
'{"crm_name":"cf_1582","field_val":"15-09-2025"}',
'{"crm_name":"cf_1584","field_val":"25-09-2025"}',
'{"crm_name":"mobile","field_val":"921 862-69-44"}',
'{"crm_name":"cf_1740","field_val":"E1000-302542604"}',
'{"crm_name":"firstname","field_val":"Александра"}',
'{"crm_name":"cf_1157","field_val":"Алексеевна"}',
'{"crm_name":"birthday","field_val":"04-09-1996"}',
'{"crm_name":"cf_1869","field_val":"Козлова Александра Алексеевна"}',
'{"crm_name":"cf_1265","field_val":"СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК"}',
'{"crm_name":"cf_1267","field_val":"044030653"}',
'{"crm_name":"cf_1271","field_val":"30101810500000000653"}',
'{"crm_name":"cf_1269","field_val":"40817810755868418791"}',
'{"crm_name":"cf_1273","field_val":""}',
'{"crm_name":"cf_1163","field_val":""}',
'{"crm_name":"cf_1161","field_val":"Prime beach hotel"}',
'{"crm_name":"cf_1165","field_val":"Cumhuriyet Bulv. No 35 Siteler Marmaris / Muğla / Türkiye"}',
'{"crm_name":"cf_1167","field_val":"info@primebeachhotel.com"}',
'{"crm_name":"cf_1560","field_val":"0252 417 52 00"}',
'{"crm_name":"cf_1558","field_val":"https://primebeachhotel.com"}',
'{"crm_name":"cf_1173","field_val":"16-09-2025"}',
'{"crm_name":"cf_1726","field_val":""}',
'{"crm_name":"cf_1728","field_val":""}',
'{"crm_name":"cf_1730","field_val":"on"}',
'{"crm_name":"cf_1732","field_val":""}',
'{"crm_name":"cf_1734","field_val":""}',
'{"crm_name":"cf_1736","field_val":""}',
'{"crm_name":"mailingstreet","field_val":"Индекс 188300, Ленинградская область, г. Гатчина, ул. Изотова, д.12, к.2, кв. 38"}',
'{"crm_name":"cf_1257","field_val":"470519373754"}',
'{"crm_name":"cf_1800","field_val":"21"}',
'{"crm_name":"cf_1802","field_val":"4116"}',
'{"crm_name":"cf_1804","field_val":"786084"}',
'{"crm_name":"cf_1798","field_val":"643"}',
'{"crm_name":"cf_2000","field_val":"ТУРЦИЯ"}',
'{"crm_name":"email","field_val":"sashyliakoz@gmail.com"}',
'{"crm_name":"description","field_val":"Заселили в отель 16.09.2025. В ваучере (путевке)указан номер sea view, но при заезде разместили в номере без вида на море. \\nПункт 40.2.1 полиса "}',
'{"crm_name":"cf_1738","field_val":"on"}',
'{"crm_name":"cf_1706","field_val":"105540"}'
];
// Данные для отправки
$data = [
'appends' => $appends,
'lastname' => 'Козлова',
'sub_dir' => session_id(),
'upload_urls' => [],
'upload_urls_real' => [],
'files_names' => [],
'docs_names' => []
];
echo "<h3>1. Данные которые мы отправляем:</h3>";
echo "<pre>" . print_r($data, true) . "</pre>";
// Симулируем обработку как в server_new.php
echo "<h3>2. Симуляция обработки в server_new.php:</h3>";
$new_post = array(
'__vtrftk' => 'sid:ec649134ad232e44c3ad71bbd321cee986f05545,1688385374',
'publicid' => '3ddc71c2d79ef101c09b0d4e9c6bd08b',
'urlencodeenable' => '1',
'name' => 'websiteticket',
);
echo "<p>Начальный массив new_post:</p>";
echo "<pre>" . print_r($new_post, true) . "</pre>";
echo "<p>Обрабатываем appends:</p>";
foreach ($appends as $key => $itemjson) {
$item = json_decode($itemjson);
echo "<p>Обрабатываем: " . htmlspecialchars($itemjson) . "</p>";
echo "<p>crm_name: " . $item->crm_name . ", field_val: " . $item->field_val . "</p>";
$new_post[$item->crm_name] = $item->field_val;
}
echo "<p>Итоговый массив new_post для отправки в CRM:</p>";
echo "<pre>" . print_r($new_post, true) . "</pre>";
echo "<h3>3. Проверяем наличие lastname:</h3>";
if (isset($new_post['lastname'])) {
echo "<p style='color: green;'>✅ lastname найден: " . $new_post['lastname'] . "</p>";
} else {
echo "<p style='color: red;'>❌ lastname НЕ найден!</p>";
}
echo "<h3>4. Тест отправки в CRM:</h3>";
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://crm.clientright.ru/modules/Webforms/capture.php',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $new_post,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_TIMEOUT => 30
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$error = curl_error($curl);
curl_close($curl);
echo "<p><strong>HTTP код:</strong> " . $httpCode . "</p>";
echo "<p><strong>Ответ CRM:</strong></p>";
echo "<pre>" . htmlspecialchars($response) . "</pre>";
if ($error) {
echo "<p style='color: red;'><strong>Ошибка cURL:</strong> " . $error . "</p>";
}
// Проверим JSON ответ
$response_data = json_decode($response, true);
if ($response_data) {
echo "<p><strong>Расшифрованный ответ:</strong></p>";
echo "<pre>" . print_r($response_data, true) . "</pre>";
}
?>

218
debug_webservice2_flow.php Normal file
View File

@@ -0,0 +1,218 @@
<?php
// Отладочный скрипт для проверки потока данных в server_webservice2.php
// Файл: debug_webservice2_flow.php
echo "<h2>Отладка потока данных в server_webservice2.php</h2>";
echo "<p><strong>Время:</strong> " . date('Y-m-d H:i:s') . "</p>";
echo "<hr>";
// Формируем данные точно как в последнем скрипте
$appends = [
// Поля клиента
'{"ws_type":"client","ws_name":"firstname","field_val":"Александра"}',
'{"ws_type":"client","ws_name":"secondname","field_val":"Алексеевна"}',
'{"ws_type":"client","ws_name":"lastname","field_val":"Козлова"}',
'{"ws_type":"client","ws_name":"mobile","field_val":"921 862-69-44"}',
'{"ws_type":"client","ws_name":"email","field_val":"sashyliakoz@gmail.com"}',
'{"ws_type":"client","ws_name":"birthday","field_val":"1996-09-04"}',
'{"ws_type":"client","ws_name":"mailingstreet","field_val":"Индекс 188300, Ленинградская область, г. Гатчина, ул. Изотова, д.12, к.2, кв. 38"}',
'{"ws_type":"client","ws_name":"inn","field_val":"470519373754"}',
'{"ws_type":"client","ws_name":"code","field_val":"1"}',
// Поля контрагента
'{"ws_type":"contractor","ws_name":"inn","field_val":"7714312079"}',
'{"ws_type":"contractor","ws_name":"ogrn","field_val":"1037714037426"}',
'{"ws_type":"contractor","ws_name":"accountname","field_val":"Филиал ООО РСО ЕВРОИНС Туристическое"}',
'{"ws_type":"contractor","ws_name":"address","field_val":"119049 Москва, 4-й Добрынинский пер., д.8, помещ. С 14-I, ком. 21-26"}',
'{"ws_type":"contractor","ws_name":"email","field_val":"info@erv.ru"}',
'{"ws_type":"contractor","ws_name":"phone","field_val":"84956265800"}',
'{"ws_type":"contractor","ws_name":"website","field_val":"https://www.erv.ru/"}',
// Тикеты
'{"ws_type":"ticket","ws_name":"ticket1","field_val":"Отсутствие вида, заявленного и оплаченного в бронировании"}',
// Поля проекта
'{"ws_type":"project","ws_name":"cf_1187","field_val":"ЕРВ Средства размещения"}',
'{"ws_type":"project","ws_name":"cf_1590","field_val":"195.175.85.146"}',
'{"ws_type":"project","ws_name":"cf_2296","field_val":"1"}',
'{"ws_type":"project","ws_name":"cf_1592","field_val":"Мугла"}',
'{"ws_type":"project","ws_name":"cf_1582","field_val":"15-09-2025"}',
'{"ws_type":"project","ws_name":"cf_1584","field_val":"25-09-2025"}',
'{"ws_type":"project","ws_name":"cf_1740","field_val":"E1000-302542604"}',
'{"ws_type":"project","ws_name":"cf_1157","field_val":"Алексеевна"}',
'{"ws_type":"project","ws_name":"cf_1869","field_val":"Козлова Александра Алексеевна"}',
'{"ws_type":"project","ws_name":"cf_1265","field_val":"СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК"}',
'{"ws_type":"project","ws_name":"cf_1267","field_val":"044030653"}',
'{"ws_type":"project","ws_name":"cf_1271","field_val":"30101810500000000653"}',
'{"ws_type":"project","ws_name":"cf_1269","field_val":"40817810755868418791"}',
'{"ws_type":"project","ws_name":"cf_1273","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1163","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1161","field_val":"Prime beach hotel"}',
'{"ws_type":"project","ws_name":"cf_1165","field_val":"Cumhuriyet Bulv. No 35 Siteler Marmaris / Muğla / Türkiye"}',
'{"ws_type":"project","ws_name":"cf_1167","field_val":"info@primebeachhotel.com"}',
'{"ws_type":"project","ws_name":"cf_1560","field_val":"0252 417 52 00"}',
'{"ws_type":"project","ws_name":"cf_1558","field_val":"https://primebeachhotel.com"}',
'{"ws_type":"project","ws_name":"cf_1173","field_val":"16-09-2025"}',
'{"ws_type":"project","ws_name":"cf_1726","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1728","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1730","field_val":"on"}',
'{"ws_type":"project","ws_name":"cf_1732","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1734","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1736","field_val":""}',
'{"ws_type":"project","ws_name":"cf_1257","field_val":"470519373754"}',
'{"ws_type":"project","ws_name":"cf_1800","field_val":"21"}',
'{"ws_type":"project","ws_name":"cf_1802","field_val":"4116"}',
'{"ws_type":"project","ws_name":"cf_1804","field_val":"786084"}',
'{"ws_type":"project","ws_name":"cf_1798","field_val":"643"}',
'{"ws_type":"project","ws_name":"cf_2000","field_val":"ТУРЦИЯ"}',
'{"ws_type":"project","ws_name":"description","field_val":"Заселили в отель 16.09.2025. В ваучере (путевке)указан номер sea view, но при заезде разместили в номере без вида на море. \\nПункт 40.2.1 полиса "}',
'{"ws_type":"project","ws_name":"cf_1738","field_val":"on"}',
'{"ws_type":"project","ws_name":"cf_1706","field_val":"105540"}'
];
// Данные для отправки
$data = [
'appends' => $appends,
'lastname' => 'Козлова',
'sub_dir' => session_id(),
'upload_urls' => [],
'upload_urls_real' => [],
'files_names' => [],
'docs_names' => [],
'docs_ticket_files_ids' => [],
'getservice' => ''
];
echo "<h3>1. Что мы отправляем на server_webservice2.php:</h3>";
echo "<pre>" . print_r($data, true) . "</pre>";
echo "<h3>2. Симуляция обработки в server_webservice2.php:</h3>";
// Симулируем код из server_webservice2.php
$client_array = array(
'operation' => 'CreateContact',
'sessionName' => 'TEST_SESSION_ID',
);
echo "<p><strong>Начальный client_array:</strong></p>";
echo "<pre>" . print_r($client_array, true) . "</pre>";
echo "<p><strong>Обработка appends для клиента:</strong></p>";
foreach ($appends as $key => $itemjson) {
$item = json_decode($itemjson);
echo "<p>[" . $key . "] ws_type: '" . $item->ws_type . "', ws_name: '" . $item->ws_name . "', field_val: '" . $item->field_val . "'</p>";
if ($item->ws_type == "client") {
$client_array[$item->ws_name] = $item->field_val;
echo "<p style='color: green;'>✅ Добавлено в client_array: " . $item->ws_name . " = '" . $item->field_val . "'</p>";
if ($item->ws_name == "code") $sms = $item->field_val;
} else {
echo "<p style='color: gray;'>⏭️ Пропущено (ws_type != 'client')</p>";
}
}
echo "<p><strong>Итоговый client_array для отправки в CRM:</strong></p>";
echo "<pre>" . print_r($client_array, true) . "</pre>";
echo "<h3>3. Симуляция обработки контрагента:</h3>";
$contractor_array = array(
'operation' => 'CreateAccount',
'sessionName' => 'TEST_SESSION_ID',
);
echo "<p><strong>Начальный contractor_array:</strong></p>";
echo "<pre>" . print_r($contractor_array, true) . "</pre>";
echo "<p><strong>Обработка appends для контрагента:</strong></p>";
foreach ($appends as $key => $itemjson) {
$item = json_decode($itemjson);
if ($item->ws_type == "contractor" && $item->field_val != "") {
$contractor_array[$item->ws_name] = $item->field_val;
echo "<p style='color: green;'>✅ Добавлено в contractor_array: " . $item->ws_name . " = '" . $item->field_val . "'</p>";
}
}
echo "<p><strong>Итоговый contractor_array для отправки в CRM:</strong></p>";
echo "<pre>" . print_r($contractor_array, true) . "</pre>";
echo "<h3>4. Симуляция обработки проекта:</h3>";
$project_array = array(
'operation' => 'CreateProject',
'sessionName' => 'TEST_SESSION_ID',
'contactid' => 'TEST_CLIENT_ID',
'offenderid' => 'TEST_CONTRACTOR_ID',
'agentid' => '',
'sms' => $sms ?? ''
);
echo "<p><strong>Начальный project_array:</strong></p>";
echo "<pre>" . print_r($project_array, true) . "</pre>";
echo "<h3>5. Теперь отправляем реальные данные на server_webservice2.php:</h3>";
// Отправляем данные
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => 'https://form.clientright.ru/server_webservice2.php',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
]
]);
$response = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$error = curl_error($curl);
curl_close($curl);
echo "<p><strong>HTTP код:</strong> " . $httpCode . "</p>";
if ($error) {
echo "<p style='color: red;'><strong>Ошибка cURL:</strong> " . $error . "</p>";
} else {
echo "<p><strong>Ответ от server_webservice2.php:</strong></p>";
echo "<pre>" . htmlspecialchars($response) . "</pre>";
// Парсим ответ
$response_data = json_decode($response, true);
if ($response_data) {
echo "<h3>Результат:</h3>";
if (isset($response_data['status']) && $response_data['status'] === 'success') {
echo "<p style='color: green; font-weight: bold;'>✅ Статус: Успех!</p>";
if (isset($response_data['message'])) {
echo "<p style='color: green;'><strong>Сообщение:</strong> " . $response_data['message'] . "</p>";
}
echo "<p style='color: green; font-weight: bold;'>✅ Данные успешно отправлены в CRM!</p>";
} else {
echo "<p style='color: red; font-weight: bold;'>❌ Ошибка при отправке данных</p>";
}
}
}
echo "<hr>";
echo "<h3>6. Сравнение с успешным логом:</h3>";
echo "<p><strong>Успешный лог (2025-09-10):</strong></p>";
echo "<pre>";
echo '{"operation":"CreateContact","sessionName":"61c825a768c1dd749ca62","mobile":"7 (953) 167-38-19","lastname":"Кулагин ","firstname":"Андрей ","secondname":"Викторович ","mailingstreet":"Ленинградская обл...","birthday":"11-05-1967","birthplace":"г Ростов-на-Дону ","inn":"780700202965","requisites":"Реквизиты...","email":"kulaginandrey110567@gmail.com","code":"438138"}';
echo "</pre>";
echo "<p><strong>Наш client_array:</strong></p>";
echo "<pre>" . json_encode($client_array, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "</pre>";
echo "<hr>";
echo "<h3>7. Возможные проблемы:</h3>";
echo "<ul>";
echo "<li>❓ Возможно, server_webservice2.php не получает данные appends</li>";
echo "<li>❓ Возможно, есть проблема с декодированием JSON</li>";
echo "<li>❓ Возможно, поля не попадают в массивы из-за условий</li>";
echo "</ul>";
?>

21
ervws/.htaccess Normal file
View File

@@ -0,0 +1,21 @@
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
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

67
ervws/css/custom.css Normal file
View File

@@ -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;
}

604
ervws/css/main.css Normal file
View File

@@ -0,0 +1,604 @@
@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;
}
.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;
}
.suсcess-upload{
margin-bottom: 2px;
margin-top: 2px;
}
.form-text{
margin-bottom: 30px;
margin-top: 30px;
text-align: center;
display: block;
}

59
ervws/database.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
// Имя ДБ: turistpr_erv
// Пользователь: turistpr_erv
// Пароль: c7vOXbmG
// Адрес хоста: 141.8.194.131
// таблица lexrpiority
if(isset($_POST['action']) && !empty($_POST['action'])) {
$action = $_POST['action'];
switch($action) {
case 'user_verify' : user_verify(); break;
}
}
function user_verify() {
$finded_row['success']="false";
//$link = mysqli_connect("141.8.194.131", "turistpr_erv", "c7vOXbmG");
$link = mysqli_connect("localhost", "ci20465_erv", "c7vOXbmG");
if ($link == false){
}
else {
}
$birthday = $_POST['birthday'];
$inn = $_POST['inn'];
//$sql = "SELECT * FROM turistpr_erv.lexrpiority";
$sql = "SELECT * FROM ci20465_erv.lexrpiority";
$result = mysqli_query($link, $sql);
$finded_row=array("success"=>"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);
}
?>

64
ervws/file-server.php Normal file
View File

@@ -0,0 +1,64 @@
<?php
$input_name = 'file';
$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 (!isset($_FILES[$input_name])) {
$error = 'Файл не загружен.';
} else {
$file = $_FILES[$input_name];
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;
exec("convert uploads/".$name." uploads/".$name.'_'.date('m-d-Y-H-i-s').".pdf");
$success = '<p style="color: green">Файл «' . $name . '» успешно загружен.</p><a href="'.$fullpath.'">Скачать</a>';
} else {
$error = 'Не удалось загрузить файл.';
}
}
}
}
if (!empty($error)) {
$error = '<p style="color: red">' . $error . '</p>';
}
$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");

114
ervws/fileupload.php Normal file
View File

@@ -0,0 +1,114 @@
<?php
$result=array("success"=>"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 = '<p style="color: green">Файл «' . $name . '» успешно загружен.</p><a href="'.$fullpath.'">Скачать</a>';
} 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);
?>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -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;
}

42
ervws/img/check.svg Normal file
View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="405.272px" height="405.272px" viewBox="0 0 405.272 405.272" style="enable-background:new 0 0 405.272 405.272;"
xml:space="preserve">
<g>
<path d="M393.401,124.425L179.603,338.208c-15.832,15.835-41.514,15.835-57.361,0L11.878,227.836
c-15.838-15.835-15.838-41.52,0-57.358c15.841-15.841,41.521-15.841,57.355-0.006l81.698,81.699L336.037,67.064
c15.841-15.841,41.523-15.829,57.358,0C409.23,82.902,409.23,108.578,393.401,124.425z"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 953 B

1
ervws/img/close.svg Normal file
View File

@@ -0,0 +1 @@
<?xml version="1.0"?><svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="30px" height="30px"> <path d="M 7 4 C 6.744125 4 6.4879687 4.0974687 6.2929688 4.2929688 L 4.2929688 6.2929688 C 3.9019687 6.6839688 3.9019687 7.3170313 4.2929688 7.7070312 L 11.585938 15 L 4.2929688 22.292969 C 3.9019687 22.683969 3.9019687 23.317031 4.2929688 23.707031 L 6.2929688 25.707031 C 6.6839688 26.098031 7.3170313 26.098031 7.7070312 25.707031 L 15 18.414062 L 22.292969 25.707031 C 22.682969 26.098031 23.317031 26.098031 23.707031 25.707031 L 25.707031 23.707031 C 26.098031 23.316031 26.098031 22.682969 25.707031 22.292969 L 18.414062 15 L 25.707031 7.7070312 C 26.098031 7.3170312 26.098031 6.6829688 25.707031 6.2929688 L 23.707031 4.2929688 C 23.316031 3.9019687 22.682969 3.9019687 22.292969 4.2929688 L 15 11.585938 L 7.7070312 4.2929688 C 7.5115312 4.0974687 7.255875 4 7 4 z"/></svg>

After

Width:  |  Height:  |  Size: 912 B

1
ervws/img/date.svg Normal file
View File

@@ -0,0 +1 @@
<svg role="presentation" class="t-datepicker__icon " xmlns="http://www.w3.org/2000/svg" viewBox="0 0 69.5 76.2" style="width:25px;"><path d="M9.6 42.9H21V31.6H9.6v11.3zm3-8.3H18v5.3h-5.3v-5.3zm16.5 8.3h11.3V31.6H29.1v11.3zm3-8.3h5.3v5.3h-5.3v-5.3zM48 42.9h11.3V31.6H48v11.3zm3-8.3h5.3v5.3H51v-5.3zM9.6 62H21V50.6H9.6V62zm3-8.4H18V59h-5.3v-5.4zM29.1 62h11.3V50.6H29.1V62zm3-8.4h5.3V59h-5.3v-5.4zM48 62h11.3V50.6H48V62zm3-8.4h5.3V59H51v-5.4z"></path><path d="M59.7 6.8V5.3c0-2.9-2.4-5.3-5.3-5.3s-5.3 2.4-5.3 5.3v1.5H40V5.3C40 2.4 37.6 0 34.7 0s-5.3 2.4-5.3 5.3v1.5h-9.1V5.3C20.3 2.4 18 0 15 0c-2.9 0-5.3 2.4-5.3 5.3v1.5H0v69.5h69.5V6.8h-9.8zm-7.6-1.5c0-1.3 1-2.3 2.3-2.3s2.3 1 2.3 2.3v7.1c0 1.3-1 2.3-2.3 2.3s-2.3-1-2.3-2.3V5.3zm-19.7 0c0-1.3 1-2.3 2.3-2.3S37 4 37 5.3v7.1c0 1.3-1 2.3-2.3 2.3s-2.3-1-2.3-2.3V5.3zm-19.6 0C12.8 4 13.8 3 15 3c1.3 0 2.3 1 2.3 2.3v7.1c0 1.3-1 2.3-2.3 2.3-1.3 0-2.3-1-2.3-2.3V5.3zm53.7 67.9H3V9.8h6.8v2.6c0 2.9 2.4 5.3 5.3 5.3s5.3-2.4 5.3-5.3V9.8h9.1v2.6c0 2.9 2.4 5.3 5.3 5.3s5.3-2.4 5.3-5.3V9.8h9.1v2.6c0 2.9 2.4 5.3 5.3 5.3s5.3-2.4 5.3-5.3V9.8h6.8l-.1 63.4z"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

1092
ervws/index.php Normal file

File diff suppressed because it is too large Load Diff

1092
ervws/index.php1 Normal file

File diff suppressed because it is too large Load Diff

927
ervws/js/common.js Normal file
View File

@@ -0,0 +1,927 @@
$(function() {
$(document).ready(function(){
$(".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)
});
$(".js-bik-mask").inputmask("999999999");
$(".js-rs-mask").inputmask("99999999999999999999");
$(".js-ks-mask").inputmask("99999999999999999999");
document.querySelector('input').addEventListener('keydown', function (e) {
if (e.which == 9) {
e.preventDefault();
}
});
let month =['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'];
let days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
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) {
$("input[data-enableby=birthday]").removeClass('disabled');
$("input[data-disabledby=birthday]").removeClass('disabled');
$("input[data-enableby=birthday]").removeAttr("disabled");
} else {
$("input[data-enableby=birthday]").addClass('disabled');
$("input[data-disabledby=birthday]").addClass('disabled');
$("input[data-enableby=birthday]").attr("disabled","disabled");
}
}
});
var contract_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',
separateDialCode: true,
customContainer: ".form-item",
excludeCountries: ["kz"],
autoPlaceholder: 'aggressive',
geoIpLookup: function(callback) {
$.get('https://ipinfo.io', null, 'jsonp').always(resp => callback((resp && resp.country) ? resp.country : ''));
},
utilsScript: "libs/intl-tel-input-master/build/js/utils.js",
});
});
$('.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();
let isvalid=validate_step(index);
if(isvalid) {
index++;
$('.span-progress .current').text(index);
if(index==4) {
$(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());
});
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;
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 {
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);
});
if((step_index==3) && $('.form-step[data-step='+step_index+']').find('input[type="checkbox"]:checked').length<1) {
$('.form__warning').show();
$('.form__warning').text('Выберите хотя 1 страховой случай');
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
function send_sms(){
var sended_code = Math.floor(Math.random()*(999999-100000+1)+100000);
var smsFormData = new FormData();
smsFormData.append('smscode',sended_code);
smsFormData.append('phonenumber',$('input[name="phonenumber"]').val());
// $.ajax({
// url: 'sms-test.php',
// method: 'post',
// cache: false,
// contentType: false,
// processData: false,
// data: smsFormData,
// dataType : 'json',
// success: function(data) {
// console.log(data);
// return false;
// },
// error: function (jqXHR, exception) {
// return false;
// }
// });
alert(sended_code);
$('.js-code-warning').text('Код отправлен на ваш номер телефона');
$.fancybox.open({
src: '#confirm_sms',
type: 'inline'
});
$('.fancybox-close-small').click(function(e) {
$('button[type="submit"]').attr("disabled", false);
$('button[type="submit"]').attr("disabled", false);
$('button[type="submit"]').text("Подать обращение");
});
return sended_code;
}
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);
}
let sended_code;
$('.js-send-sms').on('click', function(e) {
// $('.js-send-sms').hide();
sended_code=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-checking .js-accept-sms').on('click', function(e) {
e.preventDefault();
if($('.sms-checking input[type="text"]').val() == sended_code) {
$('.sms-success').removeClass('d-none');
$('.form-step[data-step=1]').removeClass('disabled');
$('.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').text("Вы успешно подтвердили номер");
} else {
$('.modal .form-item__warning').text("Неверный код");
$('.sms-success').addClass('d-none');
}
});
$('.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();
jQuery.each(jQuery('input[type=file].js-attach').not('.disabled'), function(i, file) {
if(!$(this).hasClass('disabled')) {
let field_name=jQuery(this).data('crmname');
let docname=jQuery(this).data('docname');
let upload_url=jQuery(this).attr('data-uploadurl');
let upload_url_real=jQuery(this).attr('data-uploadurl_real');
jQuery.each(jQuery(this)[0].files, function(i, file) {
formData.append(field_name+'-'+i, file);
});
if(upload_url) { // Если файл загрузился и получили ответ от upload тогда добавляем в форму
formData.append('upload_urls[]',upload_url);
formData.append('upload_urls_real[]',upload_url_real);
formData.append('files_names[]',field_name);
formData.append('docs_names[]',docname);
if(jQuery(this).data('doctype')=="ticket") {
formData.append('docs_ticket_files_ids[]',i);
} else {
formData.append('docs_ticket_files_ids[]','simple');
}
}
}
});
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: 'https://form.clientright.ru/server_webservice2.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 f5d6928d7490cd44124ccae11a08c7fa5625d48c",
"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.length; i++) {
$('<div class="autocomplete__item" data-address="'+response.suggestions[i].data.address.value+'" data-inn="'+response.suggestions[i].data.inn+'">'+response.suggestions[i].value+'</div>').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();
});
// $(document).ready(function(){
// setTimeout(function() {
// var $form = $(".form form");
// createSuggestions($form);
// }, 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
}
$.ajax({
url: 'database.php',
method: 'post',
data: dbdata,
// dataType : 'json',
success: function(data) {
console.log(data);
let res=JSON.parse(data);
if(res.success=="true"){
$('.db-success').removeClass("d-none");
$('.js-result').html(res.message);
$('.js-result').removeClass("danger");
$('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');
$('.form-item--polis').find('input[type=file]').addClass("notvalidate");
$('.form-item--polis').find('input[type=file]').addClass("disabled");
$('.form-item--polis').addClass('d-none');
} else {
$('.db-success').removeClass("d-none");
$('.js-result').html(res.message);
$('.js-result').addClass("danger");
$('.js-indatabase').val('0');
$('.form-item--polis').find('input[type=file]').removeClass("notvalidate");
$('.form-item--polis').find('input[type=file]').removeClass("disabled");
$('.form-item--polis').removeClass('d-none');
}
return false;
},
error: function (jqXHR, exception) {
$('.js-result').html(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) {
$("input[data-enableby=birthday]").removeClass('disabled');
$("input[data-disabledby=birthday]").removeClass('disabled');
$("input[data-enableby=birthday]").removeAttr("disabled");
} else {
$("input[data-enableby=birthday]").addClass('disabled');
$("input[data-disabledby=birthday]").addClass('disabled');
$("input[data-enableby=birthday]").attr("disabled","disabled");
}
});
$('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<filesQty; i++) {
var file = elem[0].files[i],
ext = "не определилось",
parts = file.name.split('.');
if (parts.length > 1) ext = parts.pop();
if(!formats.includes(ext)) {
elem.closest('.form-item').find('.form-item__warning').append('<div> Файл '+file.name+' не подходит по формату. Доступные форматы: .pdf, .jpg, .png, .gif </div>');
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('<div>Размер файла '+file.name+' превышает 5 Мб. </div>');
} else {
elem.removeClass('error');
file_status.push(true);
}
}
}
if(file_status.every(val => val === true))
{
upload_file(elem);
$('.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');
let docname=thisfile.data('docname');
console.log(docname);
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("<p class='info-upload'></p>");
// 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("<p class='suсcess-upload'>Файл успешно загружен на сервер.</p>");
thisfile.attr('data-uploadurl',res.empty_file);
thisfile.attr('data-uploadurl_real',res.real_file);
// thisfile.closest('.form-item').append("<p class='suсcess-upload'>"+res.message+"</p>");
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 filesToUpload = [];
let filethis=$(this);
filethis.change(function (evt) {
var output = [];
let elem= $(this);
elem.closest('.form-item').find('.form-item__warning').text('');
let file_status=[];
var formats = ['pdf','jpg','png','gif','jpeg'];
console.log(evt.target.files);
if(evt.target.files.length>10) {
elem.closest('.form-item').find('.form-item__warning').text("Разрешено не более 10 файлов");
return;
}
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('<div> Файл '+file.name+' не подходит по формату. Доступные форматы: .pdf, .jpg, .png, .gif </div>');
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('<div>Размер файла '+file.name+' превышает 5 Мб. </div>');
} else {
elem.removeClass('error');
file_status.push(true);
var removeLink = "<a class=\"removefile\" href=\"#\" data-fileid=\"" + fileId + "\"></a>";
output.push("<li><strong>", file.name, "</strong> <span class='size'> ", formatBytes(file.size) , " </span> ", removeLink, "</li> ");
filesToUpload.push({
id: fileId,
file: file
});
}
}
//evt.target.value = null;
// elem.closest('.form-item').find('.upload-action').removeClass('d-none');
};
elem.closest('.form-item').find(".fileList").append(output.join(""));
let container = new DataTransfer();
for (var i = 0, len = filesToUpload.length; i < len; i++) {
container.items.add(filesToUpload[i].file);
}
elem[0].files = container.files;
if(file_status.every(val => val === true))
{
upload_file(elem);
$('.form__action').find('.js-btn-next').removeClass('disabled');
} else {
$('.form__action').find('.js-btn-next').addClass('disabled');
}
});
$(this).closest('.form-item').on("click", ".removefile", function (e) {
let elem= $(this);
e.preventDefault();
var fileId = elem.parent().children("a").data("fileid");
for (var i = 0; i < filesToUpload.length; ++i) {
if (filesToUpload[i].id === fileId) filesToUpload.splice(i, 1);
}
elem.parent().remove();
if(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 = filesToUpload.length; i < len; i++) {
container.items.add(filesToUpload[i].file);
}
filethis[0].files = container.files;
updateSize(filethis);
}
});
});
// End Загрузка файлов
});

Binary file not shown.

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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 `<ul>`s. The `::before` pseudo-element generates an element
// *within* the .breadcrumb-item and thereby inherits the `text-decoration`.
//
// To trick IE into suppressing the underline, we give the pseudo-element an
// underline and then immediately remove it.
+ .breadcrumb-item:hover::before {
text-decoration: underline;
}
// stylelint-disable-next-line no-duplicate-selectors
+ .breadcrumb-item:hover::before {
text-decoration: none;
}
&.active {
color: $breadcrumb-active-color;
}
}

View File

@@ -0,0 +1,166 @@
// stylelint-disable selector-no-qualifying-type
// Make the div behave like a button
.btn-group,
.btn-group-vertical {
position: relative;
display: inline-flex;
vertical-align: middle; // match .btn alignment given font-size hack above
> .btn {
position: relative;
flex: 0 1 auto;
// Bring the hover, focused, and "active" buttons to the front to overlay
// the borders properly
@include hover {
z-index: 1;
}
&:focus,
&:active,
&.active {
z-index: 1;
}
}
// Prevent double borders when buttons are next to each other
.btn + .btn,
.btn + .btn-group,
.btn-group + .btn,
.btn-group + .btn-group {
margin-left: -$btn-border-width;
}
}
// Optional: Group multiple button groups together for a toolbar
.btn-toolbar {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
.input-group {
width: auto;
}
}
.btn-group {
> .btn:first-child {
margin-left: 0;
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
> .btn-group:not(:last-child) > .btn {
@include border-right-radius(0);
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-left-radius(0);
}
}
// Sizing
//
// Remix the default button sizing classes into new ones for easier manipulation.
.btn-group-sm > .btn { @extend .btn-sm; }
.btn-group-lg > .btn { @extend .btn-lg; }
//
// Split button dropdowns
//
.dropdown-toggle-split {
padding-right: $btn-padding-x * .75;
padding-left: $btn-padding-x * .75;
&::after {
margin-left: 0;
}
}
.btn-sm + .dropdown-toggle-split {
padding-right: $btn-padding-x-sm * .75;
padding-left: $btn-padding-x-sm * .75;
}
.btn-lg + .dropdown-toggle-split {
padding-right: $btn-padding-x-lg * .75;
padding-left: $btn-padding-x-lg * .75;
}
// The clickable button for toggling the menu
// Set the same inset shadow as the :active state
.btn-group.show .dropdown-toggle {
@include box-shadow($btn-active-box-shadow);
// Show no shadow for `.btn-link` since it has no other button styles.
&.btn-link {
@include box-shadow(none);
}
}
//
// Vertical button groups
//
.btn-group-vertical {
flex-direction: column;
align-items: flex-start;
justify-content: center;
.btn,
.btn-group {
width: 100%;
}
> .btn + .btn,
> .btn + .btn-group,
> .btn-group + .btn,
> .btn-group + .btn-group {
margin-top: -$btn-border-width;
margin-left: 0;
}
// Reset rounded corners
> .btn:not(:last-child):not(.dropdown-toggle),
> .btn-group:not(:last-child) > .btn {
@include border-bottom-radius(0);
}
> .btn:not(:first-child),
> .btn-group:not(:first-child) > .btn {
@include border-top-radius(0);
}
}
// Checkbox and radio options
//
// In order to support the browser's form validation feedback, powered by the
// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use
// `display: none;` or `visibility: hidden;` as that also hides the popover.
// Simply visually hiding the inputs via `opacity` would leave them clickable in
// certain cases which is prevented by using `clip` and `pointer-events`.
// This way, we ensure a DOM element is visible to position the popover from.
//
// See https://github.com/twbs/bootstrap/pull/12794 and
// https://github.com/twbs/bootstrap/pull/14559 for more information.
.btn-group-toggle {
> .btn,
> .btn-group > .btn {
margin-bottom: 0; // Override default `<label>` value
input[type="radio"],
input[type="checkbox"] {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
}
}

View File

@@ -0,0 +1,143 @@
// stylelint-disable selector-no-qualifying-type
//
// Base styles
//
.btn {
display: inline-block;
font-weight: $btn-font-weight;
text-align: center;
white-space: nowrap;
vertical-align: middle;
user-select: none;
border: $btn-border-width solid transparent;
@include button-size($btn-padding-y, $btn-padding-x, $font-size-base, $btn-line-height, $btn-border-radius);
@include transition($btn-transition);
// Share hover and focus styles
@include hover-focus {
text-decoration: none;
}
&:focus,
&.focus {
outline: 0;
box-shadow: $btn-focus-box-shadow;
}
// Disabled comes first so active can properly restyle
&.disabled,
&:disabled {
opacity: $btn-disabled-opacity;
@include box-shadow(none);
}
// Opinionated: add "hand" cursor to non-disabled .btn elements
&:not(:disabled):not(.disabled) {
cursor: pointer;
}
&:not(:disabled):not(.disabled):active,
&:not(:disabled):not(.disabled).active {
background-image: none;
@include box-shadow($btn-active-box-shadow);
&:focus {
@include box-shadow($btn-focus-box-shadow, $btn-active-box-shadow);
}
}
}
// Future-proof disabling of clicks on `<a>` elements
a.btn.disabled,
fieldset:disabled a.btn {
pointer-events: none;
}
//
// Alternate buttons
//
@each $color, $value in $theme-colors {
.btn-#{$color} {
@include button-variant($value, $value);
}
}
@each $color, $value in $theme-colors {
.btn-outline-#{$color} {
@include button-outline-variant($value);
}
}
//
// Link buttons
//
// Make a button look and behave like a link
.btn-link {
font-weight: $font-weight-normal;
color: $link-color;
background-color: transparent;
@include hover {
color: $link-hover-color;
text-decoration: $link-hover-decoration;
background-color: transparent;
border-color: transparent;
}
&:focus,
&.focus {
text-decoration: $link-hover-decoration;
border-color: transparent;
box-shadow: none;
}
&:disabled,
&.disabled {
color: $btn-link-disabled-color;
}
// No need for an active state here
}
//
// Button Sizes
//
.btn-lg {
@include button-size($btn-padding-y-lg, $btn-padding-x-lg, $font-size-lg, $btn-line-height-lg, $btn-border-radius-lg);
}
.btn-sm {
@include button-size($btn-padding-y-sm, $btn-padding-x-sm, $font-size-sm, $btn-line-height-sm, $btn-border-radius-sm);
}
//
// Block button
//
.btn-block {
display: block;
width: 100%;
// Vertically space out multiple block buttons
+ .btn-block {
margin-top: $btn-block-spacing-y;
}
}
// Specificity overrides
input[type="submit"],
input[type="reset"],
input[type="button"] {
&.btn-block {
width: 100%;
}
}

View File

@@ -0,0 +1,270 @@
//
// Base styles
//
.card {
position: relative;
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: $card-bg;
background-clip: border-box;
border: $card-border-width solid $card-border-color;
@include border-radius($card-border-radius);
> hr {
margin-right: 0;
margin-left: 0;
}
> .list-group:first-child {
.list-group-item:first-child {
@include border-top-radius($card-border-radius);
}
}
> .list-group:last-child {
.list-group-item:last-child {
@include border-bottom-radius($card-border-radius);
}
}
}
.card-body {
// Enable `flex-grow: 1` for decks and groups so that card blocks take up
// as much space as possible, ensuring footers are aligned to the bottom.
flex: 1 1 auto;
padding: $card-spacer-x;
}
.card-title {
margin-bottom: $card-spacer-y;
}
.card-subtitle {
margin-top: -($card-spacer-y / 2);
margin-bottom: 0;
}
.card-text:last-child {
margin-bottom: 0;
}
.card-link {
@include hover {
text-decoration: none;
}
+ .card-link {
margin-left: $card-spacer-x;
}
}
//
// Optional textual caps
//
.card-header {
padding: $card-spacer-y $card-spacer-x;
margin-bottom: 0; // Removes the default margin-bottom of <hN>
background-color: $card-cap-bg;
border-bottom: $card-border-width solid $card-border-color;
&:first-child {
@include border-radius($card-inner-border-radius $card-inner-border-radius 0 0);
}
+ .list-group {
.list-group-item:first-child {
border-top: 0;
}
}
}
.card-footer {
padding: $card-spacer-y $card-spacer-x;
background-color: $card-cap-bg;
border-top: $card-border-width solid $card-border-color;
&:last-child {
@include border-radius(0 0 $card-inner-border-radius $card-inner-border-radius);
}
}
//
// Header navs
//
.card-header-tabs {
margin-right: -($card-spacer-x / 2);
margin-bottom: -$card-spacer-y;
margin-left: -($card-spacer-x / 2);
border-bottom: 0;
}
.card-header-pills {
margin-right: -($card-spacer-x / 2);
margin-left: -($card-spacer-x / 2);
}
// Card image
.card-img-overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
padding: $card-img-overlay-padding;
}
.card-img {
width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch
@include border-radius($card-inner-border-radius);
}
// Card image caps
.card-img-top {
width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch
@include border-top-radius($card-inner-border-radius);
}
.card-img-bottom {
width: 100%; // Required because we use flexbox and this inherently applies align-self: stretch
@include border-bottom-radius($card-inner-border-radius);
}
// Card deck
.card-deck {
display: flex;
flex-direction: column;
.card {
margin-bottom: $card-deck-margin;
}
@include media-breakpoint-up(sm) {
flex-flow: row wrap;
margin-right: -$card-deck-margin;
margin-left: -$card-deck-margin;
.card {
display: flex;
// Flexbugs #4: https://github.com/philipwalton/flexbugs#4-flex-shorthand-declarations-with-unitless-flex-basis-values-are-ignored
flex: 1 0 0%;
flex-direction: column;
margin-right: $card-deck-margin;
margin-bottom: 0; // Override the default
margin-left: $card-deck-margin;
}
}
}
//
// Card groups
//
.card-group {
display: flex;
flex-direction: column;
// The child selector allows nested `.card` within `.card-group`
// to display properly.
> .card {
margin-bottom: $card-group-margin;
}
@include media-breakpoint-up(sm) {
flex-flow: row wrap;
// The child selector allows nested `.card` within `.card-group`
// to display properly.
> .card {
// Flexbugs #4: https://github.com/philipwalton/flexbugs#4-flex-shorthand-declarations-with-unitless-flex-basis-values-are-ignored
flex: 1 0 0%;
margin-bottom: 0;
+ .card {
margin-left: 0;
border-left: 0;
}
// Handle rounded corners
@if $enable-rounded {
&:first-child {
@include border-right-radius(0);
.card-img-top,
.card-header {
border-top-right-radius: 0;
}
.card-img-bottom,
.card-footer {
border-bottom-right-radius: 0;
}
}
&:last-child {
@include border-left-radius(0);
.card-img-top,
.card-header {
border-top-left-radius: 0;
}
.card-img-bottom,
.card-footer {
border-bottom-left-radius: 0;
}
}
&:only-child {
@include border-radius($card-border-radius);
.card-img-top,
.card-header {
@include border-top-radius($card-border-radius);
}
.card-img-bottom,
.card-footer {
@include border-bottom-radius($card-border-radius);
}
}
&:not(:first-child):not(:last-child):not(:only-child) {
@include border-radius(0);
.card-img-top,
.card-img-bottom,
.card-header,
.card-footer {
@include border-radius(0);
}
}
}
}
}
}
//
// Columns
//
.card-columns {
.card {
margin-bottom: $card-columns-margin;
}
@include media-breakpoint-up(sm) {
column-count: $card-columns-count;
column-gap: $card-columns-gap;
.card {
display: inline-block; // Don't let them vertically span multiple columns
width: 100%; // Don't let their width change
}
}
}

View File

@@ -0,0 +1,191 @@
// Wrapper for the slide container and indicators
.carousel {
position: relative;
}
.carousel-inner {
position: relative;
width: 100%;
overflow: hidden;
}
.carousel-item {
position: relative;
display: none;
align-items: center;
width: 100%;
@include transition($carousel-transition);
backface-visibility: hidden;
perspective: 1000px;
}
.carousel-item.active,
.carousel-item-next,
.carousel-item-prev {
display: block;
}
.carousel-item-next,
.carousel-item-prev {
position: absolute;
top: 0;
}
// CSS3 transforms when supported by the browser
.carousel-item-next.carousel-item-left,
.carousel-item-prev.carousel-item-right {
transform: translateX(0);
@supports (transform-style: preserve-3d) {
transform: translate3d(0, 0, 0);
}
}
.carousel-item-next,
.active.carousel-item-right {
transform: translateX(100%);
@supports (transform-style: preserve-3d) {
transform: translate3d(100%, 0, 0);
}
}
.carousel-item-prev,
.active.carousel-item-left {
transform: translateX(-100%);
@supports (transform-style: preserve-3d) {
transform: translate3d(-100%, 0, 0);
}
}
//
// Left/right controls for nav
//
.carousel-control-prev,
.carousel-control-next {
position: absolute;
top: 0;
bottom: 0;
// Use flex for alignment (1-3)
display: flex; // 1. allow flex styles
align-items: center; // 2. vertically center contents
justify-content: center; // 3. horizontally center contents
width: $carousel-control-width;
color: $carousel-control-color;
text-align: center;
opacity: $carousel-control-opacity;
// We can't have a transition here because WebKit cancels the carousel
// animation if you trip this while in the middle of another animation.
// Hover/focus state
@include hover-focus {
color: $carousel-control-color;
text-decoration: none;
outline: 0;
opacity: .9;
}
}
.carousel-control-prev {
left: 0;
@if $enable-gradients {
background: linear-gradient(90deg, rgba(0, 0, 0, .25), rgba(0, 0, 0, .001));
}
}
.carousel-control-next {
right: 0;
@if $enable-gradients {
background: linear-gradient(270deg, rgba(0, 0, 0, .25), rgba(0, 0, 0, .001));
}
}
// Icons for within
.carousel-control-prev-icon,
.carousel-control-next-icon {
display: inline-block;
width: $carousel-control-icon-width;
height: $carousel-control-icon-width;
background: transparent no-repeat center center;
background-size: 100% 100%;
}
.carousel-control-prev-icon {
background-image: $carousel-control-prev-icon-bg;
}
.carousel-control-next-icon {
background-image: $carousel-control-next-icon-bg;
}
// Optional indicator pips
//
// Add an ordered list with the following class and add a list item for each
// slide your carousel holds.
.carousel-indicators {
position: absolute;
right: 0;
bottom: 10px;
left: 0;
z-index: 15;
display: flex;
justify-content: center;
padding-left: 0; // override <ol> default
// Use the .carousel-control's width as margin so we don't overlay those
margin-right: $carousel-control-width;
margin-left: $carousel-control-width;
list-style: none;
li {
position: relative;
flex: 0 1 auto;
width: $carousel-indicator-width;
height: $carousel-indicator-height;
margin-right: $carousel-indicator-spacer;
margin-left: $carousel-indicator-spacer;
text-indent: -999px;
background-color: rgba($carousel-indicator-active-bg, .5);
// Use pseudo classes to increase the hit area by 10px on top and bottom.
&::before {
position: absolute;
top: -10px;
left: 0;
display: inline-block;
width: 100%;
height: 10px;
content: "";
}
&::after {
position: absolute;
bottom: -10px;
left: 0;
display: inline-block;
width: 100%;
height: 10px;
content: "";
}
}
.active {
background-color: $carousel-indicator-active-bg;
}
}
// Optional captions
//
//
.carousel-caption {
position: absolute;
right: ((100% - $carousel-caption-width) / 2);
bottom: 20px;
left: ((100% - $carousel-caption-width) / 2);
z-index: 10;
padding-top: 20px;
padding-bottom: 20px;
color: $carousel-caption-color;
text-align: center;
}

View File

@@ -0,0 +1,34 @@
.close {
float: right;
font-size: $close-font-size;
font-weight: $close-font-weight;
line-height: 1;
color: $close-color;
text-shadow: $close-text-shadow;
opacity: .5;
@include hover-focus {
color: $close-color;
text-decoration: none;
opacity: .75;
}
// Opinionated: add "hand" cursor to non-disabled .close elements
&:not(:disabled):not(.disabled) {
cursor: pointer;
}
}
// Additional properties for button version
// iOS requires the button element instead of an anchor tag.
// If you want the anchor version, it requires `href="#"`.
// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
// stylelint-disable property-no-vendor-prefix, selector-no-qualifying-type
button.close {
padding: 0;
background-color: transparent;
border: 0;
-webkit-appearance: none;
}
// stylelint-enable

View File

@@ -0,0 +1,56 @@
// Inline and block code styles
code,
kbd,
pre,
samp {
font-family: $font-family-monospace;
}
// Inline code
code {
font-size: $code-font-size;
color: $code-color;
word-break: break-word;
// Streamline the style when inside anchors to avoid broken underline and more
a > & {
color: inherit;
}
}
// User input typically entered via keyboard
kbd {
padding: $kbd-padding-y $kbd-padding-x;
font-size: $kbd-font-size;
color: $kbd-color;
background-color: $kbd-bg;
@include border-radius($border-radius-sm);
@include box-shadow($kbd-box-shadow);
kbd {
padding: 0;
font-size: 100%;
font-weight: $nested-kbd-font-weight;
@include box-shadow(none);
}
}
// Blocks of code
pre {
display: block;
font-size: $code-font-size;
color: $pre-color;
// Account for some code outputs that place code tags in pre tags
code {
font-size: inherit;
color: inherit;
word-break: normal;
}
}
// Enable scrollable blocks of code
.pre-scrollable {
max-height: $pre-scrollable-max-height;
overflow-y: scroll;
}

View File

@@ -0,0 +1,297 @@
// Embedded icons from Open Iconic.
// Released under MIT and copyright 2014 Waybury.
// https://useiconic.com/open
// Checkboxes and radios
//
// Base class takes care of all the key behavioral aspects.
.custom-control {
position: relative;
display: block;
min-height: (1rem * $line-height-base);
padding-left: $custom-control-gutter;
}
.custom-control-inline {
display: inline-flex;
margin-right: $custom-control-spacer-x;
}
.custom-control-input {
position: absolute;
z-index: -1; // Put the input behind the label so it doesn't overlay text
opacity: 0;
&:checked ~ .custom-control-label::before {
color: $custom-control-indicator-checked-color;
@include gradient-bg($custom-control-indicator-checked-bg);
@include box-shadow($custom-control-indicator-checked-box-shadow);
}
&:focus ~ .custom-control-label::before {
// the mixin is not used here to make sure there is feedback
box-shadow: $custom-control-indicator-focus-box-shadow;
}
&:active ~ .custom-control-label::before {
color: $custom-control-indicator-active-color;
background-color: $custom-control-indicator-active-bg;
@include box-shadow($custom-control-indicator-active-box-shadow);
}
&:disabled {
~ .custom-control-label {
color: $custom-control-label-disabled-color;
&::before {
background-color: $custom-control-indicator-disabled-bg;
}
}
}
}
// Custom control indicators
//
// Build the custom controls out of psuedo-elements.
.custom-control-label {
margin-bottom: 0;
// Background-color and (when enabled) gradient
&::before {
position: absolute;
top: (($line-height-base - $custom-control-indicator-size) / 2);
left: 0;
display: block;
width: $custom-control-indicator-size;
height: $custom-control-indicator-size;
pointer-events: none;
content: "";
user-select: none;
background-color: $custom-control-indicator-bg;
@include box-shadow($custom-control-indicator-box-shadow);
}
// Foreground (icon)
&::after {
position: absolute;
top: (($line-height-base - $custom-control-indicator-size) / 2);
left: 0;
display: block;
width: $custom-control-indicator-size;
height: $custom-control-indicator-size;
content: "";
background-repeat: no-repeat;
background-position: center center;
background-size: $custom-control-indicator-bg-size;
}
}
// Checkboxes
//
// Tweak just a few things for checkboxes.
.custom-checkbox {
.custom-control-label::before {
@include border-radius($custom-checkbox-indicator-border-radius);
}
.custom-control-input:checked ~ .custom-control-label {
&::before {
@include gradient-bg($custom-control-indicator-checked-bg);
}
&::after {
background-image: $custom-checkbox-indicator-icon-checked;
}
}
.custom-control-input:indeterminate ~ .custom-control-label {
&::before {
@include gradient-bg($custom-checkbox-indicator-indeterminate-bg);
@include box-shadow($custom-checkbox-indicator-indeterminate-box-shadow);
}
&::after {
background-image: $custom-checkbox-indicator-icon-indeterminate;
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
background-color: $custom-control-indicator-checked-disabled-bg;
}
&:indeterminate ~ .custom-control-label::before {
background-color: $custom-control-indicator-checked-disabled-bg;
}
}
}
// Radios
//
// Tweak just a few things for radios.
.custom-radio {
.custom-control-label::before {
border-radius: $custom-radio-indicator-border-radius;
}
.custom-control-input:checked ~ .custom-control-label {
&::before {
@include gradient-bg($custom-control-indicator-checked-bg);
}
&::after {
background-image: $custom-radio-indicator-icon-checked;
}
}
.custom-control-input:disabled {
&:checked ~ .custom-control-label::before {
background-color: $custom-control-indicator-checked-disabled-bg;
}
}
}
// Select
//
// Replaces the browser default select with a custom one, mostly pulled from
// http://primercss.io.
//
.custom-select {
display: inline-block;
width: 100%;
height: $custom-select-height;
padding: $custom-select-padding-y ($custom-select-padding-x + $custom-select-indicator-padding) $custom-select-padding-y $custom-select-padding-x;
line-height: $custom-select-line-height;
color: $custom-select-color;
vertical-align: middle;
background: $custom-select-bg $custom-select-indicator no-repeat right $custom-select-padding-x center;
background-size: $custom-select-bg-size;
border: $custom-select-border-width solid $custom-select-border-color;
@if $enable-rounded {
border-radius: $custom-select-border-radius;
} @else {
border-radius: 0;
}
appearance: none;
&:focus {
border-color: $custom-select-focus-border-color;
outline: 0;
box-shadow: $custom-select-focus-box-shadow;
&::-ms-value {
// For visual consistency with other platforms/browsers,
// suppress the default white text on blue background highlight given to
// the selected option text when the (still closed) <select> receives focus
// in IE and (under certain conditions) Edge.
// See https://github.com/twbs/bootstrap/issues/19398.
color: $input-color;
background-color: $input-bg;
}
}
&[multiple],
&[size]:not([size="1"]) {
height: auto;
padding-right: $custom-select-padding-x;
background-image: none;
}
&:disabled {
color: $custom-select-disabled-color;
background-color: $custom-select-disabled-bg;
}
// Hides the default caret in IE11
&::-ms-expand {
opacity: 0;
}
}
.custom-select-sm {
height: $custom-select-height-sm;
padding-top: $custom-select-padding-y;
padding-bottom: $custom-select-padding-y;
font-size: $custom-select-font-size-sm;
}
.custom-select-lg {
height: $custom-select-height-lg;
padding-top: $custom-select-padding-y;
padding-bottom: $custom-select-padding-y;
font-size: $custom-select-font-size-lg;
}
// File
//
// Custom file input.
.custom-file {
position: relative;
display: inline-block;
width: 100%;
height: $custom-file-height;
margin-bottom: 0;
}
.custom-file-input {
position: relative;
z-index: 2;
width: 100%;
height: $custom-file-height;
margin: 0;
opacity: 0;
&:focus ~ .custom-file-control {
border-color: $custom-file-focus-border-color;
box-shadow: $custom-file-focus-box-shadow;
&::before {
border-color: $custom-file-focus-border-color;
}
}
@each $lang, $value in $custom-file-text {
&:lang(#{$lang}) ~ .custom-file-label::after {
content: $value;
}
}
}
.custom-file-label {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: 1;
height: $custom-file-height;
padding: $custom-file-padding-y $custom-file-padding-x;
line-height: $custom-file-line-height;
color: $custom-file-color;
background-color: $custom-file-bg;
border: $custom-file-border-width solid $custom-file-border-color;
@include border-radius($custom-file-border-radius);
@include box-shadow($custom-file-box-shadow);
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
z-index: 3;
display: block;
height: calc(#{$custom-file-height} - #{$custom-file-border-width} * 2);
padding: $custom-file-padding-y $custom-file-padding-x;
line-height: $custom-file-line-height;
color: $custom-file-button-color;
content: "Browse";
@include gradient-bg($custom-file-button-bg);
border-left: $custom-file-border-width solid $custom-file-border-color;
@include border-radius(0 $custom-file-border-radius $custom-file-border-radius 0);
}
}

View File

@@ -0,0 +1,131 @@
// The dropdown wrapper (`<div>`)
.dropup,
.dropdown {
position: relative;
}
.dropdown-toggle {
// Generate the caret automatically
@include caret;
}
// The dropdown menu
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: $zindex-dropdown;
display: none; // none by default, but block on "open" of the menu
float: left;
min-width: $dropdown-min-width;
padding: $dropdown-padding-y 0;
margin: $dropdown-spacer 0 0; // override default ul
font-size: $font-size-base; // Redeclare because nesting can cause inheritance issues
color: $body-color;
text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)
list-style: none;
background-color: $dropdown-bg;
background-clip: padding-box;
border: $dropdown-border-width solid $dropdown-border-color;
@include border-radius($dropdown-border-radius);
@include box-shadow($dropdown-box-shadow);
}
// Allow for dropdowns to go bottom up (aka, dropup-menu)
// Just add .dropup after the standard .dropdown class and you're set.
.dropup {
.dropdown-menu {
margin-top: 0;
margin-bottom: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(up);
}
}
.dropright {
.dropdown-menu {
margin-top: 0;
margin-left: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(right);
&::after {
vertical-align: 0;
}
}
}
.dropleft {
.dropdown-menu {
margin-top: 0;
margin-right: $dropdown-spacer;
}
.dropdown-toggle {
@include caret(left);
&::before {
vertical-align: 0;
}
}
}
// Dividers (basically an `<hr>`) within the dropdown
.dropdown-divider {
@include nav-divider($dropdown-divider-bg);
}
// Links, buttons, and more within the dropdown menu
//
// `<button>`-specific styles are denoted with `// For <button>s`
.dropdown-item {
display: block;
width: 100%; // For `<button>`s
padding: $dropdown-item-padding-y $dropdown-item-padding-x;
clear: both;
font-weight: $font-weight-normal;
color: $dropdown-link-color;
text-align: inherit; // For `<button>`s
white-space: nowrap; // prevent links from randomly breaking onto new lines
background-color: transparent; // For `<button>`s
border: 0; // For `<button>`s
@include hover-focus {
color: $dropdown-link-hover-color;
text-decoration: none;
@include gradient-bg($dropdown-link-hover-bg);
}
&.active,
&:active {
color: $dropdown-link-active-color;
text-decoration: none;
@include gradient-bg($dropdown-link-active-bg);
}
&.disabled,
&:disabled {
color: $dropdown-link-disabled-color;
background-color: transparent;
// Remove CSS gradients if they're enabled
@if $enable-gradients {
background-image: none;
}
}
}
.dropdown-menu.show {
display: block;
}
// Dropdown section headers
.dropdown-header {
display: block;
padding: $dropdown-padding-y $dropdown-item-padding-x;
margin-bottom: 0; // for use with heading elements
font-size: $font-size-sm;
color: $dropdown-header-color;
white-space: nowrap; // as with > li > a
}

View File

@@ -0,0 +1,333 @@
// stylelint-disable selector-no-qualifying-type
//
// Textual form controls
//
.form-control {
display: block;
width: 100%;
padding: $input-padding-y $input-padding-x;
font-size: $font-size-base;
line-height: $input-line-height;
color: $input-color;
background-color: $input-bg;
background-clip: padding-box;
border: $input-border-width solid $input-border-color;
// Note: This has no effect on <select>s in some browsers, due to the limited stylability of `<select>`s in CSS.
@if $enable-rounded {
// Manually use the if/else instead of the mixin to account for iOS override
border-radius: $input-border-radius;
} @else {
// Otherwise undo the iOS default
border-radius: 0;
}
@include box-shadow($input-box-shadow);
@include transition($input-transition);
// Unstyle the caret on `<select>`s in IE10+.
&::-ms-expand {
background-color: transparent;
border: 0;
}
// Customize the `:focus` state to imitate native WebKit styles.
@include form-control-focus();
// Placeholder
&::placeholder {
color: $input-placeholder-color;
// Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526.
opacity: 1;
}
// Disabled and read-only inputs
//
// HTML5 says that controls under a fieldset > legend:first-child won't be
// disabled if the fieldset is disabled. Due to implementation difficulty, we
// don't honor that edge case; we style them as disabled anyway.
&:disabled,
&[readonly] {
background-color: $input-disabled-bg;
// iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655.
opacity: 1;
}
}
select.form-control {
&:not([size]):not([multiple]) {
height: $input-height;
}
&:focus::-ms-value {
// Suppress the nested default white text on blue background highlight given to
// the selected option text when the (still closed) <select> receives focus
// in IE and (under certain conditions) Edge, as it looks bad and cannot be made to
// match the appearance of the native widget.
// See https://github.com/twbs/bootstrap/issues/19398.
color: $input-color;
background-color: $input-bg;
}
}
// Make file inputs better match text inputs by forcing them to new lines.
.form-control-file,
.form-control-range {
display: block;
width: 100%;
}
//
// Labels
//
// For use with horizontal and inline forms, when you need the label (or legend)
// text to align with the form controls.
.col-form-label {
padding-top: calc(#{$input-padding-y} + #{$input-border-width});
padding-bottom: calc(#{$input-padding-y} + #{$input-border-width});
margin-bottom: 0; // Override the `<label>/<legend>` default
font-size: inherit; // Override the `<legend>` default
line-height: $input-line-height;
}
.col-form-label-lg {
padding-top: calc(#{$input-padding-y-lg} + #{$input-border-width});
padding-bottom: calc(#{$input-padding-y-lg} + #{$input-border-width});
font-size: $font-size-lg;
line-height: $input-line-height-lg;
}
.col-form-label-sm {
padding-top: calc(#{$input-padding-y-sm} + #{$input-border-width});
padding-bottom: calc(#{$input-padding-y-sm} + #{$input-border-width});
font-size: $font-size-sm;
line-height: $input-line-height-sm;
}
// Readonly controls as plain text
//
// Apply class to a readonly input to make it appear like regular plain
// text (without any border, background color, focus indicator)
.form-control-plaintext {
display: block;
width: 100%;
padding-top: $input-padding-y;
padding-bottom: $input-padding-y;
margin-bottom: 0; // match inputs if this class comes on inputs with default margins
line-height: $input-line-height;
background-color: transparent;
border: solid transparent;
border-width: $input-border-width 0;
&.form-control-sm,
&.form-control-lg {
padding-right: 0;
padding-left: 0;
}
}
// Form control sizing
//
// Build on `.form-control` with modifier classes to decrease or increase the
// height and font-size of form controls.
//
// The `.form-group-* form-control` variations are sadly duplicated to avoid the
// issue documented in https://github.com/twbs/bootstrap/issues/15074.
.form-control-sm {
padding: $input-padding-y-sm $input-padding-x-sm;
font-size: $font-size-sm;
line-height: $input-line-height-sm;
@include border-radius($input-border-radius-sm);
}
select.form-control-sm {
&:not([size]):not([multiple]) {
height: $input-height-sm;
}
}
.form-control-lg {
padding: $input-padding-y-lg $input-padding-x-lg;
font-size: $font-size-lg;
line-height: $input-line-height-lg;
@include border-radius($input-border-radius-lg);
}
select.form-control-lg {
&:not([size]):not([multiple]) {
height: $input-height-lg;
}
}
// Form groups
//
// Designed to help with the organization and spacing of vertical forms. For
// horizontal forms, use the predefined grid classes.
.form-group {
margin-bottom: $form-group-margin-bottom;
}
.form-text {
display: block;
margin-top: $form-text-margin-top;
}
// Form grid
//
// Special replacement for our grid system's `.row` for tighter form layouts.
.form-row {
display: flex;
flex-wrap: wrap;
margin-right: -5px;
margin-left: -5px;
> .col,
> [class*="col-"] {
padding-right: 5px;
padding-left: 5px;
}
}
// Checkboxes and radios
//
// Indent the labels to position radios/checkboxes as hanging controls.
.form-check {
position: relative;
display: block;
padding-left: $form-check-input-gutter;
}
.form-check-input {
position: absolute;
margin-top: $form-check-input-margin-y;
margin-left: -$form-check-input-gutter;
&:disabled ~ .form-check-label {
color: $text-muted;
}
}
.form-check-label {
margin-bottom: 0; // Override default `<label>` bottom margin
}
.form-check-inline {
display: inline-flex;
align-items: center;
padding-left: 0; // Override base .form-check
margin-right: $form-check-inline-margin-x;
// Undo .form-check-input defaults and add some `margin-right`.
.form-check-input {
position: static;
margin-top: 0;
margin-right: $form-check-inline-input-margin-x;
margin-left: 0;
}
}
// Form validation
//
// Provide feedback to users when form field values are valid or invalid. Works
// primarily for client-side validation via scoped `:invalid` and `:valid`
// pseudo-classes but also includes `.is-invalid` and `.is-valid` classes for
// server side validation.
@include form-validation-state("valid", $form-feedback-valid-color);
@include form-validation-state("invalid", $form-feedback-invalid-color);
// Inline forms
//
// Make forms appear inline(-block) by adding the `.form-inline` class. Inline
// forms begin stacked on extra small (mobile) devices and then go inline when
// viewports reach <768px.
//
// Requires wrapping inputs and labels with `.form-group` for proper display of
// default HTML form controls and our custom form controls (e.g., input groups).
.form-inline {
display: flex;
flex-flow: row wrap;
align-items: center; // Prevent shorter elements from growing to same height as others (e.g., small buttons growing to normal sized button height)
// Because we use flex, the initial sizing of checkboxes is collapsed and
// doesn't occupy the full-width (which is what we want for xs grid tier),
// so we force that here.
.form-check {
width: 100%;
}
// Kick in the inline
@include media-breakpoint-up(sm) {
label {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0;
}
// Inline-block all the things for "inline"
.form-group {
display: flex;
flex: 0 0 auto;
flex-flow: row wrap;
align-items: center;
margin-bottom: 0;
}
// Allow folks to *not* use `.form-group`
.form-control {
display: inline-block;
width: auto; // Prevent labels from stacking above inputs in `.form-group`
vertical-align: middle;
}
// Make static controls behave like regular ones
.form-control-plaintext {
display: inline-block;
}
.input-group {
width: auto;
}
// Remove default margin on radios/checkboxes that were used for stacking, and
// then undo the floating of radios and checkboxes to match.
.form-check {
display: flex;
align-items: center;
justify-content: center;
width: auto;
padding-left: 0;
}
.form-check-input {
position: relative;
margin-top: 0;
margin-right: $form-check-input-margin-x;
margin-left: 0;
}
.custom-control {
align-items: center;
justify-content: center;
}
.custom-control-label {
margin-bottom: 0;
}
}
}

View File

@@ -0,0 +1,86 @@
// Bootstrap functions
//
// Utility mixins and functions for evalutating source code across our variables, maps, and mixins.
// Ascending
// Used to evaluate Sass maps like our grid breakpoints.
@mixin _assert-ascending($map, $map-name) {
$prev-key: null;
$prev-num: null;
@each $key, $num in $map {
@if $prev-num == null {
// Do nothing
} @else if not comparable($prev-num, $num) {
@warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
} @else if $prev-num >= $num {
@warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
}
$prev-key: $key;
$prev-num: $num;
}
}
// Starts at zero
// Another grid mixin that ensures the min-width of the lowest breakpoint starts at 0.
@mixin _assert-starts-at-zero($map) {
$values: map-values($map);
$first-value: nth($values, 1);
@if $first-value != 0 {
@warn "First breakpoint in `$grid-breakpoints` must start at 0, but starts at #{$first-value}.";
}
}
// Replace `$search` with `$replace` in `$string`
// Used on our SVG icon backgrounds for custom forms.
//
// @author Hugo Giraudel
// @param {String} $string - Initial string
// @param {String} $search - Substring to replace
// @param {String} $replace ('') - New value
// @return {String} - Updated string
@function str-replace($string, $search, $replace: "") {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
// Color contrast
@function color-yiq($color) {
$r: red($color);
$g: green($color);
$b: blue($color);
$yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
@if ($yiq >= $yiq-contrasted-threshold) {
@return $yiq-text-dark;
} @else {
@return $yiq-text-light;
}
}
// Retrieve color Sass maps
@function color($key: "blue") {
@return map-get($colors, $key);
}
@function theme-color($key: "primary") {
@return map-get($theme-colors, $key);
}
@function gray($key: "100") {
@return map-get($grays, $key);
}
// Request a theme color level
@function theme-color-level($color-name: "primary", $level: 0) {
$color: theme-color($color-name);
$color-base: if($level > 0, #000, #fff);
$level: abs($level);
@return mix($color-base, $color, $level * $theme-color-interval);
}

View File

@@ -0,0 +1,52 @@
// Container widths
//
// Set the container width, and override it for fixed navbars in media queries.
@if $enable-grid-classes {
.container {
@include make-container();
@include make-container-max-widths();
}
}
// Fluid container
//
// Utilizes the mixin meant for fixed width containers, but with 100% width for
// fluid, full width layouts.
@if $enable-grid-classes {
.container-fluid {
@include make-container();
}
}
// Row
//
// Rows contain and clear the floats of your columns.
@if $enable-grid-classes {
.row {
@include make-row();
}
// Remove the negative margin from default .row, then the horizontal padding
// from all immediate children columns (to prevent runaway style inheritance).
.no-gutters {
margin-right: 0;
margin-left: 0;
> .col,
> [class*="col-"] {
padding-right: 0;
padding-left: 0;
}
}
}
// Columns
//
// Common styles for small and large grid columns
@if $enable-grid-classes {
@include make-grid-columns();
}

View File

@@ -0,0 +1,42 @@
// Responsive images (ensure images don't scale beyond their parents)
//
// This is purposefully opt-in via an explicit class rather than being the default for all `<img>`s.
// We previously tried the "images are responsive by default" approach in Bootstrap v2,
// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)
// which weren't expecting the images within themselves to be involuntarily resized.
// See also https://github.com/twbs/bootstrap/issues/18178
.img-fluid {
@include img-fluid;
}
// Image thumbnails
.img-thumbnail {
padding: $thumbnail-padding;
background-color: $thumbnail-bg;
border: $thumbnail-border-width solid $thumbnail-border-color;
@include border-radius($thumbnail-border-radius);
@include box-shadow($thumbnail-box-shadow);
// Keep them at most 100% wide
@include img-fluid;
}
//
// Figures
//
.figure {
// Ensures the caption's text aligns with the image.
display: inline-block;
}
.figure-img {
margin-bottom: ($spacer / 2);
line-height: 1;
}
.figure-caption {
font-size: $figure-caption-font-size;
color: $figure-caption-color;
}

View File

@@ -0,0 +1,159 @@
// stylelint-disable selector-no-qualifying-type
//
// Base styles
//
.input-group {
position: relative;
display: flex;
flex-wrap: wrap; // For form validation feedback
align-items: stretch;
width: 100%;
> .form-control,
> .custom-select,
> .custom-file {
position: relative; // For focus state's z-index
flex: 1 1 auto;
// Add width 1% and flex-basis auto to ensure that button will not wrap out
// the column. Applies to IE Edge+ and Firefox. Chrome does not require this.
width: 1%;
margin-bottom: 0;
// Bring the "active" form control to the top of surrounding elements
&:focus {
z-index: 3;
}
+ .form-control,
+ .custom-select,
+ .custom-file {
margin-left: -$input-border-width;
}
}
> .form-control,
> .custom-select {
&:not(:last-child) { @include border-right-radius(0); }
&:not(:first-child) { @include border-left-radius(0); }
}
// Custom file inputs have more complex markup, thus requiring different
// border-radius overrides.
> .custom-file {
display: flex;
align-items: center;
&:not(:last-child) .custom-file-label,
&:not(:last-child) .custom-file-label::before { @include border-right-radius(0); }
&:not(:first-child) .custom-file-label,
&:not(:first-child) .custom-file-label::before { @include border-left-radius(0); }
}
}
// Prepend and append
//
// While it requires one extra layer of HTML for each, dedicated prepend and
// append elements allow us to 1) be less clever, 2) simplify our selectors, and
// 3) support HTML5 form validation.
.input-group-prepend,
.input-group-append {
display: flex;
// Ensure buttons are always above inputs for more visually pleasing borders.
// This isn't needed for `.input-group-text` since it shares the same border-color
// as our inputs.
.btn {
position: relative;
z-index: 2;
}
.btn + .btn,
.btn + .input-group-text,
.input-group-text + .input-group-text,
.input-group-text + .btn {
margin-left: -$input-border-width;
}
}
.input-group-prepend { margin-right: -$input-border-width; }
.input-group-append { margin-left: -$input-border-width; }
// Textual addons
//
// Serves as a catch-all element for any text or radio/checkbox input you wish
// to prepend or append to an input.
.input-group-text {
display: flex;
align-items: center;
padding: $input-padding-y $input-padding-x;
margin-bottom: 0; // Allow use of <label> elements by overriding our default margin-bottom
font-size: $font-size-base; // Match inputs
font-weight: $font-weight-normal;
line-height: $input-line-height;
color: $input-group-addon-color;
text-align: center;
white-space: nowrap;
background-color: $input-group-addon-bg;
border: $input-border-width solid $input-group-addon-border-color;
@include border-radius($input-border-radius);
// Nuke default margins from checkboxes and radios to vertically center within.
input[type="radio"],
input[type="checkbox"] {
margin-top: 0;
}
}
// Sizing
//
// Remix the default form control sizing classes into new ones for easier
// manipulation.
.input-group-lg > .form-control,
.input-group-lg > .input-group-prepend > .input-group-text,
.input-group-lg > .input-group-append > .input-group-text,
.input-group-lg > .input-group-prepend > .btn,
.input-group-lg > .input-group-append > .btn {
@extend .form-control-lg;
}
.input-group-sm > .form-control,
.input-group-sm > .input-group-prepend > .input-group-text,
.input-group-sm > .input-group-append > .input-group-text,
.input-group-sm > .input-group-prepend > .btn,
.input-group-sm > .input-group-append > .btn {
@extend .form-control-sm;
}
// Prepend and append rounded corners
//
// These rulesets must come after the sizing ones to properly override sm and lg
// border-radius values when extending. They're more specific than we'd like
// with the `.input-group >` part, but without it, we cannot override the sizing.
.input-group > .input-group-prepend > .btn,
.input-group > .input-group-prepend > .input-group-text,
.input-group > .input-group-append:not(:last-child) > .btn,
.input-group > .input-group-append:not(:last-child) > .input-group-text,
.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),
.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {
@include border-right-radius(0);
}
.input-group > .input-group-append > .btn,
.input-group > .input-group-append > .input-group-text,
.input-group > .input-group-prepend:not(:first-child) > .btn,
.input-group > .input-group-prepend:not(:first-child) > .input-group-text,
.input-group > .input-group-prepend:first-child > .btn:not(:first-child),
.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {
@include border-left-radius(0);
}

View File

@@ -0,0 +1,16 @@
.jumbotron {
padding: $jumbotron-padding ($jumbotron-padding / 2);
margin-bottom: $jumbotron-padding;
background-color: $jumbotron-bg;
@include border-radius($border-radius-lg);
@include media-breakpoint-up(sm) {
padding: ($jumbotron-padding * 2) $jumbotron-padding;
}
}
.jumbotron-fluid {
padding-right: 0;
padding-left: 0;
@include border-radius(0);
}

View File

@@ -0,0 +1,115 @@
// Base class
//
// Easily usable on <ul>, <ol>, or <div>.
.list-group {
display: flex;
flex-direction: column;
// No need to set list-style: none; since .list-group-item is block level
padding-left: 0; // reset padding because ul and ol
margin-bottom: 0;
}
// Interactive list items
//
// Use anchor or button elements instead of `li`s or `div`s to create interactive
// list items. Includes an extra `.active` modifier class for selected items.
.list-group-item-action {
width: 100%; // For `<button>`s (anchors become 100% by default though)
color: $list-group-action-color;
text-align: inherit; // For `<button>`s (anchors inherit)
// Hover state
@include hover-focus {
color: $list-group-action-hover-color;
text-decoration: none;
background-color: $list-group-hover-bg;
}
&:active {
color: $list-group-action-active-color;
background-color: $list-group-action-active-bg;
}
}
// Individual list items
//
// Use on `li`s or `div`s within the `.list-group` parent.
.list-group-item {
position: relative;
display: block;
padding: $list-group-item-padding-y $list-group-item-padding-x;
// Place the border on the list items and negative margin up for better styling
margin-bottom: -$list-group-border-width;
background-color: $list-group-bg;
border: $list-group-border-width solid $list-group-border-color;
&:first-child {
@include border-top-radius($list-group-border-radius);
}
&:last-child {
margin-bottom: 0;
@include border-bottom-radius($list-group-border-radius);
}
@include hover-focus {
z-index: 1; // Place hover/active items above their siblings for proper border styling
text-decoration: none;
}
&.disabled,
&:disabled {
color: $list-group-disabled-color;
background-color: $list-group-disabled-bg;
}
// Include both here for `<a>`s and `<button>`s
&.active {
z-index: 2; // Place active items above their siblings for proper border styling
color: $list-group-active-color;
background-color: $list-group-active-bg;
border-color: $list-group-active-border-color;
}
}
// Flush list items
//
// Remove borders and border-radius to keep list group items edge-to-edge. Most
// useful within other components (e.g., cards).
.list-group-flush {
.list-group-item {
border-right: 0;
border-left: 0;
@include border-radius(0);
}
&:first-child {
.list-group-item:first-child {
border-top: 0;
}
}
&:last-child {
.list-group-item:last-child {
border-bottom: 0;
}
}
}
// Contextual variants
//
// Add modifier classes to change text and background color on individual items.
// Organizationally, this must come after the `:hover` states.
@each $color, $value in $theme-colors {
@include list-group-item-variant($color, theme-color-level($color, -9), theme-color-level($color, 6));
}

View File

@@ -0,0 +1,8 @@
.media {
display: flex;
align-items: flex-start;
}
.media-body {
flex: 1;
}

Some files were not shown because too many files have changed in this diff Show More