85 Commits

Author SHA1 Message Date
f4facf146c build fix 2025-07-30 15:27:32 +03:00
80ed5826e2 novyie parvki 2025-07-30 15:25:18 +03:00
9adc737028 Merge pull request 'catalog and bags' (#32) from bag3007 into main
Reviewed-on: #32
2025-07-30 13:57:50 +03:00
95e6b33b56 catalog and bags 2025-07-30 13:57:16 +03:00
a8f783767f Merge pull request 'catalog' (#31) from catalog into main
Reviewed-on: #31
2025-07-30 00:25:53 +03:00
b363b88e33 catalog 2025-07-30 00:25:14 +03:00
72a9772934 parvki 2025-07-29 18:55:22 +03:00
1da9c6ac09 Merge branch 'main' of https://gittea.biveki.ru/BivekiGroup/protekauto-frontend 2025-07-18 19:29:53 +03:00
649ddbfa8a prices and shit 2025-07-18 19:15:25 +03:00
f3d21959c9 prices optimised 2025-07-18 18:12:42 +03:00
61b50d10ba Merge pull request 'сonfidentiality' (#30) from сonfidentiality into main
Reviewed-on: #30
2025-07-18 13:43:52 +03:00
ea76106caa Confidentiality 2025-07-18 13:42:48 +03:00
b7edd73ce0 fixed prices, but still working on filters 2025-07-18 04:22:37 +03:00
b6f9d017d6 catalog prices fix 2025-07-17 21:22:45 +03:00
27d378154f fix1707 2025-07-17 16:35:45 +03:00
5fd2cf1b8c Merge pull request 'fix1607' (#29) from fix1607 into main
Reviewed-on: #29
2025-07-16 14:29:58 +03:00
2703137ca1 fix1607 2025-07-16 14:28:47 +03:00
3e98f8fed6 Добавлено получение баннеров для главного слайдера с использованием GraphQL. Обновлен компонент HeroSlider для отображения активных баннеров с сортировкой. Реализована логика отображения дефолтного баннера при отсутствии данных. Обновлены стили и структура компонента для улучшения пользовательского интерфейса. 2025-07-15 09:03:32 +03:00
9c152501db Merge pull request 'fix1407' (#28) from fix1407 into main
Reviewed-on: #28
2025-07-14 10:45:51 +03:00
47844749eb fix1407 2025-07-14 10:45:27 +03:00
074eb120b4 Merge remote changes, resolve conflicts in BottomHead.tsx 2025-07-14 10:03:35 +03:00
4dfc081214 Добавлено получение истории поиска с автодополнением в компоненте Header. Обновлены обработчики ввода для управления отображением истории и плейсхолдера. Внедрен запрос для получения последних поисковых запросов. Обновлены стили и логика отображения в компоненте Header. 2025-07-14 10:01:09 +03:00
d95d008c0c Merge pull request 'coolie' (#27) from cookie into main
Reviewed-on: #27
2025-07-14 01:07:32 +03:00
657016731c coolie 2025-07-14 01:06:42 +03:00
87339d577e Удалены временные стили и отладочные логи для навигационных иконок в компоненте BottomHead. Обновлен рендеринг иконок с использованием условного отображения для IMG и SVG. 2025-07-13 21:44:49 +03:00
ad5dcc03e3 Добавлено получение навигационных категорий с иконками и обновление логики отображения иконок в компоненте BottomHead. Обновлены типы данных и стили для навигационных иконок. Оптимизирована загрузка групп для категорий в компоненте BottomHeadPartsIndex. 2025-07-13 21:42:06 +03:00
132e39b87e Merge pull request 'fix1207' (#26) from fix1207 into main
Reviewed-on: #26
2025-07-12 22:40:15 +03:00
aef3915dde fix1207 2025-07-12 22:39:47 +03:00
e22828039f Merge pull request 'footer' (#25) from footer into main
Reviewed-on: #25
2025-07-12 21:33:32 +03:00
d25970946c footer 2025-07-12 21:32:09 +03:00
320b7500e0 Merge pull request 'fix1207' (#24) from 1207 into main
Reviewed-on: #24
2025-07-12 18:22:10 +03:00
cebe3a10ac fix1207 2025-07-12 18:21:09 +03:00
791152a862 Исправление UI страницы результатов поиска при отсутствии результатов
- Скрыт компонент InfoSearch когда нет результатов поиска
- Изменен текст с 'Найдено 0 предложений от -' на 'Ничего не найдено'
- Убраны фильтры и форма поиска внизу страницы при отсутствии результатов
- Разрешен конфликт в ProfileHistoryItem.tsx
2025-07-11 08:32:18 +03:00
b11142ad0f Добавлено условное отображение компонента InfoSearch и мобильных фильтров только при наличии результатов поиска. Обновлена логика отображения информации о найденных предложениях в компоненте InfoSearch. 2025-07-11 08:31:05 +03:00
508ad8cff3 Merge pull request '1234' (#23) from 1234 into main
Reviewed-on: #23
2025-07-11 03:02:39 +03:00
53a398a072 fix11072025 histitem 2025-07-11 02:54:25 +03:00
268e6d3315 fix11072025 2025-07-11 02:49:25 +03:00
26e4a95ae4 Удалены уведомления об удалении товара из избранного в компонентах BestPriceItem и TopSalesItem. Добавлен новый запрос GraphQL для получения новых поступлений, реализована логика загрузки и отображения данных в компоненте NewArrivalsSection. Обновлены компоненты ProfileHistoryItem и ProfileHistoryMain для поддержки новых пропсов и пагинации. Улучшено взаимодействие с пользователем через обработку кликов и отображение состояния загрузки. 2025-07-11 02:42:46 +03:00
9fc7d0fbf5 Удален временный вывод количества точек в компоненте KnotIn для упрощения кода и улучшения читаемости. 2025-07-11 01:51:04 +03:00
7abe016f0f Завершение rebase с обновленными компонентами 2025-07-11 01:49:13 +03:00
90d1beb15e Merge pull request 'fix1107' (#21) from fix1107 into main
Reviewed-on: #21
2025-07-11 01:23:26 +03:00
475b02ea2d fix1107 2025-07-11 01:22:55 +03:00
ed76c97915 Merge pull request 'fix1007' (#20) from fix1007 into main
Reviewed-on: #20
2025-07-10 17:22:39 +03:00
3b5defe3d9 fix1007 2025-07-10 17:21:51 +03:00
c703fc839a Добавлены новые компоненты для отображения лучших цен и товаров дня с использованием GraphQL. Реализована логика загрузки данных, обработка ошибок и отображение состояния загрузки. Обновлены компоненты BestPriceSection, ProductOfDaySection и TopSalesSection для интеграции с новыми запросами. Улучшено взаимодействие с пользователем через уведомления и обработку кликов. 2025-07-10 00:13:51 +03:00
2a983c956c Merge pull request 'изменение хэдера и исправление ключеых багов' (#19) from mobile into main
Reviewed-on: #19
2025-07-08 17:33:15 +03:00
96e11b0220 изменение хэдера и исправление ключеых багов 2025-07-08 17:27:26 +03:00
8055886082 Исправлены расчеты цен в корзине и добавлена логика загрузки единиц для категорий. Обновлены компоненты CartInfo, CartList, CartSummary, VinCategory и VinLeftbar для корректного отображения итоговых цен и улучшения взаимодействия с пользователем. 2025-07-08 13:25:49 +03:00
f0e873fdd1 Merge pull request 'добавлен роутинг между категориями' (#18) from router into main
Reviewed-on: #18
2025-07-08 01:55:26 +03:00
029dbb7732 добавлен роутинг между категориями 2025-07-08 01:54:26 +03:00
212e3f5dda Merge pull request 'добавление логики карточкам' (#17) from pricecard into main
Reviewed-on: #17
2025-07-08 00:13:06 +03:00
b318fbd779 добавление логики карточкам 2025-07-08 00:12:31 +03:00
ea097d9df8 Merge pull request 'адаптация мобилок от 1743' (#16) from adapt into main
Reviewed-on: #16
2025-07-07 17:44:12 +03:00
aeff49ae78 адаптация мобилок от 1743 2025-07-07 17:43:33 +03:00
391d47ed2b Добавлены новые элементы в меню профиля, включая настройки cookies. Внедрен компонент CookieConsent в приложение для управления согласиями на использование cookies. Добавлена анимация для отображения уведомления о cookies. 2025-07-06 20:39:07 +03:00
a67a4438ad Обновлены мета-теги для страниц профиля и оплаты, заменены статические мета-теги на компонент MetaTags для динамического получения данных. Это улучшает SEO и упрощает управление мета-тегами на этих страницах. 2025-07-06 20:23:49 +03:00
c7ba306a57 Удалена старая версия главной страницы '/home-new' и обновлены мета-теги для главной страницы. Теперь главная страница использует динамическое получение мета-данных через getMetaByPath. Это улучшает SEO и упрощает управление мета-тегами. 2025-07-06 18:51:43 +03:00
8284385e3c Добавлена микроразметка для улучшения SEO на страницах каталога, карточки товара, о компании и контактов. Внедрены схемы Organization, Product, BreadcrumbList и LocalBusiness для соответствующих страниц. Обновлены компоненты для поддержки новых атрибутов микроразметки. 2025-07-06 18:46:00 +03:00
2b5f787fbe Обновлены страницы с мета-тегами: заменены статические мета-теги на компонент MetaTags, который динамически получает данные через getMetaByPath. Добавлены новые страницы с мета-тегами, включая карточку товара, страницы оплаты и профиля, а также обновлены существующие страницы для улучшения SEO. 2025-07-06 18:36:45 +03:00
08ae507c36 Обновлены страницы с мета-тегами: заменены статические мета-теги на компонент MetaTags, который динамически получает данные через getMetaByPath. Изменен язык документа на русский в _document.tsx. Добавлены мета-теги для страниц: brands, catalog, contacts, index, search-result, thankyoupage, vin, wholesale и vehicle-search. 2025-07-06 18:13:13 +03:00
795ebf875a Merge pull request '1318' (#15) from 1318 into main
Reviewed-on: #15
2025-07-06 13:23:24 +03:00
fd0000d77e ой 2025-07-06 13:22:46 +03:00
97422f7c4b демо исправление карточки 2025-07-06 13:21:45 +03:00
7d9f611fe5 Merge pull request 'демо исправление карточки' (#14) from 1318 into main
Reviewed-on: #14
2025-07-06 13:20:06 +03:00
8820f4e835 демо исправление карточки 2025-07-06 13:18:53 +03:00
ac7b2de49f Обновлена логика добавления товаров в корзину во всех компонентах. Теперь добавление происходит асинхронно с обработкой успешных и ошибочных результатов. Добавлена информация о наличии товара при добавлении в корзину. Улучшены уведомления о добавлении товара с учетом статуса операции. 2025-07-06 02:21:33 +03:00
a8c8ae60bb Добавлен компонент CartIcon и обновлены уведомления о добавлении товара в корзину во всех соответствующих компонентах. Изменены стили текста и иконки в уведомлениях для улучшения визуального восприятия. 2025-07-05 18:38:12 +03:00
78e17a94ab Исправлены размеры SVG в компонентах CartRecommendedProductCard, Footer и Header. Обновлены атрибуты width и height для корректного отображения и устранения опечаток. В компоненте VinQuick улучшена уникальность ключей при отображении деталей. 2025-07-05 18:00:31 +03:00
36c5990921 Улучшена обработка SSD в компонентах QuickDetailSection, UnitDetailsSection и KnotIn. Добавлены отладочные логи для отслеживания значений SSD и состояния загрузки данных. Обновлены условия пропуска запросов в зависимости от наличия SSD. Исправлена логика передачи SSD в компонент KnotIn с использованием значения узла или родительского SSD. 2025-07-05 13:35:49 +03:00
e989d402a3 Добавлены отладочные логи в компонент WizardSearchForm для отслеживания обновлений и сброса параметров. Обновлена логика автовыбора параметров с учетом состояния загрузки. В компонент VinLeftbar добавлена загрузка единиц для категории при открытии. Улучшена обработка SSD при сбросе параметров. 2025-07-05 12:55:08 +03:00
65710a35be Merge pull request 'newpravki' (#13) from newpravki into main
Reviewed-on: #13
2025-07-04 21:52:14 +03:00
9a604b39b3 переделаны счетчки фильтр рэндж, настроены выборы категорий и подкатегорий 2025-07-04 21:51:28 +03:00
d6d086299f Создание прототипа главной страницы home-new, изменение стилей на остальных страницах. Требуется дальнейшее тестирование. логика выбора авто сделана 2025-07-04 18:51:57 +03:00
5b8ff6c02e Добавлены отладочные логи для отслеживания значений SSD в компонентах QuickDetailSection и UnitDetailsSection. Обновлены GraphQL запросы для включения поля ssd. Отключено кэширование для получения актуальных данных SSD в UnitDetailsSection. 2025-07-03 15:35:37 +03:00
513163b174 Добавлены новые компоненты Header и MobileMenuBottomSection, улучшено отображение информации о производителях. Упрощены классы и улучшена читаемость кода. Обновлены заголовки и сообщения об ошибках для лучшего пользовательского опыта. 2025-07-03 14:24:02 +03:00
8aa5ab007d Удалены модальные окна выбора бренда из компонентов PartDetailCard, KnotParts, VinPartCard и VinQuick. Вместо этого добавлена логика для перехода на страницу выбора бренда при клике на деталь. Обновлены компоненты для передачи параметров catalogCode и vehicleId. Исправлены типы и улучшена читаемость кода. 2025-07-02 18:03:37 +03:00
a9e4b74179 Обновлены компоненты VehicleSearchResults и InfoVin для поддержки отображения дополнительных атрибутов автомобиля. В VehicleSearchResults добавлена логика для отображения атрибутов из API, а в InfoVin реализован вывод основных параметров и tooltip с полной информацией об автомобиле. Обновлены GraphQL запросы для получения атрибутов. Исправлены типы для LaximoVehicleSearchResult. 2025-07-02 17:52:55 +03:00
35d807f234 Исправлено определение типов для inputRefs в компоненте WizardSearchForm: добавлено поддержка null для HTMLInputElement. 2025-07-02 17:02:31 +03:00
f71672dfa8 Исправление GraphQL ошибок: удаление несуществующих полей из запросов Laximo
- Удалены несуществующие поля doors, frame, frames, framefrom, frameto, engine1, engine2, attributes
- Обновлены GraphQL запросы для соответствия схеме
- Обновлены TypeScript типы LaximoVehicleSearchResult
- Сохранены улучшения UI из удаленного репозитория
- Исправлены ApolloError при поиске автомобилей
2025-07-02 16:36:27 +03:00
cfddecd686 Merge pull request '0207' (#11) from jule into main
Reviewed-on: #11
2025-07-02 16:31:18 +03:00
985ba8aeb1 0207 2025-07-02 16:28:37 +03:00
58991e4226 Обновлены компоненты для улучшения обработки групп быстрого поиска и деталей узлов. Внесены изменения в логику отображения подкатегорий в VinCategory и VinLeftbar, добавлены комментарии для ясности. Исправлены условия пропуска запросов и обновлены типы для поддержки SSD узлов. 2025-07-01 22:24:59 +03:00
1613732775 Изменен порт фронтенда в файле docker-compose.yml с использования переменной окружения на фиксированное значение 3001. Обновлены комментарии для ясности конфигурации. 2025-06-30 19:01:11 +03:00
66d443c58a Добавлены переменные окружения для порта фронтенда и режима работы, обновлены комментарии в файле docker-compose.yml для улучшения читаемости конфигурации. 2025-06-30 18:31:49 +03:00
8e7c398b95 Обновлены компоненты для обработки необязательных свойств и добавлены заглушки для случаев отсутствия данных о транспортном средстве и деталях. Изменены условия пропуска запросов в компонентах InfoVin, KnotIn, KnotParts, VinCategory и VinLeftbar. 2025-06-30 17:42:36 +03:00
185 changed files with 17534 additions and 7750 deletions

View File

@ -10,15 +10,28 @@ services:
NEXT_PUBLIC_UPLOAD_URL: ${NEXT_PUBLIC_UPLOAD_URL:-http://localhost:4000/upload}
NEXT_PUBLIC_MAINTENANCE_MODE: ${NEXT_PUBLIC_MAINTENANCE_MODE:-false}
NEXT_PUBLIC_YANDEX_MAPS_API_KEY: ${NEXT_PUBLIC_YANDEX_MAPS_API_KEY}
PARTSAPI_URL: ${PARTSAPI_URL:-https://api.parts-index.com}
FRONTEND_PORT: ${FRONTEND_PORT:-3000}
NODE_ENV: ${NODE_ENV:-production}
container_name: protekauto-frontend
ports:
- "3001:3000"
environment:
- NODE_ENV=production
# Порт приложения
- FRONTEND_PORT=${FRONTEND_PORT:-3000}
# Окружение
- NODE_ENV=${NODE_ENV:-production}
# API конфигурация
- NEXT_PUBLIC_CMS_GRAPHQL_URL=${NEXT_PUBLIC_CMS_GRAPHQL_URL:-http://localhost:4000/graphql}
- NEXT_PUBLIC_UPLOAD_URL=${NEXT_PUBLIC_UPLOAD_URL:-http://localhost:4000/upload}
- NEXT_PUBLIC_MAINTENANCE_MODE=${NEXT_PUBLIC_MAINTENANCE_MODE:-false}
- PARTSAPI_URL=${PARTSAPI_URL:-https://api.parts-index.com}
# Yandex Maps API
- NEXT_PUBLIC_YANDEX_MAPS_API_KEY=${NEXT_PUBLIC_YANDEX_MAPS_API_KEY}
restart: unless-stopped
networks:
- protekauto-network

92
package-lock.json generated
View File

@ -16,6 +16,7 @@
"@types/uuid": "^10.0.0",
"graphql": "^16.11.0",
"next": "15.3.3",
"node-fetch": "^3.3.2",
"qrcode": "^1.5.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@ -1540,6 +1541,15 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
@ -1602,6 +1612,29 @@
"node": ">=6"
}
},
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@ -1615,6 +1648,18 @@
"node": ">=8"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"license": "MIT",
"dependencies": {
"fetch-blob": "^3.1.2"
},
"engines": {
"node": ">=12.20.0"
}
},
"node_modules/fraction.js": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@ -2140,6 +2185,44 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@ -2690,6 +2773,15 @@
"uuid": "dist/esm/bin/uuid"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",

View File

@ -17,6 +17,7 @@
"@types/uuid": "^10.0.0",
"graphql": "^16.11.0",
"next": "15.3.3",
"node-fetch": "^3.3.2",
"qrcode": "^1.5.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6836c4d0a840357cccfad382" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6836c4d0a840357cccfad382" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>About</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6849d22099f3f43006edf7a6" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6849d22099f3f43006edf7a6" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>brand</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6834155694a300ddf57ddb5f" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6834155694a300ddf57ddb5f" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Search result</title>
@ -228,7 +228,7 @@
<h1 class="heading-bi">Аккумуляторная батарея SPEEDMATE AGM 60А</h1>
<div class="div-block-127">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
</div>
@ -439,7 +439,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -468,7 +468,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -497,7 +497,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -526,7 +526,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -555,7 +555,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -584,7 +584,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6835795623b8021d75a042a1" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6835795623b8021d75a042a1" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Cart Step 2</title>
@ -214,7 +214,7 @@
<a href="#" class="button-for-mobile-menu-block w-inline-block">
<div class="block-for-moble-menu-icon">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="name-mobile-menu-item">Избранное</div>
@ -829,7 +829,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -858,7 +858,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -887,7 +887,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -916,7 +916,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -945,7 +945,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -974,7 +974,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="68341f3cfbb7c099cf08ee05" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="68341f3cfbb7c099cf08ee05" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Cart</title>
@ -214,7 +214,7 @@
<a href="#" class="button-for-mobile-menu-block w-inline-block">
<div class="block-for-moble-menu-icon">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="name-mobile-menu-item">Избранное</div>
@ -1123,7 +1123,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -1152,7 +1152,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -1181,7 +1181,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -1210,7 +1210,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -1239,7 +1239,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -1268,7 +1268,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="68258c4d57e731a25c8493c1" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="68258c4d57e731a25c8493c1" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Catalog</title>
@ -214,7 +214,7 @@
<a href="#" class="button-for-mobile-menu-block w-inline-block">
<div class="block-for-moble-menu-icon">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="name-mobile-menu-item">Избранное</div>
@ -478,7 +478,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -507,7 +507,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -536,7 +536,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -565,7 +565,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -594,7 +594,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -623,7 +623,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -652,7 +652,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -681,7 +681,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -710,7 +710,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -739,7 +739,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -768,7 +768,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -797,7 +797,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -826,7 +826,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -855,7 +855,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -884,7 +884,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -913,7 +913,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -942,7 +942,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -971,7 +971,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -1000,7 +1000,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -1029,7 +1029,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b281fe71c1c06a5b41" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b281fe71c1c06a5b41" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Protek</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="68455d9b5af7aa0e7725a609" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="68455d9b5af7aa0e7725a609" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>contacts</title>

View File

@ -4601,7 +4601,6 @@ body {
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
margin-bottom: 80px;
display: flex;
}
@ -6533,6 +6532,14 @@ body {
margin-left: 30px;
}
.flex-block-108-copy {
grid-column-gap: 18px;
grid-row-gap: 18px;
justify-content: space-between;
align-items: flex-start;
overflow: scroll;
}
@media screen and (min-width: 1440px) {
.body {
--_fonts---font-family: Onest, sans-serif;
@ -6838,6 +6845,10 @@ body {
margin-top: 0;
}
.ci1:hover {
background-color: var(--light-blue);
}
.vinleftbar {
width: 320px;
}
@ -6855,18 +6866,32 @@ body {
max-width: none;
}
.flex-block-118 {
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.heading-20 {
font-size: 48px;
}
.flex-block-119 {
width: 520px;
width: 480px;
}
.bestpriceitem.end {
display: flex;
}
.flex-block-121 {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.ci2:hover, .ci3:hover, .ci4:hover, .ci5:hover, .ci6:hover, .ci7:hover, .ci8:hover, .ci9:hover {
background-color: var(--light-blue);
}
.flex-block-124 {
width: 540px;
}
@ -7251,6 +7276,7 @@ body {
}
.slider {
align-self: stretch;
height: auto;
display: flex;
}
@ -7348,6 +7374,8 @@ body {
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
padding-top: 40px;
padding-bottom: 40px;
}
.div-block-12, .div-block-12.small {
@ -8032,8 +8060,12 @@ body {
margin-top: 0;
}
.code-embed-15 {
width: 160px;
}
.topnav {
margin-left: 0;
margin-left: 190px;
}
.topmenub {
@ -8061,6 +8093,10 @@ body {
padding-bottom: 40px;
}
.div-block-128 {
background-position: 0%;
}
.catnav {
padding-left: 30px;
padding-right: 30px;
@ -8089,10 +8125,19 @@ body {
flex-flow: row;
}
.supportheading {
font-size: 30px;
}
.image-5-copy {
object-fit: contain;
}
.image-27 {
margin-bottom: -280px;
margin-left: 530px;
}
.container-copy, .container-copy.nav, .container-copy.info {
padding-left: 0;
padding-right: 0;
@ -8324,6 +8369,10 @@ body {
align-items: center;
}
.flex-block-26 {
min-width: 120px;
}
.container2 {
padding: 20px 30px;
}
@ -8976,6 +9025,18 @@ body {
padding-bottom: 90px;
}
.div-block-129 {
justify-content: flex-start;
align-self: stretch;
align-items: center;
}
.flex-block-108 {
flex-flow: column;
justify-content: space-between;
align-items: center;
}
.container-vin {
padding-top: 20px;
}
@ -8989,7 +9050,24 @@ body {
padding-bottom: 90px;
}
.inbt, .news-index-block-copy {
.inbt {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.flex-block-123 {
flex-flow: column;
}
.flex-block-124 {
flex: 1;
}
.image-27 {
margin-left: 330px;
}
.news-index-block-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
@ -9349,7 +9427,7 @@ body {
}
.heading_news {
line-height: 28px;
line-height: 20px;
}
.text-block-20 {
@ -9881,6 +9959,10 @@ body {
padding-bottom: 5px;
}
.icon-setting {
border: 0 #000;
}
.section-3 {
padding-left: 15px;
padding-right: 15px;
@ -10246,11 +10328,16 @@ body {
}
.favcardcat {
border-style: none;
width: 30px;
height: 30px;
margin-top: 0;
}
.code-embed-15 {
display: none;
}
.topnav {
margin-left: 0;
top: 58px;
@ -10319,6 +10406,17 @@ body {
padding: 40px 0 90px;
}
.div-block-129 {
justify-content: flex-start;
align-items: flex-start;
width: 100%;
height: auto;
}
.flex-block-109 {
margin-bottom: 10px;
}
.saletag {
padding-top: 3px;
padding-bottom: 3px;
@ -10326,6 +10424,15 @@ body {
top: -15px;
}
.flex-block-110 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column-reverse;
justify-content: flex-start;
align-items: flex-start;
margin-bottom: 20px;
}
.catnav {
padding-left: 15px;
padding-right: 15px;
@ -10452,16 +10559,17 @@ body {
}
.submit-button-copy {
align-self: stretch;
padding: 15px 30px;
align-self: auto;
padding: 15px 25px;
}
.supportheading {
font-size: var(--_fonts---font-size--heading-2);
width: 220px;
}
.image-5-copy {
width: 100%;
width: 60%;
height: 100%;
min-height: auto;
}
@ -10470,6 +10578,10 @@ body {
margin-bottom: 10px;
}
.image-27 {
display: none;
}
.container-copy {
max-width: 100%;
margin-left: 0;

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b281fe71c1c06a5aec" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b281fe71c1c06a5aec" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Protek</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b281fe71c1c06a5b04" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b281fe71c1c06a5b04" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Protek</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b281fe71c1c06a5af0" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b281fe71c1c06a5af0" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Protek</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="683564cb54d9f20bcc1ef4b9" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="683564cb54d9f20bcc1ef4b9" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Favorite</title>
@ -605,7 +605,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -634,7 +634,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -663,7 +663,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -692,7 +692,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -721,7 +721,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="685be6dfd87db2e01cbdb7a2" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="685be6dfd87db2e01cbdb7a2" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Home New</title>
@ -20,7 +20,7 @@
<section class="topmenuh">
<div class="w-layout-blockcontainer container nav w-container">
<div class="w-layout-hflex flex-block-93">
<div class="code-embed-15 w-embed"><svg width="190" height="72" viewbox="0 0 190 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<div class="code-embed-15 w-embed"><svg width="currentwidht" height="currentheight" viewbox="0 0 190 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M138.377 29.5883V23.1172H112.878V29.5883H138.377Z" fill="white"></path>
<path d="M107.423 18.1195C109.21 18.1195 110.658 16.6709 110.658 14.884C110.658 13.097 109.21 11.6484 107.423 11.6484L88.395 11.6484C86.6082 11.6484 85.1596 13.097 85.1596 14.884C85.1596 16.6709 86.6082 18.1195 88.395 18.1195H107.423Z" fill="white"></path>
<path d="M130.288 34.2491C128.773 35.3865 126.89 36.0628 124.852 36.0628C119.849 36.0628 115.791 32.0052 115.791 27.0013C115.791 21.9974 119.849 17.9399 124.852 17.9399C129.856 17.9399 133.913 21.9974 133.913 27.0013C133.913 27.9022 133.779 28.7696 133.536 29.5893H140.169C140.31 28.7481 140.384 27.8831 140.384 27.0013C140.384 18.4226 133.431 11.4688 124.852 11.4688C116.274 11.4688 109.32 18.4226 109.32 27.0013C109.32 35.5801 116.274 42.5339 124.852 42.5339C129.249 42.5339 133.218 40.7058 136.045 37.769L130.288 34.2491Z" fill="white"></path>
@ -284,7 +284,7 @@
</section>
<section class="catnav">
<div class="w-layout-blockcontainer batd w-container">
<div class="w-layout-hflex flex-block-108">
<div class="w-layout-hflex flex-block-108-copy">
<div class="ci1">
<div class="text-block-54-copy">Детали для ТО</div>
</div>
@ -456,7 +456,7 @@
<div class="w-layout-vflex bestpriceitem">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="imgitembp"><img width="Auto" height="Auto" alt="" src="images/162615.webp" loading="lazy" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -482,7 +482,7 @@
<div class="w-layout-vflex bestpriceitem">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="imgitembp"><img width="Auto" height="Auto" alt="" src="images/162615.webp" loading="lazy" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -508,7 +508,7 @@
<div class="w-layout-vflex bestpriceitem">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="imgitembp"><img width="Auto" height="Auto" alt="" src="images/162615.webp" loading="lazy" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -534,7 +534,7 @@
<div class="w-layout-vflex bestpriceitem">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="imgitembp"><img width="Auto" height="Auto" alt="" src="images/162615.webp" loading="lazy" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -560,7 +560,7 @@
<div class="w-layout-vflex bestpriceitem">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="imgitembp"><img width="Auto" height="Auto" alt="" src="images/162615.webp" loading="lazy" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -597,7 +597,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -626,7 +626,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -655,7 +655,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -684,7 +684,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -713,7 +713,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -742,7 +742,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -791,7 +791,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -820,7 +820,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -849,7 +849,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -878,7 +878,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -907,7 +907,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -936,7 +936,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -970,7 +970,7 @@
<div class="w-layout-blockcontainer container-copy w-container"><img src="images/support_img.png" loading="lazy" alt="" class="image-27">
<div class="div-block-11">
<div class="w-layout-vflex flex-block-30">
<h3 class="supportheading">МЫ ВСЕГДА РАДЫ ПОМОЧЬ</h3>
<h3 class="supportheading">МЫ ВСЕГДА РАДЫ ПОМОЧЬ</h3>
<div class="text-block-19">Если вам нужна помощь с подбором автозапчастей, то воспользуйтесь формой VIN-запроса. Введите идентификационный номер (VIN) вашего автомобиля — и мы найдём нужную деталь.</div>
</div>
<a href="#" class="submit-button-copy w-button">Отправить VIN-запрос</a>
@ -1173,6 +1173,51 @@
</div>
</div>
</footer>
<nav class="mobile-menu-buttom-section">
<div class="w-layout-blockcontainer mobile-menu-bottom w-container">
<div class="w-layout-hflex flex-block-87">
<a href="#" class="button-for-mobile-menu-block w-inline-block">
<div class="block-for-moble-menu-icon">
<div class="icon-setting w-embed"><svg width="30" height="30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M27 10.8V24H24.6V13.2H5.4V24H3V10.8L15 6L27 10.8ZM23.4 14.4H6.6V16.8H23.4V14.4ZM23.4 18H6.6V20.4H23.4V18Z" fill="currentColor"></path>
<path d="M6.6 21.6H23.4V24H6.6V21.6Z" fill="currentColor"></path>
</svg></div>
</div>
<div class="name-mobile-menu-item">Гараж</div>
</a>
<a href="#" class="button-for-mobile-menu-block w-inline-block">
<div class="block-for-moble-menu-icon">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="name-mobile-menu-item">Избранное</div>
</a>
<a href="#" class="button-for-mobile-menu-block w-inline-block">
<div class="block-for-moble-menu-icon">
<div class="icon-setting w-embed"><svg width="currentWidht" height="currentHeight" viewbox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor"></path>
</svg></div>
<div class="pcs-info">
<div class="text-block-39">12</div>
</div>
</div>
<div class="name-mobile-menu-item">Корзина</div>
</a>
<a href="#" class="button-for-mobile-menu-block w-inline-block">
<div class="block-for-moble-menu-icon">
<div class="icon-setting w-embed"><svg width="currentwidht" height="currentheight" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 3C8.376 3 3 8.376 3 15C3 21.624 8.376 27 15 27C21.624 27 27 21.624 27 15C27 8.376 21.624 3 15 3ZM15 7.8C17.316 7.8 19.2 9.684 19.2 12C19.2 14.316 17.316 16.2 15 16.2C12.684 16.2 10.8 14.316 10.8 12C10.8 9.684 12.684 7.8 15 7.8ZM15 24.6C12.564 24.6 9.684 23.616 7.632 21.144C9.73419 19.4955 12.3285 18.5995 15 18.5995C17.6715 18.5995 20.2658 19.4955 22.368 21.144C20.316 23.616 17.436 24.6 15 24.6Z" fill="currentColor"></path>
</svg></div>
<div class="info-satus">
<div class="text-block-39">!</div>
</div>
</div>
<div class="name-mobile-menu-item">Кабинет</div>
</a>
</div>
</div>
</nav>
<script src="https://d3e54v103j8qbb.cloudfront.net/js/jquery-3.5.1.min.dc5e7f18c8.js?site=6800f7e35fcfd4ca3b323269" type="text/javascript" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="js/webflow.js" type="text/javascript"></script>
</body>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f7e35fcfd4ca3b3232bc" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f7e35fcfd4ca3b3232bc" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Protek</title>
@ -491,7 +491,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -520,7 +520,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -549,7 +549,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -578,7 +578,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -607,7 +607,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -636,7 +636,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -690,7 +690,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -719,7 +719,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -748,7 +748,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -777,7 +777,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -806,7 +806,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -835,7 +835,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">

View File

@ -273,7 +273,7 @@ spurious results.`)}}return!1};class z{constructor(e,t="GraphQL request",n={line
count
product {
id
f_name_: name
f_name_
}
sku {
id
@ -285,7 +285,7 @@ spurious results.`)}}return!1};class z{constructor(e,t="GraphQL request",n={line
}
}
}
`,w=(e,t)=>e.query({query:N,variables:{finalizedOrder:t}}).then(e=>e?.data?.database?.commerceOrder),R=(e,t)=>{if("undefined"==typeof fbq&&"undefined"==typeof gtag)return;let n={};try{let e=window.localStorage.getItem("wf-seen-orders");e&&(n=JSON.parse(e))}catch(e){return}n[t.orderId]||w(e,t).then(e=>{if(!e)return;let{decimalValue:r,unit:i}=e.total;"undefined"!=typeof fbq&&"function"==typeof fbq&&fbq("track","Purchase",{value:r,currency:i,content_ids:(e.userItems||[]).map(e=>e.sku.id),content_type:"product",contents:(e.userItems||[]).map(e=>({id:e.sku.id,quantity:e.count,item_price:e.price.decimalValue}))}),"undefined"!=typeof gtag&&"function"==typeof gtag&&gtag("event","purchase",{transaction_id:e.id,value:r,currency:i,items:(e.userItems||[]).map(e=>({id:e.sku.id,name:e.product.f_name_,quantity:e.count,price:e.price.decimalValue}))}),n[t.orderId]=!0;try{window.localStorage.setItem("wf-seen-orders",JSON.stringify(n))}catch(e){return}})}},14155:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"PillGroups",{enumerable:!0,get:function(){return a}});let r=n(10873),i=Object.freeze({RETURN:13,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40});class a{form;pillGroups;onSelect;static hasPillGroups(e){return e.querySelectorAll(`[${r.DATA_ATTR_NODE_TYPE}="${r.NODE_TYPE_COMMERCE_ADD_TO_CART_PILL_GROUP}"]`).length>0}constructor(e,t){this.form=e,this.pillGroups={},this.onSelect=t}init(){for(let e of this.form.querySelectorAll(`[${r.DATA_ATTR_NODE_TYPE}="${r.NODE_TYPE_COMMERCE_ADD_TO_CART_PILL_GROUP}"]`)){let t=new o(e,this.onSelect,this);t.init(),this.pillGroups[t.optionSetId]=t}}setSelectedPillsForSkuValues(e){for(let t of Object.keys(e)){let n=e[t],r=this.pillGroups[t];if(r){let e=r.findPillById(String(n));r.updatePillsWithNewSelected(e)}}}}class o{node;optionSetId;onSelect;pills;groups;constructor(e,t,n){this.node=e,this.optionSetId=String(e.getAttribute(r.DATA_ATTR_COMMERCE_OPTION_SET_ID)),this.onSelect=t,this.pills=[],this.groups=n}get firstEnabledPill(){return this.pills.find(e=>!1===e.disabled)}get value(){let e=this.pills.find(e=>!0===e.checked);return e?e.value:""}get options(){return this.pills}set selectedIndex(e){let t=this.pills[e]||null;this.emitSelected(t)}getAttribute(e){if(e===r.DATA_ATTR_COMMERCE_OPTION_SET_ID)return this.optionSetId;throw Error(`PillGroup: Attempted to fetch unsupported attribute ${e}`)}init(){let e=this.node.querySelectorAll(`[${r.DATA_ATTR_NODE_TYPE}="${r.NODE_TYPE_COMMERCE_ADD_TO_CART_PILL}"]`);this.pills=Array.from(e).map(e=>{let t=new s(e,this);return t.init(),t}),this.firstEnabledPill&&(this.firstEnabledPill.tabIndex=0),this.node._wfPillGroup=this}findPillById(e){return this.pills.find(t=>t.optionId===e)}updatePillsWithNewSelected(e){for(let e of this.pills)e.tabIndex=-1,e.checked=!1;e instanceof s?(e.tabIndex=0,e.checked=!0):this.firstEnabledPill&&(this.firstEnabledPill.tabIndex=0)}emitSelected(e){this.onSelect({optionId:e.optionId,optionSetId:this.optionSetId,groups:Object.values(this.groups.pillGroups)})}traverseAndEmitSelected(e,t){let n,r=this.pills.indexOf(e),i=!1,a=r;for(;!i;){if("previous"===t)(n=a-1)<0&&(n=this.pills.length-1);else if("next"===t)(n=a+1)===this.pills.length&&(n=0);else throw Error(`Unknown pill traversal direction "${t}", use "previous" or "next"`);if(n===r)break;let e=this.pills[n];e.disabled?a=n:(this.emitSelected(e),e.focus(),i=!0)}}}class s{node;optionId;group;constructor(e,t){this.node=e,this.optionId=String(this.node.getAttribute("data-option-id")),this.group=t}init(){this.tabIndex=-1,this.checked=!1,this.node.addEventListener("keydown",this.handleKeyDown),this.node.addEventListener("click",this.handleClick)}get tabIndex(){return this.node.tabIndex}set tabIndex(e){this.node.tabIndex=e}get value(){return this.optionId}get checked(){return"true"===this.node.getAttribute("aria-checked")}set checked(e){this.node.setAttribute("aria-checked",String(e)),e?this.node.classList.add("w--ecommerce-pill-selected"):this.node.classList.remove("w--ecommerce-pill-selected")}get disabled(){return"true"===this.node.getAttribute("aria-disabled")}set disabled(e){this.node.setAttribute("aria-disabled",String(e)),e?(this.node.classList.add("w--ecommerce-pill-disabled"),this.checked=!1,this.tabIndex=-1):this.node.classList.remove("w--ecommerce-pill-disabled")}get enabled(){return!this.disabled}set enabled(e){this.disabled=!e}focus(){this.node.focus()}handleKeyDown=e=>{let t=!1;if(!e.altKey&&!e.metaKey){switch(e.keyCode){case i.RETURN:case i.SPACE:this.handleClick(),t=!0;break;case i.UP:case i.LEFT:this.group.traverseAndEmitSelected(this,"previous"),t=!0;break;case i.DOWN:case i.RIGHT:this.group.traverseAndEmitSelected(this,"next"),t=!0}t&&(e.stopPropagation(),e.preventDefault())}};handleClick=()=>{this.disabled||this.checked||(this.focus(),this.group.emitSelected(this))}}},85986:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return n}});let n={log:(...e)=>{},error:(...e)=>{}}},48873:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});let n=(e,t=[])=>null==e?t:t.concat(n(Object.getPrototypeOf(e))).concat(Object.keys(e)),r=(e,t)=>{let r=n(e).filter(e=>"currentTarget"!==e).reduce((t,n)=>(t[n]="function"==typeof e[n]?{value:(...t)=>e[n](...t)}:{get:()=>e[n]},t),{});return Object.create(e,{currentTarget:{value:t},...r})};class i{apolloClient;stripeStore;eventHandlers;constructor(e,t){this.eventHandlers={},this.apolloClient=e,this.stripeStore=t}on=(e,t,n)=>{let r=this.eventHandlers[e]instanceof Array?this.eventHandlers[e]:[];return this.eventHandlers[e]=[...r,this.createHandlerProxy(e,t,n)],this};createHandlerProxy=(e,t,n)=>e=>{let i=t(e),a=i instanceof Element?r(e,i):e;i&&n(a,this.apolloClient,this.stripeStore)};attachHandlers=e=>(Object.keys(this.eventHandlers).forEach(t=>{this.eventHandlers[t].forEach(n=>e.addEventListener(t,n,!0))}),this);removeHandlers=e=>(Object.keys(this.eventHandlers).forEach(t=>{this.eventHandlers[t].forEach(n=>e.removeEventListener(t,n,!0))}),this)}},2330:function(e,t,n){"use strict";let r,i,a;Object.defineProperty(t,"__esModule",{value:!0});var o={design:function(){return L},destroy:function(){return O},init:function(){return E},preview:function(){return v}};for(var s in o)Object.defineProperty(t,s,{enumerable:!0,get:o[s]});n(84037),n(68259),n(60033),n(9246),n(67321),n(52897),n(233),n(49754),n(30971),n(62374),n(55152),n(35273),n(30172),n(65723),n(48258),n(89433);let c=n(25195),u=b(n(48873)),l=b(n(84303)),d=b(n(82150)),f=b(n(82333)),p=b(n(45870)),m=b(n(94492)),_=n(5841),h=n(54556);n(67304),n(14362);let M=n(17696),y=function(e,t){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=A(t);if(n&&n.has(e))return n.get(e);var r={__proto__:null},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var o=i?Object.getOwnPropertyDescriptor(e,a):null;o&&(o.get||o.set)?Object.defineProperty(r,a,o):r[a]=e[a]}return r.default=e,n&&n.set(e,r),r}(n(86365));function b(e){return e&&e.__esModule?e:{default:e}}function A(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(A=function(e){return e?n:t})(e)}function g(){r&&r.attachHandlers(window)}function T(){r&&r.removeHandlers(window)}function E({siteId:e}){i=(0,c.createApolloClient)({path:window.Webflow.env("design")||window.Webflow.env("preview")?`/api/v2/sites/${e}/apollo`:"/.wf_graphql/apollo",retryConfig:{maxAttempts:5},useCsrf:!0}),a=new _.StripeStore(document),r=new u.default(i,a),l.default.register(r),d.default.register(r),f.default.register(r),p.default.register(r),m.default.register(r),y.default.register(r),(0,M.initializeStripeElements)(a),T(),g(),(0,h.triggerRender)(null,!0),window.Webflow.env()||window.Webflow.load((0,y.renderPaypalButtons)(i))}function v(){T(),g(),(0,h.triggerRender)(null,!0)}function L(){T(),i&&i.store&&i.resetStore()}function O(){T()}},45870:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r={default:function(){return m},register:function(){return p}};for(var i in r)Object.defineProperty(t,i,{enumerable:!0,get:r[i]});let a=l(n(28160)),o=l(n(26882)),s=n(54556),c=n(91898),u=n(10873);function l(e){return e&&e.__esModule?e:{default:e}}let d=(e,t)=>{(0,c.renderTree)(e,t)},f=(e,t)=>{if(window.Webflow.env("design")||window.Webflow.env("preview")||!(e instanceof CustomEvent&&e.type===u.RENDER_TREE_EVENT))return;let n=[],{detail:r}=e;null!=r&&r.error&&n.push(r.error);let i=(0,s.findElementByNodeType)(u.NODE_TYPE_COMMERCE_ORDER_CONFIRMATION_WRAPPER);if(!i)return;let{orderId:c,token:l}=o.default.parse(window.location.search.substring(1));if(!c||!l)return;let f={orderId:c,token:l};(0,s.trackOrder)(t,f);let p=(0,s.findAllElementsByNodeType)(u.NODE_TYPE_COMMERCE_ORDER_CONFIRMATION_WRAPPER);t.query({query:(0,a.default)`
`,w=(e,t)=>e.query({query:N,variables:{finalizedOrder:t}}).then(e=>e?.data?.database?.commerceOrder),R=(e,t)=>{if("undefined"==typeof fbq&&"undefined"==typeof gtag)return;let n={};try{let e=window.localStorage.getItem("wf-seen-orders");e&&(n=JSON.parse(e))}catch(e){return}n[t.orderId]||w(e,t).then(e=>{if(!e)return;let{decimalValue:r,unit:i}=e.total;"undefined"!=typeof fbq&&"function"==typeof fbq&&fbq("track","Purchase",{value:r,currency:i,content_ids:(e.userItems||[]).map(e=>e.sku.id),content_type:"product",contents:(e.userItems||[]).map(e=>({id:e.sku.id,quantity:e.count,item_price:e.price.decimalValue}))}),"undefined"!=typeof gtag&&"function"==typeof gtag&&gtag("event","purchase",{transaction_id:e.id,value:r,currency:i,items:(e.userItems||[]).map(e=>({id:e.sku.id,name:e.product.f_name_,quantity:e.count,price:e.price.decimalValue}))}),n[t.orderId]=!0;try{window.localStorage.setItem("wf-seen-orders",JSON.stringify(n))}catch(e){return}})}},14155:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"PillGroups",{enumerable:!0,get:function(){return a}});let r=n(10873),i=Object.freeze({RETURN:13,SPACE:32,LEFT:37,UP:38,RIGHT:39,DOWN:40});class a{form;pillGroups;onSelect;static hasPillGroups(e){return e.querySelectorAll(`[${r.DATA_ATTR_NODE_TYPE}="${r.NODE_TYPE_COMMERCE_ADD_TO_CART_PILL_GROUP}"]`).length>0}constructor(e,t){this.form=e,this.pillGroups={},this.onSelect=t}init(){for(let e of this.form.querySelectorAll(`[${r.DATA_ATTR_NODE_TYPE}="${r.NODE_TYPE_COMMERCE_ADD_TO_CART_PILL_GROUP}"]`)){let t=new o(e,this.onSelect,this);t.init(),this.pillGroups[t.optionSetId]=t}}setSelectedPillsForSkuValues(e){for(let t of Object.keys(e)){let n=e[t],r=this.pillGroups[t];if(r){let e=r.findPillById(String(n));r.updatePillsWithNewSelected(e)}}}}class o{node;optionSetId;onSelect;pills;groups;constructor(e,t,n){this.node=e,this.optionSetId=String(e.getAttribute(r.DATA_ATTR_COMMERCE_OPTION_SET_ID)),this.onSelect=t,this.pills=[],this.groups=n}get firstEnabledPill(){return this.pills.find(e=>!1===e.disabled)}get value(){let e=this.pills.find(e=>!0===e.checked);return e?e.value:""}get options(){return this.pills}set selectedIndex(e){let t=this.pills[e]||null;this.emitSelected(t)}getAttribute(e){if(e===r.DATA_ATTR_COMMERCE_OPTION_SET_ID)return this.optionSetId;throw Error(`PillGroup: Attempted to fetch unsupported attribute ${e}`)}init(){let e=this.node.querySelectorAll(`[${r.DATA_ATTR_NODE_TYPE}="${r.NODE_TYPE_COMMERCE_ADD_TO_CART_PILL}"]`);this.pills=Array.from(e).map(e=>{let t=new s(e,this);return t.init(),t}),this.firstEnabledPill&&(this.firstEnabledPill.tabIndex=0),this.node._wfPillGroup=this}findPillById(e){return this.pills.find(t=>t.optionId===e)}updatePillsWithNewSelected(e){for(let e of this.pills)e.tabIndex=-1,e.checked=!1;e instanceof s?(e.tabIndex=0,e.checked=!0):this.firstEnabledPill&&(this.firstEnabledPill.tabIndex=0)}emitSelected(e){this.onSelect({optionId:e.optionId,optionSetId:this.optionSetId,groups:Object.values(this.groups.pillGroups)})}traverseAndEmitSelected(e,t){let n,r=this.pills.indexOf(e),i=!1,a=r;for(;!i;){if("previous"===t)(n=a-1)<0&&(n=this.pills.length-1);else if("next"===t)(n=a+1)===this.pills.length&&(n=0);else throw Error(`Unknown pill traversal direction "${t}", use "previous" or "next"`);if(n===r)break;let e=this.pills[n];e.disabled?a=n:(this.emitSelected(e),e.focus(),i=!0)}}}class s{node;optionId;group;constructor(e,t){this.node=e,this.optionId=String(this.node.getAttribute("data-option-id")),this.group=t}init(){this.tabIndex=-1,this.checked=!1,this.node.addEventListener("keydown",this.handleKeyDown),this.node.addEventListener("click",this.handleClick)}get tabIndex(){return this.node.tabIndex}set tabIndex(e){this.node.tabIndex=e}get value(){return this.optionId}get checked(){return"true"===this.node.getAttribute("aria-checked")}set checked(e){this.node.setAttribute("aria-checked",String(e)),e?this.node.classList.add("w--ecommerce-pill-selected"):this.node.classList.remove("w--ecommerce-pill-selected")}get disabled(){return"true"===this.node.getAttribute("aria-disabled")}set disabled(e){this.node.setAttribute("aria-disabled",String(e)),e?(this.node.classList.add("w--ecommerce-pill-disabled"),this.checked=!1,this.tabIndex=-1):this.node.classList.remove("w--ecommerce-pill-disabled")}get enabled(){return!this.disabled}set enabled(e){this.disabled=!e}focus(){this.node.focus()}handleKeyDown=e=>{let t=!1;if(!e.altKey&&!e.metaKey){switch(e.keyCode){case i.RETURN:case i.SPACE:this.handleClick(),t=!0;break;case i.UP:case i.LEFT:this.group.traverseAndEmitSelected(this,"previous"),t=!0;break;case i.DOWN:case i.RIGHT:this.group.traverseAndEmitSelected(this,"next"),t=!0}t&&(e.stopPropagation(),e.preventDefault())}};handleClick=()=>{this.disabled||this.checked||(this.focus(),this.group.emitSelected(this))}}},85986:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return n}});let n={log:(...e)=>{},error:(...e)=>{}}},48873:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});let n=(e,t=[])=>null==e?t:t.concat(n(Object.getPrototypeOf(e))).concat(Object.keys(e)),r=(e,t)=>{let r=n(e).filter(e=>"currentTarget"!==e).reduce((t,n)=>(t[n]="function"==typeof e[n]?{value:(...t)=>e[n](...t)}:{get:()=>e[n]},t),{});return Object.create(e,{currentTarget:{value:t},...r})};class i{apolloClient;stripeStore;eventHandlers;constructor(e,t){this.eventHandlers={},this.apolloClient=e,this.stripeStore=t}on=(e,t,n)=>{let r=this.eventHandlers[e]instanceof Array?this.eventHandlers[e]:[];return this.eventHandlers[e]=[...r,this.createHandlerProxy(e,t,n)],this};createHandlerProxy=(e,t,n)=>e=>{let i=t(e),a=i instanceof Element?r(e,i):e;i&&n(a,this.apolloClient,this.stripeStore)};attachHandlers=e=>(Object.keys(this.eventHandlers).forEach(t=>{this.eventHandlers[t].forEach(n=>e.addEventListener(t,n,!0))}),this);removeHandlers=e=>(Object.keys(this.eventHandlers).forEach(t=>{this.eventHandlers[t].forEach(n=>e.removeEventListener(t,n,!0))}),this)}},2330:function(e,t,n){"use strict";let r,i,a;Object.defineProperty(t,"__esModule",{value:!0});var o={design:function(){return L},destroy:function(){return O},init:function(){return E},preview:function(){return v}};for(var s in o)Object.defineProperty(t,s,{enumerable:!0,get:o[s]});n(84037),n(68259),n(60033),n(9246),n(67321),n(52897),n(233),n(49754),n(30971),n(62374),n(55152),n(35273),n(30172),n(65723),n(48258),n(89433);let c=n(25195),u=b(n(48873)),l=b(n(84303)),d=b(n(82150)),f=b(n(82333)),p=b(n(45870)),m=b(n(94492)),_=n(5841),h=n(54556);n(67304),n(14362);let M=n(17696),y=function(e,t){if(e&&e.__esModule)return e;if(null===e||"object"!=typeof e&&"function"!=typeof e)return{default:e};var n=A(t);if(n&&n.has(e))return n.get(e);var r={__proto__:null},i=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var a in e)if("default"!==a&&Object.prototype.hasOwnProperty.call(e,a)){var o=i?Object.getOwnPropertyDescriptor(e,a):null;o&&(o.get||o.set)?Object.defineProperty(r,a,o):r[a]=e[a]}return r.default=e,n&&n.set(e,r),r}(n(86365));function b(e){return e&&e.__esModule?e:{default:e}}function A(e){if("function"!=typeof WeakMap)return null;var t=new WeakMap,n=new WeakMap;return(A=function(e){return e?n:t})(e)}function g(){r&&r.attachHandlers(window)}function T(){r&&r.removeHandlers(window)}function E({siteId:e}){let t=window.Webflow.env("design")||window.Webflow.env("preview");i=(0,c.createApolloClient)({path:t?`/api/v2/sites/${e}/apollo`:"/.wf_graphql/apollo",retryConfig:{maxAttempts:5},useCsrf:!0,disableBatching:!t}),a=new _.StripeStore(document),r=new u.default(i,a),l.default.register(r),d.default.register(r),f.default.register(r),p.default.register(r),m.default.register(r),y.default.register(r),(0,M.initializeStripeElements)(a),T(),g(),(0,h.triggerRender)(null,!0),window.Webflow.env()||window.Webflow.load((0,y.renderPaypalButtons)(i))}function v(){T(),g(),(0,h.triggerRender)(null,!0)}function L(){T(),i&&i.store&&i.resetStore()}function O(){T()}},45870:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r={default:function(){return m},register:function(){return p}};for(var i in r)Object.defineProperty(t,i,{enumerable:!0,get:r[i]});let a=l(n(28160)),o=l(n(26882)),s=n(54556),c=n(91898),u=n(10873);function l(e){return e&&e.__esModule?e:{default:e}}let d=(e,t)=>{(0,c.renderTree)(e,t)},f=(e,t)=>{if(window.Webflow.env("design")||window.Webflow.env("preview")||!(e instanceof CustomEvent&&e.type===u.RENDER_TREE_EVENT))return;let n=[],{detail:r}=e;null!=r&&r.error&&n.push(r.error);let i=(0,s.findElementByNodeType)(u.NODE_TYPE_COMMERCE_ORDER_CONFIRMATION_WRAPPER);if(!i)return;let{orderId:c,token:l}=o.default.parse(window.location.search.substring(1));if(!c||!l)return;let f={orderId:c,token:l};(0,s.trackOrder)(t,f);let p=(0,s.findAllElementsByNodeType)(u.NODE_TYPE_COMMERCE_ORDER_CONFIRMATION_WRAPPER);t.query({query:(0,a.default)`
${i.getAttribute(u.ORDER_QUERY)}
`,variables:{finalizedOrder:f},fetchPolicy:"network-only",errorPolicy:"all"}).then(e=>{p.forEach(t=>{d(t,{...e,errors:n.concat(e.errors).filter(Boolean)})})}).catch(e=>{n.push(e),p.forEach(e=>{d(e,{errors:n})})})},p=e=>{e.on(u.RENDER_TREE_EVENT,Boolean,f)},m={register:p}},86365:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r,i={default:function(){return T},renderPaypalButtons:function(){return g}};for(var a in i)Object.defineProperty(t,a,{enumerable:!0,get:i[a]});let o=n(54556),s=n(17696),c=n(69773),u=(r=n(85986))&&r.__esModule?r:{default:r},l=n(94797),d=n(10873),f=({target:e})=>!!((0,o.findClosestElementByNodeType)(d.NODE_TYPE_COMMERCE_CHECKOUT_PLACE_ORDER_BUTTON,e)&&e instanceof Element)&&e,p=!1,m=(e,t)=>{if(window.Webflow.env("design")||window.Webflow.env("preview")||!(e instanceof CustomEvent&&e.type===d.RENDER_TREE_EVENT))return;let n=(0,o.findAllElementsByNodeType)(d.NODE_TYPE_COMMERCE_PAYPAL_CHECKOUT_FORM_CONTAINER);if(!n||0===n.length)return;let r=[],{detail:i}=e;null!=i&&i.error&&r.push(i.error);let a=window.document.activeElement,c=(0,o.findClosestElementByNodeType)(d.NODE_TYPE_COMMERCE_PAYPAL_CHECKOUT_FORM_CONTAINER,a),u=null;a instanceof HTMLInputElement&&c&&((u=a.id)||(u=a.getAttribute("data-wf-bindings")),u=u?null:u),(p?Promise.resolve():t.mutate({mutation:l.syncPayPalOrderInfo})).then(()=>{p=!0,(0,s.renderCheckoutFormContainers)(n,r,t,void 0,u)})},_=!1,h=e=>{_=!0,window.addEventListener("beforeunload",s.beforeUnloadHandler);let t=e.innerHTML,n=e.getAttribute(d.DATA_ATTR_LOADING_TEXT);return e.innerHTML=n||d.CHECKOUT_PLACE_ORDER_LOADING_TEXT_DEFAULT,(n=!1)=>{n||(_=!1),window.removeEventListener("beforeunload",s.beforeUnloadHandler),e.innerHTML=t||d.CHECKOUT_PLACE_ORDER_BUTTON_TEXT_DEFAULT}},M=({shippingInfo:e,additionalInfo:t,requiresShipping:n})=>!HTMLFormElement.prototype.reportValidity||!(n&&!e.reportValidity()||t&&t instanceof HTMLFormElement&&!t.reportValidity()),y=(e,t)=>{if(window.Webflow.env("design")||window.Webflow.env("preview")||_)return;let{currentTarget:n}=e;if(!(n instanceof Element))return;let r=(0,o.findClosestElementByNodeType)(d.NODE_TYPE_COMMERCE_PAYPAL_CHECKOUT_FORM_CONTAINER,n);if(!(r instanceof Element))return;let i=(0,o.findElementByNodeType)(d.NODE_TYPE_COMMERCE_PAYPAL_CHECKOUT_ERROR_STATE,r),a=(0,o.findElementByNodeType)(d.NODE_TYPE_COMMERCE_CHECKOUT_SHIPPING_METHODS_WRAPPER,r),c=(0,o.findElementByNodeType)(d.NODE_TYPE_COMMERCE_CHECKOUT_PLACE_ORDER_BUTTON,r),l=(0,o.findElementByNodeType)(d.NODE_TYPE_COMMERCE_CHECKOUT_ADDITIONAL_INFO,r);if(!(i instanceof HTMLElement)||!(a instanceof HTMLFormElement)||!(c instanceof Element))return;let f=i.querySelector(d.CART_CHECKOUT_ERROR_MESSAGE_SELECTOR);if(f&&f.hasAttribute(d.NEEDS_REFRESH))return;let p=l&&l instanceof HTMLElement,m=h(c);i.style.setProperty("display","none"),(0,o.fetchOrderStatusFlags)(t).then(({requiresShipping:e})=>{if(!M({shippingInfo:a,additionalInfo:l,requiresShipping:e}))return void m();let n="";if(e&&a.elements["shipping-method-choice"]){let e=a.querySelector('input[name="shipping-method-choice"]:checked');e&&(n=e.value)}let r=p?(0,o.customDataFormToArray)(l):[];Promise.all([e?(0,s.createOrderShippingMethodMutation)(t,n):Promise.resolve(),p?(0,s.createCustomDataMutation)(t,r):Promise.resolve()]).then(()=>(0,s.createAttemptSubmitOrderRequest)(t,{checkoutType:"paypal"})).then(e=>{u.default.log(e);let t=(0,s.getOrderDataFromGraphQLResponse)(e);t.ok&&(m(!0),(0,s.redirectToOrderConfirmation)(t,!0))}).catch(e=>{if(m(),u.default.error(e),i.style.removeProperty("display"),(0,s.updateErrorMessage)(i,e),e.graphQLErrors&&e.graphQLErrors[0]&&e.graphQLErrors[0].message){let t=(0,o.safeParseJson)(e.graphQLErrors[0].message);t&&t.details&&t.details[0]&&"INSTRUMENT_DECLINED"===t.details[0].issue&&window.parent.postMessage(JSON.stringify({isWebflow:!0,type:"error",detail:t}),window.location.origin)}})})},b=`
display: block;

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="684561bc22684ab14f0094e7" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="684561bc22684ab14f0094e7" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>news open</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6836bfe4b6ece7af45e3e6f2" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6836bfe4b6ece7af45e3e6f2" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>News</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b381fe71c1c06a5b70" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b381fe71c1c06a5b70" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Protek</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6836cad8b1a5806f12459deb" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6836cad8b1a5806f12459deb" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Payments method</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b381fe71c1c06a5b52" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6800f8b381fe71c1c06a5b52" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<base target="_parent">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="68274b3afd7280478878db26" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="68274b3afd7280478878db26" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Search result</title>
@ -214,7 +214,7 @@
<a href="#" class="button-for-mobile-menu-block w-inline-block">
<div class="block-for-moble-menu-icon">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="name-mobile-menu-item">Избранное</div>
@ -251,7 +251,7 @@
<div class="text-block-53">INA 530059210</div>
<div class="fsfav">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
</div>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="682311e1c6efc75810555f64" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="682311e1c6efc75810555f64" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Search Results</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6847e8e8e1b22b8406a39690" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6847e8e8e1b22b8406a39690" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>Test</title>
@ -214,7 +214,7 @@
<a href="#" class="button-for-mobile-menu-block w-inline-block">
<div class="block-for-moble-menu-icon">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="name-mobile-menu-item">Избранное</div>
@ -478,7 +478,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -507,7 +507,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -536,7 +536,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -565,7 +565,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -594,7 +594,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -623,7 +623,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -652,7 +652,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -681,7 +681,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -710,7 +710,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -739,7 +739,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -768,7 +768,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -797,7 +797,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -826,7 +826,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -855,7 +855,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -884,7 +884,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -913,7 +913,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -942,7 +942,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">
@ -971,7 +971,7 @@
<div class="w-layout-vflex flex-block-15-copy">
<div class="favcardcat">
<div class="icon-setting w-embed"><svg width="currenWidth" height="currentHeight" viewbox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="black"></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" stroke="currentColor"></path>
</svg></div>
</div>
<div class="div-block-4"><img src="images/162615.webp" loading="lazy" width="Auto" height="Auto" alt="" srcset="images/162615-p-500.webp 500w, images/162615.webp 600w" sizes="(max-width: 600px) 100vw, 600px" class="image-5">

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="684e73c998b3d031bf41baf3" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="684e73c998b3d031bf41baf3" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>thankyoupage</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="685e5d95b4304bd8428c1631" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="685e5d95b4304bd8428c1631" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>VIN knot</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="685d5d68623a85f76b103bfa" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="685d5d68623a85f76b103bfa" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>VIN knot</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="685d5478c4ebd5c8793f8c54" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="685d5478c4ebd5c8793f8c54" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>VIN</title>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Fri Jun 27 2025 11:05:44 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6845507cd7ed248b659ca3b9" data-wf-site="6800f7e35fcfd4ca3b323269">
<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Wed Jul 02 2025 10:20:31 GMT+0000 (Coordinated Universal Time) -->
<html data-wf-page="6845507cd7ed248b659ca3b9" data-wf-site="6800f7e35fcfd4ca3b323269" lang="en">
<head>
<meta charset="utf-8">
<title>wholesale</title>

BIN
public/images/noimage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
public/images/resource2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

4
public/images/tg.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" stroke="#3666AF"/>
<path d="M34.2793 16.5068C34.3984 16.5224 34.4333 16.557 34.4463 16.5742C34.4631 16.5968 34.5228 16.6979 34.4912 16.9902V16.9912C34.4154 17.6921 33.7439 21.3424 33.0635 24.957C32.3861 28.5553 31.7089 32.0707 31.6309 32.4668L31.626 32.4932L31.623 32.5195C31.6053 32.7215 31.53 32.9106 31.4102 33.0635C31.2905 33.216 31.1329 33.3249 30.959 33.3789C30.7854 33.4328 30.6006 33.4312 30.4277 33.374C30.2548 33.3168 30.0986 33.2054 29.9814 33.0508L29.9463 33.0049L29.9023 32.9678L29.2803 32.458C28.6312 31.931 27.9193 31.3671 27.3184 30.8965C26.9173 30.5824 26.5647 30.3094 26.3125 30.1143C26.1864 30.0167 26.0852 29.9384 26.0156 29.8848C25.9808 29.858 25.9538 29.8373 25.9355 29.8232C25.9266 29.8164 25.9197 29.8112 25.915 29.8076C25.9127 29.8058 25.9104 29.8037 25.9092 29.8027L25.9082 29.8018L25.4434 29.4453L25.1641 29.96L25.1631 29.9619C25.1623 29.9634 25.1608 29.9658 25.1592 29.9688C25.1559 29.9748 25.1509 29.9843 25.1445 29.9961C25.1317 30.0197 25.1129 30.0545 25.0889 30.0986C25.0405 30.1873 24.9701 30.3143 24.8857 30.4678C24.7169 30.775 24.4887 31.1878 24.251 31.6104C24.0128 32.0336 23.7668 32.4646 23.5615 32.8086C23.4587 32.9808 23.3679 33.1279 23.2949 33.2402C23.2584 33.2964 23.228 33.3408 23.2041 33.374C23.1962 33.385 23.1886 33.3929 23.1836 33.3994C23.1637 33.4152 23.1446 33.4313 23.123 33.4434L23.0352 33.4814C22.9745 33.4999 22.9106 33.5047 22.8486 33.4961C22.7869 33.4875 22.7264 33.4651 22.6709 33.4297C22.635 33.4068 22.6029 33.3768 22.5732 33.3438L23.499 28.5059L30.4111 21.4072L29.7969 20.6299L20.418 26.2236C20.3006 26.1868 20.1399 26.1365 19.9482 26.0762C19.5336 25.9455 18.9738 25.769 18.3945 25.584C17.2605 25.2217 16.0903 24.8387 15.7441 24.7031C15.673 24.6568 15.611 24.5917 15.5674 24.5098C15.519 24.4187 15.4953 24.3133 15.501 24.2061C15.5067 24.0988 15.5416 23.9979 15.5986 23.9141C15.6556 23.8303 15.7317 23.7678 15.8164 23.7314L15.8359 23.7227L15.8545 23.7129C16.0536 23.6069 17.1957 23.1279 18.8613 22.4463C20.5063 21.7731 22.6204 20.9185 24.7236 20.0742C26.8267 19.23 28.9184 18.396 30.5176 17.7627C31.317 17.4461 31.9927 17.1794 32.4854 16.9873C32.7318 16.8912 32.9321 16.8144 33.0781 16.7588C33.1512 16.731 33.2102 16.7094 33.2539 16.6934C33.2757 16.6853 33.2928 16.6783 33.3057 16.6738C33.3155 16.6704 33.3206 16.6685 33.3223 16.668C33.6166 16.5781 34.0014 16.4706 34.2793 16.5068Z" fill="white" stroke="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

16
public/images/tg2.svg Normal file
View File

@ -0,0 +1,16 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_124_6073)">
<circle opacity="0.5" cx="9.16667" cy="9.16667" r="8.41667" transform="matrix(-1 0 0 1 19.1665 0.832031)" stroke="white" stroke-width="1.5"/>
<g clip-path="url(#clip1_124_6073)">
<path d="M11.9858 12.9316L10.1548 11.582L9.81592 11.332L9.51221 11.624L9.12451 11.9961L9.17041 11.3467L12.5698 8.27344L12.5708 8.27441C12.6421 8.21083 12.7861 8.06052 12.7944 7.8291C12.8031 7.58109 12.6598 7.42067 12.519 7.33691L13.231 7.0625L11.9858 12.9316ZM7.53564 10.043L6.40967 9.69043L10.7515 8.01758L7.53564 10.043Z" fill="white" stroke="white"/>
</g>
</g>
<defs>
<clipPath id="clip0_124_6073">
<rect width="20" height="20" fill="white"/>
</clipPath>
<clipPath id="clip1_124_6073">
<rect width="8.33333" height="8.33333" fill="white" transform="translate(5.4165 5.83203)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 920 B

3
public/images/union.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 7C12.2091 7 14 8.79086 14 11C14 13.2091 12.2091 15 10 15C7.79086 15 6 13.2091 6 11C6 8.79086 7.79086 7 10 7ZM11 0C11.5523 0 12 0.447715 12 1V6.41602C11.3875 6.14842 10.7111 6 10 6C8.36426 6 6.91221 6.78565 6 8H2.55859C2.28262 8.0002 2.05859 8.22398 2.05859 8.5C2.05859 8.77602 2.28262 8.9998 2.55859 9H5.41699C5.1493 9.61255 5 10.2888 5 11H2.5C2.22386 11 2 11.2239 2 11.5C2 11.7761 2.22386 12 2.5 12H5.10059C5.25067 12.7388 5.56324 13.4186 6 14H1C0.447715 14 2.41598e-08 13.5523 0 13V1C1.93278e-07 0.447715 0.447715 1.61064e-08 1 0H11ZM10 8.5C9.72386 8.5 9.5 8.72386 9.5 9V10.5H8.5C8.22386 10.5 8 10.7239 8 11C8.00005 11.2761 8.22389 11.5 8.5 11.5H10L10.1006 11.4902C10.3284 11.4437 10.5 11.2416 10.5 11V9C10.5 8.72391 10.2761 8.50009 10 8.5ZM2.5 5C2.22386 5 2 5.22386 2 5.5C2 5.77614 2.22386 6 2.5 6H9.5C9.77614 6 10 5.77614 10 5.5C10 5.22386 9.77614 5 9.5 5H2.5ZM2.5 2C2.22386 2 2 2.22386 2 2.5C2 2.77614 2.22386 3 2.5 3H9.5C9.77614 3 10 2.77614 10 2.5C10 2.22386 9.77614 2 9.5 2H2.5Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

4
public/images/vk.svg Normal file
View File

@ -0,0 +1,4 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" stroke="#3666AF"/>
<path d="M23.4375 20.917L23.3975 20.8223C23.1956 20.3506 22.8792 19.9429 22.4844 19.6279C22.6135 19.568 22.7523 19.5305 22.8945 19.5205C23.2821 19.5044 23.9699 19.497 24.6172 19.501C25.2655 19.5049 25.816 19.5212 26.0049 19.5459C26.1226 19.5701 26.2295 19.6331 26.3076 19.7256C26.3848 19.8171 26.4293 19.9319 26.4346 20.0518V24.3838H26.4365C26.4322 24.5232 26.4562 24.6623 26.5117 24.791C26.5715 24.9296 26.6639 25.0523 26.7803 25.1484C26.8965 25.2444 27.0341 25.3113 27.1816 25.3438C27.3129 25.3726 27.4481 25.3711 27.5791 25.3438C27.7616 25.3168 27.8822 25.2101 27.9355 25.1562C28.0039 25.0873 28.0583 25.008 28.0996 24.9395C28.1838 24.7998 28.2655 24.6207 28.3418 24.4316C28.4964 24.0483 28.6638 23.5411 28.8262 23.0264C28.994 22.4944 29.1477 21.9811 29.2969 21.5127C29.4092 21.1601 29.5047 20.8789 29.5791 20.6924L29.6465 20.5381L29.6514 20.5273C29.8222 20.161 30.097 19.8537 30.4404 19.6426L30.4795 19.6182L30.5146 19.5859C30.5643 19.541 30.6272 19.5147 30.6934 19.5088H34.4189L34.4561 19.5254C34.4712 19.5376 34.4835 19.5536 34.4912 19.5723C34.4989 19.5911 34.5013 19.6118 34.499 19.6318C34.4966 19.6521 34.489 19.6715 34.4775 19.6875L34.4492 19.7275L34.4287 19.7734C34.3677 19.9094 34.1674 20.2666 33.8779 20.7598C33.5957 21.2408 33.2449 21.8232 32.8984 22.3916C32.5521 22.9599 32.2108 23.513 31.9482 23.9336C31.8171 24.1436 31.706 24.3198 31.624 24.4482C31.5412 24.578 31.5033 24.6348 31.499 24.6416L31.4932 24.6484L31.4883 24.6553C31.2828 24.9337 31.1446 25.256 31.084 25.5967L31.0693 25.6816L31.083 25.7666C31.1505 26.1721 31.3358 26.5486 31.6152 26.8496V26.8506C31.8697 27.139 33.9028 29.3836 34.2236 29.7168H34.2246C34.3319 29.8404 34.4083 29.987 34.4512 30.1445C34.4172 30.2326 34.3603 30.3104 34.2852 30.3682H34.2842C34.1854 30.444 34.0625 30.4811 33.9385 30.4727L33.9219 30.4717H30.8867C30.5116 30.3295 30.1654 30.1187 29.8672 29.8486L29.8643 29.8467L29.7363 29.7207C29.5756 29.5559 29.3261 29.2837 29.0674 28.9971C28.7448 28.6397 28.4081 28.2609 28.3135 28.166H28.3125C28.2096 28.0527 28.0842 27.9621 27.9424 27.9033C27.8249 27.8546 27.6996 27.8277 27.5732 27.8242L27.4941 27.8262C27.3925 27.8262 27.077 27.8223 26.8086 28.0508C26.5266 28.291 26.4234 28.668 26.4336 29.1162L26.4346 29.1328L26.4355 29.1504C26.4716 29.551 26.3934 29.9522 26.2139 30.3105C26.1467 30.3642 26.0718 30.4073 25.9912 30.4375C25.8869 30.4765 25.7752 30.4939 25.6641 30.4883L25.6367 30.4873L25.6094 30.4883C24.354 30.5646 23.1049 30.2526 22.0322 29.5938C21.0667 28.9919 19.9953 27.8569 19.0127 26.5635C18.0362 25.278 17.1816 23.8807 16.6426 22.8008L16.6338 22.7842L16.624 22.7676L16.4014 22.3721C15.9326 21.4819 15.6341 20.5114 15.5166 19.5098H18.6152C18.6843 19.5593 18.7426 19.6231 18.7861 19.6973L18.791 19.7061C18.7975 19.7204 18.8074 19.7426 18.8203 19.7744C18.8482 19.8433 18.8848 19.9395 18.9287 20.0576C19.0166 20.2938 19.1266 20.6002 19.2432 20.9209C19.3588 21.2391 19.4804 21.5702 19.5879 21.8477C19.6893 22.1094 19.7944 22.367 19.876 22.5078C19.9784 22.6845 20.3421 23.3664 20.7451 23.9805C20.9472 24.2884 21.1742 24.6028 21.4014 24.8457C21.5147 24.9669 21.6411 25.0846 21.7764 25.1748C21.9051 25.2606 22.0897 25.3535 22.3086 25.3535H22.3574L22.4053 25.3447C22.7412 25.2785 23.0369 25.0814 23.2285 24.7979C23.4082 24.5317 23.4811 24.2091 23.4375 23.8926V20.917Z" fill="white" stroke="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.5" d="M10.2144 1.73047C12.4066 1.73142 14.4613 2.58407 16.0083 4.13281C17.5553 5.68162 18.4056 7.73816 18.4048 9.92773C18.4026 14.4434 14.7277 18.1182 10.2144 18.1182H10.2114C8.84031 18.1177 7.49315 17.7733 6.29736 17.1211L6.12256 17.0264L5.93115 17.0762L2.16162 18.0645L3.16553 14.3975L3.22119 14.1963L3.1167 14.0156C2.4002 12.774 2.02332 11.3651 2.02393 9.9209C2.02563 5.54635 5.47414 1.96113 9.79346 1.74121L10.2144 1.73047ZM10.2173 2.19824C5.95688 2.19825 2.49238 5.6622 2.49072 9.9209C2.49017 11.2885 2.84942 12.6235 3.53174 13.7988L3.67236 14.0312L3.72607 14.1172L3.05518 16.5723L2.82666 17.4072L3.66455 17.1875L6.1958 16.5234L6.27979 16.5732C7.39217 17.2333 8.6565 17.6016 9.95166 17.6455L10.2114 17.6504H10.2144C14.472 17.6504 17.9363 14.1856 17.938 9.92676C17.9387 7.86425 17.1356 5.92198 15.6782 4.46289C14.2207 3.00372 12.2793 2.19888 10.2173 2.19824Z" fill="white" stroke="white"/>
<path d="M7.13721 6.40625C7.28076 6.40625 7.41557 6.40743 7.52881 6.41309C7.53489 6.41339 7.54078 6.41291 7.54639 6.41309C7.55519 6.42998 7.56882 6.45033 7.58057 6.47852C7.67057 6.69484 7.82362 7.06815 7.9624 7.40527C8.08594 7.70537 8.21122 8.007 8.25732 8.10547C8.19617 8.22907 8.18583 8.25503 8.1333 8.31641C7.99284 8.48046 7.91484 8.58784 7.83447 8.66797C7.79349 8.70879 7.65299 8.83899 7.58252 9.02832C7.49435 9.2655 7.53677 9.5058 7.65967 9.7168C7.79467 9.94847 8.26243 10.7141 8.96729 11.3428C9.86049 12.1394 10.6526 12.4097 10.8218 12.4941C10.9634 12.5651 11.1564 12.6379 11.3755 12.6113C11.6149 12.5822 11.782 12.4493 11.8931 12.3223C11.9998 12.2001 12.353 11.7824 12.5493 11.5146C12.6501 11.5567 12.9118 11.6791 13.2104 11.8232L13.9526 12.1865C14.0105 12.2155 14.0651 12.2414 14.1069 12.2617C14.1112 12.2638 14.1156 12.2666 14.1196 12.2686C14.1189 12.2815 14.1209 12.2959 14.1196 12.3105C14.1074 12.4519 14.0704 12.645 13.9946 12.8574C13.962 12.9488 13.821 13.1158 13.5522 13.2881C13.2955 13.4526 13.0412 13.5463 12.9263 13.5635C12.5937 13.6132 12.2043 13.6297 11.7837 13.4961V13.4951C11.4758 13.3974 11.0892 13.2699 10.5933 13.0557C8.56391 12.1794 7.22183 10.119 7.08545 9.93652V9.93555L6.91846 9.69922C6.83849 9.57792 6.7412 9.418 6.64697 9.23242C6.45516 8.85459 6.29834 8.4111 6.29834 7.98926C6.29842 7.1292 6.73054 6.7305 6.92725 6.51562C7.01528 6.41949 7.10687 6.40632 7.13721 6.40625Z" fill="white" stroke="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

11
public/images/ws.svg Normal file
View File

@ -0,0 +1,11 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="49" height="49" rx="24.5" stroke="#3666AF"/>
<g clip-path="url(#clip0_124_6100)">
<path d="M24.959 15.5H25.04C28.9933 15.5 31.1211 15.9124 32.5967 17.3398C34.1048 18.8793 34.5 20.9914 34.5 24.9443V25.0557C34.5 29.0116 34.1024 31.1363 32.5977 32.6582C31.1219 34.0869 29.0112 34.5 25.04 34.5H24.96C20.9866 34.5 18.8612 34.0884 17.4033 32.6611C15.8948 31.1216 15.5 29.0089 15.5 25.0557V24.9443C15.5 20.9895 15.8954 18.8639 17.4014 17.3408C18.8758 15.9121 20.9887 15.5 24.959 15.5ZM24.8389 16.7891C24.4867 16.7891 24.2102 17.0698 24.1943 17.4053V17.4102C24.0896 20.1796 23.7351 21.6827 22.7041 22.7139C21.7385 23.6795 20.3601 24.0516 17.9141 24.1807L17.4102 24.2031C17.0654 24.2155 16.7891 24.4995 16.7891 24.8477V25.1709C16.7893 25.4787 17.0039 25.7286 17.2822 25.7969L17.4053 25.8145H17.4102C20.1743 25.9192 21.6753 26.2758 22.7041 27.3047C23.6684 28.2693 24.0399 29.6454 24.1699 32.0869L24.1934 32.5898C24.2058 32.9343 24.489 33.2107 24.8369 33.2109H25.1611C25.5131 33.2109 25.7898 32.9303 25.8057 32.5947V32.5898C25.9116 29.8307 26.2663 28.3336 27.2949 27.3047C28.3249 26.2746 29.8246 25.9192 32.5889 25.8145L32.5879 25.8135C32.8717 25.8035 33.1074 25.6103 33.1826 25.3477H33.2109V24.8477C33.2109 24.4954 32.9302 24.22 32.5947 24.2041L32.5898 24.2031C29.8258 24.0984 28.3247 23.7428 27.2959 22.7139C26.3293 21.7471 25.9572 20.3666 25.8281 17.915L25.8057 17.4102C25.7932 17.0656 25.5094 16.7891 25.1611 16.7891H24.8389Z" fill="white" stroke="white"/>
</g>
<defs>
<clipPath id="clip0_124_6100">
<rect width="20" height="20" fill="white" transform="translate(15 15)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -5,22 +5,33 @@ import { useArticleImage } from '@/hooks/useArticleImage';
import { useCatalogPrices } from '@/hooks/useCatalogPrices';
import { PartsAPIArticle } from '@/types/partsapi';
import toast from 'react-hot-toast';
import { useCart } from '@/contexts/CartContext';
interface ArticleCardProps {
article: PartsAPIArticle;
index: number;
onVisibilityChange?: (index: number, isVisible: boolean) => void;
image?: string; // optional image override
}
const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibilityChange }) => {
const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibilityChange, image }) => {
const [shouldShow, setShouldShow] = useState(false);
const [isChecking, setIsChecking] = useState(true);
// Cart context
const { isInCart: isItemInCart } = useCart();
// Используем хук для получения изображения
const { imageUrl, isLoading: imageLoading, error } = useArticleImage(article.artId, {
enabled: !!article.artId
});
// MOCK: fallback image if none loaded
const fallbackImage =
image || // use prop if provided
imageUrl ||
'/images/162615.webp'; // путь к картинке из public или любой другой
// Проверяем и очищаем данные артикула и бренда
const articleNumber = article.artArticleNr?.trim();
const brandName = article.artSupBrand?.trim();
@ -28,7 +39,10 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
// Используем хук для получения цен только если есть и артикул, и бренд
const { getPriceData, addToCart } = useCatalogPrices();
const shouldFetchPrices = articleNumber && brandName && articleNumber !== '' && brandName !== '';
const priceData = shouldFetchPrices ? getPriceData(articleNumber, brandName) : { minPrice: null, cheapestOffer: null, isLoading: false, hasOffers: false };
// MOCK: fallback price data
const priceData = shouldFetchPrices
? getPriceData(articleNumber, brandName)
: { minPrice: 17087, cheapestOffer: null, isLoading: false, hasOffers: true };
// Определяем, должен ли отображаться товар
useEffect(() => {
@ -66,9 +80,32 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
return <CatalogProductCardSkeleton />;
}
// Не отображаем ничего если товар не должен показываться
// MOCK: всегда показывать карточку для демо
if (!shouldShow) {
return null;
// return null;
// MOCK: показываем карточку даже если не должен
// (можно убрать это после подключения реальных данных)
// Формируем название товара
const title = [brandName || 'N/A', articleNumber || 'N/A'].filter(part => part !== 'N/A').join(', ');
const brand = brandName || 'Unknown';
let priceText = 'от 17 087 ₽';
const isInCartFlag = isItemInCart(undefined, undefined, articleNumber, brandName);
return (
<CatalogProductCard
image={fallbackImage}
discount="-35%"
price={priceText}
oldPrice="22 347 ₽"
title={title}
brand={brand}
articleNumber={articleNumber}
brandName={brandName}
artId={article.artId}
onAddToCart={() => {}}
isInCart={isInCartFlag}
/>
);
}
// Формируем название товара
@ -79,6 +116,8 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
const brand = brandName || 'Unknown';
const isInCartFlag = isItemInCart(undefined, undefined, articleNumber, brandName);
// Формируем цену для отображения
let priceText = '';
if (priceData.isLoading) {
@ -104,7 +143,7 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
return (
<CatalogProductCard
image={imageUrl}
image={fallbackImage}
discount="Новинка"
price={priceText}
oldPrice=""
@ -114,6 +153,7 @@ const ArticleCard: React.FC<ArticleCardProps> = memo(({ article, index, onVisibi
brandName={brandName}
artId={article.artId}
onAddToCart={handleAddToCart}
isInCart={isInCartFlag}
/>
);
});

View File

@ -1,6 +1,7 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { useCart } from "@/contexts/CartContext";
import toast from "react-hot-toast";
import CartIcon from "./CartIcon";
interface BestPriceCardProps {
bestOfferType: string;
@ -27,6 +28,11 @@ const BestPriceCard: React.FC<BestPriceCardProps> = ({
const parsedStock = parseInt(stock.replace(/[^\d]/g, ""), 10);
const maxCount = isNaN(parsedStock) ? undefined : parsedStock;
const [count, setCount] = useState(1);
const [inputValue, setInputValue] = useState("1");
useEffect(() => {
setInputValue(count.toString());
}, [count]);
const handleMinus = () => setCount(prev => Math.max(1, prev - 1));
const handlePlus = () => {
@ -38,7 +44,13 @@ const BestPriceCard: React.FC<BestPriceCardProps> = ({
};
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
let value = parseInt(e.target.value, 10);
const val = e.target.value;
setInputValue(val);
if (val === "") {
// Не обновляем count, пока не будет blur
return;
}
let value = parseInt(val, 10);
if (isNaN(value) || value < 1) value = 1;
if (maxCount !== undefined && value > maxCount) {
toast.error(`Максимум ${maxCount} шт.`);
@ -47,14 +59,25 @@ const BestPriceCard: React.FC<BestPriceCardProps> = ({
setCount(value);
};
const handleInputBlur = () => {
if (inputValue === "") {
setInputValue("1");
setCount(1);
}
};
// Функция для парсинга цены из строки
const parsePrice = (priceStr: string): number => {
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
return parseFloat(cleanPrice) || 0;
};
// Note: BestPriceCard doesn't receive isInCart flags from backend
// Since it's a summary component, we'll remove cart state checking for now
const inCart = false; // Disabled for BestPriceCard
// Обработчик добавления в корзину
const handleAddToCart = (e: React.MouseEvent) => {
const handleAddToCart = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
@ -69,14 +92,8 @@ const BestPriceCard: React.FC<BestPriceCardProps> = ({
return;
}
// Проверяем наличие
if (maxCount !== undefined && count > maxCount) {
toast.error(`Недостаточно товара в наличии. Доступно: ${maxCount} шт.`);
return;
}
try {
addItem({
const result = await addItem({
productId: offer.productId,
offerKey: offer.offerKey,
name: description,
@ -86,6 +103,7 @@ const BestPriceCard: React.FC<BestPriceCardProps> = ({
price: numericPrice,
currency: offer.currency || 'RUB',
quantity: count,
stock: maxCount, // передаем информацию о наличии
deliveryTime: delivery,
warehouse: offer.warehouse || 'Склад',
supplier: offer.supplier || (offer.isExternal ? 'AutoEuro' : 'Protek'),
@ -93,17 +111,26 @@ const BestPriceCard: React.FC<BestPriceCardProps> = ({
image: offer.image,
});
// Показываем тоастер об успешном добавлении
toast.success(
<div>
<div className="font-semibold">Товар добавлен в корзину!</div>
<div className="text-sm text-gray-600">{`${offer.brand} ${offer.articleNumber} (${count} шт.)`}</div>
</div>,
{
duration: 3000,
icon: '🛒',
}
);
if (result.success) {
// Показываем тоастер с разным текстом в зависимости от того, был ли товар уже в корзине
const toastMessage = inCart
? `Количество увеличено (+${count} шт.)`
: 'Товар добавлен в корзину!';
toast.success(
<div>
<div className="font-semibold" style={{ color: '#fff' }}>{toastMessage}</div>
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{`${offer.brand} ${offer.articleNumber} (${count} шт.)`}</div>
</div>,
{
duration: 3000,
icon: <CartIcon size={20} color="#fff" />,
}
);
} else {
// Показываем ошибку
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
}
} catch (error) {
console.error('Ошибка добавления в корзину:', error);
toast.error('Ошибка добавления товара в корзину');
@ -144,8 +171,9 @@ const BestPriceCard: React.FC<BestPriceCardProps> = ({
type="number"
min={1}
max={maxCount}
value={count}
value={inputValue}
onChange={handleInput}
onBlur={handleInputBlur}
className="text-block-26 w-full text-center outline-none"
aria-label="Количество"
/>
@ -156,17 +184,55 @@ const BestPriceCard: React.FC<BestPriceCardProps> = ({
</div>
</div>
<div className="w-layout-hflex flex-block-42">
<button
type="button"
onClick={handleAddToCart}
className="button-icon w-inline-block"
style={{ cursor: 'pointer', textDecoration: 'none' }}
aria-label="Добавить в корзину"
>
<div className="div-block-26">
<div className="icon-setting w-embed"><svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor"/></svg></div>
</div>
</button>
<div style={{ position: 'relative', display: 'inline-block' }}>
<button
type="button"
onClick={handleAddToCart}
className={`button-icon w-inline-block ${inCart ? 'in-cart' : ''}`}
style={{
cursor: 'pointer',
textDecoration: 'none',
opacity: inCart ? 0.5 : 1,
backgroundColor: inCart ? '#9ca3af' : undefined
}}
aria-label={inCart ? "Товар уже в корзине" : "Добавить в корзину"}
title={inCart ? "Товар уже в корзине - нажмите для добавления еще" : "Добавить в корзину"}
>
<div className="div-block-26">
<div
className="icon-setting w-embed"
style={{
filter: inCart ? 'brightness(0.7)' : undefined
}}
>
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor"/></svg>
</div>
</div>
</button>
{inCart && (
<div
style={{
position: 'absolute',
top: '-8px',
right: '-8px',
backgroundColor: '#22c55e',
color: 'white',
borderRadius: '50%',
width: '16px',
height: '16px',
fontSize: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold',
zIndex: 1
}}
title="В корзине"
>
</div>
)}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,205 @@
import React, { useState } from "react";
import { useCart } from "@/contexts/CartContext";
import { useFavorites } from "@/contexts/FavoritesContext";
import toast from "react-hot-toast";
interface BestPriceItemProps {
image: string;
discount: string;
price: string;
oldPrice: string;
title: string;
brand: string;
article?: string;
productId?: string;
onAddToCart?: (e: React.MouseEvent) => void;
isInCart?: boolean;
}
const BestPriceItem: React.FC<BestPriceItemProps> = ({
image,
discount,
price,
oldPrice,
title,
brand,
article,
productId,
onAddToCart,
isInCart = false,
}) => {
const { addItem, isInCart: isItemInCart, state: cartState } = useCart();
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
const [localInCart, setLocalInCart] = useState(false);
// Determine inCart via context if not provided
const inCartContext = isItemInCart(productId, undefined, article, brand);
const inCart = isInCart || inCartContext;
// Проверяем, есть ли товар в избранном
const isItemFavorite = isFavorite(productId, undefined, article, brand);
// Функция для парсинга цены из строки
const parsePrice = (priceStr: string): number => {
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
return parseFloat(cleanPrice) || 0;
};
// Обработчик добавления в корзину
const handleAddToCart = async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (!localInCart) {
setLocalInCart(true);
}
// Если передан кастомный обработчик, используем его
if (onAddToCart) {
onAddToCart(e);
return;
}
try {
const numericPrice = parsePrice(price);
if (numericPrice <= 0) {
toast.error('Цена товара не найдена');
return;
}
// Добавляем товар в корзину
const result = await addItem({
productId: productId,
name: title,
description: `${brand} - ${title}`,
brand: brand,
article: article,
price: numericPrice,
currency: 'RUB',
quantity: 1,
image: image,
supplier: 'Protek',
deliveryTime: '1 день',
isExternal: false
});
if (result.success) {
// Показываем успешный тоастер
toast.success(
<div>
<div className="font-semibold" style={{ color: '#fff' }}>Товар добавлен в корзину!</div>
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{`${brand} - ${title}`}</div>
</div>,
{
duration: 3000,
}
);
} else {
// Показываем ошибку
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
}
} catch (error) {
console.error('Ошибка добавления в корзину:', error);
toast.error('Ошибка при добавлении товара в корзину');
}
};
// Обработчик клика по иконке избранного
const handleFavoriteClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (isItemFavorite) {
// Находим товар в избранном и удаляем
const favoriteItem = favorites.find((fav: any) => {
if (productId && fav.productId === productId) return true;
if (fav.article === article && fav.brand === brand) return true;
return false;
});
if (favoriteItem) {
removeFromFavorites(favoriteItem.id);
}
} else {
// Добавляем в избранное
const numericPrice = parsePrice(price);
addToFavorites({
productId,
name: title,
brand: brand,
article: article || '',
price: numericPrice,
currency: 'RUB',
image: image
});
}
};
return (
<div className="w-layout-vflex bestpriceitem">
<div
className="favcardcat"
onClick={handleFavoriteClick}
style={{
cursor: 'pointer',
color: isItemFavorite ? '#ef4444' : 'currentColor'
}}
title={isItemFavorite ? 'Удалить из избранного' : 'Добавить в избранное'}
>
<div className="icon-setting w-embed">
<svg width="currenWidth" height="currentHeight" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z"
fill={isItemFavorite ? 'currentColor' : 'none'}
stroke="currentColor"
/>
</svg>
</div>
</div>
<div className="imgitembp">
<img
width="auto"
height="auto"
alt={title}
src={image}
loading="lazy"
className="image-5"
/>
<div className="saletagbp">{discount}</div>
</div>
<div className="div-block-3 bp-item-info">
<div className="w-layout-hflex pricecartbp">
<div className="actualprice">{price}</div>
<div className="oldpricebp">{oldPrice}</div>
</div>
<div className="w-layout-hflex flex-block-120">
<div className="nameitembp">{title}</div>
<a
href="#"
className={`button-icon w-inline-block ${inCart || localInCart ? 'in-cart' : ''}`}
onClick={inCart ? undefined : handleAddToCart}
style={{
cursor: inCart || localInCart ? 'default' : 'pointer',
textDecoration: 'none',
opacity: inCart || localInCart ? 0.5 : 1,
backgroundColor: inCart || localInCart ? '#9ca3af' : undefined
}}
aria-label={inCart || localInCart ? "Товар уже в корзине" : "Добавить в корзину"}
title={inCart || localInCart ? "Товар уже в корзине" : "Добавить в корзину"}
>
<div className="div-block-26">
<div className="icon-setting w-embed">
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor"></path>
</svg>
</div>
</div>
</a>
</div>
</div>
</div>
);
};
export default BestPriceItem;

View File

@ -1,213 +1,364 @@
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useQuery } from '@apollo/client';
import { GET_PARTSINDEX_CATEGORIES } from '@/lib/graphql';
import { PartsIndexCatalogsData, PartsIndexCatalogsVariables, PartsIndexCatalog } from '@/types/partsindex';
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useQuery } from '@apollo/client';
import { GET_PARTSINDEX_CATEGORIES, GET_NAVIGATION_CATEGORIES } from '@/lib/graphql';
import { PartsIndexCatalogsData, PartsIndexCatalogsVariables, PartsIndexCatalog } from '@/types/partsindex';
import { NavigationCategory } from '@/types';
function useIsMobile(breakpoint = 767) {
const [isMobile, setIsMobile] = React.useState(false);
React.useEffect(() => {
const check = () => setIsMobile(window.innerWidth <= breakpoint);
check();
window.addEventListener("resize", check);
return () => window.removeEventListener("resize", check);
}, [breakpoint]);
return isMobile;
}
function useIsMobile(breakpoint = 767) {
const [isMobile, setIsMobile] = React.useState(false);
React.useEffect(() => {
const check = () => setIsMobile(window.innerWidth <= breakpoint);
check();
window.addEventListener("resize", check);
return () => window.removeEventListener("resize", check);
}, [breakpoint]);
return isMobile;
}
// Fallback статичные данные
const fallbackTabData = [
{
label: "Оригинальные каталоги",
heading: "Оригинальные каталоги",
links: [
"Моторные масла",
"Трансмиссионные масла",
"Тормозные жидкости",
"Смазки",
"Дистиллированная вода",
"Жидкости для стеклоомывателей",
"Индустриальные жидкости",
"Антифриз и охлаждающие жидкости",
"Промывочные жидкости",
],
},
{
label: "Масла и технические жидкости",
heading: "Масла и технические жидкости",
links: [
"Моторные масла",
"Трансмиссионные масла",
"Тормозные жидкости",
"Смазки",
"Дистиллированная вода",
"Жидкости для стеклоомывателей",
"Индустриальные жидкости",
"Антифриз и охлаждающие жидкости",
"Промывочные жидкости",
],
},
{
label: "Оборудование",
heading: "Оборудование",
links: [
"Моторные масла",
"Трансмиссионные масла",
"Тормозные жидкости",
"Смазки",
"Дистиллированная вода",
"Жидкости для стеклоомывателей",
"Индустриальные жидкости",
"Антифриз и охлаждающие жидкости",
"Промывочные жидкости",
],
},
];
// Преобразуем данные PartsIndex в формат нашего меню
const transformPartsIndexToTabData = (catalogs: PartsIndexCatalog[]) => {
console.log('🔄 Преобразуем каталоги PartsIndex:', catalogs.length, 'элементов');
const transformed = catalogs.map(catalog => {
const groupsCount = catalog.groups?.length || 0;
console.log(`📝 Каталог: "${catalog.name}" (${groupsCount} групп)`);
let links: string[] = [];
if (catalog.groups && catalog.groups.length > 0) {
// Для каждой группы проверяем есть ли подгруппы
catalog.groups.forEach(group => {
if (group.subgroups && group.subgroups.length > 0) {
// Если есть подгруппы, добавляем их названия
links.push(...group.subgroups.slice(0, 9 - links.length).map(subgroup => subgroup.name));
} else {
// Если подгрупп нет, добавляем название самой группы
if (links.length < 9) {
links.push(group.name);
}
}
});
}
// Если подкатегорий нет, показываем название категории как указано в требованиях
if (links.length === 0) {
links = [catalog.name];
}
console.log(`🔗 Подкатегории для "${catalog.name}":`, links);
return {
label: catalog.name,
heading: catalog.name,
links: links.slice(0, 9), // Ограничиваем максимум 9 элементов
catalogId: catalog.id // Сохраняем ID каталога для навигации
};
});
console.log('✅ Преобразование завершено:', transformed.length, 'табов');
return transformed;
};
const BottomHead = ({ menuOpen, onClose }: { menuOpen: boolean; onClose: () => void }) => {
const isMobile = useIsMobile();
const router = useRouter();
const [mobileCategory, setMobileCategory] = useState<null | any>(null);
const [tabData, setTabData] = useState(fallbackTabData);
const [activeTabIndex, setActiveTabIndex] = useState(0);
console.log('🔄 BottomHead render:', {
menuOpen,
tabDataLength: tabData.length,
activeTabIndex,
isMobile
});
// --- Overlay animation state ---
const [showOverlay, setShowOverlay] = useState(false);
useEffect(() => {
if (menuOpen) {
setShowOverlay(true);
} else {
// Ждём окончания transition перед удалением из DOM
const timeout = setTimeout(() => setShowOverlay(false), 300);
return () => clearTimeout(timeout);
}
}, [menuOpen]);
// --- End overlay animation state ---
// Получаем каталоги PartsIndex
const { data: catalogsData, loading, error } = useQuery<PartsIndexCatalogsData, PartsIndexCatalogsVariables>(
GET_PARTSINDEX_CATEGORIES,
// Fallback статичные данные
const fallbackTabData = [
{
variables: {
lang: 'ru'
},
errorPolicy: 'all',
onCompleted: (data) => {
console.log('🎉 Apollo Query onCompleted - данные получены:', data);
},
onError: (error) => {
console.error('❌ Apollo Query onError:', error);
label: "Оригинальные каталоги",
heading: "Оригинальные каталоги",
links: [
"Моторные масла",
"Трансмиссионные масла",
"Тормозные жидкости",
"Смазки",
"Дистиллированная вода",
"Жидкости для стеклоомывателей",
"Индустриальные жидкости",
"Антифриз и охлаждающие жидкости",
"Промывочные жидкости",
],
},
{
label: "Масла и технические жидкости",
heading: "Масла и технические жидкости",
links: [
"Моторные масла",
"Трансмиссионные масла",
"Тормозные жидкости",
"Смазки",
"Дистиллированная вода",
"Жидкости для стеклоомывателей",
"Индустриальные жидкости",
"Антифриз и охлаждающие жидкости",
"Промывочные жидкости",
],
},
{
label: "Оборудование",
heading: "Оборудование",
links: [
"Моторные масла",
"Трансмиссионные масла",
"Тормозные жидкости",
"Смазки",
"Дистиллированная вода",
"Жидкости для стеклоомывателей",
"Индустриальные жидкости",
"Антифриз и охлаждающие жидкости",
"Промывочные жидкости",
],
},
];
// Преобразуем данные PartsIndex в формат нашего меню
const transformPartsIndexToTabData = (catalogs: PartsIndexCatalog[]) => {
console.log('🔄 Преобразуем каталоги PartsIndex:', catalogs.length, 'элементов');
const transformed = catalogs.map(catalog => {
const groupsCount = catalog.groups?.length || 0;
console.log(`📝 Каталог: "${catalog.name}" (${groupsCount} групп)`);
let links: string[] = [];
if (catalog.groups && catalog.groups.length > 0) {
// Для каждой группы проверяем есть ли подгруппы
catalog.groups.forEach(group => {
if (group.subgroups && group.subgroups.length > 0) {
// Если есть подгруппы, добавляем их названия
links.push(...group.subgroups.slice(0, 9 - links.length).map(subgroup => subgroup.name));
} else {
// Если подгрупп нет, добавляем название самой группы
if (links.length < 9) {
links.push(group.name);
}
}
});
}
}
);
// Обновляем данные табов когда получаем данные от API
useEffect(() => {
if (catalogsData?.partsIndexCategoriesWithGroups && catalogsData.partsIndexCategoriesWithGroups.length > 0) {
console.log('✅ Обновляем меню с данными PartsIndex:', catalogsData.partsIndexCategoriesWithGroups.length, 'каталогов');
console.log('🔍 Первые 3 каталога:', catalogsData.partsIndexCategoriesWithGroups.slice(0, 3).map(catalog => ({
name: catalog.name,
id: catalog.id,
groupsCount: catalog.groups?.length || 0,
groups: catalog.groups?.slice(0, 3).map(group => group.name)
})));
const apiTabData = transformPartsIndexToTabData(catalogsData.partsIndexCategoriesWithGroups);
setTabData(apiTabData);
// Сбрасываем активный таб на первый при обновлении данных
setActiveTabIndex(0);
} else if (error) {
console.warn('⚠️ Используем fallback данные из-за ошибки PartsIndex:', error);
setTabData(fallbackTabData);
setActiveTabIndex(0);
}
}, [catalogsData, error]);
// Логирование для отладки
useEffect(() => {
if (loading) {
console.log('🔄 Загружаем каталоги PartsIndex...');
}
if (error) {
console.error('❌ Ошибка загрузки каталогов PartsIndex:', error);
}
}, [loading, error]);
// Обработка клика по категории для перехода в каталог с товарами
const handleCategoryClick = (catalogId: string, categoryName: string, entityId?: string) => {
console.log('🔍 Клик по категории:', { catalogId, categoryName, entityId });
// Закрываем меню
onClose();
// Переходим на страницу каталога с параметрами PartsIndex
router.push({
pathname: '/catalog',
query: {
partsIndexCatalog: catalogId,
categoryName: encodeURIComponent(categoryName),
...(entityId && { partsIndexCategory: entityId })
// Если подкатегорий нет, показываем название категории как указано в требованиях
if (links.length === 0) {
links = [catalog.name];
}
console.log(`🔗 Подкатегории для "${catalog.name}":`, links);
return {
label: catalog.name,
heading: catalog.name,
links: links.slice(0, 9), // Ограничиваем максимум 9 элементов
catalogId: catalog.id // Сохраняем ID каталога для навигации
};
});
console.log('✅ Преобразование завершено:', transformed.length, 'табов');
return transformed;
};
// Только мобильный UX
if (isMobile && menuOpen) {
// Оверлей для мобильного меню
// Функция для поиска иконки для категории
const findCategoryIcon = (catalogId: string, navigationCategories: NavigationCategory[]): string | null => {
console.log('🔍 Ищем иконку для catalogId:', catalogId);
console.log('📋 Доступные навигационные категории:', navigationCategories);
// Ищем навигационную категорию для данного каталога (без группы)
const categoryIcon = navigationCategories.find(
nav => nav.partsIndexCatalogId === catalogId && (!nav.partsIndexGroupId || nav.partsIndexGroupId === '')
);
console.log('🎯 Найденная категория:', categoryIcon);
console.log('🖼️ Возвращаемая иконка:', categoryIcon?.icon || null);
return categoryIcon?.icon || null;
};
const BottomHead = ({ menuOpen, onClose }: { menuOpen: boolean; onClose: () => void }) => {
const isMobile = useIsMobile();
const router = useRouter();
const [mobileCategory, setMobileCategory] = useState<null | any>(null);
const [tabData, setTabData] = useState(fallbackTabData);
const [activeTabIndex, setActiveTabIndex] = useState(0);
console.log('🔄 BottomHead render:', {
menuOpen,
tabDataLength: tabData.length,
activeTabIndex,
isMobile
});
// --- Overlay animation state ---
const [showOverlay, setShowOverlay] = useState(false);
useEffect(() => {
if (menuOpen) {
setShowOverlay(true);
} else {
// Ждём окончания transition перед удалением из DOM
const timeout = setTimeout(() => setShowOverlay(false), 300);
return () => clearTimeout(timeout);
}
}, [menuOpen]);
// --- End overlay animation state ---
// Получаем каталоги PartsIndex
const { data: catalogsData, loading, error } = useQuery<PartsIndexCatalogsData, PartsIndexCatalogsVariables>(
GET_PARTSINDEX_CATEGORIES,
{
variables: {
lang: 'ru'
},
errorPolicy: 'all',
onCompleted: (data) => {
console.log('🎉 Apollo Query onCompleted - данные получены:', data);
},
onError: (error) => {
console.error('❌ Apollo Query onError:', error);
}
}
);
// Получаем навигационные категории с иконками
const { data: navigationData, loading: navigationLoading, error: navigationError } = useQuery<{ navigationCategories: NavigationCategory[] }>(
GET_NAVIGATION_CATEGORIES,
{
errorPolicy: 'all',
onCompleted: (data) => {
console.log('🎉 Навигационные категории получены:', data);
},
onError: (error) => {
console.error('❌ Ошибка загрузки навигационных категорий:', error);
}
}
);
// Обновляем данные табов когда получаем данные от API
useEffect(() => {
if (catalogsData?.partsIndexCategoriesWithGroups && catalogsData.partsIndexCategoriesWithGroups.length > 0) {
console.log('✅ Обновляем меню с данными PartsIndex:', catalogsData.partsIndexCategoriesWithGroups.length, 'каталогов');
console.log('🔍 Первые 3 каталога:', catalogsData.partsIndexCategoriesWithGroups.slice(0, 3).map(catalog => ({
name: catalog.name,
id: catalog.id,
groupsCount: catalog.groups?.length || 0,
groups: catalog.groups?.slice(0, 3).map(group => group.name)
})));
const apiTabData = transformPartsIndexToTabData(catalogsData.partsIndexCategoriesWithGroups);
setTabData(apiTabData);
// Сбрасываем активный таб на первый при обновлении данных
setActiveTabIndex(0);
} else if (error) {
console.warn('⚠️ Используем fallback данные из-за ошибки PartsIndex:', error);
setTabData(fallbackTabData);
setActiveTabIndex(0);
}
}, [catalogsData, error]);
// Логирование для отладки
useEffect(() => {
if (loading) {
console.log('🔄 Загружаем каталоги PartsIndex...');
}
if (error) {
console.error('❌ Ошибка загрузки каталогов PartsIndex:', error);
}
}, [loading, error]);
// Обработка клика по категории для перехода в каталог с товарами
const handleCategoryClick = (catalogId: string, categoryName: string, entityId?: string) => {
console.log('🔍 Клик по категории:', { catalogId, categoryName, entityId });
// Закрываем меню
onClose();
// Переходим на страницу каталога с параметрами PartsIndex
router.push({
pathname: '/catalog',
query: {
partsIndexCatalog: catalogId,
categoryName: encodeURIComponent(categoryName),
...(entityId && { partsIndexCategory: entityId })
}
});
};
// Только мобильный UX
if (isMobile && menuOpen) {
// Оверлей для мобильного меню
return (
<>
{showOverlay && (
<div
className={`fixed inset-0 bg-black/7 z-40 transition-opacity duration-300 ${menuOpen ? 'opacity-100' : 'opacity-0'}`}
onClick={onClose}
aria-label="Закрыть меню"
/>
)}
{/* Экран подкатегорий */}
{mobileCategory ? (
<div className="mobile-category-overlay z-50">
<div className="mobile-header">
<button className="mobile-back-btn" onClick={() => setMobileCategory(null)}>
</button>
<span>{mobileCategory.label}</span>
</div>
<div className="mobile-subcategories">
{mobileCategory.links.length === 1 ? (
<div
className="mobile-subcategory"
onClick={() => {
let subcategoryId = `${mobileCategory.catalogId}_0`;
if (mobileCategory.groups) {
for (const group of mobileCategory.groups) {
if (group.subgroups && group.subgroups.length > 0) {
const foundSubgroup = group.subgroups.find((subgroup: any) => subgroup.name === mobileCategory.links[0]);
if (foundSubgroup) {
subcategoryId = foundSubgroup.id;
break;
}
} else if (group.name === mobileCategory.links[0]) {
subcategoryId = group.id;
break;
}
}
}
const catalogId = mobileCategory.catalogId || 'fallback';
handleCategoryClick(catalogId, mobileCategory.links[0], subcategoryId);
}}
style={{ cursor: "pointer" }}
>
Показать все
</div>
) : (
mobileCategory.links.map((link: string, linkIndex: number) => (
<div
className="mobile-subcategory"
key={link}
onClick={() => {
let subcategoryId = `${mobileCategory.catalogId}_${linkIndex}`;
if (mobileCategory.groups) {
for (const group of mobileCategory.groups) {
if (group.subgroups && group.subgroups.length > 0) {
const foundSubgroup = group.subgroups.find((subgroup: any) => subgroup.name === link);
if (foundSubgroup) {
subcategoryId = foundSubgroup.id;
break;
}
} else if (group.name === link) {
subcategoryId = group.id;
break;
}
}
}
const catalogId = mobileCategory.catalogId || 'fallback';
handleCategoryClick(catalogId, link, subcategoryId);
}}
>
{link}
</div>
))
)}
</div>
</div>
) : (
// Экран выбора категории
<div className="mobile-category-overlay z-50">
<div className="mobile-header">
<button className="mobile-back-btn" onClick={onClose} aria-label="Закрыть меню">
<svg width="24" height="24" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M4.11 2.697L2.698 4.11 6.586 8l-3.89 3.89 1.415 1.413L8 9.414l3.89 3.89 1.413-1.415L9.414 8l3.89-3.89-1.415-1.413L8 6.586l-3.89-3.89z" fill="currentColor"></path>
</svg>
</button>
<span>Категории</span>
{loading && <span className="text-sm text-gray-500 ml-2">(загрузка...)</span>}
</div>
<div className="mobile-subcategories" style={{ maxHeight: "70vh", overflowY: "auto" }}>
{tabData.map((cat, index) => {
// Получаем ID каталога из данных PartsIndex или создаем fallback ID
const catalogId = catalogsData?.partsIndexCategoriesWithGroups?.[index]?.id || `fallback_${index}`;
const groups = catalogsData?.partsIndexCategoriesWithGroups?.[index]?.groups || [];
return (
<div
className="mobile-subcategory"
key={cat.label}
onClick={() => {
// Добавляем catalogId и groups для правильной обработки
const categoryWithData = {
...cat,
catalogId,
groups
};
setMobileCategory(categoryWithData);
}}
style={{ cursor: "pointer" }}
>
{cat.label}
</div>
);
})}
</div>
</div>
)}
</>
);
}
// Десктоп: оставить всё как есть, но добавить оверлей
return (
<>
{showOverlay && (
@ -217,285 +368,229 @@ const BottomHead = ({ menuOpen, onClose }: { menuOpen: boolean; onClose: () => v
aria-label="Закрыть меню"
/>
)}
{/* Экран подкатегорий */}
{mobileCategory ? (
<div className="mobile-category-overlay z-50">
<div className="mobile-header">
<button className="mobile-back-btn" onClick={() => setMobileCategory(null)}>
</button>
<span>{mobileCategory.label}</span>
</div>
<div className="mobile-subcategories">
{mobileCategory.links.map((link: string, linkIndex: number) => (
<div
className="mobile-subcategory"
key={link}
onClick={() => {
// Ищем соответствующую подгруппу по названию
let subcategoryId = `${mobileCategory.catalogId}_${linkIndex}`;
if (mobileCategory.groups) {
for (const group of mobileCategory.groups) {
// Проверяем в подгруппах
if (group.subgroups && group.subgroups.length > 0) {
const foundSubgroup = group.subgroups.find((subgroup: any) => subgroup.name === link);
if (foundSubgroup) {
subcategoryId = foundSubgroup.id;
break;
}
}
// Если нет подгрупп, проверяем саму группу
else if (group.name === link) {
subcategoryId = group.id;
break;
}
}
}
// Получаем catalogId из данных
const activeCatalog = catalogsData?.partsIndexCategoriesWithGroups?.[tabData.findIndex(tab => tab === mobileCategory)];
const catalogId = activeCatalog?.id || 'fallback';
handleCategoryClick(catalogId, link, subcategoryId);
}}
>
{link}
</div>
))}
</div>
</div>
) : (
// Экран выбора категории
<div className="mobile-category-overlay z-50">
<div className="mobile-header">
<button className="mobile-back-btn" onClick={onClose} aria-label="Закрыть меню">
<svg width="24" height="24" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M4.11 2.697L2.698 4.11 6.586 8l-3.89 3.89 1.415 1.413L8 9.414l3.89 3.89 1.413-1.415L9.414 8l3.89-3.89-1.415-1.413L8 6.586l-3.89-3.89z" fill="currentColor"></path>
</svg>
</button>
<span>Категории</span>
{loading && <span className="text-sm text-gray-500 ml-2">(загрузка...)</span>}
</div>
<div className="mobile-subcategories" style={{ maxHeight: "70vh", overflowY: "auto" }}>
{tabData.map((cat, index) => {
// Получаем ID каталога из данных PartsIndex или создаем fallback ID
const catalogId = catalogsData?.partsIndexCategoriesWithGroups?.[index]?.id || `fallback_${index}`;
return (
<div
className="mobile-subcategory"
key={cat.label}
onClick={() => {
// Добавляем catalogId и groups для правильной обработки
const categoryWithData = {
...cat,
catalogId,
groups: catalogsData?.partsIndexCategoriesWithGroups?.[index]?.groups
};
setMobileCategory(categoryWithData);
}}
style={{ cursor: "pointer" }}
>
{cat.label}
</div>
);
})}
</div>
</div>
{showOverlay && (
<div
className={`fixed inset-0 bg-black/7 z-1900 transition-opacity duration-300 ${menuOpen ? 'opacity-100' : 'opacity-0'}`}
onClick={onClose}
aria-label="Закрыть меню"
/>
)}
</>
);
}
<nav
role="navigation"
className="nav-menu-3 w-nav-menu z-2000"
style={{ display: menuOpen ? "block" : "none" }}
onClick={e => e.stopPropagation()} // чтобы клик внутри меню не закрывал его
>
<div className="div-block-28">
<div className="w-layout-hflex flex-block-90">
<div className="w-layout-vflex flex-block-88" style={{ maxHeight: "60vh", overflowY: "auto" }}>
{/* Меню с иконками - показываем все категории из API */}
{tabData.map((tab, idx) => {
// Получаем catalogId для поиска иконки
const catalogId = catalogsData?.partsIndexCategoriesWithGroups?.[idx]?.id || `fallback_${idx}`;
console.log(`🏷️ Обрабатываем категорию ${idx}: "${tab.label}" с catalogId: "${catalogId}"`);
const icon = navigationData?.navigationCategories ? findCategoryIcon(catalogId, navigationData.navigationCategories) : null;
console.log(`🎨 Для категории "${tab.label}" будет показана ${icon ? 'иконка: ' + icon : 'звездочка (fallback)'}`);
// Десктоп: оставить всё как есть, но добавить оверлей
return (
<>
{showOverlay && (
<div
className={`fixed inset-0 bg-black/7 z-40 transition-opacity duration-300 ${menuOpen ? 'opacity-100' : 'opacity-0'}`}
onClick={onClose}
aria-label="Закрыть меню"
/>
)}
{showOverlay && (
<div
className={`fixed inset-0 bg-black/7 z-1900 transition-opacity duration-300 ${menuOpen ? 'opacity-100' : 'opacity-0'}`}
onClick={onClose}
aria-label="Закрыть меню"
/>
)}
<nav
role="navigation"
className="nav-menu-3 w-nav-menu z-2000"
style={{ display: menuOpen ? "block" : "none" }}
onClick={e => e.stopPropagation()} // чтобы клик внутри меню не закрывал его
>
<div className="div-block-28">
<div className="w-layout-hflex flex-block-90">
<div className="w-layout-vflex flex-block-88" style={{ maxHeight: "60vh", overflowY: "auto" }}>
{/* Меню с иконками - показываем все категории из API */}
{tabData.map((tab, idx) => (
<a
href="#"
className={`link-block-7 w-inline-block${activeTabIndex === idx ? " w--current" : ""}`}
key={tab.label}
onClick={() => {
setActiveTabIndex(idx);
}}
style={{ cursor: "pointer" }}
>
<div className="div-block-29">
<div className="code-embed-12 w-embed">
{/* SVG-звезда */}
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.3158 0.643914C10.4674 0.365938 10.8666 0.365938 11.0182 0.643914L14.0029 6.11673C14.0604 6.22222 14.1623 6.29626 14.2804 6.31838L20.4077 7.46581C20.7189 7.52409 20.8423 7.9037 20.6247 8.13378L16.3421 12.6636C16.2595 12.7509 16.2206 12.8707 16.2361 12.9899L17.0382 19.1718C17.079 19.4858 16.7561 19.7204 16.47 19.5847L10.8385 16.9114C10.73 16.8599 10.604 16.8599 10.4955 16.9114L4.86394 19.5847C4.5779 19.7204 4.25499 19.4858 4.29573 19.1718L5.0979 12.9899C5.11336 12.8707 5.07444 12.7509 4.99189 12.6636L0.709252 8.13378C0.491728 7.9037 0.615069 7.52409 0.926288 7.46581L7.05357 6.31838C7.17168 6.29626 7.27358 6.22222 7.33112 6.11673L10.3158 0.643914Z" fill="CurrentColor"></path>
</svg>
</div>
</div>
<div className="text-block-47">{tab.label}</div>
</a>
))}
</div>
{/* Правая часть меню с подкатегориями и картинками */}
<div className="w-layout-vflex flex-block-89">
<h3 className="heading-16">{tabData[activeTabIndex]?.heading || tabData[0].heading}{loading && <span className="text-sm text-gray-500 ml-2">(обновление...)</span>}</h3>
<div className="w-layout-hflex flex-block-92">
<div className="w-layout-vflex flex-block-91">
{(tabData[activeTabIndex]?.links || tabData[0].links).map((link, linkIndex) => {
const activeCatalog = catalogsData?.partsIndexCategoriesWithGroups?.[activeTabIndex];
return (
<a
href="#"
className={`link-block-7 w-inline-block${activeTabIndex === idx ? " w--current" : ""}`}
key={tab.label}
onClick={() => {
setActiveTabIndex(idx);
}}
style={{ cursor: "pointer" }}
>
<div className="div-block-29">
<div className="code-embed-12 w-embed">
{icon ? (
<img
src={icon}
alt={tab.label}
width="21"
height="20"
/>
) : (
<svg
width="21"
height="20"
viewBox="0 0 21 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M10.3158 0.643914C10.4674 0.365938 10.8666 0.365938 11.0182 0.643914L14.0029 6.11673C14.0604 6.22222 14.1623 6.29626 14.2804 6.31838L20.4077 7.46581C20.7189 7.52409 20.8423 7.9037 20.6247 8.13378L16.3421 12.6636C16.2595 12.7509 16.2206 12.8707 16.2361 12.9899L17.0382 19.1718C17.079 19.4858 16.7561 19.7204 16.47 19.5847L10.8385 16.9114C10.73 16.8599 10.604 16.8599 10.4955 16.9114L4.86394 19.5847C4.5779 19.7204 4.25499 19.4858 4.29573 19.1718L5.0979 12.9899C5.11336 12.8707 5.07444 12.7509 4.99189 12.6636L0.709252 8.13378C0.491728 7.9037 0.615069 7.52409 0.926288 7.46581L7.05357 6.31838C7.17168 6.29626 7.27358 6.22222 7.33112 6.11673L10.3158 0.643914Z" fill="CurrentColor"></path>
</svg>
)}
</div>
</div>
<div className="text-block-47">{tab.label}</div>
</a>
);
})}
</div>
{/* Правая часть меню с подкатегориями и картинками */}
<div className="w-layout-vflex flex-block-89">
<h3 className="heading-16">{tabData[activeTabIndex]?.heading || tabData[0].heading}{loading && <span className="text-sm text-gray-500 ml-2">(обновление...)</span>}</h3>
<div className="w-layout-hflex flex-block-92">
<div className="w-layout-vflex flex-block-91">
{(tabData[activeTabIndex]?.links || tabData[0].links).map((link, linkIndex) => {
const activeCatalog = catalogsData?.partsIndexCategoriesWithGroups?.[activeTabIndex];
// Ищем соответствующую подгруппу по названию
let subcategoryId = `fallback_${activeTabIndex}_${linkIndex}`;
// Ищем соответствующую подгруппу по названию
let subcategoryId = `fallback_${activeTabIndex}_${linkIndex}`;
if (activeCatalog?.groups) {
for (const group of activeCatalog.groups) {
// Проверяем в подгруппах
if (group.subgroups && group.subgroups.length > 0) {
const foundSubgroup = group.subgroups.find((subgroup: any) => subgroup.name === link);
if (foundSubgroup) {
subcategoryId = foundSubgroup.id;
if (activeCatalog?.groups) {
for (const group of activeCatalog.groups) {
// Проверяем в подгруппах
if (group.subgroups && group.subgroups.length > 0) {
const foundSubgroup = group.subgroups.find((subgroup: any) => subgroup.name === link);
if (foundSubgroup) {
subcategoryId = foundSubgroup.id;
break;
}
}
// Если нет подгрупп, проверяем саму группу
else if (group.name === link) {
subcategoryId = group.id;
break;
}
}
// Если нет подгрупп, проверяем саму группу
else if (group.name === link) {
subcategoryId = group.id;
break;
}
}
}
return (
<div
className="link-2"
key={link}
onClick={() => {
const catalogId = activeCatalog?.id || 'fallback';
handleCategoryClick(catalogId, link, subcategoryId);
}}
style={{ cursor: "pointer" }}
>
{link}
</div>
);
})}
</div>
<div className="w-layout-vflex flex-block-91-copy">
<img src="https://d3e54v103j8qbb.cloudfront.net/plugins/Basic/assets/placeholder.60f9b1840c.svg" loading="lazy" alt="" className="image-17" />
<img src="https://d3e54v103j8qbb.cloudfront.net/plugins/Basic/assets/placeholder.60f9b1840c.svg" loading="lazy" alt="" className="image-17" />
return (
<div
className="link-2"
key={link}
onClick={() => {
const catalogId = activeCatalog?.id || 'fallback';
handleCategoryClick(catalogId, link, subcategoryId);
}}
style={{ cursor: "pointer" }}
>
{link}
</div>
);
})}
</div>
<div className="w-layout-vflex flex-block-91-copy">
<img src="https://d3e54v103j8qbb.cloudfront.net/plugins/Basic/assets/placeholder.60f9b1840c.svg" loading="lazy" alt="" className="image-17" />
<img src="https://d3e54v103j8qbb.cloudfront.net/plugins/Basic/assets/placeholder.60f9b1840c.svg" loading="lazy" alt="" className="image-17" />
</div>
</div>
</div>
</div>
</div>
{/* Табы */}
<div data-current="Tab 1" data-easing="ease" data-duration-in="300" data-duration-out="100" className="tabs w-tabs">
<div className="tabs-menu w-tab-menu" style={{ maxHeight: "70vh", overflowY: "auto" }}>
{tabData.map((tab, idx) => (
<a
key={tab.label}
data-w-tab={`Tab ${idx + 1}`}
className={`tab-link w-inline-block w-tab-link${activeTabIndex === idx ? " w--current" : ""}`}
onClick={() => {
setActiveTabIndex(idx);
}}
style={{ cursor: "pointer" }}
>
<div className="div-block-29">
<div className="code-embed-12 w-embed">
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.3158 0.643914C10.4674 0.365938 10.8666 0.365938 11.0182 0.643914L14.0029 6.11673C14.0604 6.22222 14.1623 6.29626 14.2804 6.31838L20.4077 7.46581C20.7189 7.52409 20.8423 7.9037 20.6247 8.13378L16.3421 12.6636C16.2595 12.7509 16.2206 12.8707 16.2361 12.9899L17.0382 19.1718C17.079 19.4858 16.7561 19.7204 16.47 19.5847L10.8385 16.9114C10.73 16.8599 10.604 16.8599 10.4955 16.9114L4.86394 19.5847C4.5779 19.7204 4.25499 19.4858 4.29573 19.1718L5.0979 12.9899C5.11336 12.8707 5.07444 12.7509 4.99189 12.6636L0.709252 8.13378C0.491728 7.9037 0.615069 7.52409 0.926288 7.46581L7.05357 6.31838C7.17168 6.29626 7.27358 6.22222 7.33112 6.11673L10.3158 0.643914Z" fill="CurrentColor"></path>
</svg>
{/* Табы */}
<div data-current="Tab 1" data-easing="ease" data-duration-in="300" data-duration-out="100" className="tabs w-tabs">
<div className="tabs-menu w-tab-menu" style={{ maxHeight: "70vh", overflowY: "auto" }}>
{tabData.map((tab, idx) => (
<a
key={tab.label}
data-w-tab={`Tab ${idx + 1}`}
className={`tab-link w-inline-block w-tab-link${activeTabIndex === idx ? " w--current" : ""}`}
onClick={() => {
setActiveTabIndex(idx);
}}
style={{ cursor: "pointer" }}
>
<div className="div-block-29">
<div className="code-embed-12 w-embed">
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.3158 0.643914C10.4674 0.365938 10.8666 0.365938 11.0182 0.643914L14.0029 6.11673C14.0604 6.22222 14.1623 6.29626 14.2804 6.31838L20.4077 7.46581C20.7189 7.52409 20.8423 7.9037 20.6247 8.13378L16.3421 12.6636C16.2595 12.7509 16.2206 12.8707 16.2361 12.9899L17.0382 19.1718C17.079 19.4858 16.7561 19.7204 16.47 19.5847L10.8385 16.9114C10.73 16.8599 10.604 16.8599 10.4955 16.9114L4.86394 19.5847C4.5779 19.7204 4.25499 19.4858 4.29573 19.1718L5.0979 12.9899C5.11336 12.8707 5.07444 12.7509 4.99189 12.6636L0.709252 8.13378C0.491728 7.9037 0.615069 7.52409 0.926288 7.46581L7.05357 6.31838C7.17168 6.29626 7.27358 6.22222 7.33112 6.11673L10.3158 0.643914Z" fill="CurrentColor"></path>
</svg>
</div>
</div>
</div>
<div className="text-block-49">{tab.label}</div>
</a>
))}
</div>
<div className="tabs-content w-tab-content">
{tabData.map((tab, idx) => (
<div
key={tab.label}
data-w-tab={`Tab ${idx + 1}`}
className={`tab-pane w-tab-pane${activeTabIndex === idx ? " w--tab-active" : ""}`}
style={{ display: activeTabIndex === idx ? "block" : "none" }}
>
<div className="w-layout-vflex flex-block-89">
<h3 className="heading-16">{tab.heading}</h3>
<div className="w-layout-hflex flex-block-92">
<div className="w-layout-vflex flex-block-91">
{tab.links.map((link, linkIndex) => {
const catalog = catalogsData?.partsIndexCategoriesWithGroups?.[idx];
// Ищем соответствующую подгруппу по названию
let subcategoryId = `fallback_${idx}_${linkIndex}`;
if (catalog?.groups) {
for (const group of catalog.groups) {
// Проверяем в подгруппах
if (group.subgroups && group.subgroups.length > 0) {
const foundSubgroup = group.subgroups.find((subgroup: any) => subgroup.name === link);
if (foundSubgroup) {
subcategoryId = foundSubgroup.id;
break;
}
}
// Если нет подгрупп, проверяем саму группу
else if (group.name === link) {
subcategoryId = group.id;
break;
}
}
}
return (
<div className="text-block-49">{tab.label}</div>
</a>
))}
</div>
<div className="tabs-content w-tab-content">
{tabData.map((tab, idx) => (
<div
key={tab.label}
data-w-tab={`Tab ${idx + 1}`}
className={`tab-pane w-tab-pane${activeTabIndex === idx ? " w--tab-active" : ""}`}
style={{ display: activeTabIndex === idx ? "block" : "none" }}
>
<div className="w-layout-vflex flex-block-89">
<h3 className="heading-16">{tab.heading}</h3>
<div className="w-layout-hflex flex-block-92">
<div className="w-layout-vflex flex-block-91">
{tab.links.length === 1 ? (
<div
className="link-2"
key={link}
onClick={() => {
const catalog = catalogsData?.partsIndexCategoriesWithGroups?.[idx];
let subcategoryId = `fallback_${idx}_0`;
if (catalog?.groups) {
for (const group of catalog.groups) {
if (group.subgroups && group.subgroups.length > 0) {
const foundSubgroup = group.subgroups.find((subgroup: any) => subgroup.name === tab.links[0]);
if (foundSubgroup) {
subcategoryId = foundSubgroup.id;
break;
}
} else if (group.name === tab.links[0]) {
subcategoryId = group.id;
break;
}
}
}
const catalogId = catalog?.id || 'fallback';
handleCategoryClick(catalogId, link, subcategoryId);
handleCategoryClick(catalogId, tab.links[0], subcategoryId);
}}
style={{ cursor: "pointer" }}
>
{link}
Показать все
</div>
);
})}
</div>
<div className="w-layout-vflex flex-block-91-copy">
<img src="https://d3e54v103j8qbb.cloudfront.net/plugins/Basic/assets/placeholder.60f9b1840c.svg" loading="lazy" alt="" className="image-17" />
<img src="https://d3e54v103j8qbb.cloudfront.net/plugins/Basic/assets/placeholder.60f9b1840c.svg" loading="lazy" alt="" className="image-17" />
) : (
tab.links.map((link: string, linkIndex: number) => {
const catalog = catalogsData?.partsIndexCategoriesWithGroups?.[idx];
let subcategoryId = `fallback_${idx}_${linkIndex}`;
if (catalog?.groups) {
for (const group of catalog.groups) {
if (group.subgroups && group.subgroups.length > 0) {
const foundSubgroup = group.subgroups.find((subgroup: any) => subgroup.name === link);
if (foundSubgroup) {
subcategoryId = foundSubgroup.id;
break;
}
} else if (group.name === link) {
subcategoryId = group.id;
break;
}
}
}
return (
<div
className="link-2"
key={link}
onClick={() => {
const catalogId = catalog?.id || 'fallback';
handleCategoryClick(catalogId, link, subcategoryId);
}}
style={{ cursor: "pointer" }}
>
{link}
</div>
);
})
)}
</div>
<div className="w-layout-vflex flex-block-91-copy">
<img src="https://d3e54v103j8qbb.cloudfront.net/plugins/Basic/assets/placeholder.60f9b1840c.svg" loading="lazy" alt="" className="image-17" />
<img src="https://d3e54v103j8qbb.cloudfront.net/plugins/Basic/assets/placeholder.60f9b1840c.svg" loading="lazy" alt="" className="image-17" />
</div>
</div>
</div>
</div>
</div>
))}
))}
</div>
</div>
</div>
</div>
</nav>
</>
);
};
</nav>
</>
);
};
export default BottomHead;
export default BottomHead;

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useQuery, useLazyQuery } from '@apollo/client';
import { GET_PARTSINDEX_CATEGORIES } from '@/lib/graphql';
function useIsMobile(breakpoint = 767) {
const [isMobile, setIsMobile] = React.useState(false);
@ -13,35 +14,38 @@ function useIsMobile(breakpoint = 767) {
return isMobile;
}
// Типы для Parts Index API
interface PartsIndexCatalog {
id: string;
name: string;
image: string;
}
interface PartsIndexEntityName {
id: string;
name: string;
}
interface PartsIndexGroup {
id: string;
name: string;
lang: string;
image: string;
lft: number;
rgt: number;
entityNames: PartsIndexEntityName[];
subgroups: PartsIndexGroup[];
}
// Типы данных
interface PartsIndexTabData {
label: string;
heading: string;
links: string[];
catalogId: string;
group?: PartsIndexGroup;
group?: any;
groupsLoaded?: boolean; // флаг что группы загружены
}
interface PartsIndexCatalog {
id: string;
name: string;
image?: string;
groups?: PartsIndexGroup[];
}
interface PartsIndexGroup {
id: string;
name: string;
image?: string;
entityNames?: { id: string; name: string }[];
subgroups?: { id: string; name: string }[];
}
// GraphQL типы
interface PartsIndexCatalogsData {
partsIndexCategoriesWithGroups: PartsIndexCatalog[];
}
interface PartsIndexCatalogsVariables {
lang?: 'ru' | 'en';
}
// Fallback статичные данные
@ -51,57 +55,66 @@ const fallbackTabData: PartsIndexTabData[] = [
heading: "Детали ТО",
catalogId: "parts_to",
links: ["Детали ТО"],
groupsLoaded: false,
},
{
label: "Масла",
heading: "Масла",
catalogId: "oils",
links: ["Масла"],
groupsLoaded: false,
},
{
label: "Шины",
heading: "Шины",
catalogId: "tyres",
links: ["Шины"],
groupsLoaded: false,
},
];
// Сервис для работы с Parts Index API
const PARTS_INDEX_API_BASE = 'https://api.parts-index.com';
const API_KEY = 'PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE';
// Создаем базовые табы только с названиями каталогов
const createBaseTabData = (catalogs: PartsIndexCatalog[]): PartsIndexTabData[] => {
console.log('🔄 Создаем базовые табы из каталогов:', catalogs.length, 'элементов');
async function fetchCatalogs(): Promise<PartsIndexCatalog[]> {
try {
const response = await fetch(`${PARTS_INDEX_API_BASE}/v1/catalogs?lang=ru`, {
headers: { 'Accept': 'application/json' },
});
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
return data.list;
} catch (error) {
console.error('Ошибка получения каталогов Parts Index:', error);
return [];
}
}
return catalogs.map(catalog => ({
label: catalog.name,
heading: catalog.name,
links: [catalog.name], // Изначально показываем только название каталога
catalogId: catalog.id,
groupsLoaded: false, // Группы еще не загружены
}));
};
async function fetchCatalogGroup(catalogId: string): Promise<PartsIndexGroup | null> {
try {
const response = await fetch(
`${PARTS_INDEX_API_BASE}/v1/catalogs/${catalogId}/groups?lang=ru`,
{
headers: {
'Accept': 'application/json',
'Authorization': API_KEY,
},
// Преобразуем данные PartsIndex в формат нашего меню с группами
const transformPartsIndexToTabData = (catalog: PartsIndexCatalog): string[] => {
console.log(`📝 Обрабатываем группы каталога: "${catalog.name}"`);
let links: string[] = [];
if (catalog.groups && catalog.groups.length > 0) {
// Для каждой группы проверяем есть ли подгруппы
catalog.groups.forEach(group => {
if (group.subgroups && group.subgroups.length > 0) {
// Если есть подгруппы, добавляем их названия
links.push(...group.subgroups.slice(0, 9 - links.length).map(subgroup => subgroup.name));
} else {
// Если подгрупп нет, добавляем название самой группы
if (links.length < 9) {
links.push(group.name);
}
}
);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.json();
} catch (error) {
console.error(`Ошибка получения группы каталога ${catalogId}:`, error);
return null;
});
}
}
// Если подкатегорий нет, показываем название категории
if (links.length === 0) {
links = [catalog.name];
}
console.log(`🔗 Подкатегории для "${catalog.name}":`, links);
return links.slice(0, 9); // Ограничиваем максимум 9 элементов
};
const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClose: () => void }) => {
const isMobile = useIsMobile();
@ -109,7 +122,7 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
const [mobileCategory, setMobileCategory] = useState<null | any>(null);
const [tabData, setTabData] = useState<PartsIndexTabData[]>(fallbackTabData);
const [activeTabIndex, setActiveTabIndex] = useState(0);
const [loading, setLoading] = useState(false);
const [loadingGroups, setLoadingGroups] = useState<Set<number>>(new Set());
// Пагинация категорий
const [currentPage, setCurrentPage] = useState(0);
@ -126,52 +139,116 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
}
}, [menuOpen]);
// Загрузка каталогов и их групп
// Получаем только каталоги PartsIndex (без групп для начальной загрузки)
const { data: catalogsData, loading, error } = useQuery<PartsIndexCatalogsData, PartsIndexCatalogsVariables>(
GET_PARTSINDEX_CATEGORIES,
{
variables: {
lang: 'ru'
},
errorPolicy: 'all',
fetchPolicy: 'cache-first', // Используем кэширование агрессивно
nextFetchPolicy: 'cache-first', // Продолжаем использовать кэш
notifyOnNetworkStatusChange: false,
onCompleted: (data) => {
console.log('🎉 PartsIndex каталоги получены через GraphQL (базовые):', data);
},
onError: (error) => {
console.error('❌ Ошибка загрузки PartsIndex каталогов:', error);
}
}
);
// Ленивый запрос для загрузки групп конкретного каталога
const [loadCatalogGroups, { loading: groupsLoading }] = useLazyQuery<PartsIndexCatalogsData, PartsIndexCatalogsVariables>(
GET_PARTSINDEX_CATEGORIES,
{
errorPolicy: 'all',
fetchPolicy: 'cache-first',
nextFetchPolicy: 'cache-first',
notifyOnNetworkStatusChange: false,
onCompleted: (data) => {
console.log('🎉 Группы каталога загружены:', data);
},
onError: (error) => {
console.error('❌ Ошибка загрузки групп каталога:', error);
}
}
);
// Обновляем базовые данные табов когда получаем каталоги
useEffect(() => {
const loadData = async () => {
if (tabData === fallbackTabData) { // Загружаем только если еще не загружали
setLoading(true);
try {
console.log('🔄 Загружаем каталоги Parts Index...');
const catalogs = await fetchCatalogs();
if (catalogsData?.partsIndexCategoriesWithGroups && catalogsData.partsIndexCategoriesWithGroups.length > 0) {
console.log('✅ Обновляем базовое меню PartsIndex:', catalogsData.partsIndexCategoriesWithGroups.length, 'каталогов');
if (catalogs.length > 0) {
console.log(`✅ Получено ${catalogs.length} каталогов`);
const baseTabData = createBaseTabData(catalogsData.partsIndexCategoriesWithGroups);
setTabData(baseTabData);
setActiveTabIndex(0);
} else if (error) {
console.warn('⚠️ Используем fallback данные из-за ошибки PartsIndex:', error);
setTabData(fallbackTabData);
setActiveTabIndex(0);
}
}, [catalogsData, error]);
// Загружаем группы для первых нескольких каталогов
const catalogsToLoad = catalogs.slice(0, 10);
const tabDataPromises = catalogsToLoad.map(async (catalog) => {
const group = await fetchCatalogGroup(catalog.id);
// Функция для ленивой загрузки групп при наведении на таб
const loadGroupsForTab = async (tabIndex: number) => {
const tab = tabData[tabIndex];
if (!tab || tab.groupsLoaded || loadingGroups.has(tabIndex)) {
return; // Группы уже загружены или загружаются
}
// Получаем подкатегории из entityNames или повторяем название категории
const links = group?.entityNames && group.entityNames.length > 0
? group.entityNames.slice(0, 9).map(entity => entity.name)
: [catalog.name]; // Если нет подкатегорий, повторяем название категории
console.log('🔄 Загружаем группы для каталога:', tab.catalogId);
setLoadingGroups(prev => new Set([...prev, tabIndex]));
return {
label: catalog.name,
heading: catalog.name,
links,
catalogId: catalog.id,
group
};
});
try {
const result = await loadCatalogGroups({
variables: {
lang: 'ru'
}
});
const apiTabData = await Promise.all(tabDataPromises);
console.log('✅ Данные обновлены:', apiTabData.length, 'категорий');
setTabData(apiTabData as PartsIndexTabData[]);
setActiveTabIndex(0);
}
} catch (error) {
console.error('Ошибка загрузки данных Parts Index:', error);
} finally {
setLoading(false);
if (result.data?.partsIndexCategoriesWithGroups) {
const catalog = result.data.partsIndexCategoriesWithGroups.find(c => c.id === tab.catalogId);
if (catalog) {
const links = transformPartsIndexToTabData(catalog);
// Обновляем конкретный таб с загруженными группами
setTabData(prevTabs => {
const newTabs = [...prevTabs];
newTabs[tabIndex] = {
...newTabs[tabIndex],
links,
group: catalog.groups?.[0],
groupsLoaded: true
};
return newTabs;
});
}
}
};
} catch (error) {
console.error('Ошибка загрузки групп для каталога:', tab.catalogId, error);
} finally {
setLoadingGroups(prev => {
const newSet = new Set(prev);
newSet.delete(tabIndex);
return newSet;
});
}
};
loadData();
}, []);
// Обработчик наведения на таб - загружаем группы
const handleTabHover = (tabIndex: number) => {
loadGroupsForTab(tabIndex);
};
// Обработчик клика на таб
const handleTabClick = (tabIndex: number) => {
setActiveTabIndex(tabIndex);
// Загружаем группы если еще не загружены
loadGroupsForTab(tabIndex);
};
// Обработка клика по категории для перехода в каталог
const handleCategoryClick = (catalogId: string, categoryName: string, entityId?: string) => {
@ -184,7 +261,7 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
query: {
partsIndexCatalog: catalogId,
categoryName: encodeURIComponent(categoryName),
...(entityId && { entityId })
...(entityId && { partsIndexCategory: entityId })
}
});
};
@ -294,6 +371,12 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
className="mobile-subcategory"
key={cat.catalogId}
onClick={() => {
// Загружаем группы для категории если нужно
const catIndex = tabData.findIndex(tab => tab.catalogId === cat.catalogId);
if (catIndex !== -1) {
loadGroupsForTab(catIndex);
}
const categoryWithData = {
...cat,
catalogId: cat.catalogId,
@ -304,6 +387,9 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
style={{ cursor: "pointer" }}
>
{cat.label}
{loadingGroups.has(tabData.findIndex(tab => tab.catalogId === cat.catalogId)) && (
<span className="text-xs text-gray-500 ml-2">(загрузка...)</span>
)}
</div>
))}
</div>
@ -367,9 +453,8 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
href="#"
className={`link-block-7 w-inline-block${activeTabIndex === idx ? " w--current" : ""}`}
key={tab.catalogId}
onClick={() => {
setActiveTabIndex(idx);
}}
onClick={() => handleTabClick(idx)}
onMouseEnter={() => handleTabHover(idx)}
style={{ cursor: "pointer" }}
>
<div className="div-block-29">
@ -388,6 +473,7 @@ const BottomHeadPartsIndex = ({ menuOpen, onClose }: { menuOpen: boolean; onClos
<h3 className="heading-16">
{currentPageCategories[activeTabIndex]?.heading || currentPageCategories[0]?.heading}
{loading && <span className="text-sm text-gray-500 ml-2">(обновление...)</span>}
{loadingGroups.has(activeTabIndex) && <span className="text-sm text-gray-500 ml-2">(загрузка групп...)</span>}
</h3>
<div className="w-layout-hflex flex-block-92">
<div className="w-layout-vflex flex-block-91">

View File

@ -14,6 +14,8 @@ const BrandWizardSearchSection: React.FC = () => {
const [vehicles, setVehicles] = useState<LaximoVehicleSearchResult[] | null>(null);
const [brandQuery, setBrandQuery] = useState('');
const buttonRef = useRef<HTMLButtonElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const [isOpen, setIsOpen] = useState(false);
// Получение информации о каталоге через useQuery
const {
@ -68,7 +70,7 @@ const BrandWizardSearchSection: React.FC = () => {
const catalogInfo = catalogData?.laximoCatalogInfo;
return (
<section className="max-w-[1100px] min-h-[450px] mx-auto bg-white rounded-2xl shadow p-6 md:p-10 my-8">
<section className="max-w-[1580px] min-h-[450px] mx-auto bg-white rounded-2xl shadow p-6 md:p-10 my-8">
{/* <div className="text-2xl font-bold text-gray-900 mb-6 mt-6 text-center" style={{ fontSize: '28px' }}>Подбор автомобиля по параметрам</div> */}
{/* Combobox бренда */}
<div className="mb-8 w-full">
@ -82,45 +84,64 @@ const BrandWizardSearchSection: React.FC = () => {
</div>
<Combobox value={selectedBrand} onChange={handleBrandChange} nullable>
<div className="relative">
{/* Невидимая кнопка поверх инпута */}
<button
type="button"
className="absolute top-0 left-0 w-full h-full opacity-0 z-10 cursor-pointer"
tabIndex={0}
aria-label="Открыть список брендов"
onClick={() => {
inputRef.current?.focus();
if (inputRef.current) {
inputRef.current.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
}
}}
/>
<Combobox.Input
ref={inputRef}
id="brand-combobox"
className="w-full px-6 py-4 bg-white rounded border border-stone-300 text-sm text-gray-950 placeholder:text-neutral-500 outline-none focus:shadow-none focus:border-stone-300 transition-colors"
displayValue={(brand: LaximoBrand | null) => brand?.name || ''}
onChange={e => setBrandQuery(e.target.value)}
placeholder="Начните вводить бренд..."
autoComplete="off"
onFocus={() => setIsOpen(true)}
onClick={() => setIsOpen(true)}
onBlur={() => setIsOpen(false)}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center px-3 focus:outline-none w-12">
<Combobox.Button ref={buttonRef} className="absolute inset-y-0 right-0 flex items-center px-3 focus:outline-none w-12">
<svg className="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 9l6 6 6-6" />
</svg>
</Combobox.Button>
<Combobox.Options
className="absolute left-0 top-full z-10 bg-white border-x border-b border-stone-300 rounded-b-lg shadow-lg w-full max-h-60 overflow-auto scrollbar-none"
style={{ scrollbarWidth: 'none' }}
data-hide-scrollbar
>
{brandsLoading && (
<div className="px-6 py-4 text-gray-500">Загрузка брендов...</div>
)}
{brandsError && (
<div className="px-6 py-4 text-red-500">Ошибка загрузки брендов</div>
)}
{filteredBrands.length === 0 && !brandsLoading && !brandsError && (
<div className="px-6 py-4 text-gray-500">Бренды не найдены</div>
)}
{filteredBrands.map(brand => (
<Combobox.Option
key={brand.code}
value={brand}
className={({ active, selected }) =>
`px-6 py-4 cursor-pointer hover:!bg-[rgb(236,28,36)] hover:!text-white text-sm transition-colors ${selected ? 'bg-red-50 font-semibold text-gray-950' : 'text-neutral-500'}`
}
>
{brand.name}
</Combobox.Option>
))}
</Combobox.Options>
{isOpen && (
<Combobox.Options
className="absolute left-0 top-full z-10 bg-white border-x border-b border-stone-300 rounded-b-lg shadow-lg w-full max-h-60 overflow-auto scrollbar-none"
style={{ scrollbarWidth: 'none' }}
data-hide-scrollbar
>
{brandsLoading && (
<div className="px-6 py-4 text-gray-500">Загрузка брендов...</div>
)}
{brandsError && (
<div className="px-6 py-4 text-red-500">Ошибка загрузки брендов</div>
)}
{filteredBrands.length === 0 && !brandsLoading && !brandsError && (
<div className="px-6 py-4 text-gray-500">Бренды не найдены</div>
)}
{filteredBrands.map(brand => (
<Combobox.Option
key={brand.code}
value={brand}
className={({ active, selected }) =>
`px-6 py-4 cursor-pointer hover:!bg-[rgb(236,28,36)] hover:!text-white text-sm transition-colors ${selected ? 'bg-red-50 font-semibold text-gray-950' : 'text-neutral-500'}`
}
>
{brand.name}
</Combobox.Option>
))}
</Combobox.Options>
)}
</div>
</Combobox>
</div>
@ -152,7 +173,7 @@ const BrandWizardSearchSection: React.FC = () => {
</>
)}
{catalogInfo && !catalogInfo.supportparameteridentification2 && (
<div className="text-yellow-700 bg-yellow-50 border border-yellow-200 rounded-lg p-4 mt-6 text-center">
<div className="text-blue-700 bg-blue-50 border border-blue-200 rounded-lg p-4 mt-6 text-center">
Для выбранного бренда подбор по параметрам недоступен.
</div>
)}

View File

@ -1,79 +1,54 @@
import React, { useState, useEffect } from 'react';
import React from 'react';
import { useCart } from '@/contexts/CartContext';
const CartDebug: React.FC = () => {
const { state, addItem, clearCart } = useCart();
const [debugInfo, setDebugInfo] = useState<any>({});
const { state, isInCart } = useCart();
useEffect(() => {
if (typeof window !== 'undefined') {
const cartState = localStorage.getItem('cartState');
const cartSummaryState = localStorage.getItem('cartSummaryState');
const oldCart = localStorage.getItem('cart');
if (process.env.NODE_ENV !== 'development') {
return null;
}
setDebugInfo({
cartState: cartState ? JSON.parse(cartState) : null,
cartSummaryState: cartSummaryState ? JSON.parse(cartSummaryState) : null,
oldCart: oldCart ? JSON.parse(oldCart) : null,
currentItems: state.items.length
});
}
}, [state.items]);
const addTestItem = () => {
addItem({
name: 'Тестовый товар',
description: 'Описание тестового товара',
article: 'TEST123',
brand: 'TestBrand',
price: 1000,
currency: 'RUB',
quantity: 1,
image: '',
productId: 'test-product',
offerKey: 'test-offer',
isExternal: false
});
};
const clearStorage = () => {
if (typeof window !== 'undefined') {
localStorage.removeItem('cartState');
localStorage.removeItem('cartSummaryState');
localStorage.removeItem('cart');
window.location.reload();
}
};
// Test the isInCart function with some example values from the cart
const testItem = state.items[0];
const testResult = testItem ? isInCart(testItem.productId, testItem.offerKey, testItem.article, testItem.brand) : false;
return (
<div style={{
position: 'fixed',
top: '10px',
right: '10px',
background: 'white',
border: '1px solid #ccc',
padding: '10px',
borderRadius: '5px',
maxWidth: '300px',
fontSize: '12px',
zIndex: 9999
}}>
<h4>Cart Debug</h4>
<button onClick={addTestItem} style={{ marginBottom: '5px', marginRight: '5px' }}>
Добавить товар
</button>
<button onClick={clearCart} style={{ marginBottom: '5px', marginRight: '5px' }}>
Очистить корзину
</button>
<button onClick={clearStorage} style={{ marginBottom: '10px' }}>
Очистить localStorage
</button>
<div>
<strong>Товаров в корзине:</strong> {state.items.length}
</div>
<pre style={{ fontSize: '10px', maxHeight: '200px', overflow: 'auto' }}>
{JSON.stringify(debugInfo, null, 2)}
</pre>
<div
style={{
position: 'fixed',
top: '10px',
right: '10px',
background: 'rgba(0,0,0,0.9)',
color: 'white',
padding: '10px',
borderRadius: '5px',
fontSize: '11px',
maxWidth: '350px',
zIndex: 9999,
maxHeight: '400px',
overflow: 'auto'
}}
>
<div style={{ fontWeight: 'bold', marginBottom: '5px' }}>🛒 Cart Debug: {state.items.length} items</div>
{testItem && (
<div style={{ background: 'rgba(255,255,255,0.1)', padding: '5px', marginBottom: '5px', fontSize: '10px' }}>
<div>Testing isInCart for first item:</div>
<div>Brand: {testItem.brand}, Article: {testItem.article}</div>
<div>Result: {testResult ? '✅ Found' : '❌ Not found'}</div>
</div>
)}
{state.items.slice(0, 6).map((item, idx) => (
<div key={idx} style={{ fontSize: '9px', marginTop: '3px', borderBottom: '1px solid rgba(255,255,255,0.2)', paddingBottom: '2px' }}>
{item.brand} {item.article}
{item.productId && <div style={{ color: '#90EE90' }}>PID: {item.productId.substring(0, 8)}...</div>}
{item.offerKey && <div style={{ color: '#87CEEB' }}>OK: {item.offerKey.substring(0, 15)}...</div>}
</div>
))}
{state.items.length > 6 && (
<div style={{ fontSize: '9px', marginTop: '3px', opacity: 0.7 }}>
...и еще {state.items.length - 6} товаров
</div>
)}
</div>
);
};

View File

@ -0,0 +1,25 @@
import React from 'react';
interface CartIconProps {
size?: number;
color?: string;
}
const CartIcon: React.FC<CartIconProps> = ({ size = 24, color = '#fff' }) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z"
fill={color}
/>
</svg>
);
};
export default CartIcon;

View File

@ -32,7 +32,7 @@ const CartInfo: React.FC = () => {
<h1 className="heading">Корзина</h1>
<div className="text-block-4">
{summary.totalItems > 0 ? (
<>В вашей корзине {summary.totalItems} товара на <strong>{formatPrice(summary.finalPrice)}</strong></>
<>В вашей корзине {summary.totalItems} товара на <strong>{formatPrice(summary.totalPrice - summary.totalDiscount)}</strong></>
) : (
'Ваша корзина пуста'
)}

View File

@ -38,160 +38,180 @@ const CartItem: React.FC<CartItemProps> = ({
onRemove,
isSummaryStep = false,
itemNumber,
}) => (
<div className="w-layout-hflex cart-item">
<div className="w-layout-hflex info-block-search-copy">
{isSummaryStep ? (
<div style={{ marginRight: 12, minWidth: 24, textAlign: 'center', fontWeight: 600, fontSize: 14 }}>{itemNumber}</div>
) : (
<div
className={"div-block-7" + (selected ? " active" : "")}
onClick={onSelect}
style={{ marginRight: 12, cursor: 'pointer' }}
>
{selected && (
<svg width="14" height="10" viewBox="0 0 14 10" fill="none">
<path d="M2 5.5L6 9L12 2" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)}
</div>
)}
<div className="w-layout-hflex block-name">
<h4 className="heading-9-copy">{name}</h4>
<div
className={
"text-block-21-copy" +
(isSummaryStep && itemNumber === 1 ? " border-t-0" : "")
}
style={
isSummaryStep && itemNumber === 1
? { borderTop: 'none' }
: undefined
}
>
{description}
</div>
</div>
<div className="form-block-copy w-form">
<form className="form-copy" onSubmit={e => e.preventDefault()}>
<input
className="text-field-copy w-input"
maxLength={256}
name="Search-5"
data-name="Search 5"
placeholder="Комментарий"
type="text"
id="Search-5"
value={comment}
onChange={e => onComment(e.target.value)}
disabled={isSummaryStep}
/>
</form>
<div className="success-message w-form-done">
<div>Thank you! Your submission has been received!</div>
</div>
<div className="error-message w-form-fail">
<div>Oops! Something went wrong while submitting the form.</div>
</div>
</div>
</div>
<div className="w-layout-hflex add-to-cart-block">
<div className="w-layout-hflex flex-block-39-copy">
<h4 className="delivery-cart-s1">{delivery}</h4>
<div className="text-block-21-copy-copy">{deliveryDate}</div>
</div>
<div className="w-layout-hflex pcs-cart-s1">
}) => {
// --- Фикс для input: можно стереть, при blur пустое = 1 ---
const [inputValue, setInputValue] = React.useState(count.toString());
React.useEffect(() => {
setInputValue(count.toString());
}, [count]);
return (
<div className="w-layout-hflex cart-item">
<div className="w-layout-hflex info-block-search-copy">
{isSummaryStep ? (
<div className="text-block-26" style={{ fontWeight: 600, fontSize: 14 }}>{count} шт.</div>
<div style={{ marginRight: 12, minWidth: 24, textAlign: 'center', fontWeight: 600, fontSize: 14 }}>{itemNumber}</div>
) : (
<>
<div
className="minus-plus"
onClick={() => onCountChange && onCountChange(count - 1)}
style={{ cursor: 'pointer' }}
aria-label="Уменьшить количество"
tabIndex={0}
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count - 1)}
role="button"
>
<div className="pluspcs w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 10.5V9.5H14V10.5H6Z" fill="currentColor"/>
</svg>
</div>
<div
className={"div-block-7" + (selected ? " active" : "")}
onClick={onSelect}
style={{ marginRight: 12, cursor: 'pointer' }}
>
{selected && (
<svg width="14" height="10" viewBox="0 0 14 10" fill="none">
<path d="M2 5.5L6 9L12 2" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
)}
</div>
<div className="input-pcs">
<input
type="number"
min={1}
value={count}
onChange={e => {
const value = Math.max(1, parseInt(e.target.value, 10) || 1);
onCountChange && onCountChange(value);
}}
className="text-block-26 w-full text-center outline-none"
aria-label="Количество"
style={{ width: 40 }}
/>
</div>
<div
className="minus-plus"
onClick={() => onCountChange && onCountChange(count + 1)}
style={{ cursor: 'pointer' }}
aria-label="Увеличить количество"
tabIndex={0}
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count + 1)}
role="button"
>
<div className="pluspcs w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 10.5V9.5H14V10.5H6ZM9.5 6H10.5V14H9.5V6Z" fill="currentColor"/>
</svg>
</div>
</div>
</>
)}
</div>
<div className="w-layout-hflex flex-block-39-copy-copy">
<h4 className="price-in-cart-s1">{price}</h4>
<div className="price-1-pcs-cart-s1">{pricePerItem}</div>
</div>
{!isSummaryStep && (
<div className="w-layout-hflex control-element">
<div className="favorite-icon w-embed" onClick={onFavorite} style={{ cursor: 'pointer', color: favorite ? '#e53935' : undefined }}>
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 16.5L7.84 15.4929C3.72 11.93 1 9.57248 1 6.69619C1 4.33869 2.936 2.5 5.4 2.5C6.792 2.5 8.128 3.11798 9 4.08692C9.872 3.11798 11.208 2.5 12.6 2.5C15.064 2.5 17 4.33869 17 6.69619C17 9.57248 14.28 11.93 10.16 15.4929L9 16.5Z" fill={favorite ? "#e53935" : "currentColor"} />
</svg>
</div>
<div className="w-layout-hflex block-name">
<h4 className="heading-9-copy">{name}</h4>
<div
className="bdel"
role="button"
tabIndex={0}
aria-label="Удалить из корзины"
onClick={onRemove}
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onRemove && onRemove()}
style={{ display: 'inline-flex', cursor: 'pointer', transition: 'color 0.2s' }}
onMouseEnter={e => {
const path = e.currentTarget.querySelector('path');
if (path) path.setAttribute('fill', '#ec1c24');
}}
onMouseLeave={e => {
const path = e.currentTarget.querySelector('path');
if (path) path.setAttribute('fill', '#D0D0D0');
}}
className={
"text-block-21-copy" +
(isSummaryStep && itemNumber === 1 ? " border-t-0" : "")
}
style={
isSummaryStep && itemNumber === 1
? { borderTop: 'none' }
: undefined
}
>
{description}
</div>
</div>
<div className="form-block-copy w-form">
<form className="form-copy" onSubmit={e => e.preventDefault()}>
<input
className="text-field-copy w-input"
maxLength={256}
name="Search-5"
data-name="Search 5"
placeholder="Комментарий"
type="text"
id="Search-5"
value={comment}
onChange={e => onComment(e.target.value)}
disabled={isSummaryStep}
/>
</form>
<div className="success-message w-form-done">
<div>Thank you! Your submission has been received!</div>
</div>
<div className="error-message w-form-fail">
<div>Oops! Something went wrong while submitting the form.</div>
</div>
</div>
</div>
<div className="w-layout-hflex add-to-cart-block">
<div className="w-layout-hflex flex-block-39-copy">
<h4 className="delivery-cart-s1">{delivery}</h4>
<div className="text-block-21-copy-copy">{deliveryDate}</div>
</div>
<div className="w-layout-hflex pcs-cart-s1">
{isSummaryStep ? (
<div className="text-block-26" style={{ fontWeight: 600, fontSize: 14 }}>{count} шт.</div>
) : (
<>
<div
className="minus-plus"
onClick={() => onCountChange && onCountChange(count - 1)}
style={{ cursor: 'pointer' }}
aria-label="Уменьшить количество"
tabIndex={0}
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count - 1)}
role="button"
>
<div className="pluspcs w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 10.5V9.5H14V10.5H6Z" fill="currentColor"/>
</svg>
</div>
</div>
<div className="input-pcs">
<input
type="number"
min={1}
value={inputValue}
onChange={e => {
const val = e.target.value;
setInputValue(val);
if (val === "") {
// Не обновляем count, пока не будет blur
return;
}
const valueNum = Math.max(1, parseInt(val, 10) || 1);
onCountChange && onCountChange(valueNum);
}}
onBlur={() => {
if (inputValue === "") {
setInputValue("1");
onCountChange && onCountChange(1);
}
}}
className="text-block-26 w-full text-center outline-none"
aria-label="Количество"
style={{ width: 40 }}
/>
</div>
<div
className="minus-plus"
onClick={() => onCountChange && onCountChange(count + 1)}
style={{ cursor: 'pointer' }}
aria-label="Увеличить количество"
tabIndex={0}
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onCountChange && onCountChange(count + 1)}
role="button"
>
<div className="pluspcs w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 10.5V9.5H14V10.5H6ZM9.5 6H10.5V14H9.5V6Z" fill="currentColor"/>
</svg>
</div>
</div>
</>
)}
</div>
<div className="w-layout-hflex flex-block-39-copy-copy">
<h4 className="price-in-cart-s1">{price}</h4>
<div className="price-1-pcs-cart-s1">{pricePerItem}</div>
</div>
{!isSummaryStep && (
<div className="w-layout-hflex control-element">
<div className="favorite-icon w-embed" onClick={onFavorite} style={{ cursor: 'pointer', color: favorite ? '#e53935' : undefined }}>
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
fill="#D0D0D0"
style={{ transition: 'fill 0.2s' }}
/>
<path d="M9 16.5L7.84 15.4929C3.72 11.93 1 9.57248 1 6.69619C1 4.33869 2.936 2.5 5.4 2.5C6.792 2.5 8.128 3.11798 9 4.08692C9.872 3.11798 11.208 2.5 12.6 2.5C15.064 2.5 17 4.33869 17 6.69619C17 9.57248 14.28 11.93 10.16 15.4929L9 16.5Z" fill={favorite ? "#e53935" : "currentColor"} />
</svg>
</div>
<div
className="bdel"
role="button"
tabIndex={0}
aria-label="Удалить из корзины"
onClick={onRemove}
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && onRemove && onRemove()}
style={{ display: 'inline-flex', cursor: 'pointer', transition: 'color 0.2s' }}
onMouseEnter={e => {
const path = e.currentTarget.querySelector('path');
if (path) path.setAttribute('fill', '#ec1c24');
}}
onMouseLeave={e => {
const path = e.currentTarget.querySelector('path');
if (path) path.setAttribute('fill', '#D0D0D0');
}}
>
<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.625 17.5C4.14375 17.5 3.73192 17.3261 3.3895 16.9782C3.04708 16.6304 2.87558 16.2117 2.875 15.7222V4.16667H2V2.38889H6.375V1.5H11.625V2.38889H16V4.16667H15.125V15.7222C15.125 16.2111 14.9538 16.6298 14.6114 16.9782C14.269 17.3267 13.8568 17.5006 13.375 17.5H4.625ZM6.375 13.9444H8.125V5.94444H6.375V13.9444ZM9.875 13.9444H11.625V5.94444H9.875V13.9444Z"
fill="#D0D0D0"
style={{ transition: 'fill 0.2s' }}
/>
</svg>
</div>
</div>
)}
</div>
)}
</div>
</div>
);
);
};
export default CartItem;

View File

@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import CartItem from "./CartItem";
import { useCart } from "@/contexts/CartContext";
import { useFavorites } from "@/contexts/FavoritesContext";
@ -8,7 +8,7 @@ interface CartListProps {
}
const CartList: React.FC<CartListProps> = ({ isSummaryStep = false }) => {
const { state, toggleSelect, updateComment, removeItem, selectAll, removeSelected, updateQuantity } = useCart();
const { state, toggleSelect, updateComment, removeItem, selectAll, removeSelected, updateQuantity, clearError } = useCart();
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
const { items } = state;
@ -73,8 +73,40 @@ const CartList: React.FC<CartListProps> = ({ isSummaryStep = false }) => {
// На втором шаге показываем только выбранные товары
const displayItems = isSummaryStep ? items.filter(item => item.selected) : items;
// Автоматически очищаем ошибки через 5 секунд
useEffect(() => {
if (state.error) {
const timer = setTimeout(() => {
clearError();
}, 5000);
return () => clearTimeout(timer);
}
}, [state.error, clearError]);
return (
<div className="w-layout-vflex flex-block-48">
<div className="w-layout-vflex flex-block-48" style={{ minHeight: '420px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
{/* Отображение ошибок корзины */}
{state.error && (
<div className="alert alert-error mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
<div className="flex items-center justify-between">
<div className="flex items-center">
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
<span>{state.error}</span>
</div>
<button
onClick={clearError}
className="ml-2 text-red-500 hover:text-red-700"
aria-label="Закрыть уведомление"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
)}
<div className="w-layout-vflex product-list-cart">
{!isSummaryStep && (
<div className="w-layout-hflex multi-control">
@ -113,9 +145,15 @@ const CartList: React.FC<CartListProps> = ({ isSummaryStep = false }) => {
</div>
)}
{displayItems.length === 0 ? (
<div className="empty-cart-message" style={{ textAlign: 'center', padding: '2rem', color: '#666' }}>
<p>Ваша корзина пуста</p>
<p>Добавьте товары из каталога</p>
<div className="empty-cart-message" style={{ textAlign: 'center', width: '100%' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '20px', justifyContent: 'center' }}>
<span style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', width: 90, height: 90, borderRadius: '50%', background: '#f3f4f6', marginBottom: 8 }}>
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 36C14.3431 36 13 37.3431 13 39C13 40.6569 14.3431 42 16 42C17.6569 42 19 40.6569 19 39C19 37.3431 17.6569 36 16 36ZM6 8V12H10.68L16.44 24.016L14.16 28.08C13.7647 28.8001 13.5556 29.6352 13.5556 30.5C13.5556 32.1569 14.8987 33.5 16.5556 33.5H39V30.5H17.1756C17.0891 30.5 17.0178 30.4287 17.0178 30.3422L17.04 30.25L18.88 27H34.8C36.0212 27 37.1042 26.2627 37.6 25.16L43.048 14.352C43.1746 14.0993 43.2382 13.8132 43.2302 13.5242C43.2222 13.2352 43.1428 12.9538 42.9992 12.7087C42.8556 12.4636 42.6532 12.2632 42.4136 12.1302C42.174 11.9972 41.9062 11.9376 41.64 11.96H12.24L10.84 8H6ZM36 36C34.3431 36 33 37.3431 33 39C33 40.6569 34.3431 42 36 42C37.6569 42 39 40.6569 39 39C39 37.3431 37.6569 36 36 36Z" fill="#222"/>
</svg>
</span>
<div style={{ fontSize: '1.7rem', fontWeight: 700, color: '#222' }}>Ваша корзина пуста</div>
</div>
</div>
) : (
displayItems.map((item, idx) => {
@ -138,7 +176,7 @@ const CartList: React.FC<CartListProps> = ({ isSummaryStep = false }) => {
description={item.description}
delivery={item.deliveryTime || 'Уточняется'}
deliveryDate={item.deliveryDate || ''}
price={formatPrice(item.price, item.currency)}
price={formatPrice(item.price * item.quantity, item.currency)}
pricePerItem={`${formatPrice(item.price, item.currency)}/шт`}
count={item.quantity}
comment={item.comment || ''}

View File

@ -3,6 +3,7 @@ import CatalogProductCard from "./CatalogProductCard";
import { useArticleImage } from "@/hooks/useArticleImage";
import { useCart } from "@/contexts/CartContext";
import { toast } from "react-hot-toast";
import CartIcon from "./CartIcon";
interface CartRecommendedProps {
recommendedProducts?: any[];
@ -36,13 +37,14 @@ const RecommendedProductCard: React.FC<{
}
// Добавляем товар в корзину
addItem({
const result = await addItem({
productId: String(item.artId) || undefined,
name: item.name || `${item.brand} ${item.articleNumber}`,
description: item.name || `${item.brand} ${item.articleNumber}`,
price: numericPrice,
currency: 'RUB',
quantity: 1,
stock: undefined, // информация о наличии не доступна для рекомендуемых товаров
image: displayImage,
brand: item.brand,
article: item.articleNumber,
@ -51,17 +53,22 @@ const RecommendedProductCard: React.FC<{
isExternal: true
});
// Показываем успешный тоастер
toast.success(
<div>
<div className="font-semibold">Товар добавлен в корзину!</div>
<div className="text-sm text-gray-600">{item.name || `${item.brand} ${item.articleNumber}`}</div>
</div>,
{
duration: 3000,
icon: '🛒',
}
);
if (result.success) {
// Показываем успешный тоастер
toast.success(
<div>
<div className="font-semibold" style={{ color: '#fff' }}>Товар добавлен в корзину!</div>
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{item.name || `${item.brand} ${item.articleNumber}`}</div>
</div>,
{
duration: 3000,
icon: <CartIcon size={20} color="#fff" />,
}
);
} else {
// Показываем ошибку
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
}
} catch (error) {
console.error('Ошибка добавления в корзину:', error);
toast.error('Ошибка при добавлении товара в корзину');

View File

@ -42,7 +42,7 @@ const CartRecommendedProductCard: React.FC<CartRecommendedProductCardProps> = ({
<Link href="/cart" className="link-block-4-copy w-inline-block">
<div className="div-block-25">
<span className="icon-setting w-embed">
<svg width="currentWidht" height="currentHight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor" />
</svg>
</span>

View File

@ -285,7 +285,7 @@ const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
onClick={() => setShowLegalEntityDropdown(!showLegalEntityDropdown)}
style={{ cursor: 'pointer', justifyContent: 'space-between', alignItems: 'center' }}
>
<div className="text-block-31">
<div className="text-block-31" style={{ fontSize: '14px', color: '#333' }}>
{isIndividual ? 'Физическое лицо' : selectedLegalEntity || 'Выберите юридическое лицо'}
</div>
<div className="code-embed w-embed" style={{ transform: showLegalEntityDropdown ? 'rotate(180deg)' : 'rotate(0deg)', transition: 'transform 0.2s' }}>
@ -325,7 +325,7 @@ const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
borderBottom: '1px solid #f0f0f0',
backgroundColor: isIndividual ? '#f8f9fa' : 'white',
fontSize: '14px',
fontWeight: isIndividual ? 500 : 400
}}
onMouseEnter={(e) => {
if (!isIndividual) {
@ -538,7 +538,9 @@ const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
cursor: 'pointer',
borderBottom: '1px solid #f0f0f0',
backgroundColor: paymentMethod === 'yookassa' ? '#f8f9fa' : 'white',
fontSize: '14px'
fontSize: '14px',
fontWeight: paymentMethod === 'yookassa' ? 500 : 400,
color: '#222'
}}
onMouseEnter={(e) => {
if (paymentMethod !== 'yookassa') {
@ -657,9 +659,7 @@ const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
<div className="w-layout-hflex flex-block-59">
<div className="text-block-32">Итого</div>
<h4 className="heading-9-copy-copy">
{formatPrice(
summary.totalPrice - summary.totalDiscount + (selectedDeliveryAddress ? 0 : summary.deliveryPrice)
)}
{formatPrice(summary.totalPrice - summary.totalDiscount)}
</h4>
</div>
@ -835,9 +835,7 @@ const CartSummary: React.FC<CartSummaryProps> = ({ step, setStep }) => {
<div className="w-layout-hflex flex-block-59">
<div className="text-block-32">Итого</div>
<h4 className="heading-9-copy-copy">
{formatPrice(
summary.totalPrice - summary.totalDiscount + (selectedDeliveryAddress ? 0 : summary.deliveryPrice)
)}
{formatPrice(summary.totalPrice - summary.totalDiscount)}
</h4>
</div>

View File

@ -26,17 +26,35 @@ const CatalogInfoHeader: React.FC<CatalogInfoHeaderProps> = ({
<div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9">
{breadcrumbs && breadcrumbs.length > 0 && (
<div className="w-layout-hflex flex-block-7">
<div
className="w-layout-hflex flex-block-7"
itemScope
itemType="https://schema.org/BreadcrumbList"
>
{breadcrumbs.map((bc, idx) => (
<React.Fragment key={idx}>
{idx > 0 && <div className="text-block-3"></div>}
{bc.href ? (
<a href={bc.href} className="link-block w-inline-block">
<div>{bc.label}</div>
<a
href={bc.href}
className="link-block w-inline-block"
itemProp="itemListElement"
itemScope
itemType="https://schema.org/ListItem"
>
<div itemProp="name">{bc.label}</div>
<meta itemProp="position" content={String(idx + 1)} />
<meta itemProp="item" content={bc.href} />
</a>
) : (
<span className="link-block-2 w-inline-block">
<div>{bc.label}</div>
<span
className="link-block-2 w-inline-block"
itemProp="itemListElement"
itemScope
itemType="https://schema.org/ListItem"
>
<div itemProp="name">{bc.label}</div>
<meta itemProp="position" content={String(idx + 1)} />
</span>
)}
</React.Fragment>

View File

@ -1,5 +1,4 @@
import Link from "next/link";
import React from "react";
import React, { useState } from "react";
import { useFavorites } from "@/contexts/FavoritesContext";
interface CatalogProductCardProps {
@ -15,8 +14,9 @@ interface CatalogProductCardProps {
productId?: string;
offerKey?: string;
currency?: string;
priceElement?: React.ReactNode; // Элемент для отображения цены (например, скелетон)
priceElement?: React.ReactNode;
onAddToCart?: (e: React.MouseEvent) => void | Promise<void>;
isInCart?: boolean;
}
const CatalogProductCard: React.FC<CatalogProductCardProps> = ({
@ -34,43 +34,34 @@ const CatalogProductCard: React.FC<CatalogProductCardProps> = ({
currency = 'RUB',
priceElement,
onAddToCart,
isInCart = false,
}) => {
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
const [localInCart, setLocalInCart] = useState(false);
// Обрабатываем пустое изображение - используем SVG-заглушку вместо мокап-фотки
const displayImage = image || 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEwIiBoZWlnaHQ9IjE5MCIgdmlld0JveD0iMCAwIDIxMCAxOTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyMTAiIGhlaWdodD0iMTkwIiBmaWxsPSIjRjNGNEY2Ii8+CjxwYXRoIGQ9Ik04NSA5NUw5NSA4NUwxMjUgMTE1TDE0MCA5NUwxNjUgMTIwSDE2NVY5MEg0NVY5MEw4NSA5NVoiIGZpbGw9IiNEMUQ1REIiLz4KPGNpcmNsZSBjeD0iNzUiIGN5PSI3NSIgcj0iMTAiIGZpbGw9IiNEMUQ1REIiLz4KPHRleHQgeD0iMTA1IiB5PSIxNTAiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzlDQTNBRiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+Tm8gaW1hZ2U8L3RleHQ+Cjwvc3ZnPgo=';
// Создаем ссылку на card с параметрами товара
const cardUrl = articleNumber && brandName
? `/card?article=${encodeURIComponent(articleNumber)}&brand=${encodeURIComponent(brandName)}${artId ? `&artId=${artId}` : ''}`
: '/card'; // Fallback на card если нет данных
: '/card';
// Проверяем, есть ли товар в избранном
const isItemFavorite = isFavorite(productId, offerKey, articleNumber, brandName || brand);
// Обработчик клика по сердечку
const handleFavoriteClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
// Извлекаем цену как число
const numericPrice = parseFloat(price.replace(/[^\d.,]/g, '').replace(',', '.')) || 0;
if (isItemFavorite) {
// Находим товар в избранном по правильному ID
const favoriteItem = favorites.find((fav: any) => {
// Проверяем по разным комбинациям идентификаторов
if (productId && fav.productId === productId) return true;
if (offerKey && fav.offerKey === offerKey) return true;
if (fav.article === articleNumber && fav.brand === (brandName || brand)) return true;
return false;
});
if (favoriteItem) {
removeFromFavorites(favoriteItem.id);
}
} else {
// Добавляем в избранное
addToFavorites({
productId,
offerKey,
@ -84,62 +75,95 @@ const CatalogProductCard: React.FC<CatalogProductCardProps> = ({
}
};
// Обработчик клика по кнопке "Купить"
const handleBuyClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (!isInCart && !localInCart) {
setLocalInCart(true);
}
if (onAddToCart) {
onAddToCart(e);
} else {
// Fallback - переходим на страницу товара
window.location.href = cardUrl;
}
};
return (
<div className="w-layout-vflex flex-block-15-copy" data-article-card="visible">
<div className="w-layout-vflex flex-block-15-copy" data-article-card="visible" itemScope itemType="https://schema.org/Product">
<div
className={`favcardcat ${isItemFavorite ? 'favorite-active' : ''}`}
className={`favcardcat${isItemFavorite ? ' favorite-active' : ''}`}
onClick={handleFavoriteClick}
style={{
cursor: 'pointer',
color: isItemFavorite ? '#ff4444' : '#ccc'
}}
style={{ cursor: 'pointer', color: isItemFavorite ? '#ff4444' : '#ccc' }}
>
<div className="icon-setting w-embed">
<svg width="currentwidth" height="currentheight" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" ></path>
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" ></path>
</svg>
</div>
</div>
{/* Делаем картинку и контент кликабельными для перехода на card */}
<Link href={cardUrl} className="div-block-4" style={{ textDecoration: 'none', color: 'inherit' }}>
<img src={displayImage} loading="lazy" width="Auto" height="Auto" alt="" className="image-5" />
<div className="text-block-7">{discount}</div>
</Link>
<Link href={cardUrl} className="div-block-3" style={{ textDecoration: 'none', color: 'inherit' }}>
<div className="div-block-4">
<img
src={displayImage}
loading="lazy"
width="Auto"
height="Auto"
alt={title}
className="image-5"
itemProp="image"
/>
<div
className="text-block-7"
style={{
background: discount ? undefined : 'transparent',
color: discount ? undefined : 'transparent',
border: discount ? undefined : 'none',
}}
>
{discount || ''}
</div>
</div>
<div className="div-block-3">
<div className="w-layout-hflex flex-block-16">
{priceElement ? (
<div className="text-block-8">{priceElement}</div>
) : (
<div className="text-block-8">{price}</div>
<div className="text-block-8" itemProp="offers" itemScope itemType="https://schema.org/Offer">
<span itemProp="price">{price}</span>
<meta itemProp="priceCurrency" content={currency} />
</div>
)}
<div className="text-block-9">{oldPrice}</div>
</div>
<div className="text-block-10">{title}</div>
<div className="text-block-11">{brand}</div>
</Link>
{/* Обновляем кнопку купить */}
<div className="catc w-inline-block" onClick={handleBuyClick} style={{ cursor: 'pointer' }}>
<div className="div-block-25">
<div className="icon-setting w-embed">
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor"></path>
</svg>
<div className="w-layout-hflex flex-block-122">
<div className="w-layout-vflex">
<div className="text-block-10" itemProp="name">{title}</div>
<div className="text-block-11" itemProp="brand" itemScope itemType="https://schema.org/Brand">
<span itemProp="name">{brand}</span>
</div>
</div>
<a
href="#"
className="button-icon w-inline-block"
onClick={handleBuyClick}
style={{
cursor: isInCart || localInCart ? 'default' : 'pointer',
opacity: isInCart || localInCart ? 0.5 : 1,
filter: isInCart || localInCart ? 'grayscale(1)' : 'none',
background: isInCart || localInCart ? '#2563eb' : undefined
}}
aria-label={isInCart || localInCart ? 'В корзине' : 'Купить'}
tabIndex={0}
>
<div className="div-block-26">
<div className="icon-setting w-embed">
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor"></path>
</svg>
</div>
</div>
</a>
</div>
<div className="text-block-6">Купить</div>
<meta itemProp="sku" content={articleNumber || ''} />
</div>
</div>
);

View File

@ -3,16 +3,17 @@ import React, { useState, useRef, useEffect } from 'react';
interface CatalogSortDropdownProps {
active: number;
onChange: (index: number) => void;
options?: string[];
}
const sortOptions = [
const defaultSortOptions = [
'По популярности',
'Сначала дешевле',
'Сначала дороже',
'Высокий рейтинг',
];
const CatalogSortDropdown: React.FC<CatalogSortDropdownProps> = ({ active, onChange }) => {
const CatalogSortDropdown: React.FC<CatalogSortDropdownProps> = ({ active, onChange, options = defaultSortOptions }) => {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
@ -52,7 +53,7 @@ const CatalogSortDropdown: React.FC<CatalogSortDropdownProps> = ({ active, onCha
<div>Сортировка</div>
</div>
<nav className={`dropdown-list-2 w-dropdown-list${isOpen ? ' w--open' : ''}`} style={{ minWidth: 180, whiteSpace: 'normal' }}>
{sortOptions.map((option, index) => (
{options.map((option: string, index: number) => (
<a
key={index}
href="#"

View File

@ -1,10 +1,16 @@
import React from "react";
import React, { useState } from "react";
const CatalogSubscribe: React.FC = () => (
<div className="w-layout-blockcontainer container subscribe w-container">
<div className="w-layout-hflex flex-block-18">
<img
src="/images/resource2.png"
alt="Ресурс 2"
className="mt-[-18px] hide-on-991"
/>
<div className="div-block-9">
<h3 className="heading-3 sub">Подпишитесь на новостную рассылку</h3>
{/* <h3 className="heading-3 sub">Подпишитесь на новостную рассылку</h3> */}
<div className="text-block-14">Оставайтесь в курсе акций, <br />новинок и специальных предложений</div>
</div>
<div className="form-block-3 w-form">
@ -13,6 +19,38 @@ const CatalogSubscribe: React.FC = () => (
<input type="submit" className="submit-button-copy w-button" value="Подписаться" />
</form>
</div>
<div className="flex flex-row items-center mt-2 pl-0 justify-start">
{/* Кастомный чекбокс без input/label */}
{(() => {
const [checked, setChecked] = useState(false);
return (
<>
<span className="text-[#8893A1] text-[12px] leading-snug select-none mr-4">
Я даю свое согласие на обработку персональных данных<br />
и соглашаюсь с условиями <a href="/privacy-policy" className="underline hover:text-[#6c7684]">Политики конфиденциальности</a>
</span>
<div
className={`h-[24px] w-[24px] border border-[#8893A1] rounded-sm flex-shrink-0 flex items-center justify-center cursor-pointer transition-colors duration-150 ${checked ? 'bg-[#EC1C24]' : 'bg-transparent'}`}
onClick={() => setChecked(v => !v)}
role="checkbox"
aria-checked={checked}
tabIndex={0}
onKeyDown={e => { if (e.key === ' ' || e.key === 'Enter') setChecked(v => !v); }}
>
<svg
className={`w-5 h-5 text-white transition-opacity duration-150 ${checked ? 'opacity-100' : 'opacity-0'}`}
fill="none"
stroke="currentColor"
strokeWidth="2"
viewBox="0 0 24 24"
>
<path d="M5 13l4 4L19 7" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</>
);
})()}
</div>
</div>
</div>
);

View File

@ -0,0 +1,28 @@
import React from 'react';
interface CloseIconProps {
size?: number;
color?: string;
}
const CloseIcon: React.FC<CloseIconProps> = ({ size = 20, color = '#fff' }) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18 6L6 18M6 6L18 18"
stroke={color}
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default CloseIcon;

View File

@ -0,0 +1,65 @@
import * as React from "react";
const CookieConsent: React.FC = () => {
const [isVisible, setIsVisible] = React.useState(false);
React.useEffect(() => {
const cookieConsent = localStorage.getItem('cookieConsent');
if (!cookieConsent) {
setIsVisible(true);
}
}, []);
const handleAccept = () => {
localStorage.setItem('cookieConsent', 'accepted');
setIsVisible(false);
};
if (!isVisible) return null;
return (
<>
<link
href="https://fonts.googleapis.com/css2?family=Onest:wght@400;500;600;700&display=swap"
rel="stylesheet"
/>
<div
layer-name="cookie"
className="box-border flex gap-16 justify-between items-center px-16 py-10 mx-auto my-0 w-full bg-white rounded-3xl shadow-sm max-w-[1240px] max-md:flex-col max-md:gap-10 max-md:px-10 max-md:py-8 max-md:text-center max-sm:gap-5 max-sm:p-5 max-sm:rounded-2xl fixed bottom-6 left-1/2 -translate-x-1/2 z-5000"
>
<div
layer-name="Мы используем cookie-файлы, чтобы получить статистику, которая помогает нам улучшать сайт для Вас. Нажимая Принять, вы даёте согласие на использование ваших cookie-файлов. Подробнее о том, как мы используем ваши персональные данные, в нашей Политике обработки персональных данных."
className="flex-1 text-base font-medium leading-5 text-red-600 max-w-[933px] max-md:max-w-full max-sm:text-sm"
>
<span className="text-base text-gray-600">
Мы используем cookie-файлы, чтобы получить статистику, которая
помогает нам улучшать сайт для Вас. Нажимая Принять, вы даёте
согласие на использование ваших cookie-файлов. Подробнее о том, как
мы используем ваши персональные данные, в нашей{' '}
</span>
<a
href="/privacy-policy"
className="text-base text-red-600 underline hover:text-red-700"
target="_blank"
rel="noopener noreferrer"
>
Политике обработки персональных данных.
</a>
</div>
<button
onClick={handleAccept}
className="box-border flex gap-5 justify-center items-center px-8 py-4 bg-red-600 hover:bg-red-700 rounded-xl h-[51px] min-w-[126px] max-md:w-full max-md:max-w-[200px] max-sm:px-5 max-sm:py-3.5 max-sm:w-full max-sm:h-auto focus:outline-none focus:ring-2 focus:ring-red-400 transition-colors duration-200"
>
<span
layer-name="Принять"
className="text-base font-semibold leading-5 text-center text-white max-sm:text-sm"
>
Принять
</span>
</button>
</div>
</>
);
};
export default CookieConsent;

View File

@ -1,7 +1,9 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { useCart } from "@/contexts/CartContext";
import { useFavorites } from "@/contexts/FavoritesContext";
import toast from "react-hot-toast";
import CartIcon from "./CartIcon";
import { isDeliveryDate } from "@/lib/utils";
const INITIAL_OFFERS_LIMIT = 5;
@ -19,6 +21,8 @@ interface CoreProductCardOffer {
warehouse?: string;
supplier?: string;
deliveryTime?: number;
hasStock?: boolean;
isInCart?: boolean;
}
interface CoreProductCardProps {
@ -32,6 +36,7 @@ interface CoreProductCardProps {
isLoadingOffers?: boolean;
onLoadOffers?: () => void;
partsIndexPowered?: boolean;
hasStock?: boolean;
}
const CoreProductCard: React.FC<CoreProductCardProps> = ({
@ -44,18 +49,73 @@ const CoreProductCard: React.FC<CoreProductCardProps> = ({
isAnalog = false,
isLoadingOffers = false,
onLoadOffers,
partsIndexPowered = false
partsIndexPowered = false,
hasStock = true
}) => {
const { addItem } = useCart();
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
const [visibleOffersCount, setVisibleOffersCount] = useState(INITIAL_OFFERS_LIMIT);
const [sortBy, setSortBy] = useState<'stock' | 'delivery' | 'price'>('price'); // Локальная сортировка для каждого товара
const [quantities, setQuantities] = useState<{ [key: number]: number }>(
offers.reduce((acc, _, index) => ({ ...acc, [index]: 1 }), {})
);
const [inputValues, setInputValues] = useState<{ [key: number]: string }>(
offers.reduce((acc, _, index) => ({ ...acc, [index]: "1" }), {})
);
const [quantityErrors, setQuantityErrors] = useState<{ [key: number]: string }>({});
const [localInCart, setLocalInCart] = useState<{ [key: number]: boolean }>({});
const displayedOffers = offers.slice(0, visibleOffersCount);
const hasMoreOffers = visibleOffersCount < offers.length;
useEffect(() => {
setInputValues(offers.reduce((acc, _, index) => ({ ...acc, [index]: "1" }), {}));
setQuantities(offers.reduce((acc, _, index) => ({ ...acc, [index]: 1 }), {}));
}, [offers.length]);
// Функция для парсинга цены из строки
const parsePrice = (priceStr: string): number => {
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
return parseFloat(cleanPrice) || 0;
};
// Функция для парсинга количества в наличии
const parseStock = (stockStr: string): number => {
const match = stockStr.match(/\d+/);
return match ? parseInt(match[0]) : 0;
};
// Функция для парсинга времени доставки
const parseDeliveryTime = (daysStr: string): string => {
// Если это дата (содержит название месяца), возвращаем как есть
if (isDeliveryDate(daysStr)) {
return daysStr;
}
// Иначе парсим как количество дней (для обратной совместимости)
const match = daysStr.match(/\d+/);
return match ? `${match[0]} дней` : daysStr;
};
// Функция сортировки предложений
const sortOffers = (offers: CoreProductCardOffer[]) => {
const sorted = [...offers];
switch (sortBy) {
case 'stock':
return sorted.sort((a, b) => parseStock(b.pcs) - parseStock(a.pcs));
case 'delivery':
return sorted.sort((a, b) => {
const aDelivery = a.deliveryTime || 999;
const bDelivery = b.deliveryTime || 999;
return aDelivery - bDelivery;
});
case 'price':
return sorted.sort((a, b) => parsePrice(a.price) - parsePrice(b.price));
default:
return sorted;
}
};
const sortedOffers = sortOffers(offers);
const displayedOffers = sortedOffers.slice(0, visibleOffersCount);
const hasMoreOffers = visibleOffersCount < sortedOffers.length;
// Проверяем, есть ли товар в избранном
const isItemFavorite = isFavorite(
@ -65,49 +125,48 @@ const CoreProductCard: React.FC<CoreProductCardProps> = ({
brand
);
// Функция для парсинга цены из строки
const parsePrice = (priceStr: string): number => {
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
return parseFloat(cleanPrice) || 0;
// Теперь используем isInCart флаг из backend вместо frontend проверки
const handleInputChange = (idx: number, val: string) => {
setInputValues(prev => ({ ...prev, [idx]: val }));
if (val === "") return;
const valueNum = Math.max(1, parseInt(val, 10) || 1);
setQuantities(prev => ({ ...prev, [idx]: valueNum }));
};
// Функция для парсинга времени доставки
const parseDeliveryTime = (daysStr: string): string => {
const match = daysStr.match(/\d+/);
return match ? `${match[0]} дней` : daysStr;
};
// Функция для парсинга количества в наличии
const parseStock = (stockStr: string): number => {
const match = stockStr.match(/\d+/);
return match ? parseInt(match[0]) : 0;
};
const handleQuantityInput = (index: number, value: string) => {
const offer = offers[index];
const availableStock = parseStock(offer.pcs);
let num = parseInt(value, 10);
if (isNaN(num) || num < 1) num = 1;
if (num > availableStock) {
toast.error(`Максимум ${availableStock} шт.`);
return;
const handleInputBlur = (idx: number) => {
if (inputValues[idx] === "") {
setInputValues(prev => ({ ...prev, [idx]: "1" }));
setQuantities(prev => ({ ...prev, [idx]: 1 }));
}
setQuantities(prev => ({ ...prev, [index]: num }));
};
const handleAddToCart = (offer: CoreProductCardOffer, index: number) => {
const handleMinus = (idx: number) => {
setQuantities(prev => {
const newVal = Math.max(1, (prev[idx] || 1) - 1);
setInputValues(vals => ({ ...vals, [idx]: newVal.toString() }));
return { ...prev, [idx]: newVal };
});
};
const handlePlus = (idx: number, maxCount?: number) => {
setQuantities(prev => {
let newVal = (prev[idx] || 1) + 1;
if (maxCount !== undefined) newVal = Math.min(newVal, maxCount);
setInputValues(vals => ({ ...vals, [idx]: newVal.toString() }));
return { ...prev, [idx]: newVal };
});
};
const handleAddToCart = async (offer: CoreProductCardOffer, index: number) => {
setLocalInCart(prev => ({ ...prev, [index]: true }));
const quantity = quantities[index] || 1;
const availableStock = parseStock(offer.pcs);
// Проверяем наличие
if (quantity > availableStock) {
toast.error(`Недостаточно товара в наличии. Доступно: ${availableStock} шт.`);
return;
}
const inCart = offer.isInCart || false; // Use backend flag
const numericPrice = parsePrice(offer.price);
addItem({
const result = await addItem({
productId: offer.productId,
offerKey: offer.offerKey,
name: name,
@ -117,6 +176,7 @@ const CoreProductCard: React.FC<CoreProductCardProps> = ({
price: numericPrice,
currency: offer.currency || 'RUB',
quantity: quantity,
stock: availableStock, // передаем информацию о наличии
deliveryTime: parseDeliveryTime(offer.days),
warehouse: offer.warehouse || 'Склад',
supplier: offer.supplier || (offer.isExternal ? 'AutoEuro' : 'Protek'),
@ -124,17 +184,26 @@ const CoreProductCard: React.FC<CoreProductCardProps> = ({
image: image,
});
// Показываем тоастер вместо alert
toast.success(
<div>
<div className="font-semibold">Товар добавлен в корзину!</div>
<div className="text-sm text-gray-600">{`${brand} ${article} (${quantity} шт.)`}</div>
</div>,
{
duration: 3000,
icon: '🛒',
}
);
if (result.success) {
// Показываем тоастер с разным текстом в зависимости от того, был ли товар уже в корзине
const toastMessage = inCart
? `Количество увеличено (+${quantity} шт.)`
: 'Товар добавлен в корзину!';
toast.success(
<div>
<div className="font-semibold" style={{ color: '#fff' }}>{toastMessage}</div>
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{`${brand} ${article} (${quantity} шт.)`}</div>
</div>,
{
duration: 3000,
icon: <CartIcon size={20} color="#fff" />,
}
);
} else {
// Показываем ошибку
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
}
};
// Обработчик клика по сердечку
@ -200,7 +269,7 @@ const CoreProductCard: React.FC<CoreProductCardProps> = ({
if (!offers || offers.length === 0) {
return (
<div className="w-layout-hflex core-product-search-s1">
<div className={`w-layout-hflex core-product-search-s1 ${!hasStock ? 'out-of-stock-highlight' : ''}`} style={!hasStock ? { backgroundColor: '#fee', borderColor: '#f87171' } : {}}>
<div className="w-layout-vflex core-product-s1">
<div className="w-layout-vflex flex-block-47">
<div className="div-block-19">
@ -210,6 +279,19 @@ const CoreProductCard: React.FC<CoreProductCardProps> = ({
<div className="w-layout-hflex flex-block-79">
<h3 className="heading-10 name">{brand}</h3>
<h3 className="heading-10">{article}</h3>
{!hasStock && (
<span className="out-of-stock-badge" style={{
backgroundColor: '#dc2626',
color: 'white',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '12px',
fontWeight: '500',
marginLeft: '8px'
}}>
Нет в наличии
</span>
)}
</div>
<div className="text-block-21">{name}</div>
</div>
@ -243,170 +325,257 @@ const CoreProductCard: React.FC<CoreProductCardProps> = ({
return (
<>
<div className="w-layout-hflex core-product-search-s1">
<div className="w-layout-vflex core-product-s1">
<div className="w-layout-vflex flex-block-47">
<div className="div-block-19">
<img src="/images/info.svg" loading="lazy" alt="info" className="image-9" />
</div>
<div className="w-layout-vflex flex-block-50">
<div className="w-layout-hflex flex-block-79">
<h3 className="heading-10 name">{brand}</h3>
<h3 className="heading-10">{article}</h3>
<div
className="favorite-icon w-embed"
onClick={handleFavoriteClick}
style={{ cursor: 'pointer', marginLeft: '10px', color: isItemFavorite ? '#e53935' : undefined }}
>
<svg width="24" height="24" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15 25L13.405 23.5613C7.74 18.4714 4 15.1035 4 10.9946C4 7.6267 6.662 5 10.05 5C11.964 5 13.801 5.88283 15 7.26703C16.199 5.88283 18.036 5 19.95 5C23.338 5 26 7.6267 26 10.9946C26 15.1035 22.26 18.4714 16.595 23.5613L15 25Z"
fill={isItemFavorite ? "#e53935" : "currentColor"}
/>
</svg>
<div className={`w-layout-hflex core-product-search-s1 ${!hasStock ? 'out-of-stock-highlight' : ''}`} style={!hasStock ? { backgroundColor: '#fee', borderColor: '#f87171' } : {}}>
<div className="w-layout-vflex flex-block-48-copy">
<div className="w-layout-vflex product-list-search-s1">
<div className="w-layout-vflex flex-block-48-copy">
<div className="w-layout-vflex product-list-search-s1">
<div className="w-layout-vflex core-product-s1">
<div className="w-layout-vflex flex-block-47">
<div className="div-block-19">
<img src="/images/info.svg" loading="lazy" alt="info" className="image-9" />
</div>
<div className="w-layout-vflex flex-block-50">
<div className="flex flex-row flex-nowrap items-center gap-2">
<h3 className="heading-10 name" style={{marginRight: 8}}>{brand}</h3>
<h3 className="heading-10" style={{marginRight: 8}}>{article}</h3>
{!hasStock && (
<span className="out-of-stock-badge" style={{
backgroundColor: '#dc2626',
color: 'white',
padding: '2px 8px',
borderRadius: '4px',
fontSize: '12px',
fontWeight: '500',
marginLeft: '8px'
}}>
Нет в наличии
</span>
)}
<div
className="favorite-icon w-embed"
onClick={handleFavoriteClick}
style={{ cursor: 'pointer', marginLeft: '10px', color: isItemFavorite ? '#e53935' : undefined }}
>
<svg width="24" height="24" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15 25L13.405 23.5613C7.74 18.4714 4 15.1035 4 10.9946C4 7.6267 6.662 5 10.05 5C11.964 5 13.801 5.88283 15 7.26703C16.199 5.88283 18.036 5 19.95 5C23.338 5 26 7.6267 26 10.9946C26 15.1035 22.26 18.4714 16.595 23.5613L15 25Z"
fill={isItemFavorite ? "#e53935" : "currentColor"}
/>
</svg>
</div>
</div>
<div className="text-block-21 mt-1">{name}</div>
</div>
</div>
<div className="text-block-21">{name}</div>
</div>
</div>
{image && (
<div className="div-block-20">
<img src={image} loading="lazy" alt={name} className="image-10" />
{partsIndexPowered && (
<div className="text-xs text-gray-500 mt-1 text-center">
powered by <span className="font-semibold text-blue-600">Parts Index</span>
{image && (
<div className="div-block-20">
<img src={image} loading="lazy" alt={name} className="image-10" />
{partsIndexPowered && (
<div className="text-xs text-gray-500 mt-1 text-center">
powered by <span className="font-semibold text-blue-600">Parts Index</span>
</div>
)}
</div>
)}
</div>
)}
</div>
<div className="w-layout-vflex flex-block-48-copy">
<div className="w-layout-hflex sort-list-s1">
<div className="w-layout-hflex flex-block-49">
<div className="sort-item first">Наличие</div>
<div className="sort-item">Доставка</div>
</div>
<div className="sort-item price">Цена</div>
</div>
<div className="w-layout-vflex product-list-search-s1">
{displayedOffers.map((offer, idx) => {
const isLast = idx === displayedOffers.length - 1;
return (
<div
className="w-layout-hflex product-item-search-s1"
key={idx}
style={isLast ? { borderBottom: 'none' } : undefined}
>
<div className="w-layout-hflex flex-block-81">
<div className="w-layout-hflex info-block-search-s1">
<div className="pcs-search-s1">{offer.pcs}</div>
<div className="pcs-search">{offer.days}</div>
<div className="w-layout-hflex sort-list-s1">
<div className="w-layout-hflex flex-block-49">
<div
className={`sort-item first ${sortBy === 'stock' ? 'active' : ''}`}
onClick={() => setSortBy('stock')}
style={{ cursor: 'pointer' }}
>
Наличие
</div>
<div className="w-layout-hflex info-block-product-card-search-s1">
{offer.recommended && (
<>
<div className="w-layout-hflex item-recommend">
<img src="/images/ri_refund-fill.svg" loading="lazy" alt="" />
</div>
<div className="text-block-25-s1">Рекомендуем</div>
</>
)}
<div
className={`sort-item ${sortBy === 'delivery' ? 'active' : ''}`}
onClick={() => setSortBy('delivery')}
style={{ cursor: 'pointer' }}
>
Доставим
</div>
<div className="price-s1">{offer.price}</div>
</div>
<div className="w-layout-hflex add-to-cart-block-s1">
<div className="w-layout-hflex flex-block-82">
<div className="w-layout-hflex pcs-cart-s1">
<button
type="button"
className="minus-plus"
onClick={() => handleQuantityInput(idx, ((quantities[idx] || 1) - 1).toString())}
style={{ cursor: 'pointer' }}
aria-label="Уменьшить количество"
>
<div className="pluspcs w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 10.5V9.5H14V10.5H6Z" fill="currentColor" />
</svg>
</div>
</button>
<div className="input-pcs">
<input
type="number"
min={1}
max={parseStock(offer.pcs)}
value={quantities[idx] || 1}
onChange={e => handleQuantityInput(idx, e.target.value)}
className="text-block-26 w-full text-center outline-none"
aria-label="Количество"
/>
</div>
<button
type="button"
className="minus-plus"
onClick={() => handleQuantityInput(idx, ((quantities[idx] || 1) + 1).toString())}
style={{ cursor: 'pointer' }}
aria-label="Увеличить количество"
>
<div className="pluspcs w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 10.5V9.5H14V10.5H6ZM9.5 6H10.5V14H9.5V6Z" fill="currentColor" />
</svg>
</div>
</button>
</div>
<button
type="button"
onClick={() => handleAddToCart(offer, idx)}
className="button-icon w-inline-block"
style={{ cursor: 'pointer' }}
aria-label="Добавить в корзину"
>
<div className="div-block-26">
<img loading="lazy" src="/images/cart_icon.svg" alt="В корзину" className="image-11" />
</div>
</button>
</div>
<div
className={`sort-item price ${sortBy === 'price' ? 'active' : ''}`}
onClick={() => setSortBy('price')}
style={{ cursor: 'pointer' }}
>
Цена
</div>
</div>
);
})}
</div>
{hasMoreOffers || visibleOffersCount > INITIAL_OFFERS_LIMIT ? (
<div
className="w-layout-hflex show-more-search"
onClick={() => {
if (hasMoreOffers) {
setVisibleOffersCount(prev => Math.min(prev + 10, offers.length));
} else {
setVisibleOffersCount(INITIAL_OFFERS_LIMIT);
}
}}
style={{ cursor: 'pointer' }}
tabIndex={0}
role="button"
aria-label={hasMoreOffers ? `Еще ${offers.length - visibleOffersCount} предложений` : 'Скрыть предложения'}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') {
if (hasMoreOffers) {
setVisibleOffersCount(prev => Math.min(prev + 10, offers.length));
} else {
setVisibleOffersCount(INITIAL_OFFERS_LIMIT);
}
}
}}
>
<div className="text-block-27">
{hasMoreOffers ? `Еще ${offers.length - visibleOffersCount} предложений` : 'Скрыть'}
{displayedOffers.map((offer, idx) => {
const isLast = idx === displayedOffers.length - 1;
const maxCount = parseStock(offer.pcs);
const inCart = offer.isInCart || false;
const isLocallyInCart = !!localInCart[idx];
// Backend now provides isInCart flag directly
return (
<div
className="w-layout-hflex product-item-search-s1"
key={idx}
style={isLast ? { borderBottom: 'none' } : undefined}
>
<div className="w-layout-hflex flex-block-81">
<div className="w-layout-hflex info-block-search-s1">
<div className="pcs-search-s1">{offer.pcs}</div>
<div className="pcs-search">{offer.days}</div>
</div>
<div className="w-layout-hflex info-block-product-card-search-s1">
{offer.recommended && (
<>
<div className="w-layout-hflex item-recommend">
<img src="/images/ri_refund-fill.svg" loading="lazy" alt="" />
</div>
<div className="text-block-25-s1">Рекомендуем</div>
</>
)}
</div>
<div className="price-s1">{offer.price}</div>
</div>
<div className="w-layout-hflex add-to-cart-block-s1">
<div className="w-layout-hflex flex-block-82">
<div className="w-layout-hflex pcs-cart-s1">
<div
className="minus-plus"
onClick={() => handleMinus(idx)}
style={{ cursor: 'pointer' }}
aria-label="Уменьшить количество"
tabIndex={0}
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && handleMinus(idx)}
role="button"
>
<div className="pluspcs w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 10.5V9.5H14V10.5H6Z" fill="currentColor" />
</svg>
</div>
</div>
<div className="input-pcs">
<input
type="number"
min={1}
max={maxCount}
value={inputValues[idx]}
onChange={e => handleInputChange(idx, e.target.value)}
onBlur={() => handleInputBlur(idx)}
className="text-block-26 w-full text-center outline-none"
aria-label="Количество"
/>
</div>
<div
className="minus-plus"
onClick={() => handlePlus(idx, maxCount)}
style={{ cursor: 'pointer' }}
aria-label="Увеличить количество"
tabIndex={0}
onKeyDown={e => (e.key === 'Enter' || e.key === ' ') && handlePlus(idx, maxCount)}
role="button"
>
<div className="pluspcs w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 10.5V9.5H14V10.5H6ZM9.5 6H10.5V14H9.5V6Z" fill="currentColor" />
</svg>
</div>
</div>
</div>
<div style={{ position: 'relative', display: 'inline-block' }}>
<button
type="button"
onClick={() => handleAddToCart(offer, idx)}
className={`button-icon w-inline-block ${inCart || isLocallyInCart ? 'in-cart' : ''}`}
style={{
cursor: 'pointer',
opacity: inCart || isLocallyInCart ? 0.5 : 1,
backgroundColor: inCart || isLocallyInCart ? '#2563eb' : undefined
}}
aria-label={inCart || isLocallyInCart ? "Товар уже в корзине" : "Добавить в корзину"}
title={inCart || isLocallyInCart ? "Товар уже в корзине - нажмите для добавления еще" : "Добавить в корзину"}
>
<div className="div-block-26">
<img
loading="lazy"
src="/images/cart_icon.svg"
alt={inCart || isLocallyInCart ? "В корзине" : "В корзину"}
className="image-11"
style={{
filter: inCart || isLocallyInCart ? 'brightness(0.7)' : undefined
}}
/>
</div>
</button>
{inCart && (
<div
style={{
position: 'absolute',
top: '-8px',
right: '-8px',
backgroundColor: '#22c55e',
color: 'white',
borderRadius: '50%',
width: '16px',
height: '16px',
fontSize: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold',
zIndex: 1
}}
title="В корзине"
>
</div>
)}
</div>
</div>
</div>
</div>
);
})}
{hasMoreOffers || visibleOffersCount > INITIAL_OFFERS_LIMIT ? (
<div
className="w-layout-hflex show-more-search"
onClick={() => {
if (hasMoreOffers) {
setVisibleOffersCount(prev => Math.min(prev + 10, sortedOffers.length));
} else {
setVisibleOffersCount(INITIAL_OFFERS_LIMIT);
}
}}
style={{ cursor: 'pointer' }}
tabIndex={0}
role="button"
aria-label={hasMoreOffers ? `Еще ${sortedOffers.length - visibleOffersCount} предложений` : 'Скрыть предложения'}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') {
if (hasMoreOffers) {
setVisibleOffersCount(prev => Math.min(prev + 10, sortedOffers.length));
} else {
setVisibleOffersCount(INITIAL_OFFERS_LIMIT);
}
}
}}
>
<div className="text-block-27">
{hasMoreOffers ? `Еще ${sortedOffers.length - visibleOffersCount} предложений` : 'Скрыть'}
</div>
<img
src="/images/arrow_drop_down.svg"
loading="lazy"
alt=""
className={`transition-transform duration-200 ${!hasMoreOffers ? 'rotate-180' : ''}`}
/>
</div>
) : null}
</div>
<img
src="/images/arrow_drop_down.svg"
loading="lazy"
alt=""
className={`transition-transform duration-200 ${!hasMoreOffers ? 'rotate-180' : ''}`}
/>
</div>
) : null}
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
import React from 'react';
interface DeleteCartIconProps {
size?: number;
color?: string;
}
const DeleteCartIcon: React.FC<DeleteCartIconProps> = ({ size = 24, color = '#ec1c24' }) => {
return (
<svg
width={size}
height={size}
viewBox="0 0 30 30"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z"
fill={color}
/>
</svg>
);
};
export default DeleteCartIcon;

View File

@ -88,7 +88,7 @@ const Filters: React.FC<FiltersProps> = ({
if (filter.type === "range") {
return (
<FilterRange
key={filter.title + idx}
key={filter.title + idx + JSON.stringify((filterValues && filterValues[filter.title]) || null)}
title={filter.title}
min={filter.min}
max={filter.max}

View File

@ -63,6 +63,7 @@ const FiltersPanelMobile: React.FC<FiltersPanelMobileProps> = ({
setLocalFilterValues({});
onSearchChange('');
// Сбрасываем фильтры в родительском компоненте
// Используем пустые массивы для правильной очистки
Object.keys(filterValues).forEach(key => {
onFilterChange?.(key, []);
});
@ -134,7 +135,7 @@ const FiltersPanelMobile: React.FC<FiltersPanelMobileProps> = ({
if (filter.type === "range") {
return (
<FilterRange
key={filter.title + idx}
key={filter.title + idx + JSON.stringify(localFilterValues[filter.title] || null)}
title={filter.title}
min={filter.min}
max={filter.max}

View File

@ -1,104 +1,194 @@
const Footer = () => (
<footer className="section-2">
<div className="w-layout-blockcontainer container footer w-container">
<div className="w-layout-vflex flex-block-20">
<div className="w-layout-hflex flex-block-18-copy-copy">
<div className="w-layout-vflex flex-block-19">
<img src="/images/logo_gor.svg" loading="lazy" width="320" alt="" className="image-15" />
<div className="text-block-15">Пн-Пт 9:00 18:00, <br />Сб 10:00 16:00, Вс выходной</div>
<a href="#" className="link-block-5 w-inline-block">
<div className="w-layout-hflex flex-block-3">
<img src="/images/phone_icon.svg" loading="lazy" alt="" className="image-23" />
<div className="phone">+7 (495) 260-20-60</div>
</div>
</a>
</div>
<div className="w-layout-hflex flex-block-22">
<div className="w-layout-vflex flex-block-23">
<div className="w-layout-hflex flex-block-86">
<div className="text-block-17">Покупателям</div>
</div>
<a href="#" className="link">Оплата</a>
<a href="#" className="link">Возврат</a>
<a href="#" className="link">Доставка</a>
<div data-hover="false" data-delay="0" className="dropdown-3 w-dropdown">
<div className="dropdown-toggle-2 w-dropdown-toggle">
<>
{/* <footer className="section-2">
<div className="w-layout-blockcontainer container footer w-container">
<div className="w-layout-vflex flex-block-20">
<div className="w-layout-hflex flex-block-18-copy-copy">
<div className="w-layout-vflex flex-block-19">
<img src="/images/logo_gor.svg" loading="lazy" width="320" alt="" className="image-15" />
<div className="text-block-15">Пн-Пт 9:00 18:00, <br />Сб 10:00 16:00, Вс выходной</div>
<a href="#" className="link-block-5 w-inline-block">
<div className="w-layout-hflex flex-block-3">
<img src="/images/phone_icon.svg" loading="lazy" alt="" className="image-23" />
<div className="phone">+7 (495) 260-20-60</div>
</div>
</a>
</div>
<div className="w-layout-hflex flex-block-22">
<div className="w-layout-vflex flex-block-23">
<div className="w-layout-hflex flex-block-86">
<div className="text-block-17">Покупателям</div>
<div className="code-embed-10 w-embed"><svg width="currentwight" height="currentheight" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M2 6.74036L3.28446 5.5L9 11.0193L14.7155 5.5L16 6.74036L9 13.5L2 6.74036Z" fill="currentcolor"></path></svg></div>
</div>
<nav className="dropdown-list-3 w-dropdown-list">
<a href="#" className="dropdown-link-2 w-dropdown-link">Оплата</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Возврат</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Доставка</a>
</nav>
<a href="#" className="link">Оплата</a>
<a href="#" className="link">Возврат</a>
<a href="#" className="link">Доставка</a>
<div data-hover="false" data-delay="0" className="dropdown-3 w-dropdown">
<div className="dropdown-toggle-2 w-dropdown-toggle">
<div className="text-block-17">Покупателям</div>
<div className="code-embed-10 w-embed"><svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M2 6.74036L3.28446 5.5L9 11.0193L14.7155 5.5L16 6.74036L9 13.5L2 6.74036Z" fill="currentColor"></path></svg></div>
</div>
<nav className="dropdown-list-3 w-dropdown-list">
<a href="#" className="dropdown-link-2 w-dropdown-link">Оплата</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Возврат</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Доставка</a>
</nav>
</div>
</div>
</div>
<div className="w-layout-vflex flex-block-23">
<div className="w-layout-hflex flex-block-86">
<div className="text-block-17">Сотрудничество</div>
</div>
<a href="#" className="link">Поставщикам</a>
<a href="#" className="link">Дилерская сеть</a>
<a href="#" className="link">Оптовым покупателям</a>
<div data-hover="false" data-delay="0" className="dropdown-3 w-dropdown">
<div className="dropdown-toggle-2 w-dropdown-toggle">
<div className="w-layout-vflex flex-block-23">
<div className="w-layout-hflex flex-block-86">
<div className="text-block-17">Сотрудничество</div>
<div className="code-embed-10 w-embed"><svg width="currentwight" height="currentheight" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M2 6.74036L3.28446 5.5L9 11.0193L14.7155 5.5L16 6.74036L9 13.5L2 6.74036Z" fill="currentcolor"></path></svg></div>
</div>
<nav className="dropdown-list-3 w-dropdown-list">
<a href="#" className="dropdown-link-2 w-dropdown-link">Поставщикам</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Дилерская сеть</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Оптовым покупателям</a>
</nav>
<a href="#" className="link">Поставщикам</a>
<a href="#" className="link">Дилерская сеть</a>
<a href="#" className="link">Оптовым покупателям</a>
<div data-hover="false" data-delay="0" className="dropdown-3 w-dropdown">
<div className="dropdown-toggle-2 w-dropdown-toggle">
<div className="text-block-17">Сотрудничество</div>
<div className="code-embed-10 w-embed"><svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M2 6.74036L3.28446 5.5L9 11.0193L14.7155 5.5L16 6.74036L9 13.5L2 6.74036Z" fill="currentColor"></path></svg></div>
</div>
<nav className="dropdown-list-3 w-dropdown-list">
<a href="#" className="dropdown-link-2 w-dropdown-link">Поставщикам</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Дилерская сеть</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Оптовым покупателям</a>
</nav>
</div>
</div>
</div>
<div className="w-layout-vflex flex-block-23">
<div className="w-layout-hflex flex-block-86">
<div className="text-block-17">PROTEK</div>
</div>
<a href="#" className="link">Вакансии</a>
<a href="#" className="link">О компании</a>
<a href="#" className="link">Контакты</a>
<div data-hover="false" data-delay="0" className="dropdown-3 w-dropdown">
<div className="dropdown-toggle-2 w-dropdown-toggle">
<div className="w-layout-vflex flex-block-23">
<div className="w-layout-hflex flex-block-86">
<div className="text-block-17">PROTEK</div>
<div className="code-embed-10 w-embed"><svg width="currentwight" height="currentheight" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M2 6.74036L3.28446 5.5L9 11.0193L14.7155 5.5L16 6.74036L9 13.5L2 6.74036Z" fill="currentcolor"></path></svg></div>
</div>
<nav className="dropdown-list-3 w-dropdown-list">
<a href="#" className="dropdown-link-2 w-dropdown-link">Вакансии</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">О компании</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Контакты</a>
</nav>
<a href="#" className="link">Вакансии</a>
<a href="#" className="link">О компании</a>
<a href="#" className="link">Контакты</a>
<div data-hover="false" data-delay="0" className="dropdown-3 w-dropdown">
<div className="dropdown-toggle-2 w-dropdown-toggle">
<div className="text-block-17">PROTEK</div>
<div className="code-embed-10 w-embed"><svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M2 6.74036L3.28446 5.5L9 11.0193L14.7155 5.5L16 6.74036L9 13.5L2 6.74036Z" fill="currentColor"></path></svg></div>
</div>
<nav className="dropdown-list-3 w-dropdown-list">
<a href="#" className="dropdown-link-2 w-dropdown-link">Вакансии</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">О компании</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Контакты</a>
</nav>
</div>
</div>
</div>
<div className="w-layout-vflex flex-block-23">
<div className="w-layout-hflex flex-block-86">
<div className="text-block-17">Оферта</div>
</div>
<a href="#" className="link">Поставщикам</a>
<a href="#" className="link">Поставщикам</a>
<a href="#" className="link">Поставщикам</a>
<div data-hover="false" data-delay="0" className="dropdown-3 w-dropdown">
<div className="dropdown-toggle-2 w-dropdown-toggle">
<div className="w-layout-vflex flex-block-23">
<div className="w-layout-hflex flex-block-86">
<div className="text-block-17">Оферта</div>
<div className="code-embed-10 w-embed"><svg width="currentwight" height="currentheight" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M2 6.74036L3.28446 5.5L9 11.0193L14.7155 5.5L16 6.74036L9 13.5L2 6.74036Z" fill="currentcolor"></path></svg></div>
</div>
<nav className="dropdown-list-3 w-dropdown-list">
<a href="#" className="dropdown-link-2 w-dropdown-link">Поставщикам</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Поставщикам</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Поставщикам</a>
</nav>
<a href="#" className="link">Поставщикам</a>
<a href="#" className="link">Поставщикам</a>
<a href="#" className="link">Поставщикам</a>
<div data-hover="false" data-delay="0" className="dropdown-3 w-dropdown">
<div className="dropdown-toggle-2 w-dropdown-toggle">
<div className="text-block-17">Оферта</div>
<div className="code-embed-10 w-embed"><svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" clipRule="evenodd" d="M2 6.74036L3.28446 5.5L9 11.0193L14.7155 5.5L16 6.74036L9 13.5L2 6.74036Z" fill="currentColor"></path></svg></div>
</div>
<nav className="dropdown-list-3 w-dropdown-list">
<a href="#" className="dropdown-link-2 w-dropdown-link">Поставщикам</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Поставщикам</a>
<a href="#" className="dropdown-link-2 w-dropdown-link">Поставщикам</a>
</nav>
</div>
</div>
</div>
</div>
</div>
<div className="w-layout-hflex flex-block-18-copy">
<div className="w-layout-hflex flex-block-21"><img src="/images/mastercard.svg" loading="lazy" alt="" /><img src="/images/visa.svg" loading="lazy" alt="" /><img src="/images/mir.svg" loading="lazy" alt="" /></div>
<div className="text-block-16">© 2025 Protek. Все права защищены.</div>
<div className="w-layout-hflex flex-block-18-copy">
<div className="w-layout-hflex flex-block-21"><img src="/images/mastercard.svg" loading="lazy" alt="" /><img src="/images/visa.svg" loading="lazy" alt="" /><img src="/images/mir.svg" loading="lazy" alt="" /></div>
<div className="text-block-16">© 2025 Protek. Все права защищены.</div>
</div>
</div>
</div>
</div>
</footer>
</footer> */}
{/* Новый футер под основным */}
<footer className="section-2 text-white h-full">
<div className="w-layout-blockcontainer container footer w-container h-full">
<div className="mx-auto flex flex-col md:flex-row items-center md:items-end justify-between gap-2 md:gap-4 mt-2 h-full w-full">
{/* Левая часть: логотип и контакты */}
<div className="flex flex-col gap-4 min-w-[260px] items-center md:items-start mx-auto md:mx-0">
<div className="flex items-center gap-2">
<img src="/images/logo_gor.svg" alt="Protek Авто" className="h-10" />
{/* <span className="bg-[#EC1C24] text-white font-bold rounded px-2 py-1 ml-2 text-sm">АВТО</span> */}
</div>
<div className="text-xs opacity-80 leading-tight">ООО «Протек» ИНН 5007117840<br />ОРГН 1225000146282</div>
<div className="font-semibold mt-2">Есть вопросы или предложения?</div>
<button className="bg-[#23407A] rounded-lg py-2 px-6 font-medium mt-1 mb-2">Напиши нам</button>
</div>
{/* Центр: меню */}
<div className="hidden md:flex flex-1 flex-wrap gap-30 justify-center min-w-[400px]">
<div className="flex flex-col gap-3 min-w-[150px]">
<div className="link">Подбор по марке авто</div>
<a href="#" className="link">Поиск по VIN</a>
<a href="#" className="link">Добавить авто в гараж</a>
<a href="#" className="link">Личный кабинет</a>
<a href="#" className="link">История поиска</a>
<a href="#" className="link">Избранное</a>
</div>
<div className="flex flex-col gap-3 min-w-[150px]">
<div className="link">Детали для ТО</div>
<a href="#" className="link">Шины</a>
<a href="#" className="link">Диски</a>
<a href="#" className="link">Масла и жидкости</a>
<a href="#" className="link">Инструменты</a>
<a href="#" className="link">Все категории</a>
</div>
<div className="flex flex-col gap-3 min-w-[150px]">
<div className="link">О компании</div>
<a href="#" className="link">Оплата и доставка</a>
<a href="#" className="link">Гарантии и возврат</a>
<a href="#" className="link">Оптовым клиентам</a>
<a href="#" className="link">Покупателям</a>
<a href="#" className="link">Контакты</a>
</div>
</div>
{/* Правая часть: контакты и платежи */}
<div className="flex flex-col gap-3 min-w-[220px] items-center md:items-end mx-auto md:mx-0">
<div className="text-lg font-bold">+7 (495) 260-20-60</div>
<div className="text-xs opacity-80">Ежедневно 9:00 21:00</div>
<div className="text-sm font-medium">info@protekauto.ru</div>
<div className="flex gap-3 mt-2">
<a href="#" className="hover:opacity-80 flex items-center gap-1">
<img src="/images/whatsapp.svg" alt="Whatsapp" className="" />
<span className="font-medium text-sm">WhatsApp</span>
</a>
<a href="#" className="hover:opacity-80 flex items-center gap-1">
<img src="/images/tg2.svg" alt="Telegram" className="" />
<span className="font-medium text-sm">Telegram</span>
</a>
</div>
<div className="flex gap-3 mt-4 items-center">
<img src="/images/mastercard.svg" alt="Mastercard" className="h-6" />
<img src="/images/visa.svg" alt="Visa" className="h-3" />
<img src="/images/mir.svg" alt="Mir" className="h-3" />
</div>
</div>
</div>
<div className="mx-auto flex flex-col md:flex-row items-center md:items-start justify-between gap-2 md:gap-4 mt-5 h-full w-full">
<div className="hidden md:flex gap-4 min-w-[260px]">
<a href="#" className="hover:opacity-80 flex items-center gap-1">
<img src="/images/vk.svg" alt="VK" />
</a>
<a href="#" className="hover:opacity-80 flex items-center gap-1">
<img src="/images/tg.svg" alt="Telegram" />
</a>
<a href="#" className="hover:opacity-80 flex items-center gap-1">
<img src="/images/ws.svg" alt="Support" />
</a>
</div>
<div className="flex flex-col items-center md:flex-row md:items-start md:justify-center flex-1 flex-wrap gap-4 md:gap-37 md:mt-6 md:min-w-[400px]">
<a href="#" className=" hover:underline text-xs opacity-70 text-center md:w-auto md:text-left">Политика конфиденциальности</a>
<a href="#" className=" hover:underline text-xs opacity-70 text-center md:w-auto md:text-left">Согласие на обработку персональных данных</a>
<span className="text-xs opacity-70">© 2025 Protek. Все права защищены.</span>
</div>
</div>
</div>
</footer>
</>
);
export default Footer;

View File

@ -9,6 +9,8 @@ import { FIND_LAXIMO_VEHICLE, DOC_FIND_OEM, FIND_LAXIMO_VEHICLE_BY_PLATE_GLOBAL,
import { LaximoVehicleSearchResult, LaximoDocFindOEMResult, LaximoVehiclesByPartResult } from '@/types/laximo';
import Link from "next/link";
import CartButton from './CartButton';
import SearchHistoryDropdown from './SearchHistoryDropdown';
import { GET_RECENT_SEARCH_QUERIES, PartsSearchHistoryItem } from '@/lib/graphql/search-history';
interface HeaderProps {
onOpenAuthModal?: () => void;
@ -25,9 +27,14 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
const [vehiclesByPartResults, setVehiclesByPartResults] = useState<LaximoVehiclesByPartResult | null>(null);
const [searchType, setSearchType] = useState<'vin' | 'oem' | 'plate' | 'text'>('text');
const [oemSearchMode, setOemSearchMode] = useState<'parts' | 'vehicles'>('parts');
const [showSearchHistory, setShowSearchHistory] = useState(false);
const [searchHistoryItems, setSearchHistoryItems] = useState<PartsSearchHistoryItem[]>([]);
const [inputFocused, setInputFocused] = useState(false);
const [showPlaceholder, setShowPlaceholder] = useState(true);
const router = useRouter();
const searchFormRef = useRef<HTMLFormElement>(null);
const searchDropdownRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
const isClient = useIsClient();
// Эффект для восстановления поискового запроса из URL
@ -37,10 +44,8 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
// Если мы находимся на странице search-result, восстанавливаем поисковый запрос
if (router.pathname === '/search-result') {
const { article, brand } = router.query;
if (article && brand && typeof article === 'string' && typeof brand === 'string') {
// Формируем поисковый запрос из артикула и бренда
setSearchQuery(`${brand} ${article}`);
} else if (article && typeof article === 'string') {
if (article && typeof article === 'string') {
// Отображаем только артикул, без бренда
setSearchQuery(article);
}
}
@ -113,11 +118,28 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
}
});
// Запрос для получения истории поиска
const [getSearchHistory, { loading: historyLoading }] = useLazyQuery(GET_RECENT_SEARCH_QUERIES, {
onCompleted: (data) => {
setSearchHistoryItems(data.partsSearchHistory?.items || []);
},
onError: (error) => {
console.error('❌ Ошибка загрузки истории поиска:', error);
setSearchHistoryItems([]);
}
});
// Закрытие результатов при клике вне области
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (searchDropdownRef.current && !searchDropdownRef.current.contains(event.target as Node)) {
setShowResults(false);
setShowSearchHistory(false);
setInputFocused(false);
// Показываем placeholder обратно только если поле пустое
if (searchQuery.trim() === '') {
setShowPlaceholder(true);
}
}
};
@ -159,20 +181,20 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
}, []);
// Скрытие top_head при скролле
useEffect(() => {
const topHead = document.querySelector('.top_head');
if (!topHead) return;
const onScroll = () => {
if (window.scrollY > 0) {
topHead.classList.add('hide-top-head');
} else {
topHead.classList.remove('hide-top-head');
}
};
window.addEventListener('scroll', onScroll);
onScroll();
return () => window.removeEventListener('scroll', onScroll);
}, []);
// useEffect(() => {
// const topHead = document.querySelector('.top_head');
// if (!topHead) return;
// const onScroll = () => {
// if (window.scrollY > 0) {
// topHead.classList.add('hide-top-head');
// } else {
// topHead.classList.remove('hide-top-head');
// }
// };
// window.addEventListener('scroll', onScroll);
// onScroll();
// return () => window.removeEventListener('scroll', onScroll);
// }, []);
// Проверяем, является ли строка VIN номером
const isVinNumber = (query: string): boolean => {
@ -358,9 +380,57 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
router.push(url);
};
// Обработчик фокуса на поле ввода
const handleInputFocus = () => {
setInputFocused(true);
setShowResults(false);
setShowPlaceholder(false);
if (searchQuery.trim() === '') {
setShowSearchHistory(true);
getSearchHistory({ variables: { limit: 5 } });
}
};
// Обработчик изменения значения поля ввода
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setSearchQuery(value);
// Управляем placeholder в зависимости от наличия текста
if (value.trim() === '') {
setShowPlaceholder(false); // Скрываем placeholder пока в фокусе
setShowSearchHistory(true);
setShowResults(false);
getSearchHistory({ variables: { limit: 5 } });
} else {
setShowPlaceholder(false); // Скрываем placeholder когда есть текст
setShowSearchHistory(false);
}
};
// Обработчик потери фокуса
const handleInputBlur = () => {
// Показываем placeholder обратно только если поле пустое
if (searchQuery.trim() === '') {
setShowPlaceholder(true);
}
};
// Обработчик клика по элементу истории
const handleHistoryItemClick = (searchQuery: string) => {
setSearchQuery(searchQuery);
setShowSearchHistory(false);
setInputFocused(false);
setShowPlaceholder(false); // Скрываем placeholder так как теперь есть текст
// Фокусируем поле ввода для возможности редактирования
if (searchInputRef.current) {
searchInputRef.current.focus();
}
};
return (
<>
<section className="top_head">
{/* <section className="top_head">
<div className="w-layout-blockcontainer container nav w-container">
<div data-animation="default" data-collapse="medium" data-duration="400" data-easing="ease" data-easing2="ease" role="banner" className="navbar w-nav">
<Link href="/" className="brand w-nav-brand"><img src="/images/logo.svg" loading="lazy" alt="" className="image-24" /></Link>
@ -375,7 +445,7 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
<div className="w-layout-hflex flex-block-2">
<div className="w-layout-hflex flex-block-3">
<div className="w-layout-hflex flex-block-77-copy">
<div className="code-embed-4 w-embed"><svg width="currentwidth" height="currenthight" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5.51667 8.99167C6.71667 11.35 8.65 13.275 11.0083 14.4833L12.8417 12.65C13.0667 12.425 13.4 12.35 13.6917 12.45C14.625 12.7583 15.6333 12.925 16.6667 12.925C17.125 12.925 17.5 13.3 17.5 13.7583V16.6667C17.5 17.125 17.125 17.5 16.6667 17.5C8.84167 17.5 2.5 11.1583 2.5 3.33333C2.5 2.875 2.875 2.5 3.33333 2.5H6.25C6.70833 2.5 7.08333 2.875 7.08333 3.33333C7.08333 4.375 7.25 5.375 7.55833 6.30833C7.65 6.6 7.58333 6.925 7.35 7.15833L5.51667 8.99167Z" fill="currentColor" /></svg></div>
<div className="code-embed-4 w-embed"><svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5.51667 8.99167C6.71667 11.35 8.65 13.275 11.0083 14.4833L12.8417 12.65C13.0667 12.425 13.4 12.35 13.6917 12.45C14.625 12.7583 15.6333 12.925 16.6667 12.925C17.125 12.925 17.5 13.3 17.5 13.7583V16.6667C17.5 17.125 17.125 17.5 16.6667 17.5C8.84167 17.5 2.5 11.1583 2.5 3.33333C2.5 2.875 2.875 2.5 3.33333 2.5H6.25C6.70833 2.5 7.08333 2.875 7.08333 3.33333C7.08333 4.375 7.25 5.375 7.55833 6.30833C7.65 6.6 7.58333 6.925 7.35 7.15833L5.51667 8.99167Z" fill="currentColor" /></svg></div>
<div className="phone-copy">+7 (495) 260-20-60</div>
</div>
</div>
@ -383,24 +453,47 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
</div>
</div>
</div>
</section>
</section> */}
<section className="bottom_head">
<div className="w-layout-blockcontainer container nav w-container">
<div className="w-layout-hflex flex-block-93">
<div data-animation="default" data-collapse="all" data-duration="400" data-easing="ease-in" data-easing2="ease" role="banner" className="navbar-2 w-nav">
<Link href="/" className="code-embed-15 w-embed protekauto-logo" style={{ display: 'block', cursor: 'pointer'}}>
<svg width="190" height="72" viewBox="0 0 190 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M138.377 29.5883V23.1172H112.878V29.5883H138.377Z" fill="white"></path>
<path d="M107.423 18.1195C109.21 18.1195 110.658 16.6709 110.658 14.884C110.658 13.097 109.21 11.6484 107.423 11.6484L88.395 11.6484C86.6082 11.6484 85.1596 13.097 85.1596 14.884C85.1596 16.6709 86.6082 18.1195 88.395 18.1195H107.423Z" fill="white"></path>
<path d="M130.288 34.2491C128.773 35.3865 126.89 36.0628 124.852 36.0628C119.849 36.0628 115.791 32.0052 115.791 27.0013C115.791 21.9974 119.849 17.9399 124.852 17.9399C129.856 17.9399 133.913 21.9974 133.913 27.0013C133.913 27.9022 133.779 28.7696 133.536 29.5893H140.169C140.31 28.7481 140.384 27.8831 140.384 27.0013C140.384 18.4226 133.431 11.4688 124.852 11.4688C116.274 11.4688 109.32 18.4226 109.32 27.0013C109.32 35.5801 116.274 42.5339 124.852 42.5339C129.249 42.5339 133.218 40.7058 136.045 37.769L130.288 34.2491Z" fill="white"></path>
<path d="M148.633 11.4531H148.631C146.629 11.4531 145.006 13.0761 145.006 15.0782V38.9075C145.006 40.9096 146.629 42.5326 148.631 42.5326H148.633C150.635 42.5326 152.258 40.9096 152.258 38.9075V15.0782C152.258 13.0761 150.635 11.4531 148.633 11.4531Z" fill="white"></path>
<path d="M168.935 36.3511L154.515 21.9297L149.387 27.0578L163.807 41.4792C164.489 42.1603 165.411 42.5402 166.371 42.5402C169.602 42.5402 171.22 38.6356 168.935 36.3511Z" fill="white"></path>
<path d="M168.937 17.7751L154.733 31.979L149.605 26.8509L163.809 12.6469C164.49 11.9659 165.412 11.5859 166.373 11.5859C169.603 11.5859 171.221 15.4906 168.937 17.7751Z" fill="white"></path>
<path d="M186.029 36.3511L171.608 21.9297L166.48 27.0578L180.901 41.4792C181.582 42.1603 182.505 42.5402 183.465 42.5402C186.696 42.5402 188.314 38.6356 186.029 36.3511Z" fill="#EC1C24"></path>
<path d="M186.029 17.7751L171.587 32.218L166.459 27.0898L180.901 12.6469C181.582 11.9659 182.505 11.5859 183.465 11.5859C186.696 11.5859 188.314 15.4906 186.029 17.7751Z" fill="#EC1C24"></path>
<path d="M3.6249 50.4184C1.62248 50.4184 0 48.7958 0 46.7933V11.4531L7.2522 14.3207V46.7933C7.2522 48.7958 5.62971 50.4184 3.62729 50.4184H3.6249Z" fill="white"></path>
<path d="M97.9491 42.5353C95.9467 42.5353 94.3242 40.9128 94.3242 38.9103V0L101.576 2.86755V38.9103C101.576 40.9128 99.9539 42.5353 97.9515 42.5353H97.9491Z" fill="white"></path>
<path d="M38.578 42.5326C36.5756 42.5326 34.9531 40.91 34.9531 38.9075V11.4531L42.2053 14.3207V38.9075C42.2053 40.91 40.5828 42.5326 38.5804 42.5326H38.578Z" fill="white"></path>
<path d="M51.334 11.4555C42.7508 11.4555 35.7949 18.4141 35.7949 26.9953H42.2705C42.2705 21.989 46.3279 17.929 51.3364 17.929C52.0102 17.929 52.6649 18.0055 53.2958 18.1441C54.2301 16.0723 55.4798 14.1749 56.9876 12.5141C55.2361 11.8307 53.3316 11.4531 51.3364 11.4531L51.334 11.4555Z" fill="white"></path>
<path d="M70.4707 11.4531C61.8875 11.4531 54.9316 18.4117 54.9316 26.9929C54.9316 35.574 61.8899 42.5326 70.4707 42.5326C79.0515 42.5326 86.0098 35.574 86.0098 26.9929C86.0098 18.4117 79.0515 11.4531 70.4707 11.4531ZM70.4707 36.0591C65.4647 36.0591 61.4049 32.0015 61.4049 26.9929C61.4049 21.9842 65.4623 17.9266 70.4707 17.9266C75.4791 17.9266 79.5365 21.9842 79.5365 26.9929C79.5365 32.0015 75.4791 36.0591 70.4707 36.0591Z" fill="white"></path>
<path d="M16.2309 11.4531C7.64774 11.4531 0.689453 18.4093 0.689453 26.9929C0.689453 35.5764 7.64774 42.5326 16.2285 42.5326C24.8093 42.5326 31.7676 35.574 31.7676 26.9929C31.7676 18.4117 24.8117 11.4531 16.2309 11.4531ZM16.2309 36.0591C11.2249 36.0591 7.16506 32.0015 7.16506 26.9929C7.16506 21.9842 11.2225 17.9266 16.2309 17.9266C21.2393 17.9266 25.2967 21.9842 25.2967 26.9929C25.2967 32.0015 21.2393 36.0591 16.2309 36.0591Z" fill="white"></path>
<rect width="53.354" height="21.8647" rx="8" transform="matrix(0.991808 -0.127739 0.127728 0.991809 134.291 50.3047)" fill="#EC1C24"></rect>
<path d="M141.15 66.1413L144.154 54.4607L146.879 54.1098L152.697 64.6542L149.925 65.0112L149.085 63.3647L144.317 63.9787L143.906 65.7864L141.15 66.1413ZM144.828 61.5681L147.98 61.1621L145.874 57.0626L144.828 61.5681Z" fill="white"></path>
<path d="M153.767 64.5163L152.337 53.4068L157.579 52.7316C158.076 52.6677 158.536 52.6615 158.962 52.7131C159.396 52.7528 159.781 52.868 160.117 53.0587C160.462 53.2376 160.749 53.5038 160.977 53.8573C161.203 54.2003 161.353 54.6542 161.426 55.2191C161.481 55.648 161.433 56.0689 161.283 56.4818C161.132 56.8947 160.885 57.2296 160.543 57.4864C161.063 57.6108 161.499 57.8736 161.852 58.2749C162.213 58.6643 162.438 59.2043 162.527 59.8947C162.615 60.5746 162.561 61.1559 162.365 61.6383C162.179 62.109 161.886 62.5029 161.487 62.8202C161.097 63.1257 160.634 63.366 160.099 63.5414C159.563 63.7167 158.994 63.8431 158.392 63.9206L153.767 64.5163ZM156.032 61.8478L158.345 61.55C158.609 61.516 158.844 61.4645 159.049 61.3954C159.264 61.3146 159.44 61.2228 159.577 61.12C159.724 61.0055 159.824 60.8702 159.879 60.7143C159.944 60.5465 159.963 60.3632 159.937 60.1644C159.91 59.9552 159.851 59.7874 159.76 59.6609C159.679 59.5331 159.565 59.4361 159.416 59.3701C159.267 59.2937 159.095 59.252 158.901 59.2451C158.706 59.2277 158.487 59.2347 158.244 59.266L155.741 59.5883L156.032 61.8478ZM155.472 57.5013L157.516 57.2382C157.769 57.2055 157.987 57.1508 158.171 57.074C158.365 56.9959 158.519 56.9016 158.634 56.7911C158.748 56.6806 158.828 56.5533 158.874 56.4092C158.931 56.2637 158.947 56.102 158.924 55.9242C158.893 55.6836 158.811 55.5027 158.677 55.3817C158.553 55.2489 158.387 55.1692 158.18 55.1427C157.971 55.1058 157.724 55.1057 157.439 55.1424L155.206 55.43L155.472 57.5013Z" fill="white"></path>
<path d="M166.971 62.8158L165.843 54.06L162.469 54.4945L162.166 52.1408L171.511 50.9373L171.814 53.291L168.409 53.7296L169.536 62.4854L166.971 62.8158Z" fill="white"></path>
<path d="M178.85 61.4134C177.699 61.5617 176.671 61.4548 175.766 61.0929C174.87 60.7191 174.14 60.1378 173.577 59.3489C173.012 58.5496 172.658 57.5903 172.514 56.471C172.366 55.3203 172.469 54.2913 172.825 53.3842C173.189 52.4652 173.764 51.7159 174.548 51.1364C175.331 50.5464 176.297 50.1773 177.448 50.029C178.578 49.8835 179.591 49.9924 180.485 50.3557C181.391 50.7176 182.13 51.2924 182.704 52.0799C183.287 52.8555 183.652 53.8135 183.799 54.9537C183.943 56.073 183.839 57.0968 183.486 58.0248C183.132 58.9425 182.559 59.7022 181.767 60.304C180.984 60.894 180.012 61.2638 178.85 61.4134ZM178.588 59.0065C179.306 58.914 179.866 58.6771 180.268 58.2957C180.67 57.9143 180.939 57.4596 181.075 56.9316C181.211 56.4037 181.245 55.8782 181.178 55.3551C181.128 54.9681 181.02 54.5885 180.854 54.2164C180.698 53.843 180.478 53.5097 180.194 53.2167C179.92 52.9223 179.58 52.7003 179.174 52.5505C178.768 52.4007 178.286 52.3618 177.726 52.4339C177.018 52.525 176.464 52.7613 176.062 53.1427C175.659 53.5136 175.384 53.9637 175.238 54.4931C175.102 55.021 175.07 55.5622 175.141 56.1167C175.212 56.6711 175.386 57.1858 175.662 57.6607C175.938 58.1356 176.318 58.5014 176.801 58.7581C177.296 59.0135 177.892 59.0963 178.588 59.0065Z" fill="white"></path>
</svg>
</Link>
<div data-animation="default" data-collapse="all" data-duration="400" data-easing="ease-in" data-easing2="ease" role="banner" className="topnav w-nav">
<div
className={`menu-button w-nav-button${menuOpen ? " w--open" : ""}`}
onClick={() => setMenuOpen((open) => !open)}
style={{ cursor: "pointer" }}
>
<div className="code-embed-5 w-embed"><svg width="currentwidth" height="currenthight" viewBox="0 0 30 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<div className="code-embed-5 w-embed"><svg width="currentwidth" height="currenthieght" viewBox="0 0 30 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0H30V3H0V0Z" fill="currentColor"></path>
<path d="M0 7.5H30V10.5H0V7.5Z" fill="currentColor"></path>
<path d="M0 15H30V18H0V15Z" fill="currentColor"></path>
</svg></div>
</div>
</div>
<div className="form-block w-form" style={{ position: 'relative' }}>
<div className="searcj w-form" style={{ position: 'relative' }} ref={searchDropdownRef}>
<form
id="custom-search-form"
name="custom-search-form"
@ -423,23 +516,33 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
</div>
</div>
<input
ref={searchInputRef}
className="text-field w-input"
maxLength={256}
name="customSearch"
data-custom-input="true"
placeholder="Введите код запчасти, VIN номер или госномер автомобиля"
placeholder={showPlaceholder ? "Введите код запчасти, VIN номер или госномер автомобиля" : ""}
type="text"
id="customSearchInput"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onChange={handleInputChange}
onFocus={handleInputFocus}
onBlur={handleInputBlur}
disabled={isSearching}
/>
</form>
{/* История поиска */}
<SearchHistoryDropdown
isVisible={showSearchHistory && !showResults}
historyItems={searchHistoryItems}
onItemClick={handleHistoryItemClick}
loading={historyLoading}
/>
{/* Результаты поиска VIN */}
{showResults && searchResults.length > 0 && (searchType === 'vin' || searchType === 'plate') && (
<div
ref={searchDropdownRef}
className="absolute top-full left-0 right-0 bg-white border border-gray-200 rounded-lg shadow-lg mt-2 z-50 max-h-80 overflow-y-auto"
>
<div className="p-3 border-b border-gray-100">
@ -736,6 +839,11 @@ const Header: React.FC<HeaderProps> = ({ onOpenAuthModal = () => console.log('Au
</div>
</div>
<div className="w-layout-hflex flex-block-76">
<Link href="/profile-history" className="button_h w-inline-block">
<img src="/images/union.svg" alt="История заказов" width={20} />
</Link>
<Link href="/profile-gar" className="button_h w-inline-block">
<div className="code-embed-7 w-embed"><svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M27 10.8V24H24.6V13.2H5.4V24H3V10.8L15 6L27 10.8ZM23.4 14.4H6.6V16.8H23.4V14.4ZM23.4 18H6.6V20.4H23.4V18Z" fill="currentColor" /><path d="M6.6 21.6H23.4V24H6.6V21.6Z" fill="currentColor" /></svg></div>
<div className="text-block-2">Добавить в гараж</div>

View File

@ -32,7 +32,11 @@ const InfoSearch: React.FC<InfoSearchProps> = ({
<div className="w-layout-hflex flex-block-10">
<h1 className="heading">{name}</h1>
<div className="text-block-4">
Найдено {offersCount} предложений от {minPrice}
{offersCount > 0 ? (
<>Найдено {offersCount} предложений от {minPrice}</>
) : (
<>Ничего не найдено</>
)}
</div>
</div>
{/* <div className="w-layout-hflex flex-block-11">

View File

@ -0,0 +1,20 @@
import React from 'react';
import { generateJsonLdScript } from '@/lib/schema';
interface JsonLdScriptProps {
schema: object;
}
// Компонент для вставки JSON-LD разметки
const JsonLdScript: React.FC<JsonLdScriptProps> = ({ schema }) => {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: generateJsonLdScript(schema)
}}
/>
);
};
export default JsonLdScript;

View File

@ -13,6 +13,7 @@ const menuItems = [
{ label: 'Адреса доставки', href: '/profile-addresses', icon: 'https://cdn.builder.io/api/v1/image/assets/TEMP/1faca7190a7dd71a66fd3cf0127a8c6e45eac5e6?placeholderIfAbsent=true&apiKey=f5bc5a2dc9b841d0aba1cc6c74a35920' },
{ label: 'Гараж', href: '/profile-gar', icon: 'https://cdn.builder.io/api/v1/image/assets/TEMP/783501855b4cb8be4ac47a0733e298c3f3ccfc5e?placeholderIfAbsent=true&apiKey=f5bc5a2dc9b841d0aba1cc6c74a35920' },
{ label: 'Настройки аккаунта', href: '/profile-set', icon: 'https://cdn.builder.io/api/v1/image/assets/TEMP/b39907028aa6baf08adc313aed84d1294f2be013?placeholderIfAbsent=true&apiKey=f5bc5a2dc9b841d0aba1cc6c74a35920' },
{ label: 'Настройки cookies', href: '/profile-cookie-settings', icon: 'https://cdn.builder.io/api/v1/image/assets/TEMP/b39907028aa6baf08adc313aed84d1294f2be013?placeholderIfAbsent=true&apiKey=f5bc5a2dc9b841d0aba1cc6c74a35920' },
];
const financeItems = [
@ -63,7 +64,7 @@ const LKMenu = React.forwardRef<HTMLDivElement>((props, ref) => {
<div
className={`flex gap-2.5 items-center px-2.5 py-2 w-full whitespace-nowrap rounded-lg ${
isActive ? 'bg-slate-200' : 'bg-white'
}`}
} hover:bg-slate-200`}
>
<img
loading="lazy"
@ -92,7 +93,7 @@ const LKMenu = React.forwardRef<HTMLDivElement>((props, ref) => {
<div
className={`flex gap-2.5 items-center px-2.5 py-2 w-full whitespace-nowrap rounded-lg ${
isActive ? 'bg-slate-200' : 'bg-white'
}`}
} hover:bg-slate-200`}
>
<img
loading="lazy"

View File

@ -3,6 +3,7 @@ import { useRouter } from "next/router";
import Header from "./Header";
import AuthModal from "./auth/AuthModal";
import MobileMenuBottomSection from "./MobileMenuBottomSection";
import IndexTopMenuNav from "./index/IndexTopMenuNav";
const Layout = ({ children }: { children: React.ReactNode }) => {
const [authModalOpen, setAuthModalOpen] = useState(false);
@ -30,7 +31,10 @@ const Layout = ({ children }: { children: React.ReactNode }) => {
onSuccess={handleAuthSuccess}
/>
</header>
<main className="pt-[108px] md:pt-[131px]">{children}</main>
<main className="pt-[62px] md:pt-[63px]">
<IndexTopMenuNav isIndexPage={router.pathname === '/'} />
{children}</main>
<MobileMenuBottomSection onOpenAuthModal={() => setAuthModalOpen(true)} />
</>
);

View File

@ -0,0 +1,98 @@
import Head from 'next/head';
import { useRouter } from 'next/router';
interface MetaTagsProps {
title?: string;
description?: string;
keywords?: string;
ogTitle?: string;
ogDescription?: string;
ogImage?: string;
ogUrl?: string;
twitterTitle?: string;
twitterDescription?: string;
twitterImage?: string;
canonical?: string;
robots?: string;
author?: string;
viewport?: string;
charset?: string;
}
const MetaTags: React.FC<MetaTagsProps> = ({
title = 'Protek - Автозапчасти и аксессуары',
description = 'Protek - широкий ассортимент автозапчастей и аксессуаров для всех марок автомобилей. Быстрая доставка, гарантия качества.',
keywords = 'автозапчасти, запчасти, автомобили, аксессуары, доставка, protek',
ogTitle,
ogDescription,
ogImage = '/images/og-image.jpg',
ogUrl,
twitterTitle,
twitterDescription,
twitterImage,
canonical,
robots = 'index, follow',
author = 'Protek',
viewport = 'width=device-width, initial-scale=1',
charset = 'utf-8'
}) => {
const router = useRouter();
const baseUrl = 'https://protek.ru'; // Замените на ваш домен
const currentUrl = ogUrl || `${baseUrl}${router.asPath}`;
const canonicalUrl = canonical || currentUrl;
const finalOgTitle = ogTitle || title;
const finalOgDescription = ogDescription || description;
const finalTwitterTitle = twitterTitle || title;
const finalTwitterDescription = twitterDescription || description;
const finalTwitterImage = twitterImage || ogImage;
return (
<Head>
{/* Базовые meta-теги */}
<meta charSet={charset} />
<title>{title}</title>
<meta name="description" content={description} />
<meta name="keywords" content={keywords} />
<meta name="author" content={author} />
<meta name="viewport" content={viewport} />
<meta name="robots" content={robots} />
{/* Canonical URL */}
<link rel="canonical" href={canonicalUrl} />
{/* Open Graph теги */}
<meta property="og:type" content="website" />
<meta property="og:title" content={finalOgTitle} />
<meta property="og:description" content={finalOgDescription} />
<meta property="og:image" content={ogImage} />
<meta property="og:url" content={currentUrl} />
<meta property="og:site_name" content="Protek" />
<meta property="og:locale" content="ru_RU" />
{/* Twitter Card теги */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={finalTwitterTitle} />
<meta name="twitter:description" content={finalTwitterDescription} />
<meta name="twitter:image" content={finalTwitterImage} />
{/* Favicon и иконки */}
<link href="/images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="/images/webclip.png" rel="apple-touch-icon" />
{/* Preconnect для производительности */}
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
{/* Дополнительные meta-теги для SEO */}
<meta name="format-detection" content="telephone=no" />
<meta name="theme-color" content="#dc2626" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="Protek" />
</Head>
);
};
export default MetaTags;

View File

@ -0,0 +1,148 @@
import React from 'react';
interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
className?: string;
showPageInfo?: boolean;
}
const Pagination: React.FC<PaginationProps> = ({
currentPage,
totalPages,
onPageChange,
className = "",
showPageInfo = true
}) => {
const generatePageNumbers = () => {
const pages: (number | string)[] = [];
const delta = 2; // Количество страниц вокруг текущей
if (totalPages <= 7) {
// Если страниц мало, показываем все
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
// Всегда показываем первую страницу
pages.push(1);
if (currentPage > delta + 2) {
pages.push('...');
}
// Показываем страницы вокруг текущей
const start = Math.max(2, currentPage - delta);
const end = Math.min(totalPages - 1, currentPage + delta);
for (let i = start; i <= end; i++) {
pages.push(i);
}
if (currentPage < totalPages - delta - 1) {
pages.push('...');
}
// Всегда показываем последнюю страницу
if (totalPages > 1) {
pages.push(totalPages);
}
}
return pages;
};
const pageNumbers = generatePageNumbers();
if (totalPages <= 1) {
return null;
}
return (
<div className={`flex flex-col items-center space-y-3 ${className}`}>
{/* Основные кнопки пагинации */}
<div className="flex items-center justify-center space-x-2">
{/* Предыдущая страница */}
<button
onClick={() => onPageChange(currentPage - 1)}
disabled={currentPage === 1}
className="flex items-center justify-center w-10 h-10 text-sm font-medium text-gray-500 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
style={{ cursor: currentPage === 1 ? 'not-allowed' : 'pointer' }}
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M15 19l-7-7 7-7"
/>
</svg>
</button>
{/* Номера страниц */}
{pageNumbers.map((page, index) => (
<React.Fragment key={index}>
{page === '...' ? (
<span className="flex items-center justify-center w-10 h-10 text-gray-400">
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
<circle cx="3" cy="10" r="1.5" />
<circle cx="10" cy="10" r="1.5" />
<circle cx="17" cy="10" r="1.5" />
</svg>
</span>
) : (
<button
onClick={() => onPageChange(page as number)}
className={`flex items-center justify-center w-10 h-10 text-sm font-medium border rounded-lg transition-colors ${
currentPage === page
? 'text-white bg-[#ec1c24] border-[#ec1c24] hover:bg-[#d91920]'
: 'text-gray-500 bg-white border-gray-200 hover:bg-gray-50 hover:text-gray-700'
}`}
style={{ cursor: 'pointer' }}
>
{page}
</button>
)}
</React.Fragment>
))}
{/* Следующая страница */}
<button
onClick={() => onPageChange(currentPage + 1)}
disabled={currentPage === totalPages}
className="flex items-center justify-center w-10 h-10 text-sm font-medium text-gray-500 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 hover:text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
style={{ cursor: currentPage === totalPages ? 'not-allowed' : 'pointer' }}
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</button>
</div>
{/* Информация о страницах */}
{showPageInfo && (
<div className="text-sm text-gray-500">
Страница {currentPage} из {totalPages}
</div>
)}
</div>
);
};
export default Pagination;

View File

@ -3,7 +3,6 @@ import { useRouter } from 'next/router';
import { useLazyQuery } from '@apollo/client';
import { LaximoOEMResult } from '@/types/laximo';
import { SEARCH_LAXIMO_OEM } from '@/lib/graphql';
import BrandSelectionModal from './BrandSelectionModal';
interface PartDetailCardProps {
oem: string;
@ -30,7 +29,6 @@ const PartDetailCard: React.FC<PartDetailCardProps> = ({
}) => {
const router = useRouter();
const [localExpanded, setLocalExpanded] = useState(false);
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
// Используем локальное состояние если нет внешнего контроля
const expanded = onToggleExpand ? isExpanded : localExpanded;
@ -53,13 +51,12 @@ const PartDetailCard: React.FC<PartDetailCardProps> = ({
const handleFindOffers = () => {
console.log('🔍 Выбрана деталь для поиска предложений:', name, 'OEM:', oem);
// Показываем модал выбора бренда
setIsBrandModalOpen(true);
// Переходим на страницу выбора бренда
const url = `/vehicle-search/${catalogCode}/${vehicleId}/part/${oem}/brands?detailName=${encodeURIComponent(name || '')}`;
router.push(url);
};
const handleCloseBrandModal = () => {
setIsBrandModalOpen(false);
};
const handleOpenFullInfo = () => {
// Переход на отдельную страницу с детальной информацией о детали
@ -250,13 +247,6 @@ const PartDetailCard: React.FC<PartDetailCardProps> = ({
</div>
)}
{/* Модал выбора бренда */}
<BrandSelectionModal
isOpen={isBrandModalOpen}
onClose={handleCloseBrandModal}
articleNumber={oem}
detailName={name}
/>
</div>
);
};

View File

@ -59,19 +59,13 @@ const ProductListCard: React.FC<ProductListCardProps> = ({
return match ? parseInt(match[0]) : 0;
};
const handleAddToCart = () => {
const handleAddToCart = async () => {
const availableStock = parseStock(stock);
// Проверяем наличие
if (count > availableStock) {
alert(`Недостаточно товара в наличии. Доступно: ${availableStock} шт.`);
return;
}
const numericPrice = parsePrice(price);
const numericOldPrice = oldPrice ? parsePrice(oldPrice) : undefined;
addItem({
const result = await addItem({
productId: productId,
offerKey: offerKey,
name: title,
@ -81,6 +75,7 @@ const ProductListCard: React.FC<ProductListCardProps> = ({
originalPrice: numericOldPrice,
currency: currency,
quantity: count,
stock: availableStock, // передаем информацию о наличии
deliveryTime: deliveryTime || delivery,
warehouse: warehouse || address,
supplier: supplier,
@ -88,8 +83,13 @@ const ProductListCard: React.FC<ProductListCardProps> = ({
image: image,
});
// Показываем уведомление о добавлении
alert(`Товар "${title}" добавлен в корзину (${count} шт.)`);
if (result.success) {
// Показываем уведомление о добавлении
alert(`Товар "${title}" добавлен в корзину (${count} шт.)`);
} else {
// Показываем ошибку
alert(result.error || 'Ошибка при добавлении товара в корзину');
}
};
return (

View File

@ -25,9 +25,17 @@ const QuickGroupItem: React.FC<QuickGroupItemProps> = ({ group, level, onGroupCl
const handleGroupClick = () => {
if (canShowDetails) {
// Если это конечная группа с поиском деталей - переходим к просмотру деталей
onGroupClick(group);
} else if (hasChildren) {
// Если это родительская группа с подгруппами
if (group.children?.some(child => child.link)) {
// Есть подгруппы с активным поиском - показываем пользователю выбор
setIsExpanded(!isExpanded);
} else {
// Все подгруппы неактивны - просто разворачиваем
setIsExpanded(!isExpanded);
}
}
};
@ -69,6 +77,11 @@ const QuickGroupItem: React.FC<QuickGroupItemProps> = ({ group, level, onGroupCl
Доступен поиск
</span>
)}
{hasChildren && !canShowDetails && (
<span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">
{group.children?.filter(child => child.link).length || 0} подгрупп
</span>
)}
</p>
</div>
</div>
@ -143,8 +156,15 @@ const QuickDetailSection: React.FC<QuickDetailSectionProps> = ({
};
const handleUnitClick = (unit: LaximoUnit) => {
console.log('🔍 Выбран узел для детального просмотра:', unit.name, 'ID:', unit.unitid);
setSelectedUnit(unit);
// ИСПРАВЛЕНИЕ: Сохраняем SSD узла из API ответа
console.log('🔍 handleUnitClick - сохраняем узел с SSD:', {
unitId: unit.unitid,
unitName: unit.name,
unitSsd: unit.ssd ? `${unit.ssd.substring(0, 50)}...` : 'отсутствует',
unitSsdLength: unit.ssd?.length
});
setSelectedUnit(unit); // Сохраняем полный объект узла с его SSD
};
const handleBackFromUnit = () => {
@ -194,11 +214,23 @@ const QuickDetailSection: React.FC<QuickDetailSectionProps> = ({
// Если выбран узел для детального просмотра, показываем UnitDetailsSection
if (selectedUnit) {
// ИСПРАВЛЕНИЕ: Используем SSD узла из API ответа, а не родительский SSD
// API Laximo возвращает для каждого узла свой собственный SSD
console.log('🔍 QuickDetailSection передает в UnitDetailsSection:', {
parentSsd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует',
parentSsdLength: ssd?.length,
selectedUnitSsd: selectedUnit.ssd ? `${selectedUnit.ssd.substring(0, 50)}...` : 'отсутствует',
selectedUnitSsdLength: selectedUnit.ssd?.length,
unitId: selectedUnit.unitid,
unitName: selectedUnit.name,
note: 'Используем SSD УЗЛА из API ответа'
});
return (
<UnitDetailsSection
catalogCode={catalogCode}
vehicleId={vehicleId}
ssd={ssd}
ssd={selectedUnit.ssd || ssd} // Используем SSD узла, fallback на родительский SSD
unitId={selectedUnit.unitid}
unitName={selectedUnit.name}
onBack={handleBackFromUnit}

View File

@ -0,0 +1,176 @@
import React from 'react';
import { PartsSearchHistoryItem } from '@/lib/graphql/search-history';
interface SearchHistoryDropdownProps {
isVisible: boolean;
historyItems: PartsSearchHistoryItem[];
onItemClick: (searchQuery: string) => void;
loading?: boolean;
}
const SearchHistoryDropdown: React.FC<SearchHistoryDropdownProps> = ({
isVisible,
historyItems,
onItemClick,
loading = false
}) => {
if (!isVisible) return null;
// Фильтруем уникальные запросы
const uniqueQueries = Array.from(
new Map(
historyItems.map(item => [item.searchQuery.toLowerCase(), item])
).values()
);
const getSearchTypeLabel = (type: string) => {
switch (type) {
case 'VIN':
return 'VIN';
case 'PLATE':
return 'Госномер';
case 'OEM':
case 'ARTICLE':
return 'Артикул';
default:
return 'Поиск';
}
};
return (
<div className="search-history-dropdown-custom">
{loading ? (
<div className="p-4 text-center text-gray-500">
<div className="flex items-center justify-center">
<svg className="animate-spin w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Загрузка истории...
</div>
</div>
) : uniqueQueries.length > 0 ? (
<>
{uniqueQueries.map((item) => (
<button
key={item.id}
onClick={() => {
if ((item.searchType === 'ARTICLE' || item.searchType === 'OEM') && item.articleNumber) {
onItemClick(item.articleNumber);
} else {
onItemClick(item.searchQuery);
}
}}
className="search-history-item-custom"
style={{ cursor: 'pointer' }}
>
<div className="flex items-center gap-3">
<span className="search-history-icon-custom">
<svg width="18" height="18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" />
<path d="M12 8v4l3 3" />
</svg>
</span>
<span className="search-history-inline">
<span className="search-history-query-custom">{item.searchQuery}</span>
<span className="search-history-type-custom">{getSearchTypeLabel(item.searchType)}</span>
</span>
</div>
</button>
))}
</>
) : (
<div className="p-4 text-center text-gray-500">
<p className="text-sm">История поиска пуста</p>
</div>
)}
<style>{`
.search-history-dropdown-custom {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: #fff;
border-radius: 16px;
box-shadow: 0 8px 32px rgba(44,62,80,0.10), 0 1.5px 4px rgba(44,62,80,0.08);
margin-top: 12px;
z-index: 50;
max-height: 260px;
overflow-y: auto;
border: 1px solid #f0f0f0;
padding: 6px 0;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE и Edge */
}
.search-history-dropdown-custom::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
.search-history-item-custom {
width: 100%;
background: none;
border: none;
outline: none;
padding: 12px 20px;
border-radius: 0;
transition: background 0.18s;
display: block;
}
.search-history-item-custom:hover, .search-history-item-custom:focus {
background: #e5e7eb;
}
.search-history-item-custom .flex {
flex-direction: row-reverse;
}
.search-history-icon-custom {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
background: #f3f4f6;
color: #222;
flex-shrink: 0;
margin-left: 12px;
margin-right: 0;
}
.search-history-item-custom:hover .search-history-icon-custom,
.search-history-item-custom:focus .search-history-icon-custom {
background: #ec1c24;
color: #fff;
}
.search-history-inline {
display: flex;
flex: 1 1 0%;
min-width: 0;
align-items: center;
gap: 8px;
}
.search-history-query-custom {
font-size: 15px;
font-weight: 500;
color: #222;
margin: 0;
line-height: 1.2;
letter-spacing: 0.01em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1 1 0%;
min-width: 0;
}
.search-history-type-custom {
font-size: 12px;
color: #8e9aac;
margin: 0 0 0 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
}
`}</style>
</div>
);
};
export default SearchHistoryDropdown;

View File

@ -0,0 +1,169 @@
import Link from "next/link";
import React, { useState } from "react";
import { useCart } from "@/contexts/CartContext";
import { useFavorites } from "@/contexts/FavoritesContext";
import toast from "react-hot-toast";
interface TopSalesItemProps {
image: string;
price: string;
title: string;
brand: string;
article?: string;
productId?: string;
onAddToCart?: (e: React.MouseEvent) => void;
discount?: string; // Новый пропс для лейбла/скидки
isInCart?: boolean;
}
const TopSalesItem: React.FC<TopSalesItemProps> = ({
image,
price,
title,
brand,
article,
productId,
onAddToCart,
discount = 'Топ продаж', // По умолчанию как раньше
// isInCart = false, // Удаляем из пропсов
}) => {
const { addItem, isInCart: isItemInCart } = useCart();
const { addToFavorites, removeFromFavorites, isFavorite, favorites } = useFavorites();
const [localInCart, setLocalInCart] = useState(false);
const isItemFavorite = isFavorite(productId, undefined, article, brand);
const isInCart = isItemInCart(productId, undefined, article, brand);
const parsePrice = (priceStr: string): number => {
const cleanPrice = priceStr.replace(/[^\d.,]/g, '').replace(',', '.');
return parseFloat(cleanPrice) || 0;
};
const handleAddToCart = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (!localInCart) {
setLocalInCart(true);
}
if (onAddToCart) {
onAddToCart(e);
return;
}
try {
if (!article || !brand) {
toast.error('Недостаточно данных для добавления товара в корзину');
return;
}
const numericPrice = parsePrice(price);
addItem({
name: title,
brand: brand,
article: article,
description: title,
price: numericPrice,
quantity: 1,
currency: 'RUB',
image: image,
isExternal: true
});
toast.success('Товар добавлен в корзину');
} catch (error) {
console.error('Ошибка добавления в корзину:', error);
toast.error('Ошибка добавления товара в корзину');
}
};
const handleFavoriteClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
if (isItemFavorite) {
const favoriteItem = favorites.find((fav: any) => {
if (productId && fav.productId === productId) return true;
if (fav.article === article && fav.brand === brand) return true;
return false;
});
if (favoriteItem) {
removeFromFavorites(favoriteItem.id);
}
} else {
const numericPrice = parsePrice(price);
addToFavorites({
productId,
name: title,
brand: brand,
article: article || '',
price: numericPrice,
currency: 'RUB',
image: image
});
}
};
// Ссылка на карточку товара (если нужно)
const cardUrl = article && brand
? `/card?article=${encodeURIComponent(article)}&brand=${encodeURIComponent(brand)}`
: '/card';
return (
<div className="w-layout-vflex flex-block-15-copy">
<div
className={`favcardcat${isItemFavorite ? ' favorite-active' : ''}`}
onClick={handleFavoriteClick}
style={{ cursor: 'pointer', color: isItemFavorite ? '#ff4444' : '#ccc' }}
>
<div className="icon-setting w-embed">
<svg width="currentwidth" height="currentheight" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.5996 3.5C15.8107 3.5 17.5 5.1376 17.5 7.19629C17.5 8.46211 16.9057 9.65758 15.7451 11.0117C14.8712 12.0314 13.7092 13.1034 12.3096 14.3311L10.833 15.6143L10.832 15.6152L10 16.3369L9.16797 15.6152L9.16699 15.6143L7.69043 14.3311C6.29084 13.1034 5.12883 12.0314 4.25488 11.0117C3.09428 9.65758 2.50003 8.46211 2.5 7.19629C2.5 5.1376 4.18931 3.5 6.40039 3.5C7.6497 3.50012 8.85029 4.05779 9.62793 4.92188L10 5.33398L10.3721 4.92188C11.1497 4.05779 12.3503 3.50012 13.5996 3.5Z" fill="currentColor" ></path>
</svg>
</div>
</div>
<div className="div-block-4">
<img
src={image}
loading="lazy"
width="Auto"
height="Auto"
alt={title}
className="image-5"
/>
<div className="text-block-7">{discount}</div>
</div>
<div className="div-block-3">
<div className="w-layout-hflex flex-block-16">
<div className="text-block-8">{price}</div>
{/* <div className="text-block-9">oldPrice</div> */}
</div>
<div className="w-layout-hflex flex-block-122">
<div className="w-layout-vflex">
<div className="text-block-10">{title}</div>
<div className="text-block-11">{brand}</div>
</div>
<a
href="#"
className="button-icon w-inline-block"
onClick={isInCart ? undefined : handleAddToCart}
style={{
cursor: isInCart ? 'default' : (localInCart ? 'default' : 'pointer'),
background: isInCart ? '#9ca3af' : (localInCart ? '#2563eb' : undefined),
opacity: isInCart || localInCart ? 0.5 : 1,
filter: isInCart || localInCart ? 'grayscale(1)' : 'none'
}}
aria-label={isInCart ? 'В корзине' : (localInCart ? 'Добавлено' : 'Добавить в корзину')}
title={isInCart ? 'Товар уже в корзине - нажмите для добавления еще' : 'Добавить в корзину'}
>
<div className="div-block-26">
<div className="icon-setting w-embed">
<svg width="currentWidht" height="currentHeight" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.1998 22.2C8.8798 22.2 7.81184 23.28 7.81184 24.6C7.81184 25.92 8.8798 27 10.1998 27C11.5197 27 12.5997 25.92 12.5997 24.6C12.5997 23.28 11.5197 22.2 10.1998 22.2ZM3 3V5.4H5.39992L9.71977 14.508L8.09982 17.448C7.90783 17.784 7.79984 18.18 7.79984 18.6C7.79984 19.92 8.8798 21 10.1998 21H24.5993V18.6H10.7037C10.5357 18.6 10.4037 18.468 10.4037 18.3L10.4397 18.156L11.5197 16.2H20.4594C21.3594 16.2 22.1513 15.708 22.5593 14.964L26.8552 7.176C26.9542 6.99286 27.004 6.78718 26.9997 6.57904C26.9955 6.37089 26.9373 6.16741 26.8309 5.98847C26.7245 5.80952 26.5736 5.66124 26.3927 5.55809C26.2119 5.45495 26.0074 5.40048 25.7992 5.4H8.05183L6.92387 3H3ZM22.1993 22.2C20.8794 22.2 19.8114 23.28 19.8114 24.6C19.8114 25.92 20.8794 27 22.1993 27C23.5193 27 24.5993 25.92 24.5993 24.6C24.5993 23.28 23.5193 22.2 22.1993 22.2Z" fill="currentColor"></path>
</svg>
</div>
</div>
</a>
</div>
</div>
</div>
);
};
export default TopSalesItem;

View File

@ -28,8 +28,25 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
const [imageLoadTimeout, setImageLoadTimeout] = useState<NodeJS.Timeout | null>(null);
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
const [selectedDetail, setSelectedDetail] = useState<LaximoUnitDetail | null>(null);
const [highlightedDetailId, setHighlightedDetailId] = useState<string | null>(null);
// Отладочная информация для SSD
console.log('🔍 UnitDetailsSection получил SSD:', {
ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует',
ssdLength: ssd?.length,
unitId,
unitName
});
// Получаем информацию об узле
console.log('🔍 UnitDetailsSection - GET_LAXIMO_UNIT_INFO SSD:', {
ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует',
ssdLength: ssd?.length,
unitId,
unitName,
note: 'Используем SSD узла для API запросов'
});
const { data: unitInfoData, loading: unitInfoLoading, error: unitInfoError } = useQuery<{ laximoUnitInfo: LaximoUnitInfo }>(
GET_LAXIMO_UNIT_INFO,
{
@ -37,14 +54,23 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
catalogCode,
vehicleId,
unitId,
ssd: ssd || ''
ssd
},
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
errorPolicy: 'all'
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId || !ssd || ssd.trim() === '',
errorPolicy: 'all',
fetchPolicy: 'no-cache', // Отключаем кэширование для получения актуальных данных
notifyOnNetworkStatusChange: true
}
);
// Получаем детали узла
console.log('🔍 UnitDetailsSection - GET_LAXIMO_UNIT_DETAILS SSD:', {
ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует',
ssdLength: ssd?.length,
unitId,
unitName
});
const { data: unitDetailsData, loading: unitDetailsLoading, error: unitDetailsError } = useQuery<{ laximoUnitDetails: LaximoUnitDetail[] }>(
GET_LAXIMO_UNIT_DETAILS,
{
@ -52,14 +78,23 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
catalogCode,
vehicleId,
unitId,
ssd: ssd || ''
ssd
},
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
errorPolicy: 'all'
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId || !ssd || ssd.trim() === '',
errorPolicy: 'all',
fetchPolicy: 'no-cache', // Отключаем кэширование для получения актуального SSD
notifyOnNetworkStatusChange: true
}
);
// Получаем карту изображений узла
console.log('🔍 UnitDetailsSection - GET_LAXIMO_UNIT_IMAGE_MAP SSD:', {
ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует',
ssdLength: ssd?.length,
unitId,
unitName
});
const { data: unitImageMapData, loading: unitImageMapLoading, error: unitImageMapError } = useQuery<{ laximoUnitImageMap: LaximoUnitImageMap }>(
GET_LAXIMO_UNIT_IMAGE_MAP,
{
@ -67,10 +102,12 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
catalogCode,
vehicleId,
unitId,
ssd: ssd || ''
ssd
},
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
errorPolicy: 'all'
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId || !ssd || ssd.trim() === '',
errorPolicy: 'all',
fetchPolicy: 'no-cache', // Отключаем кэширование для получения актуального SSD
notifyOnNetworkStatusChange: true
}
);
@ -129,11 +166,31 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
d.detailid === coord.codeonimage
);
if (detail) {
console.log('✅ Найдена деталь для выделения:', detail.name, 'ID:', detail.detailid);
// Выделяем деталь в списке
setHighlightedDetailId(detail.detailid);
} else {
console.log('⚠️ Деталь не найдена в списке по коду:', coord.codeonimage);
setHighlightedDetailId(null);
}
};
const handleCoordinateDoubleClick = (coord: LaximoImageCoordinate) => {
console.log('🖱️ Двойной клик по интерактивной области:', coord.codeonimage);
// Сначала пытаемся найти деталь в списке
const detail = unitDetails.find(d =>
d.detailid === coord.detailid ||
d.codeonimage === coord.codeonimage ||
d.detailid === coord.codeonimage
);
if (detail && detail.oem) {
console.log('✅ Найдена деталь для выбора бренда:', detail.name, 'OEM:', detail.oem);
// Показываем модал выбора бренда
setSelectedDetail(detail);
setIsBrandModalOpen(true);
// Переходим на страницу выбора бренда
const url = `/vehicle-search/${catalogCode}/${vehicleId}/part/${detail.oem}/brands?detailName=${encodeURIComponent(detail.name || '')}`;
router.push(url);
} else {
// Если деталь не найдена в списке, переходим к общему поиску по коду на изображении
console.log('⚠️ Деталь не найдена в списке, переходим к поиску по коду:', coord.codeonimage);
@ -425,7 +482,8 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
borderRadius: coord.shape === 'circle' ? '50%' : '0'
}}
onClick={() => handleCoordinateClick(coord)}
title={detail ? `${coord.codeonimage}: ${detail.name}` : `Деталь ${coord.codeonimage}`}
onDoubleClick={() => handleCoordinateDoubleClick(coord)}
title={detail ? `${coord.codeonimage}: ${detail.name} (Клик - выделить, двойной клик - перейти к выбору бренда)` : `Деталь ${coord.codeonimage} (Клик - выделить, двойной клик - поиск)`}
>
<div className="absolute -top-6 left-1/2 transform -translate-x-1/2 bg-red-600 text-white text-xs px-2 py-1 rounded font-bold">
{coord.codeonimage}
@ -576,7 +634,11 @@ const UnitDetailsSection: React.FC<UnitDetailsSectionProps> = ({
{unitDetails.map((detail, index) => (
<div
key={`detail-${unitId}-${index}-${detail.detailid}`}
className="border border-gray-200 rounded-lg p-4 hover:border-red-300 hover:shadow-md transition-all duration-200 cursor-pointer"
className={`border rounded-lg p-4 hover:border-red-300 hover:shadow-md transition-all duration-200 cursor-pointer ${
highlightedDetailId === detail.detailid
? 'border-red-500 bg-red-50 shadow-md'
: 'border-gray-200'
}`}
onClick={() => handleDetailClick(detail)}
>
<div className="flex items-start justify-between">

View File

@ -47,96 +47,116 @@ const VehicleSearchResults: React.FC<VehicleSearchResultsProps> = ({
}
};
// Функция для условного отображения атрибута
const renderAttribute = (label: string, value: string | undefined) => {
if (!value || value === '' || value === 'undefined') return null
return (
<div className="flex justify-between py-1 border-b border-gray-100">
<span className="text-sm text-gray-600 font-medium">{label}:</span>
<span className="text-sm text-gray-900">{value}</span>
</div>
)
}
if (results.length === 0) {
return null;
}
return (
<div className="bg-white rounded-2xl md:my-8">
<div className="mb-2">
<h4 className="text-lg font-medium text-gray-900">
Найденные автомобили ({results.length})
</h4>
</div>
<div className="space-y-4">
<h3 className="text-lg font-semibold text-gray-900">
Найдено автомобилей: {results.length}
</h3>
<div className="flex flex-col gap-4">
<div className="flex flex-wrap flex-1 gap-5 size-full max-md:max-w-full">
{results.map((vehicle, index) => (
<div
key={vehicle.vehicleid || index}
className="pt-3 pb-3 bg-white border-b border-gray-200 hover:bg-neutral-50 transition-colors cursor-pointer flex flex-col sm:flex-row sm:items-center gap-4"
key={`${vehicle.vehicleid}-${index}`}
className="flex flex-col flex-1 shrink p-8 bg-white rounded-lg border border-solid basis-0 border-stone-300 max-w-[504px] md:min-w-[370px] sm:min-w-[340px] min-w-[200px] max-md:px-5 cursor-pointer transition-shadow hover:shadow-lg"
onClick={() => handleSelectVehicle(vehicle)}
>
<div className="flex-1 min-w-0">
<div className="flex flex-wrap items-center gap-3 mb-2">
<h4 className="text-lg font-semibold text-gray-900 truncate">
{vehicle.name || `${vehicle.brand || 'Unknown'} ${vehicle.model || 'Vehicle'}`}
</h4>
{vehicle.year && (
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-50 text-red-700">
{vehicle.year}
</span>
)}
</div>
{/* Заголовок автомобиля */}
<div className="">
<h4 className="text-lg font-semibold text-red-600 mb-1 truncate">
{vehicle.name || `${vehicle.brand} ${vehicle.model}`}
</h4>
{/* <p className="text-sm text-gray-500 truncate">
{vehicle.modification} ({vehicle.year})
</p> */}
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm mb-2">
{vehicle.modification && (
<div>
<span className="text-gray-500">Модификация:</span>
<span className="ml-2 font-medium text-gray-900">{vehicle.modification}</span>
</div>
)}
{vehicle.bodytype && (
<div>
<span className="text-gray-500">Тип кузова:</span>
<span className="ml-2 font-medium text-gray-900">{vehicle.bodytype}</span>
</div>
)}
{vehicle.engine && (
<div>
<span className="text-gray-500">Двигатель:</span>
<span className="ml-2 font-medium text-gray-900">{vehicle.engine}</span>
</div>
)}
</div>
{/* Основные характеристики */}
<div className="space-y-1 mb-4">
<h5 className="text-base font-semibold text-gray-900 mb-2">Основные характеристики</h5>
{renderAttribute('Марка', vehicle.brand)}
{renderAttribute('Модель', vehicle.model)}
{renderAttribute('Двигатель', vehicle.engine)}
</div>
{vehicle.notes && (
<div className="mt-3 p-3 bg-yellow-50 rounded-lg">
<div className="flex">
<div className="flex-shrink-0">
<svg className="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
</svg>
</div>
<div className="ml-3">
<p className="text-sm text-yellow-800">
<span className="font-medium">Примечание:</span> {vehicle.notes}
</p>
</div>
{/* Все атрибуты из API */}
{vehicle.attributes && vehicle.attributes.length > 0 && (
<div className="space-y-1 mb-4">
<h5 className="text-base font-semibold text-gray-900 mb-2">Дополнительные характеристики</h5>
{vehicle.attributes.map((attr, attrIndex) => (
<div key={attrIndex} className="flex justify-between py-1 border-b border-gray-100">
<span className="text-sm text-gray-600 font-medium">{attr.name || attr.key}:</span>
<span className="text-sm text-gray-900">{attr.value}</span>
</div>
))}
</div>
)}
{/* Технические характеристики (fallback для старых данных) */}
{(!vehicle.attributes || vehicle.attributes.length === 0) && (
<>
<div className="space-y-1 mb-4">
<h5 className="text-base font-semibold text-gray-900 mb-2">Дополнительные характеристики</h5>
{renderAttribute('Год', vehicle.year)}
{renderAttribute('Кузов', vehicle.bodytype)}
{renderAttribute('Трансмиссия', vehicle.transmission)}
{renderAttribute('Класс', vehicle.grade)}
{renderAttribute('Цвет кузова', vehicle.framecolor)}
{renderAttribute('Цвет салона', vehicle.trimcolor)}
{renderAttribute('Рынок', vehicle.market)}
{renderAttribute('Регион производства', vehicle.creationregion)}
{renderAttribute('Регион назначения', vehicle.destinationregion)}
</div>
)}
</div>
<div className="flex-shrink-0 flex items-center justify-end">
<button
onClick={e => {
e.stopPropagation();
handleSelectVehicle(vehicle);
}}
className="inline-flex items-center px-6 py-3 border border-transparent text-base font-medium rounded-lg bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 shadow transition"
style={{ color: '#fff' }}
>
Выбрать
</button>
</div>
<div className="space-y-1 mb-4">
<h5 className="text-base font-semibold text-gray-900 mb-2">Технические характеристики</h5>
{renderAttribute('Информация о двигателе', vehicle.engine_info)}
{renderAttribute('Номер двигателя', vehicle.engineno)}
{renderAttribute('Дата производства', vehicle.date)}
{renderAttribute('Произведен', vehicle.manufactured)}
{renderAttribute('Период производства', vehicle.prodPeriod)}
{renderAttribute('Диапазон производства', vehicle.prodRange)}
</div>
<div className="space-y-1 mb-4">
<h5 className="text-base font-semibold text-gray-900 mb-2">Даты и периоды</h5>
{renderAttribute('Дата с', vehicle.datefrom)}
{renderAttribute('Дата по', vehicle.dateto)}
{renderAttribute('Модельный год с', vehicle.modelyearfrom)}
{renderAttribute('Модельный год по', vehicle.modelyearto)}
</div>
{/* Опции и описание */}
{(vehicle.options || vehicle.description || vehicle.notes) && (
<div className="space-y-1 mb-4">
<h5 className="text-base font-semibold text-gray-900 mb-2">Опции и описание</h5>
{renderAttribute('Опции', vehicle.options)}
{renderAttribute('Описание', vehicle.description)}
{renderAttribute('Примечания', vehicle.notes)}
</div>
)}
</>
)}
{/* Системная информация */}
</div>
))}
</div>
<div className="bg-gray-50 rounded-xl pt-4 pb-4 mt-6 flex flex-col sm:flex-row items-center justify-between text-sm text-gray-600">
<span>Показано {results.length} результат{results.length === 1 ? '' : results.length < 5 ? 'а' : 'ов'}</span>
<span>Кликните на автомобиль для подбора запчастей</span>
</div>
</div>
);
};

View File

@ -20,11 +20,17 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
const [error, setError] = useState<string>('');
const [queries, setQueries] = useState<Record<string, string>>({});
const buttonRefs = useRef<Record<string, React.RefObject<HTMLButtonElement | null>>>({});
const inputRefs = useRef<Record<string, React.RefObject<HTMLInputElement | null>>>({});
const [showSearchButton, setShowSearchButton] = React.useState(true);
const [getWizard2] = useLazyQuery(GET_LAXIMO_WIZARD2, {
onCompleted: (data) => {
if (data.laximoWizard2) {
console.log('🔄 Wizard обновлен:', {
steps: data.laximoWizard2.length,
selectedParams: Object.keys(selectedParams).length,
currentSsd
});
setWizardSteps(data.laximoWizard2);
setIsLoading(false);
}
@ -75,18 +81,28 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
// --- Автовыбор единственного варианта для всех шагов ---
React.useEffect(() => {
// Предотвращаем автовыбор во время загрузки
if (isLoading) return;
wizardSteps.forEach(step => {
const options = step.options || [];
const selectedKey = selectedParams[step.conditionid]?.key || (step.determined ? options.find(o => o.value === step.value)?.key : '');
if (options.length === 1 && selectedKey !== options[0].key) {
// Автовыбираем только если есть единственный вариант и он еще не выбран
if (options.length === 1 && selectedKey !== options[0].key && !selectedParams[step.conditionid]) {
handleParamSelect(step, options[0].key, options[0].value);
}
});
// eslint-disable-next-line
}, [wizardSteps, selectedParams]);
}, [wizardSteps, selectedParams, isLoading]);
// Обработка выбора параметра
const handleParamSelect = async (step: LaximoWizardStep, optionKey: string, optionValue: string) => {
// Проверяем, не выбран ли уже этот параметр
if (selectedParams[step.conditionid]?.key === optionKey) {
return;
}
setIsLoading(true);
setError('');
@ -117,6 +133,13 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
// Сброс параметра
const handleParamReset = async (step: LaximoWizardStep) => {
console.log('🔄 Сброс параметра:', {
stepName: step.name,
conditionId: step.conditionid,
currentSsd,
selectedParamsBefore: Object.keys(selectedParams)
});
setIsLoading(true);
setError('');
@ -125,8 +148,33 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
delete newSelectedParams[step.conditionid];
setSelectedParams(newSelectedParams);
// Используем SSD для сброса параметра, если он есть
const resetSsd = step.ssd || '';
// Находим правильный SSD для сброса этого параметра
// Нужно найти SSD, который соответствует состоянию до выбора этого параметра
let resetSsd = '';
// Ищем среди шагов wizard тот, который имеет правильный SSD для восстановления
const currentStepIndex = wizardSteps.findIndex(s => s.conditionid === step.conditionid);
// Если есть предыдущие шаги с выбранными параметрами, используем их SSD
for (let i = currentStepIndex - 1; i >= 0; i--) {
const prevStep = wizardSteps[i];
if (newSelectedParams[prevStep.conditionid]) {
resetSsd = newSelectedParams[prevStep.conditionid].key;
break;
}
}
// Если не нашли предыдущий SSD, используем step.ssd или пустую строку
if (!resetSsd) {
resetSsd = step.ssd || '';
}
console.log('🔄 Новый SSD для сброса:', {
resetSsd,
selectedParamsAfter: Object.keys(newSelectedParams),
stepSsd: step.ssd
});
setCurrentSsd(resetSsd);
try {
@ -220,14 +268,18 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
? options.filter(option => option.value.toLowerCase().includes(query.toLowerCase()))
: options;
const buttonRef = buttonRefs.current[step.conditionid];
// Создаём ref для инпута, если его ещё нет
if (!inputRefs.current[step.conditionid]) {
inputRefs.current[step.conditionid] = React.createRef<HTMLInputElement>();
}
const inputRef = inputRefs.current[step.conditionid];
// Определяем выбранный ключ
const selectedKey = selectedParams[step.conditionid]?.key || (step.determined ? options.find(o => o.value === step.value)?.key : '');
// Определяем отображаемый label
const selectedLabel =
options.find(o => o.key === selectedKey)?.value ||
selectedParams[step.conditionid]?.value ||
step.value ||
'';
step.value || '';
// Если единственный вариант уже выбран — не рендерим селект
if (options.length === 1 && (selectedKey === options[0].key || step.determined)) {
@ -252,7 +304,21 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
disabled={isLoading || options.length === 0}
>
<div className="relative">
{/* Невидимая кнопка поверх инпута */}
<button
type="button"
className="absolute top-0 left-0 w-full h-full opacity-0 z-10 cursor-pointer"
tabIndex={0}
aria-label="Открыть список опций"
onClick={() => {
inputRef.current?.focus();
if (inputRef.current) {
inputRef.current.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true }));
}
}}
/>
<Combobox.Input
ref={inputRef}
id={`wizard-combobox-${step.conditionid}`}
className={`w-full px-6 py-4 rounded text-sm text-gray-950 placeholder:text-neutral-500 outline-none focus:shadow-none transition-colors pr-12 ${selectedLabel ? 'bg-gray-50 border-gray-200' : 'bg-white border border-stone-300'}`}
displayValue={() => selectedLabel}
@ -264,7 +330,7 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
{selectedLabel ? (
<button
type="button"
className="absolute inset-y-0 right-0 w-12 flex items-center justify-center text-gray-400 hover:text-red-600 focus:outline-none"
className="absolute inset-y-0 right-0 w-12 flex items-center justify-center text-gray-400 hover:text-red-600 focus:outline-none z-10"
aria-label="Сбросить"
tabIndex={0}
onClick={() => handleParamReset(step)}
@ -306,36 +372,36 @@ const WizardSearchForm: React.FC<WizardSearchFormProps> = ({
</div>
)}
{/* Кнопка поиска автомобилей */}
{!isLoading && canListVehicles && showSearchButton && (
<div className="pt-4 border-t">
{/* Информация о недостаточности параметров и кнопка поиска */}
{!isLoading && wizardSteps.length > 0 && (
<div className="flex flex-row gap-4 items-center w-full mx-auto max-sm:flex-col max-sm:items-stretch">
<button
onClick={() => {
handleFindVehicles();
setShowSearchButton(false);
}}
disabled={isLoading}
className="w-full sm:w-auto px-8 py-3 bg-red-600 !text-white font-medium rounded-lg shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
disabled={!canListVehicles || isLoading}
className="w-full sm:w-auto px-8 py-3 bg-red-600 !text-white font-medium rounded-lg shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center max-sm:w-full"
style={{ minWidth: 180 }}
>
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
Найти автомобили
Найти
</button>
<div className="mt-3 text-sm text-gray-600">
Определено параметров: {wizardSteps.filter(s => s.determined).length} из {wizardSteps.length}
<div
layer-name="Выберите больше параметров для поиска автомобилей"
className="box-border inline-flex gap-5 items-center px-10 py-4 rounded-xl bg-slate-50 h-[52px] max-md:px-8 max-md:py-3.5 max-md:w-full max-md:h-auto max-md:max-w-[524px] max-md:min-h-[52px] max-sm:gap-3 max-sm:px-5 max-sm:py-3 max-sm:w-full max-sm:rounded-lg max-sm:justify-center"
>
<div>
<img src="/images/info.svg" alt="info" style={{ width: 18, height: 20, flexShrink: 0 }} />
</div>
<div
layer-name="Выберите больше параметров для поиска автомобилей"
className="relative text-base font-medium leading-5 text-center text-gray-950 max-md:text-sm max-sm:text-sm max-sm:leading-4 max-sm:text-center"
>
Выберите больше параметров для поиска автомобилей
</div>
</div>
</div>
)}
{/* Информация о недостаточности параметров */}
{!isLoading && !canListVehicles && wizardSteps.length > 0 && (
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded-lg">
<p className="text-yellow-800 text-sm">
Выберите больше параметров для поиска автомобилей
</p>
</div>
)}
</div>
);
};

View File

@ -1,6 +1,8 @@
import React, { useState } from "react";
import { useCart } from "@/contexts/CartContext";
import { toast } from "react-hot-toast";
import CartIcon from "../CartIcon";
import { isDeliveryDate } from "@/lib/utils";
interface ProductBuyBlockProps {
offer?: any;
@ -37,7 +39,7 @@ const ProductBuyBlock = ({ offer }: ProductBuyBlockProps) => {
}
// Добавляем товар в корзину
addItem({
const result = await addItem({
productId: offer.id ? String(offer.id) : undefined,
offerKey: offer.offerKey || undefined,
name: offer.name || `${offer.brand} ${offer.articleNumber}`,
@ -45,25 +47,33 @@ const ProductBuyBlock = ({ offer }: ProductBuyBlockProps) => {
price: offer.price,
currency: 'RUB',
quantity: quantity,
stock: offer.quantity, // передаем информацию о наличии
image: offer.image || undefined,
brand: offer.brand,
article: offer.articleNumber,
supplier: offer.supplier || (offer.type === 'external' ? 'AutoEuro' : 'Внутренний'),
deliveryTime: offer.deliveryTime ? String(offer.deliveryTime) + ' дней' : '1 день',
deliveryTime: offer.deliveryTime ? (typeof offer.deliveryTime === 'string' && isDeliveryDate(offer.deliveryTime)
? offer.deliveryTime
: String(offer.deliveryTime) + ' дней') : '1 день',
isExternal: offer.type === 'external'
});
// Показываем успешный тоастер
toast.success(
<div>
<div className="font-semibold">Товар добавлен в корзину!</div>
<div className="text-sm text-gray-600">{offer.name || `${offer.brand} ${offer.articleNumber}`}</div>
</div>,
{
duration: 3000,
icon: '🛒',
}
);
if (result.success) {
// Показываем успешный тоастер
toast.success(
<div>
<div className="font-semibold" style={{ color: '#fff' }}>Товар добавлен в корзину!</div>
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{offer.name || `${offer.brand} ${offer.articleNumber}`}</div>
</div>,
{
duration: 3000,
icon: <CartIcon size={20} color="#fff" />,
}
);
} else {
// Показываем ошибку
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
}
} catch (error) {
console.error('Ошибка добавления в корзину:', error);
toast.error('Ошибка при добавлении товара в корзину');

View File

@ -1,4 +1,5 @@
import React from "react";
import { isDeliveryDate } from "@/lib/utils";
interface ProductInfoProps {
offer?: any;
@ -17,6 +18,11 @@ const ProductInfo: React.FC<ProductInfoProps> = ({ offer }) => {
// Форматируем срок доставки
const formatDeliveryTime = (deliveryTime: number | string) => {
// Если это уже дата (содержит название месяца), возвращаем как есть
if (typeof deliveryTime === 'string' && isDeliveryDate(deliveryTime)) {
return deliveryTime;
}
const days = typeof deliveryTime === 'string' ? parseInt(deliveryTime) : deliveryTime;
if (!days || days === 0) {

View File

@ -4,9 +4,10 @@ interface FilterRangeProps {
title: string;
min?: number;
max?: number;
isMobile?: boolean; // Добавляем флаг для мобильной версии
value?: [number, number] | null; // Текущее значение диапазона
isMobile?: boolean;
value?: [number, number] | null;
onChange?: (value: [number, number]) => void;
defaultOpen?: boolean; // Добавляем параметр defaultOpen
}
const DEFAULT_MIN = 1;
@ -14,24 +15,42 @@ const DEFAULT_MAX = 32000;
const clamp = (v: number, min: number, max: number) => Math.max(min, Math.min(v, max));
const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max = DEFAULT_MAX, isMobile = false, value = null, onChange }) => {
const [from, setFrom] = useState(value ? value[0] : min);
const [to, setTo] = useState(value ? value[1] : max);
const FilterRange: React.FC<FilterRangeProps> = ({
title,
min = DEFAULT_MIN,
max = DEFAULT_MAX,
isMobile = false,
value = null,
onChange,
defaultOpen = false // Устанавливаем по умолчанию false
}) => {
const [from, setFrom] = useState<string>(value ? String(value[0]) : String(min));
const [to, setTo] = useState<string>(value ? String(value[1]) : String(max));
const [confirmedFrom, setConfirmedFrom] = useState<number>(value ? value[0] : min);
const [confirmedTo, setConfirmedTo] = useState<number>(value ? value[1] : max);
const [dragging, setDragging] = useState<null | "from" | "to">(null);
const [trackWidth, setTrackWidth] = useState(0);
const [open, setOpen] = useState(true);
const [open, setOpen] = useState(isMobile || defaultOpen); // Учитываем isMobile и defaultOpen
const trackRef = useRef<HTMLDivElement>(null);
// Обновляем локальное состояние при изменении внешнего значения
// Обновляем локальное состояние при изменении внешнего значения или границ
useEffect(() => {
if (value) {
setFrom(value[0]);
setTo(value[1]);
} else {
setFrom(min);
setTo(max);
let nextFrom = value ? value[0] : min;
let nextTo = value ? value[1] : max;
let changed = false;
// Корректируем значения, если они вне новых границ
if (nextFrom < min) { nextFrom = min; changed = true; }
if (nextTo > max) { nextTo = max; changed = true; }
if (nextFrom > nextTo) { nextFrom = nextTo; changed = true; }
setFrom(String(nextFrom));
setTo(String(nextTo));
setConfirmedFrom(nextFrom);
setConfirmedTo(nextTo);
// Если значения были скорректированы, уведомляем родителя
if (changed && onChange) {
onChange([nextFrom, nextTo]);
}
}, [value, min, max]);
}, [value, min, max, onChange]);
// Обновляем ширину полосы при монтировании и ресайзе
useLayoutEffect(() => {
@ -61,15 +80,15 @@ const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max
x = clamp(x, 0, trackWidth);
const value = clamp(pxToValue(x), min, max);
if (dragging === "from") {
setFrom(v => clamp(Math.min(value, to), min, to));
setFrom(v => String(clamp(Math.min(value, Number(to)), min, Number(to))));
} else {
setTo(v => clamp(Math.max(value, from), from, max));
setTo(v => String(clamp(Math.max(value, Number(from)), Number(from), max)));
}
};
const onUp = () => {
setDragging(null);
if (onChange) {
onChange([from, to]);
onChange([Number(from), Number(to)]);
}
};
window.addEventListener("mousemove", onMove);
@ -82,25 +101,48 @@ const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max
// Input handlers
const handleFromInput = (e: React.ChangeEvent<HTMLInputElement>) => {
let v = Number(e.target.value.replace(/\D/g, ""));
if (isNaN(v)) v = min;
setFrom(clamp(Math.min(v, to), min, to));
let v = e.target.value.replace(/\D/g, "");
setFrom(v);
};
const handleToInput = (e: React.ChangeEvent<HTMLInputElement>) => {
let v = Number(e.target.value.replace(/\D/g, ""));
if (isNaN(v)) v = max;
setTo(clamp(Math.max(v, from), from, max));
let v = e.target.value.replace(/\D/g, "");
setTo(v);
};
const handleInputBlur = () => {
if (onChange) {
onChange([from, to]);
const handleFromBlur = () => {
let v = Number(from);
if (isNaN(v) || v < min) v = min;
// если больше max — оставлять как есть
setFrom(String(v));
if (onChange) onChange([v, to === "" ? max : Number(to)]);
setConfirmedFrom(v);
};
const handleToBlur = () => {
let v = Number(to);
if (isNaN(v) || v < min) v = min;
if (v > max) v = max;
setTo(String(v));
if (onChange) onChange([from === "" ? min : Number(from), v]);
setConfirmedTo(v);
};
const handleFromKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleFromBlur();
(e.target as HTMLInputElement).blur();
}
};
const handleToKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
handleToBlur();
(e.target as HTMLInputElement).blur();
}
};
// px позиции для точек
const pxFrom = valueToPx(from);
const pxTo = valueToPx(to);
const pxFrom = valueToPx(dragging ? Number(from) : confirmedFrom);
const pxTo = valueToPx(dragging ? Number(to) : confirmedTo);
// Мобильная версия - без dropdown
if (isMobile) {
@ -124,7 +166,8 @@ const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max
id="from"
value={from}
onChange={handleFromInput}
onBlur={handleInputBlur}
onBlur={handleFromBlur}
onKeyDown={handleFromKeyDown}
style={{ padding: '8px 10px 8px 36px', fontSize: 16, width: '100%' }}
/>
</div>
@ -139,7 +182,8 @@ const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max
id="to"
value={to}
onChange={handleToInput}
onBlur={handleInputBlur}
onBlur={handleToBlur}
onKeyDown={handleToKeyDown}
style={{ padding: '8px 10px 8px 36px', fontSize: 16, width: '100%' }}
/>
</div>
@ -164,7 +208,7 @@ const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max
style={{
position: 'absolute',
top: 6,
left: pxFrom ,
left: pxFrom,
zIndex: 3,
cursor: 'pointer'
}}
@ -214,7 +258,8 @@ const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max
id="from"
value={from}
onChange={handleFromInput}
onBlur={handleInputBlur}
onBlur={handleFromBlur}
onKeyDown={handleFromKeyDown}
/>
</div>
<div className="div-block-5">
@ -228,7 +273,8 @@ const FilterRange: React.FC<FilterRangeProps> = ({ title, min = DEFAULT_MIN, max
id="to"
value={to}
onChange={handleToInput}
onBlur={handleInputBlur}
onBlur={handleToBlur}
onKeyDown={handleToKeyDown}
/>
</div>
</form>

View File

@ -0,0 +1,190 @@
import React, { useRef } from "react";
import { useQuery } from "@apollo/client";
import BestPriceItem from "../BestPriceItem";
import { GET_BEST_PRICE_PRODUCTS } from "../../lib/graphql";
interface BestPriceProductData {
id: string;
productId: string;
discount: number;
isActive: boolean;
sortOrder: number;
product: {
id: string;
name: string;
article?: string;
brand?: string;
retailPrice?: number;
images: { url: string; alt?: string }[];
};
}
const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ
const BestPriceSection: React.FC = () => {
const { data, loading, error } = useQuery(GET_BEST_PRICE_PRODUCTS);
const scrollRef = useRef<HTMLDivElement>(null);
const scrollLeft = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' });
}
};
const scrollRight = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' });
}
};
if (loading) {
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-hflex flex-block-118">
<div className="w-layout-vflex flex-block-119">
<h1 className="heading-20">ЛУЧШАЯ ЦЕНА!</h1>
<div className="text-block-58">Загрузка...</div>
</div>
</div>
</div>
</section>
);
}
if (error) {
console.error('Ошибка загрузки товаров с лучшей ценой:', error);
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-hflex flex-block-118">
<div className="w-layout-vflex flex-block-119">
<h1 className="heading-20">ЛУЧШАЯ ЦЕНА!</h1>
<div className="text-block-58">Ошибка загрузки данных</div>
</div>
</div>
</div>
</section>
);
}
const bestPriceProducts: BestPriceProductData[] = data?.bestPriceProducts || [];
// Функция для форматирования цены
const formatPrice = (price?: number) => {
if (!price) return '—';
return `от ${price.toLocaleString('ru-RU')}`;
};
// Функция для расчета цены со скидкой
const calculateDiscountedPrice = (price?: number, discount?: number) => {
if (!price || !discount) return price;
return price * (1 - discount / 100);
};
// Преобразование данных для компонента BestPriceItem
const bestPriceItems = bestPriceProducts
.filter(item => item.isActive)
.sort((a, b) => a.sortOrder - b.sortOrder)
.slice(0, 8) // Ограничиваем до 8 товаров
.map(item => ({
image: item.product.images?.[0]?.url || "images/162615.webp", // Fallback изображение
discount: `-${item.discount}%`,
price: formatPrice(calculateDiscountedPrice(item.product.retailPrice, item.discount)),
oldPrice: formatPrice(item.product.retailPrice),
title: item.product.name,
brand: item.product.brand || "",
article: item.product.article,
productId: item.product.id,
}));
// Если нет товаров, не показываем секцию
if (bestPriceItems.length === 0) {
return null;
}
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-hflex flex-block-118">
<div className="w-layout-vflex flex-block-119">
<h1 className="heading-20">ЛУЧШАЯ ЦЕНА!</h1>
<div className="text-block-58">Подборка лучших предложенийпо цене</div>
<a href="#" className="button-24 w-button">Показать все</a>
</div>
<div className="carousel-row" style={{ position: 'relative' }}>
{/* Стили для стрелок как в ProductOfDayBanner, но без абсолютного позиционирования */}
<style>{`
.carousel-arrow {
width: 40px;
height: 40px;
border: none;
background: none;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity 0.2s;
cursor: pointer;
margin: 0 8px;
}
.carousel-arrow-left {}
.carousel-arrow-right {}
.carousel-arrow .arrow-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.85);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.carousel-arrow:hover .arrow-circle,
.carousel-arrow:focus .arrow-circle {
background: #ec1c24;
}
.carousel-arrow .arrow-svg {
width: 20px;
height: 20px;
display: block;
transition: stroke 0.2s;
stroke: #222;
}
.carousel-arrow:hover .arrow-svg,
.carousel-arrow:focus .arrow-svg {
stroke: #fff;
}
.carousel-row {
display: flex;
align-items: center;
justify-content: flex-start;
}
`}</style>
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
<div className="w-layout-hflex flex-block-121 carousel-scroll" ref={scrollRef}>
{bestPriceItems.map((item, i) => (
<BestPriceItem key={i} {...item} />
))}
</div>
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 10H16.6673M16.6673 10L11.6673 5M16.6673 10L11.6673 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
</div>
</div>
</div>
</section>
);
};
export default BestPriceSection;

View File

@ -0,0 +1,191 @@
import React, { useState, useMemo, useRef } from "react";
import { useRouter } from "next/router";
import { useQuery } from "@apollo/client";
import { GET_LAXIMO_BRANDS } from "@/lib/graphql";
import { LaximoBrand } from "@/types/laximo";
import { Combobox } from '@headlessui/react';
const tabs = [
"Техническое обслуживание",
"Легковые",
"Грузовые",
"Коммерческие",
];
type Brand = { name: string; code?: string };
const BrandSelectionSection: React.FC = () => {
const [activeTab, setActiveTab] = useState(0);
const [selectedBrand, setSelectedBrand] = useState<Brand | null>(null);
const [brandQuery, setBrandQuery] = useState('');
const router = useRouter();
const { data, loading, error } = useQuery<{ laximoBrands: LaximoBrand[] }>(GET_LAXIMO_BRANDS, {
errorPolicy: 'all'
});
const staticBrands: Brand[] = [
{ name: "Audi" },
{ name: "BMW" },
{ name: "Cadillac" },
{ name: "Chevrolet" },
{ name: "Citroen" },
{ name: "Fiat" },
{ name: "Mazda" }
];
let brands: Brand[] = staticBrands;
if (data?.laximoBrands && data.laximoBrands.length > 0) {
brands = data.laximoBrands.map(brand => ({
name: brand.name,
code: brand.code
}));
} else if (error) {
console.warn('Laximo API недоступен, используются статические данные:', error.message);
}
// Combobox фильтрация
const filteredBrands = useMemo(() => {
if (!brandQuery) return brands;
return brands.filter(b => b.name.toLowerCase().includes(brandQuery.toLowerCase()));
}, [brands, brandQuery]);
const handleBrandClick = (brand: Brand) => {
if (brand.code) {
router.push(`/brands?selected=${brand.code}`);
} else {
console.warn('Brand code not available for', brand.name);
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (selectedBrand) {
const found = brands.find(b => b.code === selectedBrand.code || b.name === selectedBrand.name);
if (found && found.code) {
router.push(`/brands?selected=${found.code}`);
return;
}
}
router.push("/brands");
};
if (loading) {
return (
<section>
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex inbt">
<h2 className="heading-4">Подбор по маркам</h2>
<div className="text-center">Загрузка брендов...</div>
</div>
</div>
</section>
);
}
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex inbt">
<h2 className="heading-4">Подбор по маркам</h2>
<div className="w-layout-hflex flex-block-6-copy">
<div className="w-layout-hflex brandsortb">
<div className="w-layout-hflex tabson">
{tabs.map((tab, idx) => (
<div
className={activeTab === idx ? "tab_c tab_card-activ" : "tab_c tab_card"}
key={idx}
onClick={() => setActiveTab(idx)}
style={{ cursor: "pointer" }}
>
{tab}
</div>
))}
</div>
<div className="w-layout-hflex brandsort">
{[...Array(5)].map((_, colIdx) => (
<div className="w-layout-vflex flex-block-26" key={colIdx}>
{brands.slice(colIdx * Math.ceil(brands.length / 5), (colIdx + 1) * Math.ceil(brands.length / 5)).map((brand, idx) => (
<button
onClick={() => handleBrandClick(brand)}
className="link-block-6 w-inline-block text-left"
key={idx}
style={{ background: 'none', border: 'none', padding: 0 }}
>
<div className="indexbrandblock">{brand.name}</div>
</button>
))}
</div>
))}
</div>
<button
onClick={() => router.push('/brands')}
className="w-layout-hflex flex-block-29 cursor-pointer hover:opacity-80 transition-opacity"
style={{ background: 'none', border: 'none', padding: 0 }}
>
<div className="text-block-18">Все марки</div>
<img src="/images/Arrow_right.svg" loading="lazy" alt="Стрелка вправо" />
</button>
</div>
<div className="w-layout-vflex flex-block-124">
<h1 className="heading-21">ПОДБОР АВТОЗАПЧАСТЕЙ ПО МАРКЕ АВТО</h1>
<div className="form-block-4 w-form">
<form id="email-form" name="email-form" data-name="Email Form" method="post" data-wf-page-id="685be6dfd87db2e01cbdb7a2" data-wf-element-id="e673036c-0caf-d251-3b66-9ba9cb85064c" onSubmit={handleSubmit}>
<div style={{ width: 180, marginBottom: 16 }}>
<Combobox value={selectedBrand} onChange={setSelectedBrand} nullable>
<div className="relative">
<Combobox.Input
className="w-full px-6 py-4 bg-white rounded border border-stone-300 text-sm text-gray-950 placeholder:text-neutral-500 outline-none focus:shadow-none focus:border-stone-300 transition-colors"
displayValue={(brand: Brand | null) => brand?.name || ''}
onChange={e => setBrandQuery(e.target.value)}
placeholder="Марка"
autoComplete="off"
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center px-3 focus:outline-none w-12">
<svg className="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 9l6 6 6-6" />
</svg>
</Combobox.Button>
<Combobox.Options
className="absolute left-0 top-full z-100 bg-white border-x border-b border-stone-300 rounded-b-lg shadow-lg w-full max-h-60 overflow-auto scrollbar-none"
style={{ scrollbarWidth: 'none' }}
data-hide-scrollbar
>
{filteredBrands.length === 0 && (
<div className="px-6 py-4 text-gray-500">Бренды не найдены</div>
)}
{filteredBrands.map(brand => (
<Combobox.Option
key={brand.code || brand.name}
value={brand}
className={({ active, selected }) =>
`px-6 py-4 cursor-pointer hover:!bg-[rgb(236,28,36)] hover:!text-white text-sm transition-colors ${selected ? 'bg-red-50 font-semibold text-gray-950' : 'text-neutral-500'}`
}
>
{brand.name}
</Combobox.Option>
))}
</Combobox.Options>
</div>
</Combobox>
</div>
<div className="div-block-10-copy">
<input type="submit" data-wait="Please wait..." className="button-3-copy w-button" value="Далее" />
</div>
</form>
<div className="w-form-done">
<div>Thank you! Your submission has been received!</div>
</div>
<div className="w-form-fail">
<div>Oops! Something went wrong while submitting the form.</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default BrandSelectionSection;

View File

@ -0,0 +1,83 @@
import React from "react";
import { useQuery } from '@apollo/client';
import { GET_PARTSINDEX_CATEGORIES } from '@/lib/graphql';
import { useRouter } from 'next/router';
interface CategoryNavGroup {
id: string;
name: string;
image?: string;
}
interface CategoryNavItem {
id: string;
name: string;
image?: string;
groups?: CategoryNavGroup[];
}
const FALLBACK_CATEGORIES: CategoryNavItem[] = [
{ id: '1', name: 'Детали для ТО', image: '/images/catalog_item.png' },
{ id: '2', name: 'Шины', image: '/images/catalog_item2.png' },
{ id: '3', name: 'Диски', image: '/images/catalog_item3.png' },
{ id: '4', name: 'Масла и жидкости', image: '/images/catalog_item4.png' },
{ id: '5', name: 'Инструменты', image: '/images/catalog_item5.png' },
{ id: '6', name: 'Автохимия', image: '/images/catalog_item6.png' },
{ id: '7', name: 'Аксессуары', image: '/images/catalog_item7.png' },
{ id: '8', name: 'Электрика', image: '/images/catalog_item8.png' },
{ id: '9', name: 'АКБ', image: '/images/catalog_item9.png' },
];
const CategoryNavSection: React.FC = () => {
const router = useRouter();
const { data } = useQuery<{ partsIndexCategoriesWithGroups: CategoryNavItem[] }>(
GET_PARTSINDEX_CATEGORIES,
{
variables: { lang: 'ru' },
errorPolicy: 'all',
fetchPolicy: 'cache-first',
}
);
const categories = (data?.partsIndexCategoriesWithGroups && data.partsIndexCategoriesWithGroups.length > 0)
? data.partsIndexCategoriesWithGroups.slice(0, 9)
: FALLBACK_CATEGORIES;
const handleCategoryClick = (category: CategoryNavItem) => {
// Получаем первую доступную группу для навигации в PartsIndex режим
const firstGroupId = category.groups && category.groups.length > 0 ? category.groups[0].id : undefined;
router.push({
pathname: '/catalog',
query: {
partsIndexCatalog: category.id,
categoryName: encodeURIComponent(category.name),
...(firstGroupId && { partsIndexCategory: firstGroupId })
}
});
};
return (
<section className="catnav">
<div className="w-layout-blockcontainer batd w-container">
<div className="w-layout-hflex flex-block-108-copy">
{categories.map((category, idx) => (
<div
key={category.id}
className={`ci${idx + 1}`}
style={category.image ? { cursor: 'pointer', backgroundImage: `url('${category.image}')`, backgroundSize: 'cover', backgroundPosition: 'center' } : { cursor: 'pointer' }}
onClick={() => handleCategoryClick(category)}
>
<div className={idx === 0 ? 'text-block-54-copy' : 'text-block-54'} style={{ textAlign: 'center' }}>
{category.name}
</div>
</div>
))}
</div>
</div>
</section>
);
};
export default CategoryNavSection;

View File

@ -1,6 +1,23 @@
import React, { useEffect } from "react";
import { useQuery } from '@apollo/client';
import { GET_HERO_BANNERS } from '@/lib/graphql';
import Link from 'next/link';
interface HeroBanner {
id: string;
title: string;
subtitle?: string;
imageUrl: string;
linkUrl?: string;
isActive: boolean;
sortOrder: number;
}
const HeroSlider = () => {
const { data, loading, error } = useQuery(GET_HERO_BANNERS, {
errorPolicy: 'all'
});
useEffect(() => {
if (typeof window !== "undefined" && window.Webflow && window.Webflow.require) {
if (window.Webflow.destroy) {
@ -12,118 +29,152 @@ const HeroSlider = () => {
}
}, []);
// Фильтруем только активные баннеры и сортируем их
const banners: HeroBanner[] = data?.heroBanners
?.filter((banner: HeroBanner) => banner.isActive)
?.slice()
?.sort((a: HeroBanner, b: HeroBanner) => a.sortOrder - b.sortOrder) || [];
// Если нет данных или происходит загрузка, показываем дефолтный баннер
if (loading || error || banners.length === 0) {
return (
<section className="section-5" style={{ overflow: 'hidden' }}>
<div className="w-layout-blockcontainer container w-container">
<div data-delay="4000" data-animation="slide" className="slider w-slider" data-autoplay="false" data-easing="ease"
data-hide-arrows="false" data-disable-swipe="false" data-autoplay-limit="0" data-nav-spacing="3"
data-duration="500" data-infinite="true">
<div className="mask w-slider-mask">
<div className="slide w-slide">
<div className="w-layout-vflex flex-block-100">
<div className="div-block-35">
<img src="/images/imgfb.png" loading="lazy"
sizes="(max-width: 767px) 100vw, (max-width: 991px) 728px, 940px"
srcSet="/images/imgfb-p-500.png 500w, /images/imgfb-p-800.png 800w, /images/imgfb.png 1027w"
alt="Автозапчасти ProteK"
className="image-21" />
</div>
<div className="w-layout-vflex flex-block-99">
<h2 className="heading-17">ШИРОКИЙ ВЫБОР АВТОЗАПЧАСТЕЙ</h2>
<div className="text-block-51">
Сотрудничаем только с проверенными поставщиками. Постоянно обновляем
ассортимент, чтобы предложить самые лучшие и актуальные детали.
</div>
</div>
<div className="w-layout-hflex flex-block-101">
<div className="w-layout-hflex flex-block-102">
<img src="/images/1.png" loading="lazy" alt="" className="image-20" />
<div className="text-block-52">Быстрая доставка по всей стране</div>
</div>
<div className="w-layout-hflex flex-block-102">
<img src="/images/2.png" loading="lazy" alt="" className="image-20" />
<div className="text-block-52">Высокое качество продукции</div>
</div>
<div className="w-layout-hflex flex-block-102">
<img src="/images/3.png" loading="lazy" alt="" className="image-20" />
<div className="text-block-52">Выгодные цены</div>
</div>
<div className="w-layout-hflex flex-block-102">
<img src="/images/4.png" loading="lazy" alt="" className="image-20" />
<div className="text-block-52">Профессиональная консультация</div>
</div>
</div>
</div>
</div>
</div>
<div className="left-arrow w-slider-arrow-left">
<div className="div-block-34">
<div className="icon-2 w-icon-slider-left"></div>
</div>
</div>
<div className="right-arrow w-slider-arrow-right">
<div className="div-block-34">
<div className="icon-2 w-icon-slider-right"></div>
</div>
</div>
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round"></div>
</div>
</div>
</section>
);
}
const renderSlide = (banner: HeroBanner) => {
const slideContent = (
<div className="w-layout-vflex flex-block-100">
<div className="div-block-35">
<img
src={banner.imageUrl}
loading="lazy"
sizes="(max-width: 767px) 100vw, (max-width: 991px) 728px, 940px"
alt={banner.title}
className="image-21"
/>
</div>
<div className="w-layout-vflex flex-block-99">
<h2 className="heading-17">{banner.title}</h2>
{banner.subtitle && (
<div className="text-block-51">{banner.subtitle}</div>
)}
</div>
</div>
);
// Если есть ссылка, оборачиваем в Link
if (banner.linkUrl) {
return (
<Link href={banner.linkUrl} className="slide w-slide" style={{ cursor: 'pointer' }}>
{slideContent}
</Link>
);
}
return (
<div className="slide w-slide">
{slideContent}
</div>
);
};
return (
<section className="section-5">
<section className="section-5" style={{ overflow: 'hidden' }}>
<div className="w-layout-blockcontainer container w-container">
<div data-delay="4000" data-animation="slide" className="slider w-slider" data-autoplay="false" data-easing="ease"
data-hide-arrows="false" data-disable-swipe="false" data-autoplay-limit="0" data-nav-spacing="3"
data-duration="500" data-infinite="true">
<div
data-delay="4000"
data-animation="slide"
className="slider w-slider"
data-autoplay="true"
data-easing="ease"
data-hide-arrows="false"
data-disable-swipe="false"
data-autoplay-limit="0"
data-nav-spacing="3"
data-duration="500"
data-infinite="true"
>
<div className="mask w-slider-mask">
<div className="slide w-slide">
<div className="w-layout-vflex flex-block-100">
<div className="div-block-35"><img src="/images/imgfb.png" loading="lazy"
sizes="(max-width: 767px) 100vw, (max-width: 991px) 728px, 940px"
srcSet="/images/imgfb-p-500.png 500w, /images/imgfb-p-800.png 800w, /images/imgfb.png 1027w" alt=""
className="image-21" /></div>
<div className="w-layout-vflex flex-block-99">
<h2 className="heading-17">ШИРОКИЙ ВЫБОР АВТОЗАПЧАСТЕЙ</h2>
<div className="text-block-51">Сотрудничаем только с проверенными поставщиками.Постоянно обновляем
ассортимент, чтобы предложить самые лучшие и актуальные детали.</div>
</div>
<div className="w-layout-hflex flex-block-101">
<div className="w-layout-hflex flex-block-102"><img src="/images/1.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Быстрая доставка по всей стране</div>
</div>
<div className="w-layout-hflex flex-block-102"><img src="/images/2.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Высокое качество продукции</div>
</div>
<div className="w-layout-hflex flex-block-102"><img src="/images/3.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Выгодные цены</div>
</div>
<div className="w-layout-hflex flex-block-102"><img src="/images/4.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Профессиональная консультация</div>
</div>
{banners.map((banner) => (
<React.Fragment key={banner.id}>
{renderSlide(banner)}
</React.Fragment>
))}
</div>
{/* Показываем стрелки и навигацию только если баннеров больше одного */}
{banners.length > 1 && (
<>
<div className="left-arrow w-slider-arrow-left">
<div className="div-block-34">
<div className="icon-2 w-icon-slider-left"></div>
</div>
</div>
</div>
<div className="w-slide">
<div className="w-layout-vflex flex-block-100">
<div className="div-block-35"><img src="/images/imgfb.png" loading="lazy"
sizes="(max-width: 767px) 100vw, (max-width: 991px) 728px, 940px"
srcSet="/images/imgfb-p-500.png 500w, /images/imgfb-p-800.png 800w, /images/imgfb.png 1027w" alt=""
className="image-21" /></div>
<div className="w-layout-vflex flex-block-99">
<h2 className="heading-17">УЗКИЙ ВЫБОР АВТОЗАПЧАСТЕЙ</h2>
<div className="text-block-51">Сотрудничаем только с проверенными поставщиками.Постоянно обновляем
ассортимент, чтобы предложить самые лучшие и актуальные детали.</div>
</div>
<div className="w-layout-hflex flex-block-101">
<div className="w-layout-hflex flex-block-102"><img src="/images/1.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Быстрая доставка по всей стране</div>
</div>
<div className="w-layout-hflex flex-block-102"><img src="/images/2.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Высокое качество продукции</div>
</div>
<div className="w-layout-hflex flex-block-102"><img src="/images/3.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Выгодные цены</div>
</div>
<div className="w-layout-hflex flex-block-102"><img src="/images/4.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Профессиональная консультация</div>
</div>
<div className="right-arrow w-slider-arrow-right">
<div className="div-block-34">
<div className="icon-2 w-icon-slider-right"></div>
</div>
</div>
</div>
<div className="w-slide">
<div className="w-layout-vflex flex-block-100">
<div className="div-block-35"><img src="/images/imgfb.png" loading="lazy"
sizes="(max-width: 767px) 100vw, (max-width: 991px) 728px, 940px"
srcSet="/images/imgfb-p-500.png 500w, /images/imgfb-p-800.png 800w, /images/imgfb.png 1027w" alt=""
className="image-21" /></div>
<div className="w-layout-vflex flex-block-99">
<h2 className="heading-17">ЛУЧШИЙ ВЫБОР АВТОЗАПЧАСТЕЙ</h2>
<div className="text-block-51">Сотрудничаем только с проверенными поставщиками.Постоянно обновляем
ассортимент, чтобы предложить самые лучшие и актуальные детали.</div>
</div>
<div className="w-layout-hflex flex-block-101">
<div className="w-layout-hflex flex-block-102"><img src="/images/1.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Быстрая доставка по всей стране</div>
</div>
<div className="w-layout-hflex flex-block-102"><img src="/images/2.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Высокое качество продукции</div>
</div>
<div className="w-layout-hflex flex-block-102"><img src="/images/3.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Выгодные цены</div>
</div>
<div className="w-layout-hflex flex-block-102"><img src="/images/4.png" loading="lazy" alt=""
className="image-20" />
<div className="text-block-52">Профессиональная консультация</div>
</div>
</div>
</div>
</div>
</div>
<div className="left-arrow w-slider-arrow-left">
<div className="div-block-34">
<div className="icon-2 w-icon-slider-left"></div>
</div>
</div>
<div className="right-arrow w-slider-arrow-right">
<div className="div-block-34">
<div className="icon-2 w-icon-slider-right"></div>
</div>
</div>
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round"></div>
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round"></div>
</>
)}
</div>
</div>
</section>

View File

@ -0,0 +1,37 @@
import React from 'react';
import Link from 'next/link';
const IndexTopMenuNav = ({ isIndexPage = false }: { isIndexPage?: boolean }) => (
<section className={`topmenub${!isIndexPage ? ' topmenub-white' : ''}`} style={!isIndexPage ? { background: '#fff' } : undefined}>
<div className="w-layout-blockcontainer tb nav w-container">
<div className="w-layout-hflex flex-block-107">
<Link href="/about" className="link-block-8 w-inline-block">
<div>О компании</div>
</Link>
<Link href="/payments-method" className="link-block-8 w-inline-block">
<div>Оплата и доставка</div>
</Link>
<Link href="/" className="link-block-8 w-inline-block">
<div>Гарантия и возврат</div>
</Link>
<Link href="/payments-method" className="link-block-8 w-inline-block">
<div>Покупателям</div>
</Link>
<Link href="/wholesale" className="link-block-8 w-inline-block">
<div>Оптовым клиентам</div>
</Link>
<Link href="/contacts" className="link-block-8 w-inline-block">
<div>Контакты</div>
</Link>
<a href="#" className="link-block-8 green w-inline-block">
<div>Новые поступления товаров</div>
</a>
<a href="#" className="link-block-8 orange w-inline-block">
<div>Распродажа</div>
</a>
</div>
</div>
</section>
);
export default IndexTopMenuNav;

View File

@ -0,0 +1,215 @@
import React, { useRef } from "react";
import { useQuery } from '@apollo/client';
import ArticleCard from "../ArticleCard";
import CatalogProductCardSkeleton from "../CatalogProductCardSkeleton";
import { GET_NEW_ARRIVALS } from "@/lib/graphql";
import { PartsAPIArticle } from "@/types/partsapi";
const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ
// Интерфейс для товара из GraphQL
interface Product {
id: string;
name: string;
slug: string;
article?: string;
brand?: string;
retailPrice?: number;
wholesalePrice?: number;
createdAt: string;
images: Array<{
id: string;
url: string;
alt?: string;
order: number;
}>;
categories: Array<{
id: string;
name: string;
slug: string;
}>;
}
// Функция для преобразования Product в PartsAPIArticle
const transformProductToArticle = (product: Product, index: number): PartsAPIArticle => {
return {
artId: product.id,
artArticleNr: product.article || `PROD-${product.id}`,
artSupBrand: product.brand || 'Unknown Brand',
supBrand: product.brand || 'Unknown Brand',
supId: index + 1,
productGroup: product.categories?.[0]?.name || product.name,
ptId: index + 1,
};
};
const NewArrivalsSection: React.FC = () => {
const scrollRef = useRef<HTMLDivElement>(null);
// Получаем новые поступления через GraphQL
const { data, loading, error } = useQuery(GET_NEW_ARRIVALS, {
variables: { limit: 8 }
});
const scrollLeft = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' });
}
};
const scrollRight = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' });
}
};
// Преобразуем данные для ArticleCard
const newArrivalsArticles = data?.newArrivals?.map((product: Product, index: number) =>
transformProductToArticle(product, index)
) || [];
// Получаем изображения для товаров
const getProductImage = (product: Product): string => {
if (product.images && product.images.length > 0) {
return product.images[0].url;
}
return "/images/162615.webp"; // fallback изображение
};
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex inbt">
<div className="w-layout-hflex flex-block-31">
<h2 className="heading-4">Новое поступление</h2>
</div>
<div className="carousel-row">
{/* Стили для стрелок как в BestPriceSection и TopSalesSection */}
<style>{`
.carousel-arrow {
width: 40px;
height: 40px;
border: none;
background: none;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity 0.2s;
cursor: pointer;
margin: 0 8px;
}
.carousel-arrow-left {}
.carousel-arrow-right {}
.carousel-arrow .arrow-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.85);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.carousel-arrow:hover .arrow-circle,
.carousel-arrow:focus .arrow-circle {
background: #ec1c24;
}
.carousel-arrow .arrow-svg {
width: 20px;
height: 20px;
display: block;
transition: stroke 0.2s;
stroke: #222;
}
.carousel-arrow:hover .arrow-svg,
.carousel-arrow:focus .arrow-svg {
stroke: #fff;
}
.carousel-row {
display: flex;
align-items: center;
justify-content: flex-start;
}
`}</style>
<button
className="carousel-arrow carousel-arrow-left"
onClick={scrollLeft}
aria-label="Прокрутить влево"
style={{ cursor: 'pointer' }}
>
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
{loading ? (
// Показываем скелетоны во время загрузки
Array(8).fill(0).map((_, index) => (
<CatalogProductCardSkeleton key={`skeleton-${index}`} />
))
) : error ? (
// Показываем сообщение об ошибке
<div className="error-message" style={{
padding: '20px',
textAlign: 'center',
color: '#666',
minWidth: '300px'
}}>
<p>Не удалось загрузить новые поступления</p>
<p style={{ fontSize: '14px', marginTop: '8px' }}>
{error.message}
</p>
</div>
) : newArrivalsArticles.length > 0 ? (
// Показываем товары
newArrivalsArticles.map((article: PartsAPIArticle, index: number) => {
const product = data.newArrivals[index];
const image = getProductImage(product);
return (
<ArticleCard
key={article.artId || `article-${index}`}
article={article}
index={index}
image={image}
/>
);
})
) : (
// Показываем сообщение о том, что товаров нет
<div className="no-products-message" style={{
padding: '20px',
textAlign: 'center',
color: '#666',
minWidth: '300px'
}}>
<p>Пока нет новых поступлений</p>
</div>
)}
</div>
<button
className="carousel-arrow carousel-arrow-right"
onClick={scrollRight}
aria-label="Прокрутить вправо"
style={{ cursor: 'pointer' }}
>
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 10H16.6673M16.6673 10L11.6673 5M16.6673 10L11.6673 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
</div>
</div>
</div>
</section>
);
};
export default NewArrivalsSection;

View File

@ -1,53 +1,135 @@
import React from "react";
import React, { useRef } from "react";
import NewsCard from "@/components/news/NewsCard";
import Link from "next/link";
const NewsAndPromos = () => (
<section>
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex news-index-block">
<div className="w-layout-hflex flex-block-31">
<h2 className="heading-4">Новости и акции</h2>
<div className="w-layout-hflex flex-block-29">
<Link href="/news" className="text-block-18" style={{display: 'flex', alignItems: 'center'}}>
Ко всем новостям
<img src="/images/Arrow_right.svg" loading="lazy" alt="" style={{marginLeft: 8}} />
</Link>
const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ
const NewsAndPromos = () => {
const scrollRef = useRef<HTMLDivElement>(null);
const scrollLeft = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' });
}
};
const scrollRight = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' });
}
};
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex news-index-block">
<div className="w-layout-hflex flex-block-31">
<h2 className="heading-4">Новости и акции</h2>
<div className="w-layout-hflex flex-block-29">
<Link href="/news" className="text-block-18" style={{display: 'flex', alignItems: 'center'}}>
Ко всем новостям
<img src="/images/Arrow_right.svg" loading="lazy" alt="" style={{marginLeft: 8}} />
</Link>
</div>
</div>
<div className="carousel-row">
{/* Стили для стрелок как в других секциях */}
<style>{`
.carousel-arrow {
width: 40px;
height: 40px;
border: none;
background: none;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity 0.2s;
cursor: pointer;
margin: 0 8px;
}
.carousel-arrow-left {}
.carousel-arrow-right {}
.carousel-arrow .arrow-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.85);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.carousel-arrow:hover .arrow-circle,
.carousel-arrow:focus .arrow-circle {
background: #ec1c24;
}
.carousel-arrow .arrow-svg {
width: 20px;
height: 20px;
display: block;
transition: stroke 0.2s;
stroke: #222;
}
.carousel-arrow:hover .arrow-svg,
.carousel-arrow:focus .arrow-svg {
stroke: #fff;
}
.carousel-row {
display: flex;
align-items: center;
justify-content: flex-start;
}
`}</style>
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
<div className="w-layout-hflex flex-block-6-copy-copy carousel-scroll" ref={scrollRef}>
<NewsCard
title="Kia Syros будет выделяться необычным стилем"
description="Компания Kia готова представить новый кроссовер Syros"
category="Новости компании"
date="17.12.2024"
image="/images/news_img.png"
/>
<NewsCard
title="Kia Syros будет выделяться необычным стилем"
description="Компания Kia готова представить новый кроссовер Syros"
category="Новости компании"
date="17.12.2024"
image="/images/news_img.png"
/>
<NewsCard
title="Kia Syros будет выделяться необычным стилем"
description="Компания Kia готова представить новый кроссовер Syros"
category="Новости компании"
date="17.12.2024"
image="/images/news_img.png"
/>
<NewsCard
title="Kia Syros будет выделяться необычным стилем"
description="Компания Kia готова представить новый кроссовер Syros"
category="Новости компании"
date="17.12.2024"
image="/images/news_img.png"
/>
</div>
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 10H16.6673M16.6673 10L11.6673 5M16.6673 10L11.6673 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
</div>
</div>
<div className="w-layout-hflex flex-block-6-copy-copy">
<NewsCard
title="Kia Syros будет выделяться необычным стилем"
description="Компания Kia готова представить новый кроссовер Syros"
category="Новости компании"
date="17.12.2024"
image="/images/news_img.png"
/>
<NewsCard
title="Kia Syros будет выделяться необычным стилем"
description="Компания Kia готова представить новый кроссовер Syros"
category="Новости компании"
date="17.12.2024"
image="/images/news_img.png"
/>
<NewsCard
title="Kia Syros будет выделяться необычным стилем"
description="Компания Kia готова представить новый кроссовер Syros"
category="Новости компании"
date="17.12.2024"
image="/images/news_img.png"
/>
<NewsCard
title="Kia Syros будет выделяться необычным стилем"
description="Компания Kia готова представить новый кроссовер Syros"
category="Новости компании"
date="17.12.2024"
image="/images/news_img.png"
/>
</div>
</div>
</div>
</section>
);
</section>
);
};
export default NewsAndPromos;

View File

@ -0,0 +1,248 @@
import React, { useState, useEffect, useRef } from 'react';
import { useQuery } from '@apollo/client';
import { GET_HERO_BANNERS } from '@/lib/graphql';
import Link from 'next/link';
interface HeroBanner {
id: string;
title: string;
subtitle?: string;
imageUrl: string;
linkUrl?: string;
isActive: boolean;
sortOrder: number;
}
// Добавим CSS для стрелок
const arrowStyles = `
.pod-slider-arrow {
width: 40px;
height: 40px;
border: none;
background: none;
padding: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity 0.2s;
cursor: pointer;
}
.pod-slider-arrow-left { left: 12px; }
.pod-slider-arrow-right { right: 12px; }
.pod-slider-arrow .arrow-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.85);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.pod-slider-arrow:hover .arrow-circle,
.pod-slider-arrow:focus .arrow-circle {
background: #ec1c24;
}
.pod-slider-arrow .arrow-svg {
width: 20px;
height: 20px;
display: block;
transition: stroke 0.2s;
stroke: #222;
}
.pod-slider-arrow:hover .arrow-svg,
.pod-slider-arrow:focus .arrow-svg {
stroke: #fff;
}
`;
const slideStyles = `
.pod-slider-slide {
position: absolute;
top: 0; left: 0;
opacity: 0;
transform: translateX(40px) scale(0.98);
transition: opacity 0.5s cubic-bezier(.4,0,.2,1), transform 0.5s cubic-bezier(.4,0,.2,1);
pointer-events: none;
z-index: 1;
}
.pod-slider-slide.active {
opacity: 1;
transform: translateX(0) scale(1);
pointer-events: auto;
z-index: 2;
}
.pod-slider-slide.prev {
opacity: 0;
transform: translateX(-40px) scale(0.98);
z-index: 1;
}
.pod-slider-slide.next {
opacity: 0;
transform: translateX(40px) scale(0.98);
z-index: 1;
}
.mask.w-slider-mask { position: relative; }
`;
const ProductOfDayBanner: React.FC = () => {
const [currentSlide, setCurrentSlide] = useState(0);
const [showArrows, setShowArrows] = useState(false);
const sliderRef = useRef<HTMLDivElement>(null);
const { data } = useQuery(GET_HERO_BANNERS, { errorPolicy: 'all' });
const banners: HeroBanner[] = data?.heroBanners
?.filter((banner: HeroBanner) => banner.isActive)
?.slice()
?.sort((a: HeroBanner, b: HeroBanner) => a.sortOrder - b.sortOrder) || [];
const allBanners = banners.length > 0 ? banners : [{
id: 'default',
title: 'ДОСТАВИМ БЫСТРО!',
subtitle: 'Дополнительная скидка на товары с местного склада',
imageUrl: '/images/imgfb.png',
linkUrl: '',
isActive: true,
sortOrder: 0
}];
useEffect(() => {
if (allBanners.length > 1) {
const interval = setInterval(() => {
setCurrentSlide(prev => (prev + 1) % allBanners.length);
}, 5000);
return () => clearInterval(interval);
}
}, [allBanners.length]);
useEffect(() => {
if (currentSlide >= allBanners.length) {
setCurrentSlide(0);
}
}, [allBanners.length, currentSlide]);
const handlePrevSlide = () => {
setCurrentSlide(prev => prev === 0 ? allBanners.length - 1 : prev - 1);
};
const handleNextSlide = () => {
setCurrentSlide(prev => (prev + 1) % allBanners.length);
};
const handleSlideIndicator = (index: number) => {
setCurrentSlide(index);
};
// Показывать стрелки при наведении на слайдер или стрелки
const handleMouseEnter = () => setShowArrows(true);
const handleMouseLeave = () => setShowArrows(false);
return (
<div
className="slider w-slider"
ref={sliderRef}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
tabIndex={0}
style={{ position: 'relative' }}
>
{/* Вставляем стили для стрелок */}
<style>{arrowStyles}{slideStyles}</style>
<div className="mask w-slider-mask">
{allBanners.map((banner, idx) => {
let slideClass = 'pod-slider-slide';
if (idx === currentSlide) slideClass += ' active';
else if (idx === (currentSlide === 0 ? allBanners.length - 1 : currentSlide - 1)) slideClass += ' prev';
else if (idx === (currentSlide + 1) % allBanners.length) slideClass += ' next';
const slideContent = (
<div
className="div-block-128"
style={{
backgroundImage: `url(${banner.imageUrl})`,
// backgroundSize: 'cover',
// backgroundPosition: 'center',
// backgroundRepeat: 'no-repeat',
}}
>
{/* Можно добавить текст поверх баннера, если нужно */}
</div>
);
return (
<div
className={slideClass + ' slide w-slide'}
key={banner.id}
// style={{ display: idx === currentSlide ? 'block' : 'none', position: 'relative' }}
style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }}
>
{banner.linkUrl ? (
<Link href={banner.linkUrl} style={{ display: 'block', width: '100%', height: '100%' }}>{slideContent}</Link>
) : slideContent}
</div>
);
})}
</div>
{/* SVG-стрелки как в Webflow, поверх баннера, с hover-эффектом */}
<button
className="pod-slider-arrow pod-slider-arrow-left"
onClick={handlePrevSlide}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={{
opacity: showArrows ? 1 : 0,
pointerEvents: showArrows ? 'auto' : 'none',
}}
tabIndex={-1}
aria-label="Предыдущий баннер"
>
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
<button
className="pod-slider-arrow pod-slider-arrow-right"
onClick={handleNextSlide}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={{
opacity: showArrows ? 1 : 0,
pointerEvents: showArrows ? 'auto' : 'none',
}}
tabIndex={-1}
aria-label="Следующий баннер"
>
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 10H16.6673M16.6673 10L11.6673 5M16.6673 10L11.6673 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
<div className="slide-nav w-slider-nav w-slider-nav-invert w-round">
{allBanners.map((_, idx) => (
<div
key={idx}
className="w-slider-dot"
style={{
background: idx === currentSlide ? 'white' : 'rgba(255,255,255,0.5)',
borderRadius: '50%',
width: 10,
height: 10,
margin: 4,
display: 'inline-block',
cursor: 'pointer'
}}
onClick={() => handleSlideIndicator(idx)}
/>
))}
</div>
</div>
);
};
export default ProductOfDayBanner;

View File

@ -0,0 +1,309 @@
import React, { useState, useEffect } from "react";
import { useQuery } from '@apollo/client';
import { GET_DAILY_PRODUCTS, PARTS_INDEX_SEARCH_BY_ARTICLE } from '@/lib/graphql';
import Link from 'next/link';
import ProductOfDayBanner from './ProductOfDayBanner';
interface DailyProduct {
id: string;
discount?: number;
isActive: boolean;
sortOrder: number;
product: {
id: string;
name: string;
slug: string;
article?: string;
brand?: string;
retailPrice?: number;
wholesalePrice?: number;
images: Array<{
id: string;
url: string;
alt?: string;
order: number;
}>;
};
}
const ProductOfDaySection: React.FC = () => {
// Получаем текущую дату в формате YYYY-MM-DD
const today = new Date().toISOString().split('T')[0];
// Состояние для текущего слайда
const [currentSlide, setCurrentSlide] = useState(0);
const { data, loading, error } = useQuery<{ dailyProducts: DailyProduct[] }>(
GET_DAILY_PRODUCTS,
{
variables: { displayDate: today },
errorPolicy: 'all'
}
);
// Фильтруем только активные товары и сортируем по sortOrder
const activeProducts = React.useMemo(() => {
if (!data?.dailyProducts) return [];
return data.dailyProducts
.filter(item => item.isActive)
.sort((a, b) => a.sortOrder - b.sortOrder);
}, [data]);
// Корректный сброс currentSlide только если индекс вне диапазона
useEffect(() => {
if (currentSlide > activeProducts.length - 1) {
setCurrentSlide(activeProducts.length > 0 ? activeProducts.length - 1 : 0);
}
// Если товаров стало больше и текущий слайд = 0, ничего не делаем
// Если товаров стало меньше и текущий слайд в диапазоне, ничего не делаем
// Если товаров стало меньше и текущий слайд вне диапазона, сбрасываем на последний
}, [activeProducts.length]);
// Получаем данные из PartsIndex для текущего товара
const currentProduct = activeProducts[currentSlide];
const { data: partsIndexData } = useQuery(
PARTS_INDEX_SEARCH_BY_ARTICLE,
{
variables: {
articleNumber: currentProduct?.product?.article || '',
brandName: currentProduct?.product?.brand || '',
lang: 'ru'
},
skip: !currentProduct?.product?.article || !currentProduct?.product?.brand,
errorPolicy: 'ignore'
}
);
// Функция для расчета цены со скидкой
const calculateDiscountedPrice = (price: number, discount?: number) => {
if (!discount) return price;
return price * (1 - discount / 100);
};
// Функция для форматирования цены
const formatPrice = (price: number) => {
return new Intl.NumberFormat('ru-RU').format(Math.round(price));
};
// Функция для получения изображения товара
const getProductImage = (product: DailyProduct['product']) => {
// Сначала пытаемся использовать собственные изображения товара
const productImage = product.images
?.sort((a, b) => a.order - b.order)
?.[0];
if (productImage) {
return {
url: productImage.url,
alt: productImage.alt || product.name,
source: 'internal'
};
}
// Если нет собственных изображений, используем PartsIndex
const partsIndexImage = partsIndexData?.partsIndexSearchByArticle?.images?.[0];
if (partsIndexImage) {
return {
url: partsIndexImage,
alt: product.name,
source: 'partsindex'
};
}
// Если нет ни одной картинки, возвращаем noimage.png
return {
url: '/images/noimage.png',
alt: product.name,
source: 'noimage'
};
};
// Обработчики для навигации по товарам дня
const handlePrevSlide = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
setCurrentSlide(prev => prev === 0 ? activeProducts.length - 1 : prev - 1);
};
const handleNextSlide = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
setCurrentSlide(prev => prev === activeProducts.length - 1 ? 0 : prev + 1);
};
const handlePrevSlideTouch = (e: React.TouchEvent) => {
e.preventDefault();
e.stopPropagation();
setCurrentSlide(prev => prev === 0 ? activeProducts.length - 1 : prev - 1);
};
const handleNextSlideTouch = (e: React.TouchEvent) => {
e.preventDefault();
e.stopPropagation();
setCurrentSlide(prev => prev === activeProducts.length - 1 ? 0 : prev + 1);
};
const handleSlideIndicator = (index: number) => {
setCurrentSlide(index);
};
// Если нет активных товаров дня, не показываем секцию
if (loading || error || activeProducts.length === 0) {
return null;
}
const product = currentProduct.product;
const productImage = getProductImage(product);
const originalPrice = product.retailPrice || product.wholesalePrice || 0;
const discountedPrice = calculateDiscountedPrice(originalPrice, currentProduct.discount);
const hasDiscount = currentProduct.discount && currentProduct.discount > 0;
return (
<section className="main">
<div className="w-layout-blockcontainer batd w-container">
<div className="w-layout-hflex flex-block-108">
<ProductOfDayBanner />
<div className="div-block-129">
<div className="w-layout-hflex flex-block-109">
<h1 className="heading-18">ТОВАРЫ ДНЯ</h1>
{hasDiscount && (
<div className="saletag">-{currentProduct.discount}%</div>
)}
</div>
<div className="w-layout-hflex flex-block-110">
<div className="w-layout-vflex flex-block-111">
<div className="w-layout-hflex flex-block-16">
<div className="text-block-8">
от {formatPrice(discountedPrice)}
</div>
{hasDiscount && (
<div className="text-block-9">
{formatPrice(originalPrice)}
</div>
)}
</div>
<div className="text-block-10" title={product.name}>
{product.brand && `${product.brand} `}
{product.name}
</div>
{/* Счетчик товаров если их больше одного */}
{/* {activeProducts.length > 1 && (
<div className="text-xs text-gray-500 mt-2">
{currentSlide + 1} из {activeProducts.length}
</div>
)} */}
</div>
{productImage && (
<div className="">
<img
width="Auto"
height="Auto"
alt={productImage.alt}
src={productImage.url}
loading="lazy"
className="image-5-copy"
style={{ cursor: 'pointer' }}
/>
{/* Метка источника изображения */}
{productImage.source === 'partsindex' && (
<div className="absolute bottom-0 right-0 bg-blue-600 text-white text-xs px-2 py-1 rounded-tl">
Parts Index
</div>
)}
{productImage.source === 'noimage' && (
<div className="absolute bottom-0 right-0 bg-gray-400 text-white text-xs px-2 py-1 rounded-tl">
Нет изображения
</div>
)}
</div>
)}
</div>
<div className="w-layout-hflex flex-block-125">
{/* Левая стрелка - предыдущий товар */}
{activeProducts.length > 1 ? (
<div
className="div-block-134"
onClick={handlePrevSlide}
onMouseDown={(e) => e.preventDefault()}
onTouchStart={handlePrevSlideTouch}
style={{ cursor: 'pointer' }}
title="Предыдущий товар"
>
<div className="code-embed-17 w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
</svg>
</div>
</div>
) : (
<div className="div-block-134" style={{ opacity: 0.3 }}>
<div className="code-embed-17 w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
</svg>
</div>
</div>
)}
{/* Правая стрелка - следующий товар */}
{activeProducts.length > 1 ? (
<div
className="div-block-134-copy"
onClick={handleNextSlide}
onMouseDown={(e) => e.preventDefault()}
onTouchStart={handleNextSlideTouch}
style={{ cursor: 'pointer' }}
title="Следующий товар"
>
<div className="code-embed-17 w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
</svg>
</div>
</div>
) : (
<div className="div-block-134-copy" style={{ opacity: 0.3 }}>
<div className="code-embed-17 w-embed">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" stroke="currentcolor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
</svg>
</div>
</div>
)}
{/* Индикаторы точки */}
<div className="w-layout-hflex flex-block-126">
{activeProducts.length > 1 ? (
activeProducts.map((_, index) => (
<div
key={index}
className="div-block-135"
onClick={() => handleSlideIndicator(index)}
style={{
cursor: 'pointer',
opacity: index === currentSlide ? 1 : 0.5,
backgroundColor: index === currentSlide ? 'currentColor' : 'rgba(128,128,128,0.5)'
}}
title={`Товар ${index + 1}`}
/>
))
) : (
<>
<div className="div-block-135" style={{ backgroundColor: 'currentColor' }}></div>
</>
)}
</div>
</div>
</div>
</div>
</div>
</section>
);
};
export default ProductOfDaySection;

View File

@ -0,0 +1,21 @@
import React from "react";
const PromoImagesSection: React.FC = () => (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-hflex flex-block-123">
<div className="div-block-132">
<img src="images/Group-602.png" loading="lazy" alt="Промо 1" />
</div>
<div className="div-block-132">
<img src="images/Group-603.png" loading="lazy" alt="Промо 2" />
</div>
<div className="div-block-132">
<img src="images/Group-604.png" loading="lazy" alt="Промо 3" />
</div>
</div>
</div>
</section>
);
export default PromoImagesSection;

View File

@ -0,0 +1,25 @@
import React from "react";
const SupportVinSection: React.FC = () => (
<section className="main">
<div className="w-layout-blockcontainer container-copy w-container">
<img
src="images/support_img.png"
loading="lazy"
alt=""
className="image-27"
/>
<div className="div-block-11">
<div className="w-layout-vflex flex-block-30">
<h3 className="supportheading">МЫ ВСЕГДА РАДЫ ПОМОЧЬ</h3>
<div className="text-block-19">
Если вам нужна помощь с подбором автозапчастей, то воспользуйтесь формой VIN-запроса. Введите идентификационный номер (VIN) вашего автомобиля и мы найдём нужную деталь.
</div>
</div>
<a href="#" className="submit-button-copy w-button">Отправить VIN-запрос</a>
</div>
</div>
</section>
);
export default SupportVinSection;

View File

@ -0,0 +1,247 @@
import React, { useRef } from "react";
import { useQuery } from "@apollo/client";
import TopSalesItem from "../TopSalesItem";
import { GET_TOP_SALES_PRODUCTS } from "../../lib/graphql";
import { useCart } from "@/contexts/CartContext";
interface TopSalesProductData {
id: string;
productId: string;
isActive: boolean;
sortOrder: number;
product: {
id: string;
name: string;
article?: string;
brand?: string;
retailPrice?: number;
images: { url: string; alt?: string }[];
};
}
const SCROLL_AMOUNT = 340; // px, ширина одной карточки + отступ
const TopSalesSection: React.FC = () => {
const { data, loading, error } = useQuery(GET_TOP_SALES_PRODUCTS);
const { isInCart: isItemInCart } = useCart();
const scrollRef = useRef<HTMLDivElement>(null);
const scrollLeft = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({ left: -SCROLL_AMOUNT, behavior: 'smooth' });
}
};
const scrollRight = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({ left: SCROLL_AMOUNT, behavior: 'smooth' });
}
};
if (loading) {
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex inbt">
<div className="w-layout-hflex flex-block-31">
<h2 className="heading-4">Топ продаж</h2>
</div>
<div className="carousel-row">
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
<div className="text-block-58">Загрузка...</div>
</div>
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</div>
</div>
</div>
</section>
);
}
if (error) {
console.error('Ошибка загрузки топ продаж:', error);
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex inbt">
<div className="w-layout-hflex flex-block-31">
<h2 className="heading-4">Топ продаж</h2>
</div>
<div className="carousel-row">
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
<div className="text-block-58">Ошибка загрузки</div>
</div>
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</div>
</div>
</div>
</section>
);
}
// Фильтруем активные товары и сортируем по sortOrder
const activeTopSalesProducts = (data?.topSalesProducts || [])
.filter((item: TopSalesProductData) => item.isActive)
.sort((a: TopSalesProductData, b: TopSalesProductData) => a.sortOrder - b.sortOrder)
.slice(0, 8); // Ограничиваем до 8 товаров
if (activeTopSalesProducts.length === 0) {
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex inbt">
<div className="w-layout-hflex flex-block-31">
<h2 className="heading-4">Топ продаж</h2>
</div>
<div className="carousel-row">
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
<path d="M19.5 24L12.5 16L19.5 8" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
<div className="text-block-58">Нет товаров в топ продаж</div>
</div>
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="16" cy="16" r="16" fill="#F3F4F6"/>
<path d="M12.5 8L19.5 16L12.5 24" stroke="#222" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
</div>
</div>
</div>
</section>
);
}
return (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex inbt">
<div className="w-layout-hflex flex-block-31">
<h2 className="heading-4">Топ продаж</h2>
</div>
<div className="carousel-row">
{/* Стили для стрелок как в BestPriceSection */}
<style>{`
.carousel-arrow {
width: 40px;
height: 40px;
border: none;
background: none;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
opacity: 1;
transition: opacity 0.2s;
cursor: pointer;
margin: 0 8px;
}
.carousel-arrow-left {}
.carousel-arrow-right {}
.carousel-arrow .arrow-circle {
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.85);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.carousel-arrow:hover .arrow-circle,
.carousel-arrow:focus .arrow-circle {
background: #ec1c24;
}
.carousel-arrow .arrow-svg {
width: 20px;
height: 20px;
display: block;
transition: stroke 0.2s;
stroke: #222;
}
.carousel-arrow:hover .arrow-svg,
.carousel-arrow:focus .arrow-svg {
stroke: #fff;
}
.carousel-row {
display: flex;
align-items: center;
justify-content: flex-start;
}
`}</style>
<button className="carousel-arrow carousel-arrow-left" onClick={scrollLeft} aria-label="Прокрутить влево">
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6673 10H3.33398M3.33398 10L8.33398 5M3.33398 10L8.33398 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
<div className="w-layout-hflex core-product-search carousel-scroll" ref={scrollRef}>
{activeTopSalesProducts.map((item: TopSalesProductData) => {
const product = item.product;
const price = product.retailPrice
? `от ${product.retailPrice.toLocaleString('ru-RU')}`
: 'По запросу';
const image = product.images && product.images.length > 0
? product.images[0].url
: '/images/162615.webp'; // Fallback изображение
const title = product.name;
const brand = product.brand || 'Неизвестный бренд';
const isInCart = isItemInCart(product.id, undefined, product.article, brand);
return (
<TopSalesItem
key={item.id}
image={image}
price={price}
title={title}
brand={brand}
article={product.article}
productId={product.id}
isInCart={isInCart}
/>
);
})}
</div>
<button className="carousel-arrow carousel-arrow-right" onClick={scrollRight} aria-label="Прокрутить вправо">
<span className="arrow-circle">
<svg className="arrow-svg" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.33398 10H16.6673M16.6673 10L11.6673 5M16.6673 10L11.6673 15" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
</button>
</div>
</div>
</div>
</section>
);
};
export default TopSalesSection;

View File

@ -0,0 +1,243 @@
import React, { useState, useEffect } from 'react';
import {
getCookiePreferences,
CookiePreferences,
initializeAnalytics,
initializeMarketing,
resetCookieConsent
} from '@/lib/cookie-utils';
const CookieSettings: React.FC = () => {
const [preferences, setPreferences] = useState<CookiePreferences>({
necessary: true,
analytics: false,
marketing: false,
functional: false,
});
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [saveMessage, setSaveMessage] = useState<string | null>(null);
useEffect(() => {
// Загружаем текущие настройки
const currentPreferences = getCookiePreferences();
if (currentPreferences) {
setPreferences(currentPreferences);
}
setIsLoading(false);
}, []);
const togglePreference = (key: keyof CookiePreferences) => {
if (key === 'necessary') return; // Необходимые cookies нельзя отключить
setPreferences(prev => ({
...prev,
[key]: !prev[key]
}));
};
const handleSave = async () => {
setIsSaving(true);
setSaveMessage(null);
try {
// Сохраняем настройки
localStorage.setItem('cookieConsent', 'configured');
localStorage.setItem('cookiePreferences', JSON.stringify(preferences));
// Инициализируем сервисы согласно настройкам
if (preferences.analytics) {
initializeAnalytics();
}
if (preferences.marketing) {
initializeMarketing();
}
setSaveMessage('Настройки успешно сохранены');
// Убираем сообщение через 3 секунды
setTimeout(() => {
setSaveMessage(null);
}, 3000);
} catch (error) {
setSaveMessage('Ошибка при сохранении настроек');
} finally {
setIsSaving(false);
}
};
const handleReset = () => {
resetCookieConsent();
setPreferences({
necessary: true,
analytics: false,
marketing: false,
functional: false,
});
setSaveMessage('Настройки сброшены. Перезагрузите страницу для повторного отображения уведомления о cookies.');
};
if (isLoading) {
return (
<div className="flex justify-center items-center p-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-red-600"></div>
</div>
);
}
return (
<div className="bg-white rounded-2xl p-8 max-md:px-5">
<div className="mb-8">
<h2 className="text-2xl font-bold text-gray-950 mb-2">
Настройки файлов cookie
</h2>
<p className="text-gray-600">
Управляйте тем, как мы используем файлы cookie на нашем сайте.
</p>
</div>
{saveMessage && (
<div className={`mb-6 p-4 rounded-lg ${
saveMessage.includes('Ошибка')
? 'bg-red-50 border border-red-200 text-red-800'
: 'bg-green-50 border border-green-200 text-green-800'
}`}>
{saveMessage}
</div>
)}
<div className="space-y-6">
{/* Необходимые cookies */}
<div className="flex items-start justify-between p-6 bg-gray-50 rounded-lg">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<h3 className="font-semibold text-gray-950">Необходимые cookies</h3>
<span className="text-xs px-2 py-1 bg-gray-200 text-gray-600 rounded">
Обязательные
</span>
</div>
<p className="text-sm text-gray-600 mb-3">
Эти файлы cookie необходимы для работы сайта и не могут быть отключены.
Они обеспечивают базовую функциональность, включая корзину покупок, авторизацию и безопасность.
</p>
<div className="text-xs text-gray-500">
Включает: сессии, корзина, авторизация, безопасность
</div>
</div>
<div className="flex-shrink-0 ml-6">
<div className="w-12 h-6 bg-red-600 rounded-full flex items-center justify-end px-1">
<div className="w-4 h-4 bg-white rounded-full"></div>
</div>
</div>
</div>
{/* Аналитические cookies */}
<div className="flex items-start justify-between p-6 bg-gray-50 rounded-lg">
<div className="flex-1">
<h3 className="font-semibold text-gray-950 mb-2">Аналитические cookies</h3>
<p className="text-sm text-gray-600 mb-3">
Помогают нам понять, как посетители взаимодействуют с сайтом, чтобы улучшить его работу и пользовательский опыт.
</p>
<div className="text-xs text-gray-500">
Включает: Google Analytics, статистика посещений, анализ поведения
</div>
</div>
<div className="flex-shrink-0 ml-6">
<button
onClick={() => togglePreference('analytics')}
className={`w-12 h-6 rounded-full flex items-center transition-colors duration-200 ${
preferences.analytics ? 'bg-red-600 justify-end' : 'bg-gray-300 justify-start'
} px-1`}
>
<div className="w-4 h-4 bg-white rounded-full"></div>
</button>
</div>
</div>
{/* Маркетинговые cookies */}
<div className="flex items-start justify-between p-6 bg-gray-50 rounded-lg">
<div className="flex-1">
<h3 className="font-semibold text-gray-950 mb-2">Маркетинговые cookies</h3>
<p className="text-sm text-gray-600 mb-3">
Используются для отслеживания посетителей и показа релевантной рекламы.
Помогают измерить эффективность рекламных кампаний.
</p>
<div className="text-xs text-gray-500">
Включает: рекламные пиксели, ретаргетинг, социальные сети
</div>
</div>
<div className="flex-shrink-0 ml-6">
<button
onClick={() => togglePreference('marketing')}
className={`w-12 h-6 rounded-full flex items-center transition-colors duration-200 ${
preferences.marketing ? 'bg-red-600 justify-end' : 'bg-gray-300 justify-start'
} px-1`}
>
<div className="w-4 h-4 bg-white rounded-full"></div>
</button>
</div>
</div>
{/* Функциональные cookies */}
<div className="flex items-start justify-between p-6 bg-gray-50 rounded-lg">
<div className="flex-1">
<h3 className="font-semibold text-gray-950 mb-2">Функциональные cookies</h3>
<p className="text-sm text-gray-600 mb-3">
Обеспечивают расширенную функциональность и персонализацию сайта,
включая предпочтения и настройки пользователя.
</p>
<div className="text-xs text-gray-500">
Включает: языковые настройки, персонализация, чат-боты
</div>
</div>
<div className="flex-shrink-0 ml-6">
<button
onClick={() => togglePreference('functional')}
className={`w-12 h-6 rounded-full flex items-center transition-colors duration-200 ${
preferences.functional ? 'bg-red-600 justify-end' : 'bg-gray-300 justify-start'
} px-1`}
>
<div className="w-4 h-4 bg-white rounded-full"></div>
</button>
</div>
</div>
</div>
{/* Кнопки действий */}
<div className="flex flex-col sm:flex-row gap-4 mt-8 pt-6 border-t border-gray-200">
<button
onClick={handleSave}
disabled={isSaving}
className="px-6 py-3 text-sm font-medium text-white bg-red-600 hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg transition-colors duration-200 flex-1 sm:flex-initial min-w-[140px]"
>
{isSaving ? 'Сохранение...' : 'Сохранить настройки'}
</button>
<button
onClick={handleReset}
className="px-6 py-3 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors duration-200 flex-1 sm:flex-initial min-w-[140px]"
>
Сбросить настройки
</button>
</div>
{/* Дополнительная информация */}
<div className="mt-8 p-4 bg-blue-50 border border-blue-200 rounded-lg">
<div className="flex items-start gap-3">
<div className="flex-shrink-0 mt-1">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" className="text-blue-600">
<path d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm1 15h-2v-2h2v2zm0-4h-2V5h2v6z" fill="currentColor"/>
</svg>
</div>
<div>
<h4 className="font-medium text-blue-900 mb-1">Информация о cookies</h4>
<p className="text-sm text-blue-800">
Изменения настроек cookies вступают в силу немедленно. Некоторые функции сайта могут работать некорректно при отключении определенных типов cookies.
</p>
</div>
</div>
</div>
</div>
);
};
export default CookieSettings;

View File

@ -112,6 +112,19 @@ const LegalEntityFormBlock: React.FC<LegalEntityFormBlockProps> = ({
onAdd,
onCancel,
}) => {
// Состояния для отображения ошибок валидации
const [validationErrors, setValidationErrors] = React.useState({
inn: false,
shortName: false,
jurAddress: false,
form: false,
taxSystem: false,
});
// Функция для очистки ошибки при изменении поля
const clearError = (field: keyof typeof validationErrors) => {
setValidationErrors(prev => ({ ...prev, [field]: false }));
};
const [createLegalEntity, { loading: createLoading }] = useMutation(CREATE_CLIENT_LEGAL_ENTITY, {
onCompleted: () => {
console.log('Юридическое лицо создано');
@ -137,29 +150,27 @@ const LegalEntityFormBlock: React.FC<LegalEntityFormBlockProps> = ({
const loading = createLoading || updateLoading;
const handleSave = async () => {
// Валидация
if (!inn || inn.length < 10) {
alert('Введите корректный ИНН');
return;
}
// Сброс предыдущих ошибок
setValidationErrors({
inn: false,
shortName: false,
jurAddress: false,
form: false,
taxSystem: false,
});
if (!shortName.trim()) {
alert('Введите краткое наименование');
return;
}
// Валидация с установкой ошибок
const errors = {
inn: !inn || inn.length < 10,
shortName: !shortName.trim(),
jurAddress: !jurAddress.trim(),
form: form === 'Выбрать',
taxSystem: taxSystem === 'Выбрать',
};
if (!jurAddress.trim()) {
alert('Введите юридический адрес');
return;
}
if (form === 'Выбрать') {
alert('Выберите форму организации');
return;
}
if (taxSystem === 'Выбрать') {
alert('Выберите систему налогообложения');
// Если есть ошибки, устанавливаем их и прерываем выполнение
if (Object.values(errors).some(error => error)) {
setValidationErrors(errors);
return;
}
@ -238,13 +249,18 @@ const LegalEntityFormBlock: React.FC<LegalEntityFormBlockProps> = ({
<div className="flex flex-wrap gap-5 items-start w-full whitespace-nowrap max-md:max-w-full">
<div className="flex flex-col flex-1 shrink basis-0 min-w-[240px]">
<div className="text-gray-950">ИНН</div>
<div className="gap-2.5 self-stretch px-6 py-3.5 mt-1.5 w-full bg-white rounded border border-solid border-stone-300 min-h-[46px] max-md:px-5">
<div className={`gap-2.5 self-stretch px-6 py-3.5 mt-1.5 w-full bg-white rounded border border-solid min-h-[46px] max-md:px-5 ${
validationErrors.inn ? 'border-red-500' : 'border-stone-300'
}`}>
<input
type="text"
placeholder="ИНН"
className="w-full bg-transparent outline-none text-gray-600"
value={inn}
onChange={e => setInn(e.target.value)}
onChange={e => {
setInn(e.target.value);
clearError('inn');
}}
/>
</div>
</div>
@ -252,7 +268,9 @@ const LegalEntityFormBlock: React.FC<LegalEntityFormBlockProps> = ({
<div className="text-gray-950">Форма</div>
<div className="relative mt-1.5">
<div
className="flex gap-10 justify-between items-center px-6 py-3.5 w-full bg-white rounded border border-solid border-stone-300 min-h-[46px] text-neutral-500 max-md:px-5 cursor-pointer select-none"
className={`flex gap-10 justify-between items-center px-6 py-3.5 w-full bg-white rounded border border-solid min-h-[46px] text-neutral-500 max-md:px-5 cursor-pointer select-none ${
validationErrors.form ? 'border-red-500' : 'border-stone-300'
}`}
onClick={() => setIsFormOpen((prev: boolean) => !prev)}
tabIndex={0}
onBlur={() => setIsFormOpen(false)}
@ -266,7 +284,11 @@ const LegalEntityFormBlock: React.FC<LegalEntityFormBlockProps> = ({
<li
key={option}
className={`px-6 py-3.5 cursor-pointer hover:bg-blue-100 ${option === form ? 'bg-blue-50 font-semibold' : ''}`}
onMouseDown={() => { setForm(option); setIsFormOpen(false); }}
onMouseDown={() => {
setForm(option);
setIsFormOpen(false);
clearError('form');
}}
>
{option}
</li>
@ -303,25 +325,35 @@ const LegalEntityFormBlock: React.FC<LegalEntityFormBlockProps> = ({
<div className="flex flex-wrap gap-5 items-start mt-5 w-full max-md:max-w-full">
<div className="flex flex-col flex-1 shrink basis-0 min-w-[240px]">
<div className="text-gray-950">Юридический адрес</div>
<div className="gap-2.5 self-stretch px-6 py-3.5 mt-1.5 w-full bg-white rounded border border-solid border-stone-300 min-h-[46px] text-neutral-500 max-md:px-5">
<div className={`gap-2.5 self-stretch px-6 py-3.5 mt-1.5 w-full bg-white rounded border border-solid min-h-[46px] text-neutral-500 max-md:px-5 ${
validationErrors.jurAddress ? 'border-red-500' : 'border-stone-300'
}`}>
<input
type="text"
placeholder="Юридический адрес"
className="w-full bg-transparent outline-none text-neutral-500"
value={jurAddress}
onChange={e => setJurAddress(e.target.value)}
onChange={e => {
setJurAddress(e.target.value);
clearError('jurAddress');
}}
/>
</div>
</div>
<div className="flex flex-col flex-1 shrink basis-0 min-w-[240px]">
<div className="text-gray-950">Краткое наименование</div>
<div className="gap-2.5 self-stretch px-6 py-3.5 mt-1.5 w-full bg-white rounded border border-solid border-stone-300 min-h-[46px] text-neutral-500 max-md:px-5">
<div className={`gap-2.5 self-stretch px-6 py-3.5 mt-1.5 w-full bg-white rounded border border-solid min-h-[46px] text-neutral-500 max-md:px-5 ${
validationErrors.shortName ? 'border-red-500' : 'border-stone-300'
}`}>
<input
type="text"
placeholder="Краткое наименование"
className="w-full bg-transparent outline-none text-neutral-500"
value={shortName}
onChange={e => setShortName(e.target.value)}
onChange={e => {
setShortName(e.target.value);
clearError('shortName');
}}
/>
</div>
</div>
@ -355,7 +387,9 @@ const LegalEntityFormBlock: React.FC<LegalEntityFormBlockProps> = ({
<div className="text-gray-950">Система налогоблажения</div>
<div className="relative mt-1.5">
<div
className="flex gap-10 justify-between items-center px-6 py-3.5 w-full whitespace-nowrap bg-white rounded border border-solid border-stone-300 min-h-[46px] text-neutral-500 max-md:px-5 cursor-pointer select-none"
className={`flex gap-10 justify-between items-center px-6 py-3.5 w-full whitespace-nowrap bg-white rounded border border-solid min-h-[46px] text-neutral-500 max-md:px-5 cursor-pointer select-none ${
validationErrors.taxSystem ? 'border-red-500' : 'border-stone-300'
}`}
onClick={() => setIsTaxSystemOpen((prev: boolean) => !prev)}
tabIndex={0}
onBlur={() => setIsTaxSystemOpen(false)}
@ -369,7 +403,11 @@ const LegalEntityFormBlock: React.FC<LegalEntityFormBlockProps> = ({
<li
key={option}
className={`px-6 py-3.5 cursor-pointer hover:bg-blue-100 ${option === taxSystem ? 'bg-blue-50 font-semibold' : ''}`}
onMouseDown={() => { setTaxSystem(option); setIsTaxSystemOpen(false); }}
onMouseDown={() => {
setTaxSystem(option);
setIsTaxSystemOpen(false);
clearError('taxSystem');
}}
>
{option}
</li>

View File

@ -92,7 +92,7 @@ const LegalEntityListBlock: React.FC<LegalEntityListBlockProps> = ({ legalEntiti
<div
key={entity.id}
layer-name="legal"
className="flex relative flex-col gap-8 items-start self-stretch px-5 py-3 rounded-lg bg-slate-50 max-sm:px-4 max-sm:py-2.5"
className="flex relative flex-col gap-8 items-start self-stretch px-5 py-3 rounded-lg bg-slate-50 max-sm:px-4 max-sm:py-2.5 hover:bg-slate-200 transition-colors cursor-pointer"
>
<div className="flex relative justify-between items-center self-stretch max-sm:flex-col max-sm:gap-4 max-sm:items-start">
<div className="flex relative gap-5 items-center max-md:flex-wrap max-md:gap-4 max-sm:flex-col max-sm:gap-2.5 max-sm:items-start">

View File

@ -78,14 +78,14 @@ const ProfileActsMain = () => {
<div
key={tab}
layer-name="Tabs_button"
className={`flex relative gap-5 items-center self-stretch rounded-xl flex-[1_0_0] min-w-[200px] max-md:gap-4 max-md:w-full max-md:min-w-[unset] max-sm:gap-2.5 ${activeTab === tab ? "" : "bg-slate-200"}`}
className={`flex relative gap-5 items-center self-stretch rounded-xl flex-[1_0_0] min-w-[200px] text-[14px] max-md:gap-4 max-md:w-full max-md:min-w-[unset] max-sm:gap-2.5 ${activeTab === tab ? "" : "bg-slate-200 hover:bg-slate-200"}`}
onClick={() => setActiveTab(tab)}
style={{ cursor: 'pointer' }}
>
<div className={`flex relative gap-5 justify-center items-center px-6 py-3.5 rounded-xl flex-[1_0_0] ${activeTab === tab ? "bg-red-600" : "bg-slate-200"}`}>
<div
layer-name="Курьером"
className={`relative text-lg font-medium leading-5 text-center max-sm:text-base ${activeTab === tab ? "text-white" : "text-gray-950"}`}
className={`relative font-medium leading-5 text-center text-[14px] max-sm:text-base ${activeTab === tab ? "text-white" : "text-gray-950"}`}
>
{tab}
</div>

View File

@ -179,7 +179,7 @@ const ProfileGarageMain = () => {
{!vehiclesLoading && filteredVehicles.map((vehicle) => (
<div key={vehicle.id} className="mt-8">
<div className="flex flex-col justify-center px-5 py-3 w-full rounded-lg bg-slate-50 max-md:max-w-full">
<div className="flex flex-col justify-center px-5 py-3 w-full rounded-lg bg-slate-50 max-md:max-w-full hover:bg-slate-200 transition-colors cursor-pointer">
<div className="flex flex-wrap gap-8 items-center w-full max-md:max-w-full">
<div className="flex gap-8 items-center self-stretch my-auto min-w-[240px] max-md:flex-col max-md:min-w-0 max-md:gap-2">
<div className="self-stretch my-auto text-xl font-bold leading-none text-gray-950">
@ -247,63 +247,65 @@ const ProfileGarageMain = () => {
</button>
</div>
</div>
</div>
{/* Расширенная информация об автомобиле */}
{expandedVehicle === vehicle.id && (
<div className="mt-4 px-5 py-4 bg-white rounded-lg border border-gray-200">
{/* Расширенная информация об автомобиле — вложена внутрь карточки */}
<div
className={
`overflow-hidden transition-all duration-300 rounded-lg flex flex-col gap-4` +
(expandedVehicle === vehicle.id ? ' py-4 max-h-[1000px] opacity-100 mt-4' : ' max-h-0 opacity-0 pointer-events-none')
}
>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 text-sm">
{vehicle.brand && (
<div>
<span className="font-medium text-gray-700">Бренд:</span>
<span className="ml-2 text-gray-900">{vehicle.brand}</span>
<div className="font-bold text-gray-950">Бренд</div>
<div className="mt-1.5 text-gray-600">{vehicle.brand}</div>
</div>
)}
{vehicle.model && (
<div>
<span className="font-medium text-gray-700">Модель:</span>
<span className="ml-2 text-gray-900">{vehicle.model}</span>
<div className="font-bold text-gray-950">Модель</div>
<div className="mt-1.5 text-gray-600">{vehicle.model}</div>
</div>
)}
{vehicle.modification && (
<div>
<span className="font-medium text-gray-700">Модификация:</span>
<span className="ml-2 text-gray-900">{vehicle.modification}</span>
<div className="font-bold text-gray-950">Модификация</div>
<div className="mt-1.5 text-gray-600">{vehicle.modification}</div>
</div>
)}
{vehicle.year && (
<div>
<span className="font-medium text-gray-700">Год:</span>
<span className="ml-2 text-gray-900">{vehicle.year}</span>
<div className="font-bold text-gray-950">Год</div>
<div className="mt-1.5 text-gray-600">{vehicle.year}</div>
</div>
)}
{vehicle.frame && (
<div>
<span className="font-medium text-gray-700">Номер кузова:</span>
<span className="ml-2 text-gray-900">{vehicle.frame}</span>
<div className="font-bold text-gray-950">Номер кузова</div>
<div className="mt-1.5 text-gray-600">{vehicle.frame}</div>
</div>
)}
{vehicle.licensePlate && (
<div>
<span className="font-medium text-gray-700">Госномер:</span>
<span className="ml-2 text-gray-900">{vehicle.licensePlate}</span>
<div className="font-bold text-gray-950">Госномер</div>
<div className="mt-1.5 text-gray-600">{vehicle.licensePlate}</div>
</div>
)}
{vehicle.mileage && (
<div>
<span className="font-medium text-gray-700">Пробег:</span>
<span className="ml-2 text-gray-900">{vehicle.mileage.toLocaleString()} км</span>
<div className="font-bold text-gray-950">Пробег</div>
<div className="mt-1.5 text-gray-600">{vehicle.mileage.toLocaleString()} км</div>
</div>
)}
<div>
<span className="font-medium text-gray-700">Добавлен:</span>
<span className="ml-2 text-gray-900">
<div className="font-bold text-gray-950">Добавлен</div>
<div className="mt-1.5 text-gray-600">
{new Date(vehicle.createdAt).toLocaleDateString('ru-RU')}
</span>
</div>
</div>
</div>
</div>
)}
</div>
</div>
))}
{!showAddCar && (
@ -400,10 +402,10 @@ const ProfileGarageMain = () => {
{!historyLoading && searchHistory.length > 0 && (
<div className="flex flex-col mt-8 w-full max-md:max-w-full">
{searchHistory.map((historyItem) => (
<div key={historyItem.id} className="flex flex-col justify-center px-5 py-3 mb-2.5 w-full rounded-lg bg-slate-50 min-h-[44px] max-md:max-w-full">
<div key={historyItem.id} className="flex flex-col justify-center px-5 py-3 mb-2.5 w-full rounded-lg bg-slate-50 min-h-[44px] max-md:max-w-full hover:bg-slate-200 transition-colors cursor-pointer">
<div className="flex flex-wrap gap-10 justify-between items-center w-full max-md:max-w-full">
<div className="flex gap-8 items-center self-stretch my-auto min-w-[240px] max-md:flex-col max-md:min-w-0 max-md:gap-2">
<div className="self-stretch my-auto text-lg font-bold leading-none text-gray-950">
<div className="self-stretch my-auto text-lg font-bold leading-none text-gray-950 w-[300px]">
{historyItem.brand && historyItem.model
? `${historyItem.brand} ${historyItem.model}`
: 'Автомобиль найден'}

View File

@ -1,4 +1,5 @@
import React from "react";
import { useRouter } from "next/router";
interface VehicleInfo {
brand?: string;
@ -15,6 +16,10 @@ interface ProfileHistoryItemProps {
vehicleInfo?: VehicleInfo;
resultCount?: number;
onDelete?: (id: string) => void;
// Добавляем новые пропсы для поиска
searchType?: 'TEXT' | 'ARTICLE' | 'OEM' | 'VIN' | 'PLATE' | 'WIZARD' | 'PART_VEHICLES';
articleNumber?: string;
brand?: string;
}
const ProfileHistoryItem: React.FC<ProfileHistoryItemProps> = ({
@ -26,7 +31,12 @@ const ProfileHistoryItem: React.FC<ProfileHistoryItemProps> = ({
vehicleInfo,
resultCount,
onDelete,
searchType,
articleNumber,
brand,
}) => {
const router = useRouter();
const handleDeleteClick = (e: React.MouseEvent) => {
e.stopPropagation();
if (onDelete) {
@ -34,6 +44,28 @@ const ProfileHistoryItem: React.FC<ProfileHistoryItemProps> = ({
}
};
const handleItemClick = () => {
// Определяем куда перенаправлять в зависимости от типа поиска
if (searchType === 'VIN' || searchType === 'PLATE') {
// Для VIN и госномера перенаправляем на vehicle-search-results
router.push(`/vehicle-search-results?q=${encodeURIComponent(name)}`);
} else if (searchType === 'ARTICLE' || searchType === 'OEM' || (searchType === 'TEXT' && articleNumber)) {
// Для поиска по артикулу/OEM или текстового поиска с артикулом
const searchBrand = brand || manufacturer || '';
const searchArticle = articleNumber || name;
router.push(`/search-result?article=${encodeURIComponent(searchArticle)}&brand=${encodeURIComponent(searchBrand)}`);
} else if (searchType === 'TEXT') {
// Для обычного текстового поиска
router.push(`/search?q=${encodeURIComponent(name)}&mode=parts`);
} else if (searchType === 'PART_VEHICLES') {
// Для поиска автомобилей по детали
router.push(`/vehicles-by-part?partNumber=${encodeURIComponent(name)}`);
} else {
// По умолчанию - обычный поиск
router.push(`/search?q=${encodeURIComponent(name)}&mode=parts`);
}
};
const getSearchTypeDisplay = (article: string) => {
if (article.includes('TEXT')) return 'Текстовый поиск';
if (article.includes('ARTICLE')) return 'По артикулу';
@ -48,7 +80,11 @@ const ProfileHistoryItem: React.FC<ProfileHistoryItemProps> = ({
return (
<>
<div className="mt-1.5 w-full border border-gray-200 border-solid min-h-[1px] max-md:max-w-full" />
<div className="flex justify-between items-center px-5 pt-1.5 pb-2 mt-1.5 w-full bg-white rounded-lg max-md:max-w-full max-md:flex-col max-md:min-w-0 hover:bg-gray-50 transition-colors">
<div
className="flex justify-between items-center px-5 pt-1.5 pb-2 mt-1.5 w-full bg-white rounded-lg max-md:max-w-full max-md:flex-col max-md:min-w-0 hover:bg-gray-50 transition-colors"
onClick={handleItemClick}
style={{ cursor: 'pointer' }}
>
<div className="flex flex-wrap flex-1 shrink gap-5 items-center self-stretch pr-5 my-auto w-full basis-0 max-md:max-w-full max-md:flex-col max-md:gap-2 max-md:p-0 max-md:min-w-0">
<div className="self-stretch my-auto w-40 max-md:w-full text-sm">
<div className="font-medium text-gray-900">{date}</div>

View File

@ -3,6 +3,7 @@ import { useQuery, useMutation } from '@apollo/client';
import ProfileHistoryItem from "./ProfileHistoryItem";
import SearchInput from "./SearchInput";
import ProfileHistoryTabs from "./ProfileHistoryTabs";
import Pagination from '../Pagination';
import {
GET_PARTS_SEARCH_HISTORY,
DELETE_SEARCH_HISTORY_ITEM,
@ -20,13 +21,20 @@ const ProfileHistoryMain = () => {
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
const [filteredItems, setFilteredItems] = useState<PartsSearchHistoryItem[]>([]);
// Состояние пагинации
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(10); // Количество элементов на странице
const tabOptions = ["Все", "Сегодня", "Вчера", "Эта неделя", "Этот месяц"];
// GraphQL запросы
const { data, loading, error, refetch } = useQuery<{ partsSearchHistory: PartsSearchHistoryResponse }>(
GET_PARTS_SEARCH_HISTORY,
{
variables: { limit: 100, offset: 0 },
variables: {
limit: 1000, // Загружаем больше для клиентской пагинации с фильтрами
offset: 0
},
fetchPolicy: 'cache-and-network',
onCompleted: (data) => {
console.log('История поиска загружена:', data);
@ -161,8 +169,32 @@ const ProfileHistoryMain = () => {
}
setFilteredItems(filtered);
// Сбрасываем страницу на первую при изменении фильтров
setCurrentPage(1);
}, [historyItems, search, activeTab, selectedManufacturer, sortField, sortOrder]);
// Вычисляем элементы для текущей страницы
const totalPages = Math.ceil(filteredItems.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const currentPageItems = filteredItems.slice(startIndex, endIndex);
// Обработчик изменения страницы
const handlePageChange = (page: number) => {
setCurrentPage(page);
// Прокручиваем к началу списка при смене страницы
const historyContainer = document.querySelector('.flex.flex-col.mt-5.w-full.text-gray-400');
if (historyContainer) {
historyContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
};
// Обработчик изменения количества элементов на странице
const handleItemsPerPageChange = (newItemsPerPage: number) => {
setItemsPerPage(newItemsPerPage);
setCurrentPage(1); // Сбрасываем на первую страницу
};
const handleSort = (field: "date" | "manufacturer" | "name") => {
if (sortField === field) {
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
@ -252,7 +284,7 @@ const ProfileHistoryMain = () => {
if (loading && historyItems.length === 0) {
return (
<div className="flex flex-col justify-center text-base min-h-[526px] h-full">
<div className="flex flex-col flex-1 shrink justify-center basis-0 w-full max-md:max-w-full min-h-[526px] h-full">
<div className="flex justify-center items-center h-40">
<div className="text-gray-500">Загрузка истории поиска...</div>
</div>
@ -262,7 +294,7 @@ const ProfileHistoryMain = () => {
if (error) {
return (
<div className="flex flex-col justify-center text-base min-h-[526px]">
<div className="flex flex-col flex-1 shrink justify-center basis-0 w-full max-md:max-w-full min-h-[526px]">
<div className="flex justify-center items-center h-40">
<div className="text-red-500">Ошибка загрузки истории поиска</div>
</div>
@ -271,7 +303,7 @@ const ProfileHistoryMain = () => {
}
return (
<div className="flex flex-col min-h-[526px]">
<div className="flex flex-col flex-1 shrink justify-center basis-0 w-full max-md:max-w-full min-h-[526px]">
<div className="flex gap-5 items-center px-8 py-3 w-full leading-snug text-gray-400 whitespace-nowrap bg-white rounded-lg max-md:px-5 max-md:max-w-full">
<div className="flex-1 shrink self-stretch my-auto text-gray-400 basis-0 text-ellipsis max-md:max-w-full max-md:w-full">
<SearchInput
@ -287,6 +319,7 @@ const ProfileHistoryMain = () => {
setSelectedManufacturer("Все");
setSearch("");
setActiveTab("Все");
setCurrentPage(1);
}}
className="px-4 py-2 text-sm text-gray-600 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
>
@ -424,7 +457,7 @@ const ProfileHistoryMain = () => {
</div>
</div>
) : (
filteredItems.map((item) => (
currentPageItems.map((item) => (
<ProfileHistoryItem
key={item.id}
id={item.id}
@ -441,18 +474,58 @@ const ProfileHistoryMain = () => {
vehicleInfo={item.vehicleInfo}
resultCount={item.resultCount}
onDelete={handleDeleteItem}
searchType={item.searchType}
articleNumber={item.articleNumber}
brand={item.brand}
/>
))
)}
</div>
{/* Пагинация */}
{filteredItems.length > 0 && (
<div className="mt-4 text-center text-sm text-gray-500">
Показано {filteredItems.length} из {historyItems.length} записей
{(selectedManufacturer !== "Все" || search.trim() || activeTab !== "Все") && (
<span className="ml-2 text-blue-600">
(применены фильтры)
</span>
<div className="mt-6 space-y-4">
{/* Селектор количества элементов на странице */}
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center space-y-2 sm:space-y-0">
<div className="flex items-center space-x-2 text-sm text-gray-500">
<span>Показывать по:</span>
<select
value={itemsPerPage}
onChange={(e) => handleItemsPerPageChange(Number(e.target.value))}
className="px-2 py-1 border border-gray-200 rounded text-gray-700 bg-white focus:outline-none focus:ring-2 focus:ring-[#ec1c24] focus:border-transparent"
style={{ cursor: 'pointer' }}
>
<option value={5}>5</option>
<option value={10}>10</option>
<option value={20}>20</option>
<option value={50}>50</option>
</select>
<span>записей</span>
</div>
<div className="text-sm text-gray-500 text-center sm:text-right">
Показано {startIndex + 1}-{Math.min(endIndex, filteredItems.length)} из {filteredItems.length} записей
{filteredItems.length !== historyItems.length && (
<span className="ml-1">
(всего {historyItems.length})
</span>
)}
{(selectedManufacturer !== "Все" || search.trim() || activeTab !== "Все") && (
<span className="ml-2 text-blue-600">
(применены фильтры)
</span>
)}
</div>
</div>
{/* Компонент пагинации */}
{filteredItems.length > itemsPerPage && (
<Pagination
currentPage={currentPage}
totalPages={totalPages}
onPageChange={handlePageChange}
showPageInfo={true}
/>
)}
</div>
)}

View File

@ -69,7 +69,7 @@ const ProfileHistoryTabs: React.FC<ProfileHistoryTabsProps> = ({
{tabs.map((tab) => (
<div
key={tab}
className={`flex flex-1 shrink gap-5 items-center h-full text-center rounded-xl basis-12 min-w-[240px] ${
className={`flex flex-1 shrink gap-5 items-center h-full text-center rounded-xl basis-12 min-w-[160px] text-[14px] ${
activeTab === tab
? "text-white"
: "bg-slate-200 text-gray-950"
@ -78,7 +78,7 @@ const ProfileHistoryTabs: React.FC<ProfileHistoryTabsProps> = ({
onClick={() => onTabChange(tab)}
>
<div
className={`flex-1 shrink gap-5 self-stretch px-6 py-3.5 my-auto w-full rounded-xl basis-0 min-w-[240px] max-md:px-5 ${
className={`flex-1 shrink gap-5 self-stretch px-6 py-3.5 my-auto w-full rounded-xl basis-0 min-w-[160px] text-[14px] max-md:px-5 ${
activeTab === tab
? "text-white bg-red-600"
: "bg-slate-200 text-gray-950"
@ -89,12 +89,12 @@ const ProfileHistoryTabs: React.FC<ProfileHistoryTabsProps> = ({
</div>
))}
<div
className="relative w-[240px] max-w-full max-sm:w-full"
className="relative w-[300px] max-w-full max-sm:w-full"
ref={dropdownRef}
tabIndex={0}
>
<div
className="flex justify-between items-center px-6 py-4 text-sm leading-snug bg-white rounded border border-solid border-stone-300 text-neutral-500 cursor-pointer select-none w-full"
className="flex justify-between items-center px-6 py-3 text-sm leading-snug bg-white rounded border border-solid border-stone-300 text-neutral-500 cursor-pointer select-none w-full min-w-[200px]"
onClick={() => setIsDropdownOpen((prev) => !prev)}
>
<span className="truncate">{selectedManufacturer}</span>
@ -111,21 +111,21 @@ const ProfileHistoryTabs: React.FC<ProfileHistoryTabsProps> = ({
</span>
</div>
{isDropdownOpen && (
<ul className="absolute left-0 top-full z-10 bg-white border-x border-b border-stone-300 rounded-b-lg shadow-lg w-full max-h-60 overflow-y-auto">
<ul className="absolute px-0 pb-2 pl-0 list-none left-0 top-full z-10 bg-white border-x border-b border-stone-300 rounded-b-lg shadow-lg w-full max-h-60 overflow-y-auto dropdown-scroll-invisible">
{manufacturers.length === 0 ? (
<li className="px-6 py-4 text-gray-400 text-center">
<li className="py-2 text-xs text-gray-400 text-center">
Нет данных
</li>
) : (
manufacturers.map((manufacturer) => (
<li
key={manufacturer}
className={`px-6 py-4 cursor-pointer hover:bg-blue-100 transition-colors ${manufacturer === selectedManufacturer ? 'bg-blue-50 font-semibold text-blue-600' : ''}`}
className={`py-2 px-5 text-sm cursor-pointer hover:bg-blue-100 transition-colors ${manufacturer === selectedManufacturer ? 'bg-blue-50 text-red-600 font-normal' : 'text-neutral-500 font-medium'}`}
onMouseDown={() => handleManufacturerSelect(manufacturer)}
>
{manufacturer}
{manufacturer !== "Все" && (
<span className="ml-2 text-xs text-gray-400">
<span className="ml-2 text-[10px] text-gray-400">
({historyItems.filter(item =>
item.brand === manufacturer || item.vehicleInfo?.brand === manufacturer
).length})

View File

@ -172,12 +172,12 @@ const ProfileOrdersMain: React.FC<ProfileOrdersMainProps> = (props) => {
{tabs.map((tab, idx) => (
<div
key={tab.label}
className={`flex flex-1 shrink gap-5 items-center h-full rounded-xl basis-0 ${activeTab === idx ? "bg-red-600 text-white" : "bg-slate-200 text-gray-950"}`}
className={`flex flex-1 shrink gap-5 items-center h-full rounded-xl basis-0 text-[14px] ${activeTab === idx ? "bg-red-600 text-white" : "bg-slate-200 text-gray-950"}`}
style={{ cursor: "pointer" }}
onClick={() => setActiveTab(idx)}
>
<div
className={`flex-1 shrink gap-5 self-stretch px-6 py-3.5 my-auto w-full rounded-xl basis-0 max-md:px-5 ${activeTab === idx ? "bg-red-600 text-white" : "bg-slate-200 text-gray-950"}`}
className={`flex-1 shrink gap-5 self-stretch px-6 py-3.5 my-auto w-full rounded-xl basis-0 max-md:px-5 text-[14px] ${activeTab === idx ? "bg-red-600 text-white" : "bg-slate-200 text-gray-950"}`}
>
{tab.label}
</div>

View File

@ -1,44 +1,222 @@
import React from "react";
import React, { useState, useEffect, useRef } from "react";
import VehicleAttributesTooltip from './VehicleAttributesTooltip';
interface InfoVinProps {
vehicleName: string;
vehicleInfo: string;
interface VehicleAttribute {
key: string;
name: string;
value: string;
}
const InfoVin: React.FC<InfoVinProps> = ({ vehicleName, vehicleInfo }) => (
<section className="section-info">
<div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9">
<div className="w-layout-hflex flex-block-7">
<a href="#" className="link-block w-inline-block">
<div>Главная</div>
</a>
<div className="text-block-3"></div>
<a href="/brands" className="link-block w-inline-block">
<div>Оригинальный каталог</div>
</a>
<div className="text-block-3"></div>
<a href="#" className="link-block-2 w-inline-block">
<div>{vehicleName}</div>
</a>
</div>
<div className="w-layout-hflex flex-block-8">
<div className="w-layout-hflex flex-block-10">
<h1 className="heading">{vehicleName}</h1>
interface InfoVinProps {
vehicleName?: string;
vehicleInfo?: string;
vehicleAttributes?: VehicleAttribute[];
}
const InfoVin: React.FC<InfoVinProps> = ({
vehicleName = "VIN декодирование",
vehicleInfo = "Поиск запчастей по VIN номеру автомобиля",
vehicleAttributes = []
}) => {
const [showTooltip, setShowTooltip] = useState(false);
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const iconRef = useRef<HTMLDivElement>(null);
// Отладочный вывод атрибутов
useEffect(() => {
if (vehicleAttributes.length > 0) {
console.log('🚗 Атрибуты автомобиля:', vehicleAttributes);
console.log('🔍 Ключи атрибутов:', vehicleAttributes.map(attr => ({ key: attr.key, name: attr.name })));
}
}, [vehicleAttributes]);
// Определяем основные параметры для отображения
const getMainParameters = (attributes: VehicleAttribute[]) => {
// Приоритетные ключи для основных параметров
const priorityKeys = [
// Двигатель
{
keys: ['engine', 'enginetype', 'engine_type', 'двигатель', 'тип двигателя', 'motor'],
priority: 1
},
// VIN
{
keys: ['vin', 'вин', 'vin_code'],
priority: 2
},
// Год выпуска
{
keys: ['year', 'год', 'год выпуска', 'production_year', 'model_year'],
priority: 3
},
// Топливо
{
keys: ['fuel', 'топливо', 'тип топлива', 'fuel_type', 'fueltype'],
priority: 4
},
// Коробка передач
{
keys: ['transmission', 'коробка', 'кпп', 'gearbox', 'transmissiontype'],
priority: 5
}
];
const foundParams: Array<{ attr: VehicleAttribute; priority: number }> = [];
// Ищем атрибуты по приоритетным ключам
for (const priorityGroup of priorityKeys) {
const foundAttr = attributes.find(attr =>
priorityGroup.keys.some(key =>
attr.key.toLowerCase().includes(key.toLowerCase()) ||
attr.name.toLowerCase().includes(key.toLowerCase())
)
);
if (foundAttr) {
foundParams.push({ attr: foundAttr, priority: priorityGroup.priority });
}
}
// Сортируем по приоритету и берем максимум 4 параметра
foundParams.sort((a, b) => a.priority - b.priority);
const mainParams = foundParams.slice(0, 4).map(item => item.attr);
// Если основных параметров меньше 3, добавляем первые доступные
if (mainParams.length < 3) {
const additionalParams = attributes
.filter(attr => !mainParams.includes(attr))
.slice(0, 3 - mainParams.length);
return [...mainParams, ...additionalParams];
}
return mainParams;
};
const mainParameters = getMainParameters(vehicleAttributes);
const displayText = mainParameters.length > 0
? mainParameters.map(attr => attr.value).join(' · ')
: vehicleInfo;
// Отладочный вывод выбранных параметров
useEffect(() => {
if (mainParameters.length > 0) {
console.log('✅ Выбранные основные параметры:', mainParameters);
console.log('📝 Отображаемый текст:', displayText);
}
}, [mainParameters, displayText]);
// Вычисляем позицию tooltip
const calculateTooltipPosition = () => {
if (iconRef.current) {
const rect = iconRef.current.getBoundingClientRect();
const tooltipWidth = 500;
const tooltipHeight = 300; // примерная высота
let x = rect.left + rect.width / 2 - tooltipWidth / 2;
let y = rect.bottom + 8;
// Проверяем, не выходит ли tooltip за границы экрана
if (x < 10) x = 10;
if (x + tooltipWidth > window.innerWidth - 10) {
x = window.innerWidth - tooltipWidth - 10;
}
// Если tooltip не помещается снизу, показываем сверху
if (y + tooltipHeight > window.innerHeight - 10) {
y = rect.top - tooltipHeight - 8;
}
setTooltipPosition({ x, y });
}
};
const handleMouseEnter = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
calculateTooltipPosition();
setShowTooltip(true);
}, 300); // Задержка 300ms
};
const handleMouseLeave = () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
setShowTooltip(false);
}, 100); // Небольшая задержка перед скрытием
};
// Очищаем таймеры при размонтировании
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
return (
<>
<section className="section-info">
<div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9">
<div className="w-layout-hflex flex-block-7">
<a href="#" className="link-block w-inline-block">
<div>Главная</div>
</a>
<div className="text-block-3"></div>
<a href="/brands" className="link-block w-inline-block">
<div>Оригинальный каталог</div>
</a>
<div className="text-block-3"></div>
<a href="#" className="link-block-2 w-inline-block">
<div>{vehicleName}</div>
</a>
</div>
<div className="w-layout-hflex flex-block-8">
<div className="w-layout-hflex flex-block-10">
<h1 className="heading">{vehicleName}</h1>
</div>
</div>
</div>
<div className="w-layout-hflex flex-block-112">
<div className="text-block-55">{displayText}</div>
<div className="relative inline-block">
<div
ref={iconRef}
className="w-embed cursor-pointer hover:opacity-70 transition-opacity"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
role="button"
tabIndex={0}
aria-label="Показать полную информацию об автомобиле"
onFocus={handleMouseEnter}
onBlur={handleMouseLeave}
>
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.1 13.5H9.89999V8.1H8.1V13.5ZM8.99999 6.3C9.25499 6.3 9.46889 6.2136 9.64169 6.0408C9.81449 5.868 9.90059 5.6544 9.89999 5.4C9.89939 5.1456 9.81299 4.932 9.64079 4.7592C9.46859 4.5864 9.25499 4.5 8.99999 4.5C8.745 4.5 8.53139 4.5864 8.35919 4.7592C8.187 4.932 8.1006 5.1456 8.1 5.4C8.0994 5.6544 8.1858 5.8683 8.35919 6.0417C8.53259 6.2151 8.74619 6.3012 8.99999 6.3ZM8.99999 18C7.755 18 6.585 17.7636 5.49 17.2908C4.395 16.818 3.4425 16.1769 2.6325 15.3675C1.8225 14.5581 1.1814 13.6056 0.709201 12.51C0.237001 11.4144 0.000601139 10.2444 1.13924e-06 9C-0.00059886 7.7556 0.235801 6.5856 0.709201 5.49C1.1826 4.3944 1.8237 3.4419 2.6325 2.6325C3.4413 1.8231 4.3938 1.182 5.49 0.7092C6.5862 0.2364 7.7562 0 8.99999 0C10.2438 0 11.4138 0.2364 12.51 0.7092C13.6062 1.182 14.5587 1.8231 15.3675 2.6325C16.1763 3.4419 16.8177 4.3944 17.2917 5.49C17.7657 6.5856 18.0018 7.7556 18 9C17.9982 10.2444 17.7618 11.4144 17.2908 12.51C16.8198 13.6056 16.1787 14.5581 15.3675 15.3675C14.5563 16.1769 13.6038 16.8183 12.51 17.2917C11.4162 17.7651 10.2462 18.0012 8.99999 18Z" fill="currentcolor" />
</svg>
</div>
</div>
</div>
</div>
</div>
<div className="w-layout-hflex flex-block-112">
<div className="text-block-55">{vehicleInfo}</div>
<div className="w-embed">
{/* SVG icon */}
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.1 13.5H9.89999V8.1H8.1V13.5ZM8.99999 6.3C9.25499 6.3 9.46889 6.2136 9.64169 6.0408C9.81449 5.868 9.90059 5.6544 9.89999 5.4C9.89939 5.1456 9.81299 4.932 9.64079 4.7592C9.46859 4.5864 9.25499 4.5 8.99999 4.5C8.745 4.5 8.53139 4.5864 8.35919 4.7592C8.187 4.932 8.1006 5.1456 8.1 5.4C8.0994 5.6544 8.1858 5.8683 8.35919 6.0417C8.53259 6.2151 8.74619 6.3012 8.99999 6.3ZM8.99999 18C7.755 18 6.585 17.7636 5.49 17.2908C4.395 16.818 3.4425 16.1769 2.6325 15.3675C1.8225 14.5581 1.1814 13.6056 0.709201 12.51C0.237001 11.4144 0.000601139 10.2444 1.13924e-06 9C-0.00059886 7.7556 0.235801 6.5856 0.709201 5.49C1.1826 4.3944 1.8237 3.4419 2.6325 2.6325C3.4413 1.8231 4.3938 1.182 5.49 0.7092C6.5862 0.2364 7.7562 0 8.99999 0C10.2438 0 11.4138 0.2364 12.51 0.7092C13.6062 1.182 14.5587 1.8231 15.3675 2.6325C16.1763 3.4419 16.8177 4.3944 17.2917 5.49C17.7657 6.5856 18.0018 7.7556 18 9C17.9982 10.2444 17.7618 11.4144 17.2908 12.51C16.8198 13.6056 16.1787 14.5581 15.3675 15.3675C14.5563 16.1769 13.6038 16.8183 12.51 17.2917C11.4162 17.7651 10.2462 18.0012 8.99999 18Z" fill="currentcolor" />
</svg>
</div>
</div>
</div>
</section>
);
</section>
{/* Tooltip с фиксированным позиционированием */}
<VehicleAttributesTooltip
show={showTooltip && vehicleAttributes.length > 0}
position={tooltipPosition}
vehicleName={vehicleName}
vehicleAttributes={vehicleAttributes}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
/>
</>
);
};
export default InfoVin;

View File

@ -5,10 +5,10 @@ import { GET_LAXIMO_UNIT_INFO, GET_LAXIMO_UNIT_IMAGE_MAP } from '@/lib/graphql';
import BrandSelectionModal from '../BrandSelectionModal';
interface KnotInProps {
catalogCode: string;
vehicleId: string;
catalogCode?: string;
vehicleId?: string;
ssd?: string;
unitId: string;
unitId?: string;
unitName?: string;
parts?: Array<{
detailid?: string;
@ -21,6 +21,9 @@ interface KnotInProps {
note?: string;
attributes?: Array<{ key: string; name?: string; value: string }>;
}>;
onPartSelect?: (codeOnImage: string | number | null) => void; // Коллбек для уведомления KnotParts о выделении детали
onPartsHighlight?: (codeOnImage: string | number | null) => void; // Коллбек для подсветки при hover
selectedParts?: Set<string | number>; // Выбранные детали (множественный выбор)
}
// Функция для корректного формирования URL изображения
@ -34,37 +37,124 @@ const getImageUrl = (baseUrl: string, size: string) => {
.replace('%size%', size);
};
const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, unitName, parts }) => {
const KnotIn: React.FC<KnotInProps> = ({
catalogCode,
vehicleId,
ssd,
unitId,
unitName,
parts,
onPartSelect,
onPartsHighlight,
selectedParts = new Set()
}) => {
const imgRef = useRef<HTMLImageElement>(null);
const [imageScale, setImageScale] = useState({ x: 1, y: 1 });
const selectedImageSize = 'source';
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
const [selectedDetail, setSelectedDetail] = useState<{ oem: string; name: string } | null>(null);
const [hoveredCodeOnImage, setHoveredCodeOnImage] = useState<string | number | null>(null);
const router = useRouter();
const [isImageModalOpen, setIsImageModalOpen] = useState(false);
// Получаем инфо об узле (для картинки)
console.log('🔍 KnotIn - GET_LAXIMO_UNIT_INFO запрос:', {
catalogCode,
vehicleId,
unitId,
ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует',
ssdLength: ssd?.length,
skipCondition: !catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === ''
});
const { data: unitInfoData, loading: unitInfoLoading, error: unitInfoError } = useQuery(
GET_LAXIMO_UNIT_INFO,
{
variables: { catalogCode, vehicleId, unitId, ssd: ssd || '' },
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
variables: {
catalogCode,
vehicleId,
unitId,
ssd
},
skip: !catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === '',
errorPolicy: 'all',
}
);
// Получаем карту координат
console.log('🔍 KnotIn - GET_LAXIMO_UNIT_IMAGE_MAP запрос:', {
catalogCode,
vehicleId,
unitId,
ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует',
ssdLength: ssd?.length,
skipCondition: !catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === ''
});
const { data: imageMapData, loading: imageMapLoading, error: imageMapError } = useQuery(
GET_LAXIMO_UNIT_IMAGE_MAP,
{
variables: { catalogCode, vehicleId, unitId, ssd: ssd || '' },
skip: !catalogCode || vehicleId === undefined || vehicleId === null || !unitId,
variables: {
catalogCode,
vehicleId,
unitId,
ssd
},
skip: !catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === '',
errorPolicy: 'all',
}
);
// Если нет необходимых данных, показываем заглушку
if (!catalogCode || !vehicleId || !unitId || !ssd || ssd.trim() === '') {
console.log('⚠️ KnotIn: отсутствуют необходимые данные:', {
catalogCode: !!catalogCode,
vehicleId: !!vehicleId,
unitId: !!unitId,
ssd: !!ssd,
ssdValid: ssd ? ssd.trim() !== '' : false
});
return (
<div className="text-center py-8 text-gray-500">
<div className="text-lg font-medium mb-2">Схема узла</div>
<div className="text-sm">Выберите узел для отображения схемы</div>
{process.env.NODE_ENV === 'development' && (
<div className="text-xs text-red-500 mt-2">
Debug: catalogCode={catalogCode}, vehicleId={vehicleId}, unitId={unitId}, ssd={ssd ? 'есть' : 'нет'}
</div>
)}
</div>
);
}
const unitInfo = unitInfoData?.laximoUnitInfo;
const coordinates = imageMapData?.laximoUnitImageMap?.coordinates || [];
const imageUrl = unitInfo?.imageurl ? getImageUrl(unitInfo.imageurl, selectedImageSize) : '';
// Логируем успешную загрузку данных
React.useEffect(() => {
if (unitInfo) {
console.log('✅ KnotIn: данные узла загружены:', {
unitName: unitInfo.name,
hasImage: !!unitInfo.imageurl,
imageUrl: unitInfo.imageurl,
processedImageUrl: imageUrl
});
}
}, [unitInfo, imageUrl]);
React.useEffect(() => {
if (coordinates.length > 0) {
console.log('✅ KnotIn: координаты карты загружены:', {
coordinatesCount: coordinates.length,
firstCoordinate: coordinates[0]
});
} else if (imageMapData) {
console.log('⚠️ KnotIn: карта изображений загружена, но координаты пустые:', imageMapData);
}
}, [coordinates, imageMapData]);
// Масштабируем точки после загрузки картинки
const handleImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
const img = e.currentTarget;
@ -75,21 +165,68 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
});
};
// Клик по точке: найти part по codeonimage/detailid и открыть BrandSelectionModal
const handlePointClick = (codeonimage: string | number) => {
// Обработчик клика по картинке (zoom)
const handleImageClick = (e: React.MouseEvent<HTMLImageElement>) => {
// Если клик был по точке, не открываем модалку (точки выше по z-index)
setIsImageModalOpen(true);
};
// Обработчик наведения на точку
const handlePointHover = (coord: any) => {
// Попробуем использовать разные поля для связи
const identifierToUse = coord.detailid || coord.codeonimage || coord.code;
console.log('🔍 KnotIn - hover на точку:', {
coord,
detailid: coord.detailid,
codeonimage: coord.codeonimage,
code: coord.code,
identifierToUse,
type: typeof identifierToUse,
coordinatesLength: coordinates.length,
partsLength: parts?.length || 0,
firstCoord: coordinates[0],
firstPart: parts?.[0]
});
setHoveredCodeOnImage(identifierToUse);
if (onPartsHighlight) {
onPartsHighlight(identifierToUse);
}
};
// Клик по точке: выделить в списке деталей
const handlePointClick = (coord: any) => {
if (!parts) return;
console.log('Клик по точке:', codeonimage, 'Все детали:', parts);
const identifierToUse = coord.detailid || coord.codeonimage || coord.code;
console.log('Клик по точке:', identifierToUse, 'Координата:', coord, 'Все детали:', parts);
// Уведомляем родительский компонент о выборе детали для выделения в списке
if (onPartSelect) {
onPartSelect(identifierToUse);
}
};
// Двойной клик по точке: переход на страницу выбора бренда
const handlePointDoubleClick = (coord: any) => {
if (!parts) return;
const identifierToUse = coord.detailid || coord.codeonimage || coord.code;
console.log('Двойной клик по точке:', identifierToUse, 'Координата:', coord);
const part = parts.find(
(p) =>
(p.codeonimage && p.codeonimage.toString() === codeonimage.toString()) ||
(p.detailid && p.detailid.toString() === codeonimage.toString())
(p.detailid && p.detailid.toString() === identifierToUse?.toString()) ||
(p.codeonimage && p.codeonimage.toString() === identifierToUse?.toString())
);
console.log('Найдена деталь для точки:', part);
if (part?.oem) {
setSelectedDetail({ oem: part.oem, name: part.name || '' });
setIsBrandModalOpen(true);
// Переходим на страницу выбора бренда вместо модального окна
const url = `/vehicle-search/${catalogCode}/${vehicleId}/part/${part.oem}/brands?detailName=${encodeURIComponent(part.name || '')}`;
router.push(url);
} else {
console.warn('Нет артикула (oem) для выбранной точки:', codeonimage, part);
console.warn('Нет артикула (oem) для выбранной точки:', identifierToUse, part);
}
};
@ -97,67 +234,196 @@ const KnotIn: React.FC<KnotInProps> = ({ catalogCode, vehicleId, ssd, unitId, un
React.useEffect(() => {
console.log('KnotIn parts:', parts);
console.log('KnotIn coordinates:', coordinates);
if (coordinates.length > 0) {
console.log('🔍 Первые 5 координат:', coordinates.slice(0, 5).map((c: any) => ({
code: c.code,
codeonimage: c.codeonimage,
detailid: c.detailid,
x: c.x,
y: c.y
})));
}
if (parts && parts.length > 0) {
console.log('🔍 Первые 5 деталей:', parts.slice(0, 5).map(p => ({
name: p.name,
codeonimage: p.codeonimage,
detailid: p.detailid,
oem: p.oem
})));
}
// Попытка связать координаты с деталями
if (coordinates.length > 0 && parts && parts.length > 0) {
console.log('🔗 Попытка связать координаты с деталями:');
coordinates.forEach((coord: any, idx: number) => {
const matchingPart = parts.find(part =>
part.detailid === coord.detailid ||
part.codeonimage === coord.codeonimage ||
part.codeonimage === coord.code
);
if (matchingPart) {
console.log(` ✅ Координата ${idx}: detailid=${coord.detailid}, codeonimage=${coord.codeonimage} -> Деталь: ${matchingPart.name}`);
} else {
console.log(` ❌ Координата ${idx}: detailid=${coord.detailid}, codeonimage=${coord.codeonimage} -> НЕ НАЙДЕНА`);
}
});
}
}, [parts, coordinates]);
if (unitInfoLoading || imageMapLoading) {
console.log('🔄 KnotIn: загрузка данных...', {
unitInfoLoading,
imageMapLoading,
unitInfoError: unitInfoError?.message,
imageMapError: imageMapError?.message
});
return <div className="text-center py-8 text-gray-500">Загружаем схему узла...</div>;
}
if (unitInfoError) {
return <div className="text-center py-8 text-red-600">Ошибка загрузки схемы: {unitInfoError.message}</div>;
console.error('❌ KnotIn: ошибка загрузки информации об узле:', unitInfoError);
return (
<div className="text-center py-8 text-red-600">
Ошибка загрузки схемы: {unitInfoError.message}
{process.env.NODE_ENV === 'development' && (
<div className="text-xs mt-2 text-gray-500">
GraphQL Error: {JSON.stringify(unitInfoError, null, 2)}
</div>
)}
</div>
);
}
if (imageMapError) {
console.error('❌ KnotIn: ошибка загрузки карты изображений:', imageMapError);
}
if (!imageUrl) {
return <div className="text-center py-8 text-gray-400">Нет изображения для этого узла</div>;
console.log('⚠️ KnotIn: нет URL изображения:', {
unitInfo: !!unitInfo,
imageurl: unitInfo?.imageurl,
unitInfoData: !!unitInfoData
});
return (
<div className="text-center py-8 text-gray-400">
Нет изображения для этого узла
{process.env.NODE_ENV === 'development' && unitInfo && (
<div className="text-xs mt-2 text-gray-500">
Debug: unitInfo.imageurl = {unitInfo.imageurl || 'отсутствует'}
</div>
)}
</div>
);
}
return (
<>
<div className="relative inline-block">
{/* ВРЕМЕННО: выводим количество точек для быстрой проверки */}
<div style={{ position: 'absolute', top: 4, left: 4, zIndex: 20, background: 'rgba(255,0,0,0.1)', color: '#c00', fontWeight: 700, fontSize: 14, padding: '2px 8px', borderRadius: 6 }}>
{coordinates.length} точек
</div>
<img
ref={imgRef}
src={imageUrl}
loading="lazy"
alt={unitName || unitInfo?.name || "Изображение узла"}
onLoad={handleImageLoad}
className="max-w-full h-auto mx-auto rounded"
className="max-w-full h-auto mx-auto rounded cursor-zoom-in"
style={{ maxWidth: 400, display: 'block' }}
onClick={handleImageClick}
/>
{/* Точки/области */}
{coordinates.map((coord: any, idx: number) => {
const scaledX = coord.x * imageScale.x;
const scaledY = coord.y * imageScale.y;
const scaledWidth = coord.width * imageScale.x;
const scaledHeight = coord.height * imageScale.y;
// Кружки всегда 32x32px, центрируем по координате
const size = 22;
const scaledX = coord.x * imageScale.x - size / 2;
const scaledY = coord.y * imageScale.y - size / 2;
// Используем code или codeonimage в зависимости от структуры данных
const codeValue = coord.code || coord.codeonimage;
// Определяем состояние точки
const isSelected = selectedParts.has(codeValue);
const isHovered = hoveredCodeOnImage === codeValue;
// Определяем цвета на основе состояния
let backgroundColor = '#B7CAE2'; // Базовый цвет
let textColor = '#000';
if (isSelected) {
backgroundColor = '#22C55E'; // Зеленый для выбранных
textColor = '#fff';
} else if (isHovered) {
backgroundColor = '#EC1C24'; // Красный при наведении
textColor = '#fff';
}
return (
<div
key={`coord-${unitId}-${idx}-${coord.x}-${coord.y}`}
tabIndex={0}
aria-label={`Деталь ${coord.codeonimage}`}
aria-label={`Деталь ${codeValue}`}
onKeyDown={e => {
if (e.key === 'Enter' || e.key === ' ') handlePointClick(coord.codeonimage);
if (e.key === 'Enter' || e.key === ' ') handlePointClick(coord);
}}
className="absolute flex items-center justify-center border-2 border-red-600 bg-white rounded-full cursor-pointer"
className="absolute flex items-center justify-center cursor-pointer transition-all duration-200 ease-in-out"
style={{
left: scaledX,
top: scaledY,
width: scaledWidth,
height: scaledHeight,
width: size,
height: size,
backgroundColor,
borderRadius: '50%',
border: isSelected ? '2px solid #16A34A' : 'none',
transform: isHovered || isSelected ? 'scale(1.1)' : 'scale(1)',
zIndex: isHovered || isSelected ? 10 : 1,
pointerEvents: 'auto',
}}
title={coord.codeonimage}
onClick={() => handlePointClick(coord.codeonimage)}
title={`${codeValue} (Клик - выделить в списке, двойной клик - перейти к выбору бренда)`}
onClick={e => { e.stopPropagation(); handlePointClick(coord); }}
onDoubleClick={e => { e.stopPropagation(); handlePointDoubleClick(coord); }}
onMouseEnter={() => handlePointHover(coord)}
onMouseLeave={() => {
setHoveredCodeOnImage(null);
if (onPartsHighlight) {
onPartsHighlight(null);
}
}}
>
<span className="flex items-center justify-center w-full h-full text-black text-sm font-bold select-none pointer-events-none">
{coord.codeonimage}
<span
className="flex items-center justify-center w-full h-full text-sm font-bold select-none pointer-events-none transition-colors duration-200"
style={{ color: textColor }}
>
{codeValue}
</span>
</div>
);
})}
</div>
</div>
{/* Модалка увеличенного изображения */}
{isImageModalOpen && (
<div
className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/20 bg-opacity-70"
onClick={() => setIsImageModalOpen(false)}
style={{ cursor: 'zoom-out' }}
>
<div className="relative">
<img
src={imageUrl}
alt={unitName || unitInfo?.name || "Изображение узла"}
className="max-h-[90vh] max-w-[90vw] rounded shadow-lg"
onClick={e => e.stopPropagation()}
style={{ background: '#fff' }}
/>
{/* Убираем интерактивные точки в модальном окне */}
</div>
<button
onClick={() => setIsImageModalOpen(false)}
className="absolute top-4 right-4 text-white text-3xl font-bold bg-black bg-opacity-40 rounded-full w-10 h-10 flex items-center justify-center hover:bg-black hover:bg-opacity-60 transition-colors"
aria-label="Закрыть"
style={{ zIndex: 10000 }}
>
×
</button>
</div>
)}
{/* Модалка выбора бренда */}
<BrandSelectionModal
isOpen={isBrandModalOpen}

View File

@ -1,9 +1,8 @@
import React, { useState } from "react";
import React, { useState, useEffect, useRef } from "react";
import { useRouter } from "next/router";
import BrandSelectionModal from '../BrandSelectionModal';
interface KnotPartsProps {
parts: Array<{
parts?: Array<{
detailid?: string;
codeonimage?: string | number;
oem?: string;
@ -15,42 +14,317 @@ interface KnotPartsProps {
attributes?: Array<{ key: string; name?: string; value: string }>;
}>;
selectedCodeOnImage?: string | number;
catalogCode?: string;
vehicleId?: string;
highlightedCodeOnImage?: string | number | null; // Деталь подсвеченная при hover на изображении
selectedParts?: Set<string | number>; // Выбранные детали (множественный выбор)
onPartSelect?: (codeOnImage: string | number | null) => void; // Коллбек для выбора детали
onPartHover?: (codeOnImage: string | number | null) => void; // Коллбек для подсветки при hover
}
const KnotParts: React.FC<KnotPartsProps> = ({ parts, selectedCodeOnImage }) => {
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
const [selectedDetail, setSelectedDetail] = useState<{ oem: string; name: string } | null>(null);
const KnotParts: React.FC<KnotPartsProps> = ({
parts = [],
selectedCodeOnImage,
catalogCode,
vehicleId,
highlightedCodeOnImage,
selectedParts = new Set(),
onPartSelect,
onPartHover
}) => {
const router = useRouter();
const [showTooltip, setShowTooltip] = useState(false);
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
const [tooltipPart, setTooltipPart] = useState<any>(null);
const [clickedPart, setClickedPart] = useState<string | number | null>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const clickTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Отладочные логи для проверки данных
React.useEffect(() => {
console.log('🔍 KnotParts получил данные:', {
partsCount: parts.length,
firstPart: parts[0],
firstPartAttributes: parts[0]?.attributes?.length || 0,
allPartsWithAttributes: parts.map(part => ({
name: part.name,
oem: part.oem,
attributesCount: part.attributes?.length || 0,
attributes: part.attributes
}))
});
}, [parts]);
const handlePriceClick = (part: any) => {
if (part.oem) {
setSelectedDetail({ oem: part.oem, name: part.name || '' });
setIsBrandModalOpen(true);
if (part.oem && catalogCode && vehicleId !== undefined) {
// Переходим на страницу выбора бренда
const url = `/vehicle-search/${catalogCode}/${vehicleId}/part/${part.oem}/brands?detailName=${encodeURIComponent(part.name || '')}`;
router.push(url);
}
};
// Обработчик клика по детали в списке
const handlePartClick = (part: any) => {
const codeOnImage = part.codeonimage || part.detailid;
if (codeOnImage && onPartSelect) {
onPartSelect(codeOnImage);
}
// Также подсвечиваем деталь на схеме при клике
if (codeOnImage && onPartHover) {
// Очищаем предыдущий таймер, если он есть
if (clickTimeoutRef.current) {
clearTimeout(clickTimeoutRef.current);
}
// Устанавливаем состояние кликнутой детали
setClickedPart(codeOnImage);
// Подсвечиваем на схеме
onPartHover(codeOnImage);
// Убираем подсветку через интервал
clickTimeoutRef.current = setTimeout(() => {
setClickedPart(null);
if (onPartHover) {
onPartHover(null);
}
}, 1500); // Подсветка будет видна 1.5 секунды
}
};
// Обработчики наведения
const handlePartMouseEnter = (part: any) => {
if (part.codeonimage && onPartHover) {
onPartHover(part.codeonimage);
}
};
const handlePartMouseLeave = () => {
if (onPartHover) {
onPartHover(null);
}
};
// Вычисляем позицию tooltip
const calculateTooltipPosition = (iconElement: HTMLElement) => {
if (!iconElement) {
console.error('❌ calculateTooltipPosition: элемент не найден');
return;
}
const rect = iconElement.getBoundingClientRect();
const tooltipWidth = 400;
const tooltipHeight = 300; // примерная высота
let x = rect.left + rect.width / 2 - tooltipWidth / 2;
let y = rect.bottom + 8;
// Проверяем, не выходит ли tooltip за границы экрана
if (x < 10) x = 10;
if (x + tooltipWidth > window.innerWidth - 10) {
x = window.innerWidth - tooltipWidth - 10;
}
// Если tooltip не помещается снизу, показываем сверху
if (y + tooltipHeight > window.innerHeight - 10) {
y = rect.top - tooltipHeight - 8;
}
setTooltipPosition({ x, y });
};
const handleInfoIconMouseEnter = (event: React.MouseEvent, part: any) => {
event.stopPropagation();
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// Сохраняем ссылку на элемент до setTimeout
const target = event.currentTarget as HTMLElement;
timeoutRef.current = setTimeout(() => {
if (target && typeof target.getBoundingClientRect === 'function') {
calculateTooltipPosition(target);
setTooltipPart(part);
setShowTooltip(true);
console.log('🔍 Показываем тултип для детали:', part.name, 'Атрибуты:', part.attributes?.length || 0);
} else {
console.error('❌ handleInfoIconMouseEnter: элемент не поддерживает getBoundingClientRect:', target);
}
}, 300); // Задержка 300ms
};
const handleInfoIconMouseLeave = (event: React.MouseEvent) => {
event.stopPropagation();
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
setShowTooltip(false);
setTooltipPart(null);
}, 100); // Небольшая задержка перед скрытием
};
// Очищаем таймеры при размонтировании
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
if (clickTimeoutRef.current) {
clearTimeout(clickTimeoutRef.current);
}
};
}, []);
// Если нет деталей, показываем заглушку
if (!parts || parts.length === 0) {
return (
<div className="knot-parts">
<div className="text-center py-8 text-gray-500">
<div className="text-lg font-medium mb-2">Список деталей</div>
<div className="text-sm">Выберите узел для отображения деталей</div>
</div>
</div>
);
}
// Эффект для отслеживания изменений подсветки
useEffect(() => {
console.log('🔍 KnotParts - подсветка изменилась:', {
highlightedCodeOnImage,
highlightedType: typeof highlightedCodeOnImage,
partsCodeOnImages: parts.map(p => p.codeonimage),
partsDetailIds: parts.map(p => p.detailid),
willHighlight: parts.some(part =>
(part.codeonimage && part.codeonimage.toString() === highlightedCodeOnImage?.toString()) ||
(part.detailid && part.detailid.toString() === highlightedCodeOnImage?.toString())
),
willHighlightStrict: parts.some(part =>
part.codeonimage === highlightedCodeOnImage ||
part.detailid === highlightedCodeOnImage
),
firstPartWithCodeOnImage: parts.find(p => p.codeonimage)
});
// Детальная информация о всех деталях
console.log('📋 Все детали с их codeonimage и detailid:');
parts.forEach((part, idx) => {
console.log(` Деталь ${idx}: "${part.name}" codeonimage="${part.codeonimage}" (${typeof part.codeonimage}) detailid="${part.detailid}" (${typeof part.detailid})`);
});
console.log('🎯 Ищем подсветку для:', `"${highlightedCodeOnImage}" (${typeof highlightedCodeOnImage})`);
}, [highlightedCodeOnImage, parts]);
return (
<>
{/* Статус выбранных деталей */}
{/* {selectedParts.size > 0 && (
<div className="bg-green-50 border border-green-200 rounded-lg p-3 mb-4">
<div className="flex items-center">
<svg className="w-5 h-5 text-green-600 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span className="text-green-800 font-medium">
Выбрано деталей: {selectedParts.size}
</span>
<span className="text-green-600 text-sm ml-2">
(Кликните по детали, чтобы убрать из выбранных)
</span>
</div>
</div>
)} */}
<div className="knot-parts">
{parts.map((part, idx) => {
const isSelected = part.codeonimage && part.codeonimage === selectedCodeOnImage;
const codeOnImage = part.codeonimage || part.detailid;
const isHighlighted = highlightedCodeOnImage !== null && highlightedCodeOnImage !== undefined && (
(part.codeonimage && part.codeonimage.toString() === highlightedCodeOnImage.toString()) ||
(part.detailid && part.detailid.toString() === highlightedCodeOnImage.toString())
);
const isSelected = selectedParts.has(part.detailid || part.codeonimage || idx.toString());
const isClicked = clickedPart !== null && (
(part.codeonimage && part.codeonimage.toString() === clickedPart.toString()) ||
(part.detailid && part.detailid.toString() === clickedPart.toString())
);
// Создаем уникальный ключ
const uniqueKey = `part-${idx}-${part.detailid || part.oem || part.name || 'unknown'}`;
return (
<div
className={`w-layout-hflex knotlistitem border rounded transition-colors duration-150 ${isSelected ? 'bg-yellow-100 border-yellow-400' : 'border-transparent'}`}
key={part.detailid || idx}
key={uniqueKey}
className={`w-layout-hflex knotlistitem rounded-lg cursor-pointer transition-all duration-300 ${
isSelected
? 'bg-green-100 border-green-500'
: isClicked
? 'bg-red-100 border-red-400 shadow-md'
: isHighlighted
? 'bg-slate-200'
: 'bg-white border-gray-200 hover:border-gray-300'
}`}
onClick={() => handlePartClick(part)}
onMouseEnter={() => handlePartMouseEnter(part)}
onMouseLeave={handlePartMouseLeave}
style={{ cursor: 'pointer' }}
>
<div className="w-layout-hflex flex-block-116">
<div className="nuberlist">{part.codeonimage || idx + 1}</div>
<div className="oemnuber">{part.oem}</div>
<div
className={`nuberlist ${
isSelected
? 'text-green-700 font-bold'
: isClicked
? 'text-red-700 font-bold'
: isHighlighted
? 'font-bold'
: ''
}`}
>
{part.codeonimage || idx + 1}
</div>
<div className={`oemnuber ${
isSelected
? 'text-green-800 font-semibold'
: isClicked
? 'text-red-800 font-semibold'
: isHighlighted
? 'font-semibold'
: ''
}`}>{part.oem}</div>
</div>
<div className={`partsname ${
isSelected
? 'text-green-800 font-semibold'
: isClicked
? 'text-red-800 font-semibold'
: isHighlighted
? 'font-semibold'
: ''
}`}>
{part.name}
</div>
<div className="partsname">{part.name}</div>
<div className="w-layout-hflex flex-block-117">
<button
className="button-3 w-button"
onClick={() => handlePriceClick(part)}
onClick={(e) => {
e.stopPropagation(); // Предотвращаем срабатывание onClick родительского элемента
handlePriceClick(part);
}}
style={{ cursor: 'pointer' }}
>
Цена
</button>
<div className="code-embed-16 w-embed">
<div
className="code-embed-16 w-embed cursor-pointer hover:opacity-70 transition-opacity"
onMouseEnter={(e) => handleInfoIconMouseEnter(e, part)}
onMouseLeave={handleInfoIconMouseLeave}
style={{ cursor: 'pointer' }}
>
<svg width="18" height="20" viewBox="0 0 18 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.1 13.5H9.89999V8.1H8.1V13.5ZM8.99999 6.3C9.25499 6.3 9.46889 6.2136 9.64169 6.0408C9.81449 5.868 9.90059 5.6544 9.89999 5.4C9.89939 5.1456 9.81299 4.932 9.64079 4.7592C9.46859 4.5864 9.25499 4.5 8.99999 4.5C8.745 4.5 8.53139 4.5864 8.35919 4.7592C8.187 4.932 8.1006 5.1456 8.1 5.4C8.0994 5.6544 8.1858 5.8683 8.35919 6.0417C8.53259 6.2151 8.74619 6.3012 8.99999 6.3ZM8.99999 18C7.755 18 6.585 17.7636 5.49 17.2908C4.395 16.818 3.4425 16.1769 2.6325 15.3675C1.8225 14.5581 1.1814 13.6056 0.709201 12.51C0.237001 11.4144 0.000601139 10.2444 1.13924e-06 9C-0.00059886 7.7556 0.235801 6.5856 0.709201 5.49C1.1826 4.3944 1.8237 3.4419 2.6325 2.6325C3.4413 1.8231 4.3938 1.182 5.49 0.7092C6.5862 0.2364 7.7562 0 8.99999 0C10.2438 0 11.4138 0.2364 12.51 0.7092C13.6062 1.182 14.5587 1.8231 15.3675 2.6325C16.1763 3.4419 16.8177 4.3944 17.2917 5.49C17.7657 6.5856 18.0018 7.7556 18 9C17.9982 10.2444 17.7618 11.4144 17.2908 12.51C16.8198 13.6056 16.1787 14.5581 15.3675 15.3675C14.5563 16.1769 13.6038 16.8183 12.51 17.2917C11.4162 17.7651 10.2462 18.0012 8.99999 18Z" fill="currentcolor" />
</svg>
@ -60,12 +334,52 @@ const KnotParts: React.FC<KnotPartsProps> = ({ parts, selectedCodeOnImage }) =>
);
})}
</div>
<BrandSelectionModal
isOpen={isBrandModalOpen}
onClose={() => setIsBrandModalOpen(false)}
articleNumber={selectedDetail?.oem || ''}
detailName={selectedDetail?.name || ''}
/>
{/* Красивый тултип с информацией о детали */}
{showTooltip && tooltipPart && (
<div
className="flex overflow-hidden flex-col items-center px-8 py-8 bg-slate-50 shadow-[0px_0px_20px_rgba(0,0,0,0.15)] rounded-2xl w-[350px] min-h-[220px] max-w-full fixed z-[9999]"
style={{
position: 'fixed',
left: tooltipPosition.x,
top: tooltipPosition.y,
pointerEvents: 'none',
}}
>
<div className="flex relative flex-col w-full">
{/* Заголовок и OEM */}
<div className="mb-4">
<div className="font-semibold text-lg text-black mb-1 truncate">{tooltipPart.name}</div>
{tooltipPart.oem && (
<div className="inline-block bg-gray-100 text-gray-700 text-xs font-mono px-2 py-1 rounded mb-1">OEM: {tooltipPart.oem}</div>
)}
</div>
{/* Характеристики */}
{tooltipPart.attributes && tooltipPart.attributes.length > 0 ? (
tooltipPart.attributes.map((attr: any, idx: number) => (
<div key={idx} className="flex gap-5 items-center mt-2 w-full whitespace-normal first:mt-0">
<div className="self-stretch my-auto text-gray-400 w-[150px] break-words">
{attr.name || attr.key}
</div>
<div className="self-stretch my-auto font-medium text-black break-words">
{attr.value}
</div>
</div>
))
) : (
<div className="flex flex-col items-center justify-center w-full py-8">
<div className="text-gray-400 mb-2">Дополнительная информация недоступна</div>
</div>
)}
{tooltipPart.note && (
<div className="flex flex-col mt-6 w-full">
<div className="text-gray-400 text-xs mb-1">Примечание</div>
<div className="font-medium text-black text-sm">{tooltipPart.note}</div>
</div>
)}
</div>
</div>
)}
</>
);
};

View File

@ -0,0 +1,81 @@
import React from 'react';
interface VehicleAttribute {
key: string;
name: string;
value: string;
}
interface VehicleAttributesTooltipProps {
show: boolean;
position: { x: number; y: number };
vehicleName?: string;
vehicleAttributes: VehicleAttribute[];
onMouseEnter?: () => void;
onMouseLeave?: () => void;
imageUrl?: string; // опционально, для будущего
}
const VehicleAttributesTooltip: React.FC<VehicleAttributesTooltipProps> = ({
show,
position,
vehicleName,
vehicleAttributes,
onMouseEnter,
onMouseLeave,
imageUrl,
}) => {
if (!show) return null;
return (
<div
className="flex overflow-hidden flex-col items-center px-8 py-8 bg-slate-50 shadow-[0px_0px_20px_rgba(0,0,0,0.15)] rounded-2xl w-[450px] max-w-full fixed z-[9999]"
style={{
left: `${position.x + 120}px`,
top: `${position.y}px`,
}}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{/* Фоновое изображение, если будет нужно */}
{imageUrl && (
<img
loading="lazy"
src={imageUrl}
className="object-cover absolute inset-0 size-full rounded-2xl opacity-10 pointer-events-none"
alt="vehicle background"
/>
)}
<div className="flex relative flex-col w-full">
{/* Заголовок */}
{vehicleName && (
<div className="font-semibold text-lg text-black mb-3 truncate">{vehicleName}</div>
)}
{/* Список характеристик или сообщение */}
{vehicleAttributes.length > 0 ? (
vehicleAttributes.map((attr, idx) => (
<div
key={idx}
className="grid grid-cols-[150px_1fr] gap-x-5 items-start mt-2 w-full first:mt-0"
>
<div className="text-gray-400 break-words whitespace-normal text-left">
{attr.name}
</div>
<div
className="font-medium text-black break-words whitespace-normal text-left justify-self-start"
style={{ textAlign: 'left' }}
>
{attr.value}
</div>
</div>
))
) : (
<div className="flex flex-col items-center justify-center w-full py-8">
<div className="text-gray-400 mb-2">Дополнительная информация недоступна</div>
</div>
)}
</div>
</div>
);
};
export default VehicleAttributesTooltip;

View File

@ -1,39 +1,43 @@
import React, { useState, useEffect, useRef } from 'react';
import { useQuery, useLazyQuery } from '@apollo/client';
import { GET_LAXIMO_CATEGORIES, GET_LAXIMO_QUICK_GROUPS, GET_LAXIMO_UNITS } from '@/lib/graphql/laximo';
import { useRouter } from 'next/router';
interface VinCategoryProps {
catalogCode: string;
vehicleId: string;
ssd: string;
catalogCode?: string;
vehicleId?: string;
ssd?: string;
onNodeSelect?: (node: any) => void;
activeTab: 'uzly' | 'manufacturer';
activeTab?: 'uzly' | 'manufacturer';
onQuickGroupSelect?: (group: any) => void;
onCategoryClick?: (e?: React.MouseEvent) => void;
openedPath?: string[];
setOpenedPath?: (path: string[]) => void;
}
const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd, onNodeSelect, activeTab, onQuickGroupSelect }) => {
const [selectedCategory, setSelectedCategory] = useState<any>(null);
const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd, onNodeSelect, activeTab = 'uzly', onQuickGroupSelect, onCategoryClick, openedPath = [], setOpenedPath = () => {} }) => {
const router = useRouter();
const [unitsByCategory, setUnitsByCategory] = useState<{ [key: string]: any[] }>({});
const lastCategoryIdRef = useRef<string | null>(null);
// Сброс выбранной категории при смене вкладки
useEffect(() => {
setSelectedCategory(null);
}, [activeTab]);
// Запрос для "Узлы"
const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
variables: { catalogCode, vehicleId, ssd },
skip: !catalogCode || vehicleId === undefined || vehicleId === null || activeTab !== 'uzly',
// Запрос для "Общие" (QuickGroups)
const { data: quickGroupsData, loading: quickGroupsLoading, error: quickGroupsError } = useQuery(GET_LAXIMO_QUICK_GROUPS, {
variables: { catalogCode: catalogCode || '', vehicleId: vehicleId || '', ssd: ssd || '' },
skip: !catalogCode || !vehicleId || activeTab !== 'uzly',
errorPolicy: 'all'
});
// Запрос для получения units (подкатегорий) в режиме "Узлы"
// Запрос для "От производителя" (Categories)
const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
variables: { catalogCode: catalogCode || '', vehicleId: vehicleId || '', ssd: ssd || '' },
skip: !catalogCode || !vehicleId || activeTab !== 'manufacturer',
errorPolicy: 'all'
});
// Запрос для получения units (подкатегорий) в режиме "От производителя"
const [getUnits] = useLazyQuery(GET_LAXIMO_UNITS, {
onCompleted: (data) => {
console.log('Units loaded:', data);
if (data && data.laximoUnits && lastCategoryIdRef.current) {
console.log('Setting units for category:', lastCategoryIdRef.current, data.laximoUnits);
setUnitsByCategory(prev => ({
...prev,
[lastCategoryIdRef.current!]: data.laximoUnits || []
@ -45,40 +49,83 @@ const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd,
}
});
// Запрос для "От производителя"
const { data: quickGroupsData, loading: quickGroupsLoading, error: quickGroupsError } = useQuery(GET_LAXIMO_QUICK_GROUPS, {
variables: { catalogCode, vehicleId, ssd },
skip: !catalogCode || vehicleId === undefined || vehicleId === null || activeTab !== 'manufacturer',
errorPolicy: 'all'
});
// categories теперь зависят от activeTab
let categories = activeTab === 'uzly' ? (quickGroupsData?.laximoQuickGroups || []) : (categoriesData?.laximoCategories || []);
let selectedCategory: any = null;
let currentLevel = 0;
let currentList = categories;
while (openedPath[currentLevel]) {
const found = currentList.find((cat: any) => (cat.quickgroupid || cat.categoryid || cat.id) === openedPath[currentLevel]);
if (!found) break;
selectedCategory = found;
currentList = found.children || [];
currentLevel++;
}
const categories = activeTab === 'uzly' ? (categoriesData?.laximoCategories || []) : (quickGroupsData?.laximoQuickGroups || []);
const loading = activeTab === 'uzly' ? categoriesLoading : quickGroupsLoading;
const error = activeTab === 'uzly' ? categoriesError : quickGroupsError;
const loading = activeTab === 'uzly' ? quickGroupsLoading : categoriesLoading;
const error = activeTab === 'uzly' ? quickGroupsError : categoriesError;
const handleBack = () => {
setSelectedCategory(null);
// Загружаем units для категории если нет children (аналогично VinLeftbar)
useEffect(() => {
if (selectedCategory && activeTab === 'manufacturer') {
const categoryId = selectedCategory.categoryid || selectedCategory.quickgroupid || selectedCategory.id;
// Если нет children и нет загруженных units - загружаем units
if ((!selectedCategory.children || selectedCategory.children.length === 0) &&
!unitsByCategory[categoryId]) {
console.log('🔄 VinCategory: Загружаем units для категории', categoryId);
lastCategoryIdRef.current = categoryId;
getUnits({
variables: {
catalogCode,
vehicleId,
ssd,
categoryId
}
});
}
}
}, [selectedCategory, activeTab, catalogCode, vehicleId, ssd, getUnits, unitsByCategory]);
// Функция для обновления openedPath и catpath в URL
const updatePath = (newPath: string[]) => {
console.log('🔄 VinCategory: updatePath вызван с newPath:', newPath);
setOpenedPath(newPath);
if (router) {
router.push(
{ pathname: router.pathname, query: { ...router.query, catpath: newPath.join(',') } },
undefined,
{ shallow: true }
);
}
};
const handleCategoryClick = (category: any) => {
const handleBack = () => {
updatePath(openedPath.slice(0, openedPath.length - 1));
};
const handleCategoryClick = (category: any, level: number) => {
if (onCategoryClick) {
onCategoryClick();
return;
}
const categoryId = category.quickgroupid || category.categoryid || category.id;
// Если это режим "От производителя", всегда пытаемся войти в категорию
if (activeTab === 'manufacturer') {
if (category.children && category.children.length > 0) {
setSelectedCategory(category);
} else if (category.link && onQuickGroupSelect) {
onQuickGroupSelect(category);
} else if (onNodeSelect) {
onNodeSelect(category);
}
} else {
// Логика для вкладки "Узлы"
if (category.children && category.children.length > 0) {
setSelectedCategory(category);
// Проверяем, открыта ли уже эта категория
if (openedPath[level] === categoryId) {
// Если уже открыта - закрываем
updatePath(openedPath.slice(0, level));
} else {
// Если нет children, грузим units (подкатегории)
const categoryId = category.categoryid || category.quickgroupid || category.id;
if (!unitsByCategory[categoryId]) {
// Если не открыта - открываем (добавляем в path)
updatePath([...openedPath.slice(0, level), categoryId]);
// Если у категории нет children, загружаем units
if ((!category.children || category.children.length === 0) && !unitsByCategory[categoryId]) {
console.log('🔄 VinCategory: handleCategoryClick загружает units для категории', categoryId);
lastCategoryIdRef.current = categoryId;
console.log('Loading units for category:', { categoryId, category });
getUnits({
variables: {
catalogCode,
@ -88,34 +135,57 @@ const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd,
}
});
}
setSelectedCategory(category);
}
} else {
// Режим "Общие" - используем старую логику
if (category.children && category.children.length > 0) {
if (openedPath[level] === categoryId) {
updatePath(openedPath.slice(0, level));
} else {
updatePath([...openedPath.slice(0, level), categoryId]);
}
} else if (category.link && onQuickGroupSelect) {
// Для вкладки "Общие" с link=true используем QuickGroup
onQuickGroupSelect(category);
}
}
};
const handleSubcategoryClick = (subcat: any) => {
if (activeTab === 'uzly' && onNodeSelect) {
// Для режима "Узлы" при клике на подкатегорию открываем KnotIn
if (activeTab === 'manufacturer' && onNodeSelect) {
// Для режима "От производителя" при клике на подкатегорию открываем KnotIn
onNodeSelect({
...subcat,
unitid: subcat.unitid || subcat.categoryid || subcat.quickgroupid || subcat.id
});
} else {
handleCategoryClick(subcat);
handleCategoryClick(subcat, 0);
}
};
// Если нет данных о транспортном средстве, показываем заглушку
if (!catalogCode || !vehicleId) {
return (
<div className="w-layout-vflex flex-block-14-copy-copy">
<div className="text-center py-8 text-gray-500">
<div className="text-lg font-medium mb-2">Каталог запчастей</div>
<div className="text-sm">Выберите автомобиль для просмотра каталога</div>
</div>
</div>
);
}
if (loading) return <div>Загрузка категорий...</div>;
if (error) return <div style={{ color: "red" }}>Ошибка: {error.message}</div>;
// Определяем, какие подкатегории показывать
let subcategories: any[] = [];
if (selectedCategory) {
if (activeTab === 'manufacturer') {
// Для вкладки "От производителя" используем children
if (activeTab === 'uzly') {
// Для вкладки "Общие" используем children
subcategories = selectedCategory.children || [];
} else {
// Для вкладки "Узлы" используем либо children, либо units
// Для вкладки "От производителя" используем либо children, либо units
if (selectedCategory.children && selectedCategory.children.length > 0) {
subcategories = selectedCategory.children;
} else {
@ -133,7 +203,7 @@ const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd,
<div
className="div-block-131"
key={cat.quickgroupid || cat.categoryid || cat.id || idx}
onClick={() => handleCategoryClick(cat)}
onClick={() => handleCategoryClick(cat, 0)}
style={{ cursor: "pointer" }}
>
<div className="text-block-57">{cat.name}</div>
@ -148,32 +218,58 @@ const VinCategory: React.FC<VinCategoryProps> = ({ catalogCode, vehicleId, ssd,
) : (
// Список подкатегорий
<>
<div className="div-block-131" onClick={handleBack} style={{ cursor: "pointer", fontWeight: 500 }}>
<div className="text-block-57"> Назад</div>
<div className="w-embed">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="24" width="24" height="24" rx="12" transform="rotate(90 24 0)" fill="currentcolor"></rect>
<path fillRule="evenodd" clipRule="evenodd" d="M10.9303 17L10 16.0825L14.1395 12L10 7.91747L10.9303 7L16 12L10.9303 17Z" fill="white"></path>
</svg>
</div>
</div>
{subcategories.length === 0 && <div style={{ color: "#888", padding: 8 }}>Нет подкатегорий</div>}
{subcategories.map((subcat: any, idx: number) => (
<div
className="div-block-131"
key={subcat.quickgroupid || subcat.categoryid || subcat.unitid || subcat.id || idx}
onClick={() => handleSubcategoryClick(subcat)}
style={{ cursor: "pointer" }}
>
<div className="text-block-57">{subcat.name}</div>
<div className="w-embed">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="24" width="24" height="24" rx="12" transform="rotate(90 24 0)" fill="currentcolor"></rect>
<path fillRule="evenodd" clipRule="evenodd" d="M10.9303 17L10 16.0825L14.1395 12L10 7.91747L10.9303 7L16 12L10.9303 17Z" fill="white"></path>
</svg>
{(() => {
// Найти текущий уровень вложенности для selectedCategory
let level = 0;
let list = categories;
while (openedPath[level] && list) {
const found = list.find((cat: any) => (cat.quickgroupid || cat.categoryid || cat.id) === openedPath[level]);
if (!found) break;
if (found === selectedCategory) break;
list = found.children || [];
level++;
}
// Показываем либо children, либо units
if (subcategories.length === 0) {
// Если загружаются units для категории без children
const categoryId = selectedCategory.categoryid || selectedCategory.quickgroupid || selectedCategory.id;
if (activeTab === 'manufacturer' &&
(!selectedCategory.children || selectedCategory.children.length === 0) &&
!unitsByCategory[categoryId]) {
return <div style={{ color: "#888", padding: 8 }}>Загружаем узлы...</div>;
}
return <div style={{ color: "#888", padding: 8 }}>Нет подкатегорий</div>;
}
return subcategories.map((subcat: any, idx: number) => (
<div
className="div-block-131"
key={subcat.quickgroupid || subcat.categoryid || subcat.unitid || subcat.id || idx}
onClick={() => {
// Для узлов (units) из режима "От производителя" сразу открываем KnotIn
if (activeTab === 'manufacturer' && subcat.unitid && onNodeSelect) {
console.log('🔍 VinCategory: Открываем узел напрямую:', subcat);
onNodeSelect({
...subcat,
unitid: subcat.unitid || subcat.quickgroupid || subcat.categoryid || subcat.id
});
} else {
handleCategoryClick(subcat, level + 1);
}
}}
style={{ cursor: "pointer" }}
>
<div className="text-block-57">{subcat.name}</div>
<div className="w-embed">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="24" width="24" height="24" rx="12" transform="rotate(90 24 0)" fill="currentcolor"></rect>
<path fillRule="evenodd" clipRule="evenodd" d="M10.9303 17L10 16.0825L14.1395 12L10 7.91747L10.9303 7L16 12L10.9303 17Z" fill="white"></path>
</svg>
</div>
</div>
</div>
))}
));
})()}
</>
)}
</div>

View File

@ -1,10 +1,10 @@
import React, { useState, useEffect } from "react";
import { useLazyQuery, useQuery } from '@apollo/client';
import { GET_LAXIMO_FULLTEXT_SEARCH, GET_LAXIMO_CATEGORIES, GET_LAXIMO_UNITS, GET_LAXIMO_QUICK_GROUPS, GET_LAXIMO_QUICK_DETAIL } from '@/lib/graphql/laximo';
import VinPartCard from './VinPartCard';
import { useRouter } from 'next/router';
interface VinLeftbarProps {
vehicleInfo: {
vehicleInfo?: {
catalog: string;
vehicleid: string;
ssd: string;
@ -20,6 +20,10 @@ interface VinLeftbarProps {
onNodeSelect?: (node: any) => void;
onActiveTabChange?: (tab: 'uzly' | 'manufacturer') => void;
onQuickGroupSelect?: (group: any) => void;
activeTab?: 'uzly' | 'manufacturer';
openedPath?: string[];
setOpenedPath?: (path: string[]) => void;
onCloseQuickGroup?: () => void;
}
interface QuickGroup {
@ -29,13 +33,12 @@ interface QuickGroup {
children?: QuickGroup[];
}
const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, onNodeSelect, onActiveTabChange, onQuickGroupSelect }) => {
const catalogCode = vehicleInfo.catalog;
const vehicleId = vehicleInfo.vehicleid;
const ssd = vehicleInfo.ssd;
const [openIndex, setOpenIndex] = useState<number | null>(null);
const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, onNodeSelect, onActiveTabChange, onQuickGroupSelect, activeTab: activeTabProp, openedPath = [], setOpenedPath = () => {}, onCloseQuickGroup }) => {
const router = useRouter();
const catalogCode = vehicleInfo?.catalog || '';
const vehicleId = vehicleInfo?.vehicleid || '';
const ssd = vehicleInfo?.ssd || '';
const [searchQuery, setSearchQuery] = useState('');
const [activeTab, setActiveTab] = useState<'uzly' | 'manufacturer'>('uzly');
const [executeSearch, { data, loading, error }] = useLazyQuery(GET_LAXIMO_FULLTEXT_SEARCH, { errorPolicy: 'all' });
const { data: categoriesData, loading: categoriesLoading, error: categoriesError } = useQuery(GET_LAXIMO_CATEGORIES, {
@ -59,11 +62,60 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
const lastCategoryIdRef = React.useRef<string | null>(null);
const handleToggle = (idx: number, categoryId: string) => {
setOpenIndex(openIndex === idx ? null : idx);
if (openIndex !== idx && !unitsByCategory[categoryId]) {
lastCategoryIdRef.current = categoryId;
getUnits({ variables: { catalogCode, vehicleId, ssd, categoryId } });
// --- Синхронизация openedPath с URL ---
// Обновляем openedPath и URL
const setOpenedPathAndUrl = (newPath: string[]) => {
setOpenedPath(newPath);
if (onCloseQuickGroup) onCloseQuickGroup();
const params = new URLSearchParams(router.query as any);
if (newPath.length > 0) {
params.set('catpath', newPath.join(','));
} else {
params.delete('catpath');
}
router.push(
{ pathname: router.pathname, query: { ...router.query, catpath: newPath.join(',') } },
undefined,
{ shallow: true }
);
};
// Восстанавливаем openedPath из URL
React.useEffect(() => {
if (typeof window === 'undefined') return;
const catpath = (router.query.catpath as string) || '';
if (catpath) {
setOpenedPath(catpath.split(',').filter(Boolean));
} else {
setOpenedPath([]);
}
}, [router.query.catpath]);
const handleToggle = (categoryId: string, level: number) => {
console.log('🔄 VinLeftbar: handleToggle вызван для categoryId:', categoryId, 'level:', level, 'текущий openedPath:', openedPath);
if (openedPath[level] === categoryId) {
const newPath = openedPath.slice(0, level);
console.log('🔄 VinLeftbar: Закрываем категорию, новый path:', newPath);
setOpenedPathAndUrl(newPath);
} else {
const newPath = [...openedPath.slice(0, level), categoryId];
console.log('🔄 VinLeftbar: Открываем категорию, новый path:', newPath);
setOpenedPathAndUrl(newPath);
// Загружаем units для категории, если они еще не загружены
if (activeTabProp === 'manufacturer' && !unitsByCategory[categoryId]) {
console.log('🔄 VinLeftbar: Загружаем units для categoryId:', categoryId);
lastCategoryIdRef.current = categoryId;
getUnits({
variables: {
catalogCode,
vehicleId,
ssd,
categoryId
}
});
}
}
};
@ -73,7 +125,6 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
console.error('SSD обязателен для поиска по названию');
return;
}
console.log('SEARCH PARAMS', { catalogCode, vehicleId, searchQuery: searchQuery.trim(), ssd });
executeSearch({
variables: {
catalogCode,
@ -102,7 +153,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
query: searchQuery
});
}
}, [searchResults, loading, error, searchQuery, onSearchResults]);
}, [searchResults, loading, error, searchQuery]);
// --- Новый блок: вычисляем доступность поиска ---
const isSearchAvailable = !!catalogCode && vehicleId !== undefined && vehicleId !== null && !!ssd && ssd.trim() !== '';
@ -112,81 +163,21 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
const showTips = isSearchAvailable && !searchQuery.trim() && !loading;
// --- QuickGroups (от производителя) ---
const [selectedQuickGroup, setSelectedQuickGroup] = useState<any | null>(null);
const { data: quickGroupsData, loading: quickGroupsLoading, error: quickGroupsError } = useQuery(GET_LAXIMO_QUICK_GROUPS, {
variables: { catalogCode, vehicleId, ssd },
skip: !catalogCode || vehicleId === undefined || vehicleId === null || activeTab !== 'manufacturer',
skip: !catalogCode || vehicleId === undefined || vehicleId === null,
errorPolicy: 'all'
});
const quickGroups = quickGroupsData?.laximoQuickGroups || [];
const [expandedQuickGroups, setExpandedQuickGroups] = useState<Set<string>>(new Set());
const handleQuickGroupToggle = (groupId: string) => {
setExpandedQuickGroups(prev => {
const newSet = new Set(prev);
if (newSet.has(groupId)) {
newSet.delete(groupId);
} else {
newSet.add(groupId);
}
return newSet;
});
};
const handleQuickGroupClick = (group: any) => {
if (group.link) {
setSelectedQuickGroup(group);
const handleQuickGroupToggle = (groupId: string, level: number) => {
if (openedPath[level] === groupId) {
setOpenedPathAndUrl(openedPath.slice(0, level));
} else {
handleQuickGroupToggle(group.quickgroupid);
setOpenedPathAndUrl([...openedPath.slice(0, level), groupId]);
}
};
// Детали выбранной группы (если link: true)
console.log('QuickDetail QUERY VARS', {
catalogCode,
vehicleId,
quickGroupId: selectedQuickGroup?.quickgroupid,
ssd
});
const skipQuickDetail =
!selectedQuickGroup ||
!catalogCode ||
vehicleId === undefined ||
vehicleId === null ||
!selectedQuickGroup?.quickgroupid ||
!ssd ||
ssd.trim() === '';
console.log('QuickDetail QUERY VARS', {
catalogCode,
vehicleId,
quickGroupId: selectedQuickGroup?.quickgroupid,
ssd: ssd ? `${ssd.substring(0, 30)}...` : 'отсутствует'
});
console.log('QuickDetail SKIP CONDITIONS', {
hasSelectedQuickGroup: !!selectedQuickGroup,
hasCatalogCode: !!catalogCode,
hasVehicleId: vehicleId !== undefined && vehicleId !== null,
hasQuickGroupId: !!selectedQuickGroup?.quickgroupid,
hasSsd: !!ssd && ssd.trim() !== '',
skipQuickDetail
});
const { data: quickDetailData, loading: quickDetailLoading, error: quickDetailError } = useQuery(GET_LAXIMO_QUICK_DETAIL, {
variables: selectedQuickGroup?.quickgroupid && !skipQuickDetail ? {
catalogCode,
vehicleId,
quickGroupId: selectedQuickGroup.quickgroupid,
ssd
} : undefined,
skip: skipQuickDetail,
errorPolicy: 'all'
});
const quickDetail = quickDetailData?.laximoQuickDetail;
// === Полнотекстовый поиск деталей (аналогично FulltextSearchSection) ===
const [fulltextQuery, setFulltextQuery] = useState('');
const [executeFulltextSearch, { data: fulltextData, loading: fulltextLoading, error: fulltextError }] = useLazyQuery(GET_LAXIMO_FULLTEXT_SEARCH, { errorPolicy: 'all' });
@ -243,7 +234,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
isSearching: true
});
}
}, [fulltextData, fulltextLoading, fulltextError, onSearchResults]);
}, [fulltextData, fulltextLoading, fulltextError, fulltextQuery]);
const handleFulltextKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
@ -254,11 +245,17 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
const fulltextResults = fulltextData?.laximoFulltextSearch?.details || [];
useEffect(() => {
if (onActiveTabChange) {
onActiveTabChange(activeTab);
}
}, [activeTab, onActiveTabChange]);
// Если нет данных о транспортном средстве, показываем заглушку
if (!vehicleInfo) {
return (
<div className="w-layout-vflex vinleftbar">
<div className="text-center py-8 text-gray-500">
<div className="text-lg font-medium mb-2">Поиск запчастей</div>
<div className="text-sm">Выберите автомобиль для поиска запчастей</div>
</div>
</div>
);
}
return (
<div className="w-layout-vflex vinleftbar">
@ -316,14 +313,16 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
className={
searchQuery
? 'button-23 w-button'
: activeTab === 'uzly'
: activeTabProp === 'uzly'
? 'button-3 w-button'
: 'button-23 w-button'
}
onClick={e => {
e.preventDefault();
if (searchQuery) setSearchQuery('');
setActiveTab('uzly');
if (onActiveTabChange) onActiveTabChange('uzly');
if (onQuickGroupSelect) onQuickGroupSelect(null);
if (onCloseQuickGroup) onCloseQuickGroup();
}}
>
Узлы
@ -333,78 +332,24 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
className={
searchQuery
? 'button-23 w-button'
: activeTab === 'manufacturer'
: activeTabProp === 'manufacturer'
? 'button-3 w-button'
: 'button-23 w-button'
}
onClick={e => {
e.preventDefault();
if (searchQuery) setSearchQuery('');
setActiveTab('manufacturer');
if (onActiveTabChange) onActiveTabChange('manufacturer');
if (onQuickGroupSelect) onQuickGroupSelect(null);
if (onCloseQuickGroup) onCloseQuickGroup();
}}
>
От производителя
</a>
</div>
{/* Tab content start */}
{activeTab === 'uzly' ? (
categoriesLoading ? (
<div style={{ padding: 16, textAlign: 'center' }}>Загружаем категории...</div>
) : categoriesError ? (
<div style={{ color: 'red', padding: 16 }}>Ошибка загрузки категорий: {categoriesError.message}</div>
) : (
<>
{categories.map((category: any, idx: number) => {
const isOpen = openIndex === idx;
const subcategories = category.children && category.children.length > 0
? category.children
: unitsByCategory[category.quickgroupid] || [];
return (
<div
key={category.quickgroupid}
data-hover="false"
data-delay="0"
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
>
<div
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`}
onClick={() => handleToggle(idx, category.quickgroupid)}
style={{ cursor: "pointer" }}
>
<div className="w-icon-dropdown-toggle"></div>
<div className="text-block-56">{category.name}</div>
</div>
<nav className={`dropdown-list-4 w-dropdown-list${isOpen ? " w--open" : ""}`}>
{subcategories.length > 0 ? (
subcategories.map((subcat: any) => (
<a
href="#"
key={subcat.quickgroupid || subcat.unitid}
className="dropdown-link-3 w-dropdown-link pl-0"
onClick={e => {
e.preventDefault();
if (onNodeSelect) {
onNodeSelect({
...subcat,
unitid: subcat.unitid || subcat.quickgroupid || subcat.id
});
}
}}
>
{subcat.name}
</a>
))
) : (
<span style={{ color: '#888', padding: 8 }}>Нет подкатегорий</span>
)}
</nav>
</div>
);
})}
</>
)
) : (
// Manufacturer tab content (QuickGroups)
{activeTabProp === 'uzly' ? (
// Общие (QuickGroups - бывшие "От производителя")
quickGroupsLoading ? (
<div style={{ padding: 16, textAlign: 'center' }}>Загружаем группы быстрого поиска...</div>
) : quickGroupsError ? (
@ -413,7 +358,7 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
<>
{(quickGroups as QuickGroup[]).map((group: QuickGroup) => {
const hasChildren = group.children && group.children.length > 0;
const isOpen = expandedQuickGroups.has(group.quickgroupid);
const isOpen = openedPath.includes(group.quickgroupid);
if (!hasChildren) {
return (
@ -423,8 +368,11 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
className="dropdown-link-3 w-dropdown-link"
onClick={(e) => {
e.preventDefault();
// Если это конечная группа с link=true, открываем QuickGroup
if (group.link && onQuickGroupSelect) {
onQuickGroupSelect(group);
} else {
handleQuickGroupToggle(group.quickgroupid, 0);
}
}}
>
@ -441,8 +389,11 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
>
<div
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`}
onClick={() => handleQuickGroupToggle(group.quickgroupid)}
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open active" : ""}`}
onClick={(e) => {
e.preventDefault();
handleQuickGroupToggle(group.quickgroupid, 0);
}}
style={{ cursor: "pointer" }}
>
<div className="w-icon-dropdown-toggle"></div>
@ -451,18 +402,21 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
<nav className={`dropdown-list-4 w-dropdown-list${isOpen ? " w--open" : ""}`}>
{group.children?.map((child: QuickGroup) => {
const hasSubChildren = child.children && child.children.length > 0;
const isChildOpen = expandedQuickGroups.has(child.quickgroupid);
const isChildOpen = openedPath.includes(child.quickgroupid);
if (!hasSubChildren) {
return (
<a
href="#"
key={child.quickgroupid}
className="dropdown-link-3 w-dropdown-link "
className="dropdown-link-3 w-dropdown-link"
onClick={(e) => {
e.preventDefault();
// Если это конечная группа с link=true, открываем QuickGroup
if (child.link && onQuickGroupSelect) {
onQuickGroupSelect(child);
} else {
handleQuickGroupToggle(child.quickgroupid, 1);
}
}}
>
@ -479,8 +433,11 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
className={`dropdown-4 w-dropdown pl-0${isChildOpen ? " w--open" : ""}`}
>
<div
className={`dropdown-toggle-card w-dropdown-toggle pl-0${isChildOpen ? " w--open" : ""}`}
onClick={() => handleQuickGroupToggle(child.quickgroupid)}
className={`dropdown-toggle-card w-dropdown-toggle pl-0${isChildOpen ? " w--open active" : ""}`}
onClick={(e) => {
e.preventDefault();
handleQuickGroupToggle(child.quickgroupid, 2);
}}
style={{ cursor: "pointer" }}
>
<div className="w-icon-dropdown-toggle"></div>
@ -491,11 +448,14 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
<a
href="#"
key={subChild.quickgroupid}
className="dropdown-link-3 w-dropdown-link "
className="dropdown-link-3 w-dropdown-link"
onClick={(e) => {
e.preventDefault();
// Если это конечная группа с link=true, открываем QuickGroup
if (subChild.link && onQuickGroupSelect) {
onQuickGroupSelect(subChild);
} else {
handleQuickGroupToggle(subChild.quickgroupid, 3);
}
}}
>
@ -511,50 +471,81 @@ const VinLeftbar: React.FC<VinLeftbarProps> = ({ vehicleInfo, onSearchResults, o
);
})}
{/* Quick Detail Modal */}
{selectedQuickGroup && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg max-w-2xl w-full max-h-[90vh] overflow-y-auto p-6">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold">{selectedQuickGroup.name}</h3>
<button
onClick={() => setSelectedQuickGroup(null)}
className="text-gray-500 hover:text-gray-700"
>
</button>
</div>
{quickDetailLoading ? (
<div className="text-center py-4">Загружаем детали...</div>
) : quickDetailError ? (
<div className="text-red-600 py-4">Ошибка загрузки деталей: {quickDetailError.message}</div>
) : quickDetail?.units?.length > 0 ? (
<div className="space-y-4">
{quickDetail.units.map((unit: any) => (
<div key={unit.unitid} className="border border-gray-200 rounded-lg p-4">
<div className="font-medium text-gray-900 mb-2">{unit.name}</div>
{unit.details && unit.details.length > 0 && (
<div className="space-y-2">
{unit.details.map((detail: any) => (
<div key={detail.detailid} className="flex items-center justify-between bg-gray-50 p-2 rounded">
<span className="font-medium text-gray-700">{detail.name}</span>
<span className="text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded">
OEM: {detail.oem}
</span>
</div>
))}
</div>
)}
</div>
))}
</div>
) : (
<div className="text-center text-gray-500 py-4">Нет деталей для этой группы</div>
)}
</>
)
) : (
// От производителя (Categories - узлы)
categoriesLoading ? (
<div style={{ padding: 16, textAlign: 'center' }}>Загружаем категории...</div>
) : categoriesError ? (
<div style={{ color: 'red', padding: 16 }}>Ошибка загрузки категорий: {categoriesError.message}</div>
) : (
<>
{categories.map((category: any, idx: number) => {
// ИСПРАВЛЕНИЕ: Используем тот же приоритет ID, что и в VinCategory
const categoryId = category.quickgroupid || category.categoryid || category.id;
const isOpen = openedPath.includes(categoryId);
const subcategories = category.children && category.children.length > 0
? category.children
: unitsByCategory[categoryId] || [];
return (
<div
key={categoryId}
data-hover="false"
data-delay="0"
className={`dropdown-4 w-dropdown${isOpen ? " w--open" : ""}`}
>
<div
className={`dropdown-toggle-3 w-dropdown-toggle${isOpen ? " w--open" : ""}`}
onClick={(e) => {
e.preventDefault();
handleToggle(categoryId, 0);
}}
style={{ cursor: "pointer" }}
>
<div className="w-icon-dropdown-toggle"></div>
<div className="text-block-56">{category.name}</div>
</div>
<nav className={`dropdown-list-4 w-dropdown-list${isOpen ? " w--open" : ""}`}>
{subcategories.length > 0 ? (
subcategories.map((subcat: any) => (
<a
href="#"
key={subcat.quickgroupid || subcat.unitid}
className="dropdown-link-3 w-dropdown-link pl-0"
onClick={e => {
e.preventDefault();
// Для вкладки "От производителя" всегда открываем узел, не используем QuickGroup
if (onNodeSelect) {
const nodeToSelect = {
...subcat,
unitid: subcat.unitid || subcat.quickgroupid || subcat.id
};
// ОТЛАДКА: Логируем передачу узла
console.log('🔍 VinLeftbar передает узел:', {
unitId: nodeToSelect.unitid,
unitName: nodeToSelect.name,
hasOriginalSsd: !!subcat.ssd,
originalSsd: subcat.ssd ? `${subcat.ssd.substring(0, 50)}...` : 'отсутствует',
finalSsd: nodeToSelect.ssd ? `${nodeToSelect.ssd.substring(0, 50)}...` : 'отсутствует'
});
onNodeSelect(nodeToSelect);
}
}}
>
{subcat.name}
</a>
))
) : (
<span style={{ color: '#888', padding: 8 }}>Нет подкатегорий</span>
)}
</nav>
</div>
</div>
)}
);
})}
</>
)
)}

View File

@ -1,22 +1,27 @@
import React, { useState } from "react";
import React from "react";
import { useRouter } from 'next/router';
import BrandSelectionModal from '../BrandSelectionModal';
interface VinPartCardProps {
n?: number;
oem: string;
name: string;
onPriceClick?: () => void;
catalogCode?: string;
vehicleId?: string;
}
const VinPartCard: React.FC<VinPartCardProps> = ({ n, oem, name, onPriceClick }) => {
const VinPartCard: React.FC<VinPartCardProps> = ({ n, oem, name, onPriceClick, catalogCode, vehicleId }) => {
const router = useRouter();
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
const handlePriceClick = (e: React.MouseEvent) => {
e.preventDefault();
if (onPriceClick) onPriceClick();
setIsBrandModalOpen(true);
if (catalogCode && vehicleId !== undefined) {
// Переходим на страницу выбора бренда
const url = `/vehicle-search/${catalogCode}/${vehicleId}/part/${oem}/brands?detailName=${encodeURIComponent(name || '')}`;
router.push(url);
}
};
return (
@ -36,12 +41,6 @@ const VinPartCard: React.FC<VinPartCardProps> = ({ n, oem, name, onPriceClick })
</div>
</div>
</div>
<BrandSelectionModal
isOpen={isBrandModalOpen}
onClose={() => setIsBrandModalOpen(false)}
articleNumber={oem}
detailName={name}
/>
</>
);
};

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useQuery } from '@apollo/client';
import { useRouter } from 'next/router';
import { GET_LAXIMO_QUICK_DETAIL } from '@/lib/graphql/laximo';
import BrandSelectionModal from '../BrandSelectionModal';
interface VinQuickProps {
quickGroup: any;
@ -13,6 +13,8 @@ interface VinQuickProps {
}
const VinQuick: React.FC<VinQuickProps> = ({ quickGroup, catalogCode, vehicleId, ssd, onBack, onNodeSelect }) => {
const router = useRouter();
const { data, loading, error } = useQuery(GET_LAXIMO_QUICK_DETAIL, {
variables: {
catalogCode,
@ -24,9 +26,6 @@ const VinQuick: React.FC<VinQuickProps> = ({ quickGroup, catalogCode, vehicleId,
});
const quickDetail = data?.laximoQuickDetail;
const [isBrandModalOpen, setIsBrandModalOpen] = useState(false);
const [selectedDetail, setSelectedDetail] = useState<any>(null);
const handleUnitClick = (unit: any) => {
onNodeSelect({
...unit,
@ -34,18 +33,20 @@ const VinQuick: React.FC<VinQuickProps> = ({ quickGroup, catalogCode, vehicleId,
name: unit.name,
catalogCode,
vehicleId,
ssd
ssd: unit.ssd || ssd // Используем SSD узла, а не родительский
});
};
const handleDetailClick = (detail: any) => {
setSelectedDetail(detail);
setIsBrandModalOpen(true);
};
const handleCloseBrandModal = () => {
setIsBrandModalOpen(false);
setSelectedDetail(null);
if (detail.oem) {
// Переходим на страницу выбора бренда
const url = `/vehicle-search/${catalogCode}/${vehicleId}/part/${detail.oem}/brands?detailName=${encodeURIComponent(detail.name || '')}`;
router.push(url);
}
};
const [shownCounts, setShownCounts] = useState<{ [unitid: string]: number }>({});
return (
<div className="w-full">
{/* <button onClick={onBack} className="mb-4 px-4 py-2 bg-gray-200 rounded self-start">Назад</button> */}
@ -63,6 +64,8 @@ const VinQuick: React.FC<VinQuickProps> = ({ quickGroup, catalogCode, vehicleId,
alt={unit.name}
className="image-26"
onError={e => { (e.currentTarget as HTMLImageElement).src = '/images/image-44.jpg'; }}
onClick={() => handleUnitClick(unit)}
style={{ cursor: 'pointer' }}
/>
) : (
<img src="/images/image-44.jpg" alt="Нет изображения" className="image-26" />
@ -70,30 +73,52 @@ const VinQuick: React.FC<VinQuickProps> = ({ quickGroup, catalogCode, vehicleId,
</div>
<div className="knot-img">
<h1 className="heading-19">{unit.name}</h1>
{unit.details && unit.details.length > 0 && unit.details.map((detail: any) => (
<div className="w-layout-hflex flex-block-115" key={detail.detailid}>
<div className="oemnuber">{detail.oem}</div>
<div className="partsname">{detail.name}</div>
<a href="#" className="button-3 w-button" onClick={e => { e.preventDefault(); handleDetailClick(detail); }}>Показать цены</a>
</div>
))}
<a href="#" className="showallparts w-button" onClick={e => { e.preventDefault(); handleUnitClick(unit); }}>Подробнее</a>
{(() => {
const details = unit.details || [];
const total = details.length;
const shownCount = shownCounts[unit.unitid] ?? 3;
return (
<>
{details.slice(0, shownCount).map((detail: any, index: number) => (
<div className="w-layout-hflex flex-block-115" key={`${unit.unitid}-${detail.detailid || index}`}>
<div className="oemnuber">{detail.oem}</div>
<div className="partsname">{detail.name}</div>
<a href="#" className="button-3 w-button" onClick={e => { e.preventDefault(); handleDetailClick(detail); }}>Показать цены</a>
</div>
))}
{total > 3 && shownCount < total && (
<div className="flex gap-2 mt-2 w-full">
<button
className="expand-btn"
onClick={() => setShownCounts(prev => ({ ...prev, [unit.unitid]: total }))}
style={{ border: '1px solid #EC1C24', borderRadius: 8, background: '#fff', color: '#222', padding: '6px 18px', minWidth: 180 }}
>
Развернуть
<svg width="16" height="16" viewBox="0 0 16 16" style={{ display: 'inline', verticalAlign: 'middle', marginLeft: 4 }}>
<path d="M4 6l4 4 4-4" stroke="#222" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</button>
<button
className="showall-btn"
onClick={() => handleUnitClick(unit)}
style={{ background: '#e9eef5', borderRadius: 8, color: '#222', padding: '6px 18px', border: 'none'}}
>
Показать все
</button>
</div>
)}
{shownCount >= total && (
<a href="#" className="showallparts w-button" onClick={e => { e.preventDefault(); handleUnitClick(unit); }}>Подробнее</a>
)}
</>
);
})()}
</div>
</div>
))
) : (
<div className="text-center text-gray-500 py-4">Нет деталей для этой группы</div>
)}
{isBrandModalOpen && selectedDetail && (
<BrandSelectionModal
isOpen={isBrandModalOpen}
onClose={handleCloseBrandModal}
articleNumber={selectedDetail.oem}
detailName={selectedDetail.name}
/>
)}
</div>
);
};

View File

@ -1,7 +1,10 @@
'use client'
import React, { createContext, useContext, useReducer, useEffect, useState } from 'react'
import React, { createContext, useContext, useState, useEffect } from 'react'
import { useMutation, useQuery } from '@apollo/client'
import { CartState, CartContextType, CartItem, DeliveryInfo } from '@/types/cart'
import { ADD_TO_CART, REMOVE_FROM_CART, UPDATE_CART_ITEM_QUANTITY, CLEAR_CART, GET_CART } from '@/lib/graphql'
import { toast } from 'react-hot-toast'
// Начальное состояние корзины
const initialState: CartState = {
@ -22,38 +25,53 @@ const initialState: CartState = {
isLoading: false
}
// Типы действий
type CartAction =
| { type: 'ADD_ITEM'; payload: Omit<CartItem, 'id' | 'selected' | 'favorite'> }
| { type: 'REMOVE_ITEM'; payload: string }
| { type: 'UPDATE_QUANTITY'; payload: { id: string; quantity: number } }
| { type: 'TOGGLE_SELECT'; payload: string }
| { type: 'TOGGLE_FAVORITE'; payload: string }
| { type: 'UPDATE_COMMENT'; payload: { id: string; comment: string } }
| { type: 'UPDATE_ORDER_COMMENT'; payload: string }
| { type: 'SELECT_ALL' }
| { type: 'REMOVE_ALL' }
| { type: 'REMOVE_SELECTED' }
| { type: 'UPDATE_DELIVERY'; payload: Partial<DeliveryInfo> }
| { type: 'CLEAR_CART' }
| { type: 'LOAD_CART'; payload: CartItem[] }
| { type: 'LOAD_FULL_STATE'; payload: { items: CartItem[]; delivery: DeliveryInfo; orderComment: string } }
| { type: 'SET_LOADING'; payload: boolean }
| { type: 'SET_ERROR'; payload: string }
// Создаем контекст
const CartContext = createContext<CartContextType | undefined>(undefined)
// Функция для генерации ID
const generateId = () => Math.random().toString(36).substr(2, 9)
// Утилитарная функция для парсинга количества в наличии
const parseStock = (stockStr: string | number | undefined): number => {
if (stockStr === undefined || stockStr === null) return 0
if (typeof stockStr === 'number') return stockStr
if (typeof stockStr === 'string') {
// Извлекаем числа из строки типа "10 шт" или "В наличии: 5"
const match = stockStr.match(/\d+/)
return match ? parseInt(match[0], 10) : 0
}
return 0
}
// Функция для расчета итогов
const calculateSummary = (items: CartItem[], deliveryPrice: number) => {
const selectedItems = items.filter(item => item.selected)
const totalItems = selectedItems.reduce((sum, item) => sum + item.quantity, 0)
const totalPrice = selectedItems.reduce((sum, item) => sum + (item.price * item.quantity), 0)
const totalDiscount = selectedItems.reduce((sum, item) => {
const discount = item.originalPrice ? (item.originalPrice - item.price) * item.quantity : 0
return sum + discount
}, 0)
const finalPrice = totalPrice + deliveryPrice
// Функция для преобразования backend cart items в frontend format
const transformBackendItems = (backendItems: any[]): CartItem[] => {
return backendItems.map(item => ({
id: item.id,
productId: item.productId,
offerKey: item.offerKey,
name: item.name,
description: item.description,
brand: item.brand,
article: item.article,
price: item.price,
currency: item.currency || 'RUB',
quantity: item.quantity,
stock: item.stock,
deliveryTime: item.deliveryTime,
warehouse: item.warehouse,
supplier: item.supplier,
isExternal: item.isExternal,
image: item.image,
selected: true,
favorite: false,
comment: ''
}))
}
// Функция для подсчета статистики корзины
const calculateSummary = (items: CartItem[]) => {
const totalItems = items.reduce((sum, item) => sum + item.quantity, 0)
const totalPrice = items.reduce((sum, item) => sum + (item.price * item.quantity), 0)
const totalDiscount = 0 // TODO: Implement discount logic
const deliveryPrice = 39
const finalPrice = totalPrice + deliveryPrice - totalDiscount
return {
totalItems,
@ -64,332 +82,317 @@ const calculateSummary = (items: CartItem[], deliveryPrice: number) => {
}
}
// Редьюсер корзины
const cartReducer = (state: CartState, action: CartAction): CartState => {
switch (action.type) {
case 'ADD_ITEM': {
const existingItemIndex = state.items.findIndex(
item =>
(item.productId && item.productId === action.payload.productId) ||
(item.offerKey && item.offerKey === action.payload.offerKey)
)
let newItems: CartItem[]
if (existingItemIndex >= 0) {
// Увеличиваем количество существующего товара
newItems = state.items.map((item, index) =>
index === existingItemIndex
? { ...item, quantity: item.quantity + action.payload.quantity }
: item
)
} else {
// Добавляем новый товар
const newItem: CartItem = {
...action.payload,
id: generateId(),
selected: true,
favorite: false
}
newItems = [...state.items, newItem]
}
const newSummary = calculateSummary(newItems, state.delivery.price)
return {
...state,
items: newItems,
summary: newSummary
}
}
case 'REMOVE_ITEM': {
const newItems = state.items.filter(item => item.id !== action.payload)
const newSummary = calculateSummary(newItems, state.delivery.price)
return {
...state,
items: newItems,
summary: newSummary
}
}
case 'UPDATE_QUANTITY': {
const newItems = state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: Math.max(1, action.payload.quantity) }
: item
)
const newSummary = calculateSummary(newItems, state.delivery.price)
return {
...state,
items: newItems,
summary: newSummary
}
}
case 'TOGGLE_SELECT': {
const newItems = state.items.map(item =>
item.id === action.payload
? { ...item, selected: !item.selected }
: item
)
const newSummary = calculateSummary(newItems, state.delivery.price)
return {
...state,
items: newItems,
summary: newSummary
}
}
case 'TOGGLE_FAVORITE': {
const newItems = state.items.map(item =>
item.id === action.payload
? { ...item, favorite: !item.favorite }
: item
)
return {
...state,
items: newItems
}
}
case 'UPDATE_COMMENT': {
const newItems = state.items.map(item =>
item.id === action.payload.id
? { ...item, comment: action.payload.comment }
: item
)
return {
...state,
items: newItems
}
}
case 'UPDATE_ORDER_COMMENT': {
return {
...state,
orderComment: action.payload
}
}
case 'SELECT_ALL': {
const allSelected = state.items.every(item => item.selected)
const newItems = state.items.map(item => ({
...item,
selected: !allSelected
}))
const newSummary = calculateSummary(newItems, state.delivery.price)
return {
...state,
items: newItems,
summary: newSummary
}
}
case 'REMOVE_ALL': {
const newSummary = calculateSummary([], state.delivery.price)
return {
...state,
items: [],
summary: newSummary
}
}
case 'REMOVE_SELECTED': {
const newItems = state.items.filter(item => !item.selected)
const newSummary = calculateSummary(newItems, state.delivery.price)
return {
...state,
items: newItems,
summary: newSummary
}
}
case 'UPDATE_DELIVERY': {
const newDelivery = { ...state.delivery, ...action.payload }
const newSummary = calculateSummary(state.items, newDelivery.price)
return {
...state,
delivery: newDelivery,
summary: newSummary
}
}
case 'CLEAR_CART': {
const newSummary = calculateSummary([], state.delivery.price)
return {
...state,
items: [],
summary: newSummary
}
}
case 'LOAD_CART': {
const newSummary = calculateSummary(action.payload, state.delivery.price)
return {
...state,
items: action.payload,
summary: newSummary
}
}
case 'LOAD_FULL_STATE': {
const newSummary = calculateSummary(action.payload.items, action.payload.delivery.price || state.delivery.price)
return {
...state,
items: action.payload.items,
delivery: action.payload.delivery,
orderComment: action.payload.orderComment,
summary: newSummary
}
}
case 'SET_LOADING': {
return {
...state,
isLoading: action.payload
}
}
case 'SET_ERROR': {
return {
...state,
error: action.payload,
isLoading: false
}
}
default:
return state
}
}
// Создание контекста
const CartContext = createContext<CartContextType | undefined>(undefined)
// Провайдер корзины
// Провайдер контекста
export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(cartReducer, initialState)
const [isInitialized, setIsInitialized] = useState(false)
const [state, setState] = useState<CartState>(initialState)
const [error, setError] = useState<string>('')
// Загрузка корзины из localStorage при инициализации
// GraphQL operations
const { data: cartData, loading: cartLoading, refetch: refetchCart } = useQuery(GET_CART, {
errorPolicy: 'ignore' // Don't show errors for unauthenticated users
})
const [addToCartMutation] = useMutation(ADD_TO_CART)
const [removeFromCartMutation] = useMutation(REMOVE_FROM_CART)
const [updateQuantityMutation] = useMutation(UPDATE_CART_ITEM_QUANTITY)
const [clearCartMutation] = useMutation(CLEAR_CART)
// Load cart from backend when component mounts or cart data changes
useEffect(() => {
if (typeof window === 'undefined') return
if (cartData?.getCart) {
const backendItems = transformBackendItems(cartData.getCart.items)
const summary = calculateSummary(backendItems)
console.log('🔄 Загружаем состояние корзины из localStorage...')
setState(prev => ({
...prev,
items: backendItems,
summary,
isLoading: false
}))
} else {
setState(prev => ({
...prev,
items: [],
summary: calculateSummary([]),
isLoading: false
}))
}
}, [cartData])
const savedCartState = localStorage.getItem('cartState')
if (savedCartState) {
try {
const cartState = JSON.parse(savedCartState)
console.log('✅ Найдено сохраненное состояние корзины:', cartState)
// Загружаем полное состояние корзины
dispatch({ type: 'LOAD_FULL_STATE', payload: cartState })
} catch (error) {
console.error('❌ Ошибка загрузки корзины из localStorage:', error)
// Попытаемся загрузить старый формат (только товары)
const savedCart = localStorage.getItem('cart')
if (savedCart) {
try {
const cartItems = JSON.parse(savedCart)
console.log('✅ Найдены товары в старом формате:', cartItems)
dispatch({ type: 'LOAD_CART', payload: cartItems })
} catch (error) {
console.error('❌ Ошибка загрузки старой корзины:', error)
// Set loading state
useEffect(() => {
setState(prev => ({
...prev,
isLoading: cartLoading
}))
}, [cartLoading])
// GraphQL-based cart operations
const addItem = async (item: Omit<CartItem, 'id' | 'selected' | 'favorite'>) => {
try {
setError('')
setState(prev => ({ ...prev, isLoading: true }))
console.log('🛒 Adding item to backend cart:', item)
const { data } = await addToCartMutation({
variables: {
input: {
productId: item.productId || null,
offerKey: item.offerKey || null,
name: item.name,
description: item.description,
brand: item.brand,
article: item.article,
price: item.price,
currency: item.currency || 'RUB',
quantity: item.quantity,
stock: item.stock || null,
deliveryTime: item.deliveryTime || null,
warehouse: item.warehouse || null,
supplier: item.supplier || null,
isExternal: item.isExternal || false,
image: item.image || null
}
}
})
if (data?.addToCart?.success) {
// Update local state with backend response
if (data.addToCart.cart) {
const backendItems = transformBackendItems(data.addToCart.cart.items)
const summary = calculateSummary(backendItems)
setState(prev => ({
...prev,
items: backendItems,
summary,
isLoading: false
}))
}
// Refetch to ensure data consistency
refetchCart()
return { success: true }
} else {
const errorMessage = data?.addToCart?.error || 'Ошибка добавления товара'
setError(errorMessage)
setState(prev => ({ ...prev, isLoading: false }))
toast.error(errorMessage)
return { success: false, error: errorMessage }
}
} else {
console.log(' Сохраненное состояние корзины не найдено')
} catch (error) {
console.error('❌ Error adding item to cart:', error)
const errorMessage = 'Ошибка добавления товара в корзину'
setError(errorMessage)
setState(prev => ({ ...prev, isLoading: false }))
toast.error(errorMessage)
return { success: false, error: errorMessage }
}
}
setIsInitialized(true)
}, [])
const removeItem = async (id: string) => {
try {
setError('')
setState(prev => ({ ...prev, isLoading: true }))
// Сохранение полного состояния корзины в localStorage при изменении (только после инициализации)
useEffect(() => {
if (!isInitialized || typeof window === 'undefined') return
console.log('🗑️ Removing item from backend cart:', id)
const stateToSave = {
items: state.items,
delivery: state.delivery,
orderComment: state.orderComment
const { data } = await removeFromCartMutation({
variables: { itemId: id }
})
if (data?.removeFromCart?.success) {
// Update local state
if (data.removeFromCart.cart) {
const backendItems = transformBackendItems(data.removeFromCart.cart.items)
const summary = calculateSummary(backendItems)
setState(prev => ({
...prev,
items: backendItems,
summary,
isLoading: false
}))
}
toast.success(data.removeFromCart.message || 'Товар удален из корзины')
refetchCart()
} else {
const errorMessage = data?.removeFromCart?.error || 'Ошибка удаления товара'
setError(errorMessage)
setState(prev => ({ ...prev, isLoading: false }))
toast.error(errorMessage)
}
} catch (error) {
console.error('❌ Error removing item from cart:', error)
const errorMessage = 'Ошибка удаления товара из корзины'
setError(errorMessage)
setState(prev => ({ ...prev, isLoading: false }))
toast.error(errorMessage)
}
console.log('💾 Сохраняем состояние корзины:', stateToSave)
localStorage.setItem('cartState', JSON.stringify(stateToSave))
// Сохраняем также старый формат для совместимости
localStorage.setItem('cart', JSON.stringify(state.items))
}, [state.items, state.delivery, state.orderComment, isInitialized])
// Функции для работы с корзиной
const addItem = (item: Omit<CartItem, 'id' | 'selected' | 'favorite'>) => {
dispatch({ type: 'ADD_ITEM', payload: item })
}
const removeItem = (id: string) => {
dispatch({ type: 'REMOVE_ITEM', payload: id })
const updateQuantity = async (id: string, quantity: number) => {
try {
if (quantity < 1) return
setError('')
setState(prev => ({ ...prev, isLoading: true }))
console.log('📝 Updating item quantity in backend cart:', id, quantity)
const { data } = await updateQuantityMutation({
variables: { itemId: id, quantity }
})
if (data?.updateCartItemQuantity?.success) {
// Update local state
if (data.updateCartItemQuantity.cart) {
const backendItems = transformBackendItems(data.updateCartItemQuantity.cart.items)
const summary = calculateSummary(backendItems)
setState(prev => ({
...prev,
items: backendItems,
summary,
isLoading: false
}))
}
toast.success(data.updateCartItemQuantity.message || 'Количество обновлено')
refetchCart()
} else {
const errorMessage = data?.updateCartItemQuantity?.error || 'Ошибка обновления количества'
setError(errorMessage)
setState(prev => ({ ...prev, isLoading: false }))
toast.error(errorMessage)
}
} catch (error) {
console.error('❌ Error updating item quantity:', error)
const errorMessage = 'Ошибка обновления количества товара'
setError(errorMessage)
setState(prev => ({ ...prev, isLoading: false }))
toast.error(errorMessage)
}
}
const updateQuantity = (id: string, quantity: number) => {
dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } })
const clearCart = async () => {
try {
setError('')
setState(prev => ({ ...prev, isLoading: true }))
console.log('🧹 Clearing backend cart')
const { data } = await clearCartMutation()
if (data?.clearCart?.success) {
setState(prev => ({
...prev,
items: [],
summary: calculateSummary([]),
isLoading: false
}))
toast.success(data.clearCart.message || 'Корзина очищена')
refetchCart()
} else {
const errorMessage = data?.clearCart?.error || 'Ошибка очистки корзины'
setError(errorMessage)
setState(prev => ({ ...prev, isLoading: false }))
toast.error(errorMessage)
}
} catch (error) {
console.error('❌ Error clearing cart:', error)
const errorMessage = 'Ошибка очистки корзины'
setError(errorMessage)
setState(prev => ({ ...prev, isLoading: false }))
toast.error(errorMessage)
}
}
// Local-only operations (not synced with backend)
const toggleSelect = (id: string) => {
dispatch({ type: 'TOGGLE_SELECT', payload: id })
setState(prev => ({
...prev,
items: prev.items.map(item =>
item.id === id ? { ...item, selected: !item.selected } : item
)
}))
}
const toggleFavorite = (id: string) => {
dispatch({ type: 'TOGGLE_FAVORITE', payload: id })
setState(prev => ({
...prev,
items: prev.items.map(item =>
item.id === id ? { ...item, favorite: !item.favorite } : item
)
}))
}
const updateComment = (id: string, comment: string) => {
dispatch({ type: 'UPDATE_COMMENT', payload: { id, comment } })
setState(prev => ({
...prev,
items: prev.items.map(item =>
item.id === id ? { ...item, comment } : item
)
}))
}
const updateOrderComment = (comment: string) => {
dispatch({ type: 'UPDATE_ORDER_COMMENT', payload: comment })
setState(prev => ({
...prev,
orderComment: comment
}))
}
const selectAll = () => {
dispatch({ type: 'SELECT_ALL' })
setState(prev => ({
...prev,
items: prev.items.map(item => ({ ...item, selected: true }))
}))
}
const removeAll = () => {
dispatch({ type: 'REMOVE_ALL' })
clearCart()
}
const removeSelected = () => {
dispatch({ type: 'REMOVE_SELECTED' })
}
const updateDelivery = (delivery: Partial<DeliveryInfo>) => {
dispatch({ type: 'UPDATE_DELIVERY', payload: delivery })
}
const clearCart = () => {
dispatch({ type: 'CLEAR_CART' })
// Очищаем localStorage при очистке корзины
if (typeof window !== 'undefined') {
localStorage.removeItem('cartState')
localStorage.removeItem('cart')
const removeSelected = async () => {
const selectedItems = state.items.filter(item => item.selected)
for (const item of selectedItems) {
await removeItem(item.id)
}
}
const updateDelivery = (delivery: Partial<DeliveryInfo>) => {
setState(prev => ({
...prev,
delivery: { ...prev.delivery, ...delivery }
}))
}
const clearError = () => {
setError('')
}
// Check if item is in cart (using backend data)
const isInCart = (productId?: string, offerKey?: string, article?: string, brand?: string): boolean => {
return state.items.some(item => {
if (productId && item.productId === productId) return true
if (offerKey && item.offerKey === offerKey) return true
if (article && brand && item.article === article && item.brand === brand) return true
return false
})
}
const contextValue: CartContextType = {
state,
state: {
...state,
error
},
addItem,
removeItem,
updateQuantity,
@ -401,7 +404,9 @@ export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children
removeAll,
removeSelected,
updateDelivery,
clearCart
clearCart,
clearError,
isInCart
}
return (
@ -411,7 +416,6 @@ export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children
)
}
// Хук для использования контекста корзины
export const useCart = (): CartContextType => {
const context = useContext(CartContext)

View File

@ -4,6 +4,7 @@ import React, { createContext, useContext, useReducer, useEffect, ReactNode } fr
import { useMutation, useQuery } from '@apollo/client'
import toast from 'react-hot-toast'
import { GET_FAVORITES, ADD_TO_FAVORITES, REMOVE_FROM_FAVORITES, CLEAR_FAVORITES } from '@/lib/favorites-queries'
import CloseIcon from '@/components/CloseIcon'
// Типы
export interface FavoriteItem {
@ -133,7 +134,14 @@ const FavoritesProvider: React.FC<FavoritesProviderProps> = ({ children }) => {
const [removeFavoriteMutation] = useMutation(REMOVE_FROM_FAVORITES, {
onCompleted: () => {
toast.success('Товар удален из избранного')
toast('Товар удален из избранного', {
icon: <CloseIcon size={20} color="#fff" />,
style: {
background: '#6b7280', // Серый фон
color: '#fff', // Белый текст
},
duration: 3000,
})
},
onError: (error) => {
console.error('Ошибка удаления из избранного:', error)

View File

@ -231,6 +231,7 @@ export const useCatalogPrices = (): UseCatalogPricesReturn => {
price: cheapestOffer.price,
currency: cheapestOffer.currency || 'RUB',
quantity: 1,
stock: cheapestOffer.quantity, // передаем информацию о наличии
deliveryTime: cheapestOffer.deliveryDays?.toString() || '0',
warehouse: cheapestOffer.warehouse || 'Склад',
supplier: cheapestOffer.supplierName || 'Неизвестный поставщик',
@ -238,10 +239,14 @@ export const useCatalogPrices = (): UseCatalogPricesReturn => {
image: '', // Убираем мокап-фотку, изображения будут загружаться отдельно
};
addItem(itemToAdd);
const result = await addItem(itemToAdd);
// Показываем уведомление
toast.success(`Товар "${brand} ${articleNumber}" добавлен в корзину за ${cheapestOffer.price}`);
if (result.success) {
// Показываем уведомление
toast.success(`Товар "${brand} ${articleNumber}" добавлен в корзину за ${cheapestOffer.price}`);
} else {
toast.error(result.error || 'Ошибка добавления товара в корзину');
}
} catch (error) {
console.error('Ошибка добавления в корзину:', error);

30
src/hooks/useMetaTags.ts Normal file
View File

@ -0,0 +1,30 @@
import { useRouter } from 'next/router';
import { useMemo } from 'react';
import { getMetaByPath } from '@/lib/meta-config';
interface MetaTagsData {
title: string;
description: string;
keywords: string;
ogTitle?: string;
ogDescription?: string;
}
export const useMetaTags = (customMeta?: Partial<MetaTagsData>): MetaTagsData => {
const router = useRouter();
const metaData = useMemo(() => {
// Получаем базовые meta-теги для текущего пути
const baseMeta = getMetaByPath(router.asPath);
// Объединяем с пользовательскими meta-тегами
return {
...baseMeta,
...customMeta
};
}, [router.asPath, customMeta]);
return metaData;
};
export default useMetaTags;

View File

@ -1,6 +1,7 @@
import { useState, useEffect } from 'react';
import { useState, useCallback } from 'react';
import { useLazyQuery } from '@apollo/client';
import { SEARCH_PRODUCT_OFFERS } from '@/lib/graphql';
import { useCart } from '@/contexts/CartContext';
interface ProductOffer {
offerKey: string;
@ -15,6 +16,7 @@ interface ProductOffer {
warehouse: string;
supplier: string;
canPurchase: boolean;
isInCart: boolean;
}
interface ProductPriceData {
@ -25,35 +27,60 @@ interface ProductPriceData {
externalOffers: ProductOffer[];
analogs: number;
hasInternalStock: boolean;
isInCart: boolean;
};
}
interface CartItemInput {
productId?: string;
offerKey?: string;
article: string;
brand: string;
quantity: number;
}
interface ProductPriceVariables {
articleNumber: string;
brand: string;
cartItems?: CartItemInput[];
}
export const useProductPrices = (products: Array<{ code: string; brand: string; id: string }>) => {
export const useProductPrices = () => {
const [pricesMap, setPricesMap] = useState<Map<string, ProductOffer | null>>(new Map());
const [loadingPrices, setLoadingPrices] = useState<Set<string>>(new Set());
const [loadedPrices, setLoadedPrices] = useState<Set<string>>(new Set());
const { state: cartState } = useCart();
const [searchOffers] = useLazyQuery<ProductPriceData, ProductPriceVariables>(SEARCH_PRODUCT_OFFERS);
const loadPrice = async (product: { code: string; brand: string; id: string }) => {
const loadPrice = useCallback(async (product: { code: string; brand: string; id: string }) => {
const key = `${product.id}_${product.code}_${product.brand}`;
if (pricesMap.has(key) || loadingPrices.has(key)) {
return; // Уже загружено или загружается
// Если уже загружено или загружается - не делаем повторный запрос
if (loadedPrices.has(key) || loadingPrices.has(key)) {
return;
}
console.log('💰 Загружаем цену для:', product.code, product.brand);
setLoadingPrices(prev => new Set([...prev, key]));
try {
// Преобразуем товары корзины в формат для запроса
const cartItems: CartItemInput[] = cartState.items
.filter(item => item.article && item.brand) // Фильтруем товары с обязательными полями
.map(item => ({
productId: item.productId,
offerKey: item.offerKey,
article: item.article!,
brand: item.brand!,
quantity: item.quantity
}));
const result = await searchOffers({
variables: {
articleNumber: product.code,
brand: product.brand
brand: product.brand,
cartItems
}
});
@ -87,35 +114,31 @@ export const useProductPrices = (products: Array<{ code: string; brand: string;
newSet.delete(key);
return newSet;
});
setLoadedPrices(prev => new Set([...prev, key]));
}
};
}, [searchOffers, loadedPrices, loadingPrices]);
useEffect(() => {
// Загружаем цены для всех товаров с небольшой задержкой между запросами
products.forEach((product, index) => {
setTimeout(() => {
loadPrice(product);
}, index * 100); // Задержка 100мс между запросами
});
}, [products]);
const getPrice = (product: { code: string; brand: string; id: string }) => {
const getPrice = useCallback((product: { code: string; brand: string; id: string }) => {
const key = `${product.id}_${product.code}_${product.brand}`;
return pricesMap.get(key);
};
}, [pricesMap]);
const isLoadingPrice = (product: { code: string; brand: string; id: string }) => {
const isLoadingPrice = useCallback((product: { code: string; brand: string; id: string }) => {
const key = `${product.id}_${product.code}_${product.brand}`;
return loadingPrices.has(key);
};
}, [loadingPrices]);
const loadPriceOnDemand = (product: { code: string; brand: string; id: string }) => {
loadPrice(product);
};
const ensurePriceLoaded = useCallback((product: { code: string; brand: string; id: string }) => {
const key = `${product.id}_${product.code}_${product.brand}`;
if (!loadedPrices.has(key) && !loadingPrices.has(key)) {
loadPrice(product);
}
}, [loadPrice, loadedPrices, loadingPrices]);
return {
getPrice,
isLoadingPrice,
loadPriceOnDemand
loadPrice,
ensurePriceLoaded
};
};

View File

@ -20,16 +20,25 @@ const authLink = setContext((_, { headers }) => {
const user = JSON.parse(userData);
// Создаем токен в формате, который ожидает CMS
token = `client_${user.id}`;
console.log('Apollo Client: создан токен:', token);
console.log('Apollo Client: user data:', user);
console.log('Apollo Client: заголовки:', { authorization: `Bearer ${token}` });
console.log('Apollo Client: создан токен для авторизованного пользователя:', token);
} catch (error) {
console.error('Apollo Client: ошибка парсинга userData:', error);
localStorage.removeItem('userData');
localStorage.removeItem('authToken');
}
} else {
console.log('Apollo Client: userData не найден в localStorage');
}
// Если нет авторизованного пользователя, создаем анонимную сессию для корзины
if (!token) {
let sessionId = localStorage.getItem('anonymousSessionId');
if (!sessionId) {
// Генерируем уникальный ID сессии
sessionId = 'anon_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
localStorage.setItem('anonymousSessionId', sessionId);
console.log('Apollo Client: создана новая анонимная сессия:', sessionId);
}
token = `client_${sessionId}`;
console.log('Apollo Client: используется анонимная сессия:', token);
}
}

61
src/lib/cookie-utils.ts Normal file
View File

@ -0,0 +1,61 @@
interface CookiePreferences {
necessary: boolean;
analytics: boolean;
marketing: boolean;
functional: boolean;
}
export const getCookieConsent = (): string | null => {
if (typeof window === 'undefined') return null;
return localStorage.getItem('cookieConsent');
};
export const getCookiePreferences = (): CookiePreferences | null => {
if (typeof window === 'undefined') return null;
const preferences = localStorage.getItem('cookiePreferences');
return preferences ? JSON.parse(preferences) : null;
};
export const hasConsentForAnalytics = (): boolean => {
const preferences = getCookiePreferences();
return preferences?.analytics || false;
};
export const hasConsentForMarketing = (): boolean => {
const preferences = getCookiePreferences();
return preferences?.marketing || false;
};
export const hasConsentForFunctional = (): boolean => {
const preferences = getCookiePreferences();
return preferences?.functional || false;
};
export const isConsentGiven = (): boolean => {
const consent = getCookieConsent();
return consent !== null && consent !== 'declined';
};
export const resetCookieConsent = (): void => {
if (typeof window === 'undefined') return;
localStorage.removeItem('cookieConsent');
localStorage.removeItem('cookiePreferences');
};
// Функция для интеграции с аналитикой (например, Google Analytics)
export const initializeAnalytics = (): void => {
if (!hasConsentForAnalytics()) return;
// Здесь можно добавить инициализацию Google Analytics или других сервисов
console.log('Analytics initialized with user consent');
};
// Функция для интеграции с маркетинговыми инструментами
export const initializeMarketing = (): void => {
if (!hasConsentForMarketing()) return;
// Здесь можно добавить инициализацию маркетинговых пикселей
console.log('Marketing tools initialized with user consent');
};
export type { CookiePreferences };

View File

@ -1,5 +1,64 @@
import { gql } from '@apollo/client'
export const GET_BEST_PRICE_PRODUCTS = gql`
query GetBestPriceProducts {
bestPriceProducts {
id
productId
discount
isActive
sortOrder
product {
id
name
article
brand
retailPrice
images {
url
alt
}
}
}
}
`
export const GET_TOP_SALES_PRODUCTS = gql`
query GetTopSalesProducts {
topSalesProducts {
id
productId
isActive
sortOrder
product {
id
name
article
brand
retailPrice
images {
url
alt
}
}
}
}
`
export const GET_HERO_BANNERS = gql`
query GetHeroBanners {
heroBanners {
id
title
subtitle
imageUrl
linkUrl
isActive
sortOrder
}
}
`
export const CHECK_CLIENT_BY_PHONE = gql`
mutation CheckClientByPhone($phone: String!) {
checkClientByPhone(phone: $phone) {
@ -328,6 +387,8 @@ export const CREATE_PAYMENT = gql`
}
`
export const GET_ORDERS = gql`
query GetOrders($clientId: String, $status: OrderStatus, $search: String, $limit: Int, $offset: Int) {
orders(clientId: $clientId, status: $status, search: $search, limit: $limit, offset: $offset) {
@ -540,7 +601,9 @@ export const FIND_LAXIMO_VEHICLE_BY_WIZARD = gql`
query FindLaximoVehicleByWizard($catalogCode: String!, $ssd: String!) {
laximoFindVehicleByWizard(catalogCode: $catalogCode, ssd: $ssd) {
vehicleid
name
brand
catalog
model
modification
year
@ -548,12 +611,34 @@ export const FIND_LAXIMO_VEHICLE_BY_WIZARD = gql`
engine
notes
ssd
transmission
date
manufactured
framecolor
trimcolor
engine_info
engineno
market
prodRange
prodPeriod
destinationregion
creationregion
datefrom
dateto
modelyearfrom
modelyearto
options
description
grade
attributes {
key
name
value
}
}
}
`;
export const FIND_LAXIMO_VEHICLE_BY_PLATE = gql`
query FindLaximoVehicleByPlate($catalogCode: String!, $plateNumber: String!) {
laximoFindVehicleByPlate(catalogCode: $catalogCode, plateNumber: $plateNumber) {
@ -587,6 +672,11 @@ export const FIND_LAXIMO_VEHICLE_BY_PLATE = gql`
options
description
grade
attributes {
key
name
value
}
}
}
`;
@ -624,12 +714,15 @@ export const FIND_LAXIMO_VEHICLE_BY_PLATE_GLOBAL = gql`
options
description
grade
attributes {
key
name
value
}
}
}
`;
export const FIND_LAXIMO_PART_REFERENCES = gql`
query FindLaximoPartReferences($partNumber: String!) {
laximoFindPartReferences(partNumber: $partNumber)
@ -669,6 +762,11 @@ export const FIND_LAXIMO_APPLICABLE_VEHICLES = gql`
options
description
grade
attributes {
key
name
value
}
}
}
`;
@ -695,6 +793,30 @@ export const FIND_LAXIMO_VEHICLES_BY_PART_NUMBER = gql`
engine
notes
ssd
transmission
date
manufactured
framecolor
trimcolor
engine_info
engineno
market
prodRange
prodPeriod
destinationregion
creationregion
datefrom
dateto
modelyearfrom
modelyearto
options
description
grade
attributes {
key
name
value
}
}
}
}
@ -799,6 +921,7 @@ export const GET_LAXIMO_UNITS = gql`
code
imageurl
largeimageurl
ssd
children {
quickgroupid
name
@ -806,6 +929,7 @@ export const GET_LAXIMO_UNITS = gql`
code
imageurl
largeimageurl
ssd
children {
quickgroupid
name
@ -813,6 +937,7 @@ export const GET_LAXIMO_UNITS = gql`
code
imageurl
largeimageurl
ssd
}
}
}
@ -831,6 +956,7 @@ export const GET_LAXIMO_QUICK_DETAIL = gql`
description
imageurl
largeimageurl
ssd
details {
detailid
name
@ -999,14 +1125,25 @@ export const GET_LAXIMO_UNIT_IMAGE_MAP = gql`
`
export const SEARCH_PRODUCT_OFFERS = gql`
query SearchProductOffers($articleNumber: String!, $brand: String!) {
searchProductOffers(articleNumber: $articleNumber, brand: $brand) {
query SearchProductOffers($articleNumber: String!, $brand: String!, $cartItems: [CartItemInput!]) {
searchProductOffers(articleNumber: $articleNumber, brand: $brand, cartItems: $cartItems) {
articleNumber
brand
name
description
hasInternalStock
totalOffers
isInCart
stockCalculation {
totalInternalStock
totalExternalStock
availableInternalOffers
availableExternalOffers
hasInternalStock
hasExternalStock
totalStock
hasAnyStock
}
images {
id
url
@ -1042,6 +1179,7 @@ export const SEARCH_PRODUCT_OFFERS = gql`
available
rating
supplier
isInCart
}
externalOffers {
offerKey
@ -1059,6 +1197,7 @@ export const SEARCH_PRODUCT_OFFERS = gql`
weight
volume
canPurchase
isInCart
}
analogs {
brand
@ -1066,6 +1205,16 @@ export const SEARCH_PRODUCT_OFFERS = gql`
name
type
}
stockCalculation {
totalInternalStock
totalExternalStock
availableInternalOffers
availableExternalOffers
hasInternalStock
hasExternalStock
totalStock
hasAnyStock
}
}
}
`
@ -1257,6 +1406,23 @@ export const GET_PARTSINDEX_CATEGORIES = gql`
}
`;
// Навигационные категории с иконками
export const GET_NAVIGATION_CATEGORIES = gql`
query GetNavigationCategories {
navigationCategories {
id
partsIndexCatalogId
partsIndexGroupId
name
catalogName
groupName
icon
sortOrder
isHidden
}
}
`;
// Новый запрос для получения товаров каталога PartsIndex
export const GET_PARTSINDEX_CATALOG_ENTITIES = gql`
query GetPartsIndexCatalogEntities(
@ -1542,3 +1708,217 @@ export const GET_CATEGORY_PRODUCTS_WITH_OFFERS = gql`
}
}
`
// Запрос для получения товаров дня
export const GET_DAILY_PRODUCTS = gql`
query GetDailyProducts($displayDate: String!) {
dailyProducts(displayDate: $displayDate) {
id
discount
isActive
sortOrder
product {
id
name
slug
article
brand
retailPrice
wholesalePrice
images {
id
url
alt
order
}
}
}
}
`
// Запрос для получения новых поступлений
export const GET_NEW_ARRIVALS = gql`
query GetNewArrivals($limit: Int) {
newArrivals(limit: $limit) {
id
name
slug
article
brand
retailPrice
wholesalePrice
createdAt
images {
id
url
alt
order
}
categories {
id
name
slug
}
}
}
`;
// Cart mutations and queries
export const GET_CART = gql`
query GetCart {
getCart {
id
clientId
items {
id
productId
offerKey
name
description
brand
article
price
currency
quantity
stock
deliveryTime
warehouse
supplier
isExternal
image
createdAt
updatedAt
}
createdAt
updatedAt
}
}
`;
export const ADD_TO_CART = gql`
mutation AddToCart($input: AddToCartInput!) {
addToCart(input: $input) {
success
message
error
cart {
id
clientId
items {
id
productId
offerKey
name
description
brand
article
price
currency
quantity
stock
deliveryTime
warehouse
supplier
isExternal
image
createdAt
updatedAt
}
createdAt
updatedAt
}
}
}
`;
export const REMOVE_FROM_CART = gql`
mutation RemoveFromCart($itemId: ID!) {
removeFromCart(itemId: $itemId) {
success
message
error
cart {
id
clientId
items {
id
productId
offerKey
name
description
brand
article
price
currency
quantity
stock
deliveryTime
warehouse
supplier
isExternal
image
}
createdAt
updatedAt
}
}
}
`;
export const UPDATE_CART_ITEM_QUANTITY = gql`
mutation UpdateCartItemQuantity($itemId: ID!, $quantity: Int!) {
updateCartItemQuantity(itemId: $itemId, quantity: $quantity) {
success
message
error
cart {
id
clientId
items {
id
productId
offerKey
name
description
brand
article
price
currency
quantity
stock
deliveryTime
warehouse
supplier
isExternal
image
}
createdAt
updatedAt
}
}
}
`;
export const CLEAR_CART = gql`
mutation ClearCart {
clearCart {
success
message
error
cart {
id
clientId
items {
id
name
brand
article
quantity
price
}
createdAt
updatedAt
}
}
}
`;

View File

@ -84,6 +84,7 @@ export const GET_LAXIMO_UNITS = gql`
code
imageurl
largeimageurl
ssd
children {
quickgroupid
name
@ -91,6 +92,7 @@ export const GET_LAXIMO_UNITS = gql`
code
imageurl
largeimageurl
ssd
children {
quickgroupid
name
@ -98,6 +100,7 @@ export const GET_LAXIMO_UNITS = gql`
code
imageurl
largeimageurl
ssd
}
}
}
@ -135,6 +138,7 @@ export const GET_LAXIMO_QUICK_DETAIL = gql`
code
imageurl
largeimageurl
ssd
details {
detailid
name

View File

@ -24,6 +24,20 @@ export const GET_PARTS_SEARCH_HISTORY = gql`
}
`;
// Запрос для получения последних поисковых запросов для автодополнения
export const GET_RECENT_SEARCH_QUERIES = gql`
query GetRecentSearchQueries($limit: Int = 5) {
partsSearchHistory(limit: $limit, offset: 0) {
items {
id
searchQuery
searchType
createdAt
}
}
}
`;
export const DELETE_SEARCH_HISTORY_ITEM = gql`
mutation DeletePartsSearchHistoryItem($id: ID!) {
deletePartsSearchHistoryItem(id: $id)

397
src/lib/meta-config.ts Normal file
View File

@ -0,0 +1,397 @@
interface MetaConfig {
title: string;
description: string;
keywords: string;
ogTitle?: string;
ogDescription?: string;
}
export const metaConfig: Record<string, MetaConfig> = {
// Главная страница
'/': {
title: 'Protek - Автозапчасти и аксессуары для всех марок автомобилей',
description: 'Protek - широкий ассортимент автозапчастей и аксессуаров для всех марок автомобилей. Быстрая доставка по России, гарантия качества, низкие цены.',
keywords: 'автозапчасти, запчасти для автомобилей, автоаксессуары, доставка запчастей, protek, протек',
ogTitle: 'Protek - Автозапчасти и аксессуары',
ogDescription: 'Широкий ассортимент автозапчастей и аксессуаров для всех марок автомобилей. Быстрая доставка, гарантия качества.'
},
// Каталог
'/catalog': {
title: 'Каталог автозапчастей - Protek',
description: 'Полный каталог автозапчастей для всех марок автомобилей. Более 1 миллиона наименований запчастей в наличии и под заказ.',
keywords: 'каталог запчастей, автозапчасти каталог, запчасти для авто, поиск запчастей',
ogTitle: 'Каталог автозапчастей - Protek',
ogDescription: 'Полный каталог автозапчастей для всех марок автомобилей. Более 1 миллиона наименований.'
},
// Марки автомобилей
'/brands': {
title: 'Все марки автомобилей - Каталог запчастей Protek',
description: 'Полный каталог автомобильных брендов для поиска запчастей. Выберите марку вашего автомобиля и найдите нужные запчасти.',
keywords: 'марки автомобилей, бренды авто, запчасти по маркам, автомобильные марки',
ogTitle: 'Все марки автомобилей - Protek',
ogDescription: 'Полный каталог автомобильных брендов для поиска запчастей.'
},
// Поиск по VIN
'/vin': {
title: 'Поиск запчастей по VIN коду - Protek',
description: 'Быстрый и точный поиск автозапчастей по VIN коду автомобиля. Определите совместимые запчасти для вашего авто.',
keywords: 'поиск по VIN, VIN код, запчасти по VIN, определение запчастей, совместимость',
ogTitle: 'Поиск запчастей по VIN коду - Protek',
ogDescription: 'Быстрый и точный поиск автозапчастей по VIN коду автомобиля.'
},
// Контакты
'/contacts': {
title: 'Контакты - Protek',
description: 'Контактная информация компании Protek. Адреса магазинов, телефоны, режим работы. Свяжитесь с нами для консультации.',
keywords: 'контакты protek, адрес, телефон, режим работы, магазины запчастей',
ogTitle: 'Контакты - Protek',
ogDescription: 'Контактная информация компании Protek. Адреса магазинов, телефоны, режим работы.'
},
// О компании
'/about': {
title: 'О компании Protek - Автозапчасти и аксессуары',
description: 'Компания Protek - надежный поставщик автозапчастей с многолетним опытом. Узнайте больше о нашей истории и преимуществах.',
keywords: 'о компании protek, история компании, преимущества, автозапчасти',
ogTitle: 'О компании Protek',
ogDescription: 'Компания Protek - надежный поставщик автозапчастей с многолетним опытом.'
},
// Оптовые продажи
'/wholesale': {
title: 'Оптовые продажи автозапчастей - Protek',
description: 'Оптовые продажи автозапчастей для автосервисов и дилеров. Специальные цены, гибкие условия сотрудничества.',
keywords: 'оптовые продажи, запчасти оптом, для автосервисов, дилерам, оптовые цены',
ogTitle: 'Оптовые продажи автозапчастей - Protek',
ogDescription: 'Оптовые продажи автозапчастей для автосервисов и дилеров. Специальные цены.'
},
// Корзина
'/cart': {
title: 'Корзина - Protek',
description: 'Корзина покупок. Оформите заказ на выбранные автозапчасти с быстрой доставкой.',
keywords: 'корзина покупок, оформление заказа, заказать запчасти',
ogTitle: 'Корзина - Protek',
ogDescription: 'Корзина покупок. Оформите заказ на выбранные автозапчасти.'
},
// Новости
'/news': {
title: 'Новости - Protek',
description: 'Актуальные новости компании Protek, события автомобильной индустрии и мира автозапчастей.',
keywords: 'новости protek, автомобильные новости, события автоиндустрии',
ogTitle: 'Новости - Protek',
ogDescription: 'Актуальные новости компании Protek и автомобильной индустрии.'
},
// Карточка товара
'/card': {
title: 'Карточка товара - Protek',
description: 'Подробная информация о товаре: характеристики, цены, наличие, отзывы.',
keywords: 'карточка товара, характеристики запчасти, цена, наличие',
ogTitle: 'Карточка товара - Protek',
ogDescription: 'Подробная информация о товаре: характеристики, цены, наличие.'
},
// Поиск автомобилей по артикулу
'/vehicles-by-part': {
title: 'Автомобили по артикулу - Protek',
description: 'Поиск автомобилей, в которых используется деталь с указанным артикулом.',
keywords: 'автомобили по артикулу, применимость детали, где используется',
ogTitle: 'Автомобили по артикулу - Protek',
ogDescription: 'Поиск автомобилей, в которых используется деталь с указанным артикулом.'
},
// Страницы оплаты
'/payment/success': {
title: 'Оплата прошла успешно - Protek',
description: 'Ваш платеж успешно обработан. Спасибо за покупку! Мы приступим к обработке заказа.',
keywords: 'оплата успешна, платеж прошел, заказ оплачен',
ogTitle: 'Оплата прошла успешно - Protek',
ogDescription: 'Ваш платеж успешно обработан. Спасибо за покупку!'
},
'/payment/cancelled': {
title: 'Оплата отменена - Protek',
description: 'Платеж был отменен. Вы можете попробовать оплатить заказ повторно.',
keywords: 'оплата отменена, платеж отклонен, повторная оплата',
ogTitle: 'Оплата отменена - Protek',
ogDescription: 'Платеж был отменен. Вы можете попробовать оплатить заказ повторно.'
},
'/payment/failed': {
title: 'Ошибка оплаты - Protek',
description: 'Произошла ошибка при обработке платежа. Попробуйте еще раз или выберите другой способ оплаты.',
keywords: 'ошибка оплаты, платеж не прошел, проблема с оплатой',
ogTitle: 'Ошибка оплаты - Protek',
ogDescription: 'Произошла ошибка при обработке платежа. Попробуйте еще раз.'
},
'/payment/invoice': {
title: 'Счёт на оплату - Protek',
description: 'Счёт на оплату заказа. Вы можете оплатить удобным для вас способом.',
keywords: 'счет на оплату, инвойс, оплата заказа',
ogTitle: 'Счёт на оплату - Protek',
ogDescription: 'Счёт на оплату заказа. Вы можете оплатить удобным для вас способом.'
},
// Дополнительные страницы профиля
'/profile-req': {
title: 'Реквизиты - Личный кабинет Protek',
description: 'Управление реквизитами организации в личном кабинете.',
keywords: 'реквизиты организации, личный кабинет, данные компании',
ogTitle: 'Реквизиты - Protek',
ogDescription: 'Управление реквизитами организации в личном кабинете.'
},
'/profile-acts': {
title: 'Акты сверки - Личный кабинет Protek',
description: 'Акты сверки взаиморасчетов в личном кабинете.',
keywords: 'акты сверки, взаиморасчеты, личный кабинет',
ogTitle: 'Акты сверки - Protek',
ogDescription: 'Акты сверки взаиморасчетов в личном кабинете.'
},
'/profile-balance': {
title: 'Баланс - Личный кабинет Protek',
description: 'Информация о балансе и финансовых операциях в личном кабинете.',
keywords: 'баланс счета, финансы, личный кабинет',
ogTitle: 'Баланс - Protek',
ogDescription: 'Информация о балансе и финансовых операциях.'
},
// Процесс заказа
'/order-confirmation': {
title: 'Подтверждение заказа - Protek',
description: 'Подтверждение оформленного заказа. Проверьте данные перед финальным подтверждением.',
keywords: 'подтверждение заказа, проверка заказа, финальный шаг',
ogTitle: 'Подтверждение заказа - Protek',
ogDescription: 'Подтверждение оформленного заказа. Проверьте данные.'
},
'/cart-step-2': {
title: 'Оформление заказа - Шаг 2 - Protek',
description: 'Второй шаг оформления заказа. Выберите способ доставки и оплаты.',
keywords: 'оформление заказа шаг 2, доставка, способ оплаты',
ogTitle: 'Оформление заказа - Шаг 2',
ogDescription: 'Второй шаг оформления заказа. Выберите способ доставки и оплаты.'
},
'/payments-method': {
title: 'Способы оплаты - Protek',
description: 'Выберите удобный способ оплаты: наличными, картой, банковским переводом.',
keywords: 'способы оплаты, оплата картой, наличные, банковский перевод',
ogTitle: 'Способы оплаты - Protek',
ogDescription: 'Выберите удобный способ оплаты: наличными, картой, банковским переводом.'
},
'/checkout': {
title: 'Оформление заказа - Protek',
description: 'Оформление заказа автозапчастей. Быстро и безопасно.',
keywords: 'оформление заказа, checkout, заказать запчасти',
ogTitle: 'Оформление заказа - Protek',
ogDescription: 'Оформление заказа автозапчастей. Быстро и безопасно.'
},
// Детальные страницы
'/detail_category': {
title: 'Категория товаров - Protek',
description: 'Просмотр товаров в выбранной категории автозапчастей.',
keywords: 'категория товаров, группа запчастей, каталог',
ogTitle: 'Категория товаров - Protek',
ogDescription: 'Просмотр товаров в выбранной категории автозапчастей.'
},
'/detail_product': {
title: 'Детальная информация о товаре - Protek',
description: 'Подробная информация о товаре: технические характеристики, совместимость, цены.',
keywords: 'детальная информация, технические характеристики, совместимость',
ogTitle: 'Детальная информация о товаре - Protek',
ogDescription: 'Подробная информация о товаре: технические характеристики, совместимость.'
},
'/detail_sku': {
title: 'Информация о SKU - Protek',
description: 'Детальная информация о конкретном артикуле товара.',
keywords: 'информация SKU, артикул товара, детали товара',
ogTitle: 'Информация о SKU - Protek',
ogDescription: 'Детальная информация о конкретном артикуле товара.'
},
// Избранное
'/favorite': {
title: 'Избранные товары - Protek',
description: 'Ваши избранные автозапчасти. Сохраните интересующие товары для быстрого доступа.',
keywords: 'избранные товары, сохраненные запчасти, избранное',
ogTitle: 'Избранные товары - Protek',
ogDescription: 'Ваши избранные автозапчасти. Сохраните интересующие товары.'
},
// Страница благодарности
'/thankyoupage': {
title: 'Спасибо за заказ - Protek',
description: 'Ваш заказ успешно оформлен. Мы свяжемся с вами в ближайшее время для подтверждения.',
keywords: 'заказ оформлен, спасибо за заказ, подтверждение заказа',
ogTitle: 'Спасибо за заказ - Protek',
ogDescription: 'Ваш заказ успешно оформлен. Мы свяжемся с вами в ближайшее время.'
},
// Новости - открытая статья
'/news-open': {
title: 'Новости - Protek',
description: 'Читайте актуальные новости и статьи от компании Protek о мире автозапчастей.',
keywords: 'новости protek, статьи, автозапчасти новости',
ogTitle: 'Новости - Protek',
ogDescription: 'Читайте актуальные новости и статьи от компании Protek.'
},
// Поиск
'/search': {
title: 'Поиск запчастей - Protek',
description: 'Универсальный поиск автозапчастей по артикулу, VIN коду или модели автомобиля.',
keywords: 'поиск запчастей, поиск по артикулу, поиск по VIN, универсальный поиск',
ogTitle: 'Поиск запчастей - Protek',
ogDescription: 'Универсальный поиск автозапчастей по артикулу, VIN коду или модели автомобиля.'
},
// Поиск по артикулу
'/article-search': {
title: 'Поиск деталей по артикулу - Protek',
description: 'Найдите автозапчасти по артикулу или номеру детали. Быстрый и точный поиск в каталоге.',
keywords: 'поиск по артикулу, номер детали, поиск запчастей по номеру',
ogTitle: 'Поиск деталей по артикулу - Protek',
ogDescription: 'Найдите автозапчасти по артикулу или номеру детали.'
},
// Профиль - заказы
'/profile-orders': {
title: 'Мои заказы - Личный кабинет Protek',
description: 'Управляйте своими заказами в личном кабинете. Отслеживайте статус и историю заказов.',
keywords: 'мои заказы, личный кабинет, история заказов, статус заказа',
ogTitle: 'Мои заказы - Protek',
ogDescription: 'Управляйте своими заказами в личном кабинете.'
},
// Профиль - настройки
'/profile-set': {
title: 'Настройки профиля - Личный кабинет Protek',
description: 'Настройки личного кабинета. Управляйте персональными данными и настройками аккаунта.',
keywords: 'настройки профиля, личные данные, настройки аккаунта',
ogTitle: 'Настройки профиля - Protek',
ogDescription: 'Настройки личного кабинета и персональных данных.'
},
// Профиль - адреса
'/profile-addresses': {
title: 'Мои адреса - Личный кабинет Protek',
description: 'Управляйте адресами доставки в личном кабинете. Добавляйте и редактируйте адреса.',
keywords: 'адреса доставки, мои адреса, личный кабинет',
ogTitle: 'Мои адреса - Protek',
ogDescription: 'Управляйте адресами доставки в личном кабинете.'
},
// Профиль - гараж
'/profile-gar': {
title: 'Мой гараж - Личный кабинет Protek',
description: 'Мой гараж - сохраняйте информацию о ваших автомобилях для быстрого подбора запчастей.',
keywords: 'мой гараж, мои автомобили, сохраненные авто',
ogTitle: 'Мой гараж - Protek',
ogDescription: 'Сохраняйте информацию о ваших автомобилях для быстрого подбора запчастей.'
},
// Профиль - история
'/profile-history': {
title: 'История просмотров - Личный кабинет Protek',
description: 'История просмотренных товаров и запчастей. Быстро найдите ранее просмотренные товары.',
keywords: 'история просмотров, просмотренные товары, личный кабинет',
ogTitle: 'История просмотров - Protek',
ogDescription: 'История просмотренных товаров и запчастей.'
},
// VIN поиск (шаг 2)
'/vin-step-2': {
title: 'Поиск запчастей по VIN - Шаг 2 - Protek',
description: 'Второй шаг поиска запчастей по VIN коду. Выберите нужные детали для вашего автомобиля.',
keywords: 'VIN поиск шаг 2, выбор деталей, поиск по VIN',
ogTitle: 'Поиск запчастей по VIN - Шаг 2',
ogDescription: 'Второй шаг поиска запчастей по VIN коду.'
},
};
// Функция для получения meta-тегов по пути
export const getMetaByPath = (path: string): MetaConfig => {
// Нормализуем путь (убираем query параметры)
const normalizedPath = path.split('?')[0];
// Проверяем точное совпадение
if (metaConfig[normalizedPath]) {
return metaConfig[normalizedPath];
}
// Проверяем динамические пути
if (normalizedPath.startsWith('/vehicle-search/')) {
return {
title: 'Поиск запчастей по автомобилю - Protek',
description: 'Найдите подходящие запчасти для вашего автомобиля. Точный подбор по марке, модели и году выпуска.',
keywords: 'поиск запчастей, подбор по автомобилю, запчасти для авто'
};
}
if (normalizedPath.startsWith('/search-result')) {
return {
title: 'Результаты поиска - Protek',
description: 'Результаты поиска автозапчастей. Найдите нужные запчасти среди широкого ассортимента.',
keywords: 'результаты поиска, поиск запчастей, найти запчасти'
};
}
if (normalizedPath.startsWith('/payment/')) {
return {
title: 'Оплата заказа - Protek',
description: 'Оплата заказа автозапчастей. Безопасные способы оплаты онлайн.',
keywords: 'оплата заказа, онлайн оплата, безопасная оплата'
};
}
// Возвращаем дефолтные meta-теги
return metaConfig['/'];
};
// Функция для создания динамических meta-тегов для товаров
export const createProductMeta = (product: {
name: string;
brand: string;
articleNumber: string;
price?: number;
}): MetaConfig => {
return {
title: `${product.brand} ${product.articleNumber} - ${product.name} - Protek`,
description: `Купить ${product.name} ${product.brand} артикул ${product.articleNumber}${product.price ? ` по цене ${product.price} руб.` : ''}. Гарантия качества, быстрая доставка.`,
keywords: `${product.name}, ${product.brand}, ${product.articleNumber}, запчасти, автозапчасти`,
ogTitle: `${product.brand} ${product.articleNumber} - ${product.name}`,
ogDescription: `Купить ${product.name} ${product.brand} артикул ${product.articleNumber}. Гарантия качества, быстрая доставка.`
};
};
// Функция для создания meta-тегов для категорий
export const createCategoryMeta = (categoryName: string, count?: number): MetaConfig => {
return {
title: `${categoryName} - Каталог запчастей Protek`,
description: `Купить ${categoryName.toLowerCase()} для автомобилей${count ? `. В наличии ${count} товаров` : ''}. Широкий выбор, низкие цены, быстрая доставка.`,
keywords: `${categoryName.toLowerCase()}, запчасти, автозапчасти, каталог`,
ogTitle: `${categoryName} - Protek`,
ogDescription: `Купить ${categoryName.toLowerCase()} для автомобилей. Широкий выбор, низкие цены.`
};
};

View File

@ -1,8 +1,14 @@
import { PartsIndexCatalogsResponse, PartsIndexGroup, PartsIndexEntityInfoResponse } from '@/types/partsindex';
const PARTS_INDEX_API_BASE = 'https://api.parts-index.com';
const PARTS_INDEX_API_BASE = process.env.PARTSAPI_URL || 'https://api.parts-index.com';
const API_KEY = 'PI-E1C0ADB7-E4A8-4960-94A0-4D9C0A074DAE';
// Debug logging for development
if (process.env.NODE_ENV === 'development') {
console.log('🔍 PartsIndex API Base URL:', PARTS_INDEX_API_BASE);
console.log('🔍 Environment variable NEXT_PUBLIC_PARTSAPI_URL:', process.env.NEXT_PUBLIC_PARTSAPI_URL);
}
class PartsIndexService {
/**
* Получить список каталогов

257
src/lib/schema.ts Normal file
View File

@ -0,0 +1,257 @@
// Утилиты для генерации микроразметки schema.org
export interface SchemaOrgProduct {
name: string;
description?: string;
brand: string;
sku: string;
image?: string;
category?: string;
offers: SchemaOrgOffer[];
}
export interface SchemaOrgOffer {
price: number;
currency: string;
availability: string;
seller: string;
deliveryTime?: string;
warehouse?: string;
}
export interface SchemaOrgBreadcrumb {
name: string;
url: string;
}
export interface SchemaOrgOrganization {
name: string;
description?: string;
url: string;
logo?: string;
contactPoint?: {
telephone: string;
email?: string;
contactType: string;
};
address?: {
streetAddress: string;
addressLocality: string;
addressRegion: string;
postalCode: string;
addressCountry: string;
};
}
export interface SchemaOrgLocalBusiness extends SchemaOrgOrganization {
openingHours?: string[];
geo?: {
latitude: number;
longitude: number;
};
}
// Генератор микроразметки для товара
export const generateProductSchema = (product: SchemaOrgProduct): object => {
return {
"@context": "https://schema.org",
"@type": "Product",
name: product.name,
description: product.description || `${product.brand} ${product.sku} - ${product.name}`,
brand: {
"@type": "Brand",
name: product.brand
},
sku: product.sku,
mpn: product.sku,
image: product.image,
category: product.category || "Автозапчасти",
offers: product.offers.map(offer => ({
"@type": "Offer",
price: offer.price,
priceCurrency: offer.currency,
availability: offer.availability,
seller: {
"@type": "Organization",
name: offer.seller
},
deliveryLeadTime: offer.deliveryTime,
availableAtOrFrom: {
"@type": "Place",
name: offer.warehouse || "Склад"
}
}))
};
};
// Генератор микроразметки для организации
export const generateOrganizationSchema = (org: SchemaOrgOrganization): object => {
const schema: any = {
"@context": "https://schema.org",
"@type": "Organization",
name: org.name,
url: org.url,
description: org.description
};
if (org.logo) {
schema.logo = org.logo;
}
if (org.contactPoint) {
schema.contactPoint = {
"@type": "ContactPoint",
telephone: org.contactPoint.telephone,
email: org.contactPoint.email,
contactType: org.contactPoint.contactType
};
}
if (org.address) {
schema.address = {
"@type": "PostalAddress",
streetAddress: org.address.streetAddress,
addressLocality: org.address.addressLocality,
addressRegion: org.address.addressRegion,
postalCode: org.address.postalCode,
addressCountry: org.address.addressCountry
};
}
return schema;
};
// Генератор микроразметки для местного бизнеса
export const generateLocalBusinessSchema = (business: SchemaOrgLocalBusiness): object => {
const schema: any = {
"@context": "https://schema.org",
"@type": "LocalBusiness",
name: business.name,
url: business.url,
description: business.description
};
if (business.contactPoint) {
schema.contactPoint = {
"@type": "ContactPoint",
telephone: business.contactPoint.telephone,
email: business.contactPoint.email,
contactType: business.contactPoint.contactType
};
}
if (business.address) {
schema.address = {
"@type": "PostalAddress",
streetAddress: business.address.streetAddress,
addressLocality: business.address.addressLocality,
addressRegion: business.address.addressRegion,
postalCode: business.address.postalCode,
addressCountry: business.address.addressCountry
};
}
if (business.openingHours) {
schema.openingHours = business.openingHours;
}
if (business.geo) {
schema.geo = {
"@type": "GeoCoordinates",
latitude: business.geo.latitude,
longitude: business.geo.longitude
};
}
return schema;
};
// Генератор микроразметки для хлебных крошек
export const generateBreadcrumbSchema = (breadcrumbs: SchemaOrgBreadcrumb[]): object => {
return {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: breadcrumbs.map((breadcrumb, index) => ({
"@type": "ListItem",
position: index + 1,
name: breadcrumb.name,
item: breadcrumb.url
}))
};
};
// Генератор микроразметки для сайта с поиском
export const generateWebSiteSchema = (name: string, url: string, searchUrl?: string): object => {
const schema: any = {
"@context": "https://schema.org",
"@type": "WebSite",
name: name,
url: url
};
if (searchUrl) {
schema.potentialAction = {
"@type": "SearchAction",
target: {
"@type": "EntryPoint",
urlTemplate: `${searchUrl}?q={search_term_string}`
},
"query-input": "required name=search_term_string"
};
}
return schema;
};
// Утилита для конвертации доступности товара в schema.org формат
export const convertAvailability = (stock: string | number): string => {
const stockNum = typeof stock === 'string' ? parseInt(stock) || 0 : stock;
if (stockNum > 0) {
return "https://schema.org/InStock";
} else {
return "https://schema.org/OutOfStock";
}
};
// Утилита для генерации JSON-LD скрипта
export const generateJsonLdScript = (schema: object): string => {
return JSON.stringify(schema, null, 2);
};
// Интерфейс для компонента JSON-LD (компонент будет в отдельном файле)
export interface JsonLdScriptProps {
schema: object;
}
// Данные организации Protek
export const PROTEK_ORGANIZATION: SchemaOrgOrganization = {
name: "Protek",
description: "Protek - широкий ассортимент автозапчастей и аксессуаров для всех марок автомобилей. Быстрая доставка по России, гарантия качества, низкие цены.",
url: "https://protek.ru",
logo: "https://protek.ru/images/logo.svg",
contactPoint: {
telephone: "+7-800-555-0123",
email: "info@protek.ru",
contactType: "customer service"
},
address: {
streetAddress: "ул. Примерная, 123",
addressLocality: "Москва",
addressRegion: "Москва",
postalCode: "123456",
addressCountry: "RU"
}
};
// Данные для LocalBusiness
export const PROTEK_LOCAL_BUSINESS: SchemaOrgLocalBusiness = {
...PROTEK_ORGANIZATION,
openingHours: [
"Mo-Fr 09:00-18:00",
"Sa 10:00-16:00"
],
geo: {
latitude: 55.7558,
longitude: 37.6176
}
};

View File

@ -93,3 +93,13 @@ export const memoize = <T extends (...args: any[]) => any>(
export const clearMemoCache = () => {
memoCache.clear();
};
// Проверка, является ли строка датой доставки
export const isDeliveryDate = (dateString: string): boolean => {
const months = [
'января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'
];
return months.some(month => dateString.includes(month));
};

View File

@ -14,6 +14,7 @@ import { CartProvider } from '@/contexts/CartContext';
import { FavoritesProvider } from '@/contexts/FavoritesContext';
import Layout from "@/components/Layout";
import { Toaster } from 'react-hot-toast';
import CookieConsent from '@/components/CookieConsent';
export default function App({ Component, pageProps }: AppProps) {
const [isMaintenanceMode, setIsMaintenanceMode] = useState(false);
@ -51,7 +52,7 @@ export default function App({ Component, pageProps }: AppProps) {
<Component {...pageProps} />
</Layout>
<Toaster
position="top-right"
position="top-center"
toastOptions={{
duration: 4000,
style: {
@ -60,25 +61,37 @@ export default function App({ Component, pageProps }: AppProps) {
},
success: {
duration: 3000,
style: {
background: '#22c55e', // Зеленый фон для успешных уведомлений
color: '#fff', // Белый текст
},
iconTheme: {
primary: '#4ade80',
primary: '#22c55e',
secondary: '#fff',
},
},
error: {
duration: 5000,
style: {
background: '#ef4444',
color: '#fff',
},
iconTheme: {
primary: '#ef4444',
secondary: '#fff',
},
},
}}
containerStyle={{
top: '80px', // Отступ для всего контейнера toast'ов
}}
/>
<Script src="/js/webflow.js" strategy="beforeInteractive" />
<Script
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"
strategy="afterInteractive"
/>
<CookieConsent />
</CartProvider>
</FavoritesProvider>
</ApolloProvider>

View File

@ -2,8 +2,23 @@ import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<Html lang="ru">
<Head>
{/* Базовые meta-теги */}
<meta name="format-detection" content="telephone=no" />
<meta name="theme-color" content="#dc2626" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="Protek" />
{/* Preconnect для производительности */}
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
{/* Favicon */}
<link href="/images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="/images/webclip.png" rel="apple-touch-icon" />
</Head>
<body>
<Main />
<NextScript />

View File

@ -8,18 +8,28 @@ import AboutIntro from "@/components/about/AboutIntro";
import AboutOffers from "@/components/about/AboutOffers";
import AboutProtekInfo from "@/components/about/AboutProtekInfo";
import AboutHelp from "@/components/about/AboutHelp";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
import JsonLdScript from "@/components/JsonLdScript";
import { generateOrganizationSchema, generateBreadcrumbSchema, PROTEK_ORGANIZATION } from "@/lib/schema";
export default function About() {
const metaData = getMetaByPath('/about');
// Генерируем микроразметку Organization для страницы "О компании"
const organizationSchema = generateOrganizationSchema(PROTEK_ORGANIZATION);
// Генерируем микроразметку BreadcrumbList
const breadcrumbSchema = generateBreadcrumbSchema([
{ name: "Главная", url: "https://protek.ru/" },
{ name: "О компании", url: "https://protek.ru/about" }
]);
return (
<>
<Head>
<title>About</title>
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<JsonLdScript schema={organizationSchema} />
<JsonLdScript schema={breadcrumbSchema} />
<CatalogInfoHeader
title="О компании"
breadcrumbs={[

View File

@ -7,6 +7,8 @@ import Footer from "@/components/Footer";
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import { DOC_FIND_OEM } from "@/lib/graphql";
import { LaximoDocFindOEMResult } from "@/types/laximo";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
const InfoArticleSearch = () => (
<section className="section-info">
@ -57,14 +59,11 @@ const ArticleSearchPage = () => {
const result: LaximoDocFindOEMResult | null = data?.laximoDocFindOEM || null;
const hasResults = result && result.details && result.details.length > 0;
const metaData = getMetaByPath('/article-search');
return (
<>
<Head>
<title>Поиск деталей по артикулу {searchQuery} - Protek</title>
<meta name="description" content={`Результаты поиска деталей по артикулу ${searchQuery}`} />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<InfoArticleSearch />
<div className="page-wrapper bg-[#F5F8FB] min-h-screen">
<div className="flex flex-col px-32 pt-10 pb-16 max-md:px-5">

View File

@ -8,6 +8,8 @@ import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import { GET_LAXIMO_BRANDS } from "@/lib/graphql";
import { LaximoBrand } from "@/types/laximo";
import BrandWizardSearchSection from "@/components/BrandWizardSearchSection";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
const InfoBrands = () => (
<section className="section-info">
@ -85,14 +87,11 @@ const BrandsPage = () => {
setSelectedLetter(selectedLetter === letter ? '' : letter);
};
const metaData = getMetaByPath('/brands');
return (
<>
<Head>
<title>Все марки автомобилей - Protek</title>
<meta name="description" content="Полный каталог автомобильных брендов для поиска запчастей" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<InfoBrands />
<BrandWizardSearchSection />
<Footer />

View File

@ -1,4 +1,7 @@
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath, createProductMeta } from "../lib/meta-config";
import JsonLdScript from "@/components/JsonLdScript";
import { generateProductSchema, convertAvailability, type SchemaOrgProduct } from "@/lib/schema";
import { useRouter } from "next/router";
import { useEffect, useState, useMemo } from "react";
import { useQuery, useLazyQuery } from "@apollo/client";
@ -190,14 +193,45 @@ export default function CardPage() {
setVisibleOffersCount(INITIAL_OFFERS_COUNT); // Сбрасываем к начальному количеству
};
// Создаем meta-теги
const metaConfig = result ? createProductMeta({
name: result.name,
brand: result.brand,
articleNumber: result.articleNumber,
price: allOffers.length > 0 ? Math.min(...allOffers.map(offer => offer.sortPrice)) : undefined
}) : getMetaByPath('/card');
// Генерируем микроразметку Product
const productSchema = useMemo(() => {
if (!result || allOffers.length === 0) return null;
const schemaProduct: SchemaOrgProduct = {
name: result.name,
description: `${result.brand} ${result.articleNumber} - ${result.name}`,
brand: result.brand,
sku: result.articleNumber,
image: mainImageUrl || (result?.partsIndexImages && result.partsIndexImages.length > 0 ? result.partsIndexImages[0].url : undefined),
category: "Автозапчасти",
offers: allOffers.map(offer => ({
price: offer.sortPrice,
currency: "RUB",
availability: convertAvailability(offer.quantity || 0),
seller: offer.type === 'internal' ? 'Protek' : 'AutoEuro',
deliveryTime: offer.deliveryTime ? `${offer.deliveryTime} дней` : undefined,
warehouse: offer.warehouse || 'Склад'
}))
};
return generateProductSchema(schemaProduct);
}, [result, allOffers, mainImageUrl]);
if (loading) {
return (
<>
<Head>
<title>Загрузка товара - Protek</title>
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title="Загрузка товара - Protek"
description="Загрузка информации о товаре..."
/>
<main className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-red-600 mx-auto"></div>
@ -211,14 +245,14 @@ export default function CardPage() {
return (
<>
<Head>
<title>{result ? `${result.brand} ${result.articleNumber} - ${result.name}` : `Карточка товара`} - Protek</title>
<meta name="description" content={`Подробная информация о товаре ${result?.name}`} />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="/images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="/images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
{productSchema && <JsonLdScript schema={productSchema} />}
<InfoCard
brand={result ? result.brand : brandQuery}
articleNumber={result ? result.articleNumber : searchQuery}

View File

@ -1,5 +1,6 @@
import React from "react";
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import CatalogSubscribe from "@/components/CatalogSubscribe";
@ -10,15 +11,17 @@ import CartSummary2 from "../components/CartSummary2";
import MobileMenuBottomSection from "../components/MobileMenuBottomSection";
export default function CartStep2() {
const metaConfig = getMetaByPath('/cart-step-2');
return (
<>
<Head>
<title>Cart Step 2</title>
<meta name="description" content="Cart Step 2" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<CartInfo />
<section className="main">

View File

@ -8,19 +8,16 @@ import CartRecommended from "../components/CartRecommended";
import CatalogSubscribe from "@/components/CatalogSubscribe";
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import React, { useState } from "react";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
export default function CartPage() {
const [step, setStep] = useState(1);
const metaData = getMetaByPath('/cart');
return (
<><Head>
<title>Cart</title>
<meta name="description" content="Cart" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<>
<MetaTags {...metaData} />
<CartInfo />

View File

@ -23,6 +23,11 @@ import { useProductPrices } from '@/hooks/useProductPrices';
import { PriceSkeleton } from '@/components/skeletons/ProductListSkeleton';
import { useCart } from '@/contexts/CartContext';
import toast from 'react-hot-toast';
import CartIcon from '@/components/CartIcon';
import MetaTags from "@/components/MetaTags";
import { getMetaByPath, createCategoryMeta } from "@/lib/meta-config";
import JsonLdScript from "@/components/JsonLdScript";
import { generateBreadcrumbSchema, generateWebSiteSchema } from "@/lib/schema";
const mockData = Array(12).fill({
image: "",
@ -33,12 +38,13 @@ const mockData = Array(12).fill({
brand: "Borsehung",
});
const ITEMS_PER_PAGE = 20;
const MAX_BRANDS_DISPLAY = 10; // Сколько брендов показывать изначально
export default function Catalog() {
const ITEMS_PER_PAGE = 24; // Показывать 12 карточек за раз
const PARTSINDEX_PAGE_SIZE = 25; // Синхронизировано для оптимальной скорости
const MAX_BRANDS_DISPLAY = 24; // Сколько брендов показывать изначально
const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE);
const router = useRouter();
const { addItem } = useCart();
const { addItem, isInCart: isItemInCart } = useCart();
const {
partsApiCategory: strId,
categoryName,
@ -50,6 +56,36 @@ export default function Catalog() {
const [showSortMobile, setShowSortMobile] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [selectedFilters, setSelectedFilters] = useState<{[key: string]: string[]}>({});
// Инициализация фильтров из URL при загрузке
useEffect(() => {
if (router.isReady) {
const urlFilters: {[key: string]: string[]} = {};
const urlSearchQuery = router.query.q as string || '';
// Восстанавливаем фильтры из URL
Object.keys(router.query).forEach(key => {
if (key.startsWith('filter_')) {
const filterName = key.replace('filter_', '');
const filterValue = router.query[key];
if (typeof filterValue === 'string') {
urlFilters[filterName] = [filterValue];
} else if (Array.isArray(filterValue)) {
urlFilters[filterName] = filterValue;
}
}
});
console.log('🔗 Восстанавливаем фильтры из URL:', { urlFilters, urlSearchQuery });
if (Object.keys(urlFilters).length > 0) {
setSelectedFilters(urlFilters);
}
if (urlSearchQuery) {
setSearchQuery(urlSearchQuery);
}
}
}, [router.isReady]);
const [visibleArticles, setVisibleArticles] = useState<PartsAPIArticle[]>([]);
const [visibleEntities, setVisibleEntities] = useState<PartsIndexEntity[]>([]);
const [currentPage, setCurrentPage] = useState(1);
@ -68,6 +104,14 @@ export default function Catalog() {
const [partsIndexPage, setPartsIndexPage] = useState(1); // Текущая страница для PartsIndex
const [totalPages, setTotalPages] = useState(1); // Общее количество страниц
// Новые состояния для логики автоподгрузки PartsIndex
const [accumulatedEntities, setAccumulatedEntities] = useState<PartsIndexEntity[]>([]); // Все накопленные товары
const [entitiesWithOffers, setEntitiesWithOffers] = useState<PartsIndexEntity[]>([]); // Товары с предложениями
const [isAutoLoading, setIsAutoLoading] = useState(false); // Автоматическая подгрузка в процессе
const [currentUserPage, setCurrentUserPage] = useState(1); // Текущая пользовательская страница
const [entitiesCache, setEntitiesCache] = useState<Map<number, PartsIndexEntity[]>>(new Map()); // Кэш страниц
const [isFilterChanging, setIsFilterChanging] = useState(false); // Флаг изменения фильтров
// Карта видимости товаров по индексу
const [visibilityMap, setVisibilityMap] = useState<Map<number, boolean>>(new Map());
@ -103,7 +147,8 @@ export default function Catalog() {
categoryName,
isPartsAPIMode,
isPartsIndexMode,
isPartsIndexCatalogOnly
isPartsIndexCatalogOnly,
'router.query': router.query
});
// Загружаем артикулы PartsAPI
@ -130,10 +175,10 @@ export default function Catalog() {
catalogId: catalogId as string,
groupId: groupId as string,
lang: 'ru',
limit: ITEMS_PER_PAGE,
limit: PARTSINDEX_PAGE_SIZE,
page: partsIndexPage,
q: searchQuery || undefined,
params: Object.keys(selectedFilters).length > 0 ? JSON.stringify(selectedFilters) : undefined
params: undefined // Будем обновлять через refetch
},
skip: !isPartsIndexMode || !groupId, // Пропускаем запрос если нет groupId
fetchPolicy: 'cache-and-network'
@ -141,7 +186,7 @@ export default function Catalog() {
);
// Загружаем параметры фильтрации для PartsIndex
const { data: paramsData, loading: paramsLoading, error: paramsError } = useQuery<PartsIndexParamsData, PartsIndexParamsVariables>(
const { data: paramsData, loading: paramsLoading, error: paramsError, refetch: refetchParams } = useQuery<PartsIndexParamsData, PartsIndexParamsVariables>(
GET_PARTSINDEX_CATALOG_PARAMS,
{
variables: {
@ -149,7 +194,7 @@ export default function Catalog() {
groupId: groupId as string,
lang: 'ru',
q: searchQuery || undefined,
params: Object.keys(selectedFilters).length > 0 ? JSON.stringify(selectedFilters) : undefined
params: undefined // Будем обновлять через refetch
},
skip: !isPartsIndexMode || !groupId, // Пропускаем запрос если нет groupId
fetchPolicy: 'cache-first'
@ -159,12 +204,25 @@ export default function Catalog() {
// allEntities больше не используется - используем allLoadedEntities
// Хук для загрузки цен товаров PartsIndex
const productsForPrices = visibleEntities.map(entity => ({
id: entity.id,
code: entity.code,
brand: entity.brand.name
}));
const { getPrice, isLoadingPrice, loadPriceOnDemand } = useProductPrices(productsForPrices);
const { getPrice, isLoadingPrice, ensurePriceLoaded } = useProductPrices();
// Загружаем цены для видимых товаров PartsIndex (для отображения конкретных цен)
useEffect(() => {
if (isPartsIndexMode && visibleEntities.length > 0) {
// Загружаем цены только для видимых товаров для отображения точных цен
visibleEntities.forEach((entity, index) => {
const productForPrice = {
id: entity.id,
code: entity.code,
brand: entity.brand.name
};
// Загружаем с небольшой задержкой
setTimeout(() => {
ensurePriceLoaded(productForPrice);
}, index * 50);
});
}
}, [isPartsIndexMode, visibleEntities, ensurePriceLoaded]);
useEffect(() => {
if (articlesData?.partsAPIArticles) {
@ -182,15 +240,19 @@ export default function Catalog() {
console.log('📊 Обновляем entitiesData:', {
listLength: entitiesData.partsIndexCatalogEntities.list.length,
pagination: entitiesData.partsIndexCatalogEntities.pagination,
currentPage: entitiesData.partsIndexCatalogEntities.pagination?.page?.current || 1
currentPage: entitiesData.partsIndexCatalogEntities.pagination?.page?.current || 1,
isFilterChanging
});
// Если изменяются фильтры, сбрасываем флаг после получения новых данных
if (isFilterChanging) {
setIsFilterChanging(false);
console.log('🔄 Сброшен флаг isFilterChanging - получены новые отфильтрованные данные');
}
const newEntities = entitiesData.partsIndexCatalogEntities.list;
const pagination = entitiesData.partsIndexCatalogEntities.pagination;
// Обновляем список товаров
setVisibleEntities(newEntities);
// Обновляем информацию о пагинации
const currentPage = pagination?.page?.current || 1;
const hasNext = pagination?.page?.next !== null;
@ -199,6 +261,24 @@ export default function Catalog() {
setPartsIndexPage(currentPage);
setHasMoreEntities(hasNext);
// Сохраняем в кэш
setEntitiesCache(prev => new Map(prev).set(currentPage, newEntities));
// Если это первая страница или сброс, заменяем накопленные товары
if (currentPage === 1) {
setAccumulatedEntities(newEntities);
// Устанавливаем visibleEntities сразу, только если не идет изменение фильтров
if (!isFilterChanging) {
setVisibleEntities(newEntities);
console.log('✅ Установлены visibleEntities для первой страницы:', newEntities.length);
} else {
console.log('🔄 Пропускаем установку visibleEntities - фильтры изменяются');
}
} else {
// Добавляем к накопленным товарам
setAccumulatedEntities(prev => [...prev, ...newEntities]);
}
// Вычисляем общее количество страниц (приблизительно)
if (hasNext) {
setTotalPages(currentPage + 1); // Минимум еще одна страница
@ -208,7 +288,98 @@ export default function Catalog() {
console.log('✅ Пагинация обновлена:', { currentPage, hasNext, hasPrev });
}
}, [entitiesData]);
}, [entitiesData, isFilterChanging]);
// Преобразование выбранных фильтров в формат PartsIndex API
const convertFiltersToPartsIndexParams = useMemo((): Record<string, any> => {
if (!paramsData?.partsIndexCatalogParams?.list || Object.keys(selectedFilters).length === 0) {
return {};
}
const apiParams: Record<string, any> = {};
paramsData.partsIndexCatalogParams.list.forEach((param: any) => {
const selectedValues = selectedFilters[param.name];
if (selectedValues && selectedValues.length > 0) {
// Находим соответствующие значения из API данных
const matchingValues = param.values.filter((value: any) =>
selectedValues.includes(value.title || value.value)
);
if (matchingValues.length > 0) {
// Используем ID параметра из API и значения
apiParams[param.id] = matchingValues.map((v: any) => v.value);
}
}
});
return apiParams;
}, [paramsData, selectedFilters]);
// Функция автоматической подгрузки дополнительных страниц PartsIndex
const autoLoadMoreEntities = useCallback(async () => {
if (isAutoLoading || !hasMoreEntities || !isPartsIndexMode) {
return;
}
console.log('🔄 Автоподгрузка: проверяем товары с предложениями...');
// Восстанавливаем автоподгрузку
console.log('🔄 Автоподгрузка активна');
// Подсчитываем текущее количество товаров (все уже отфильтрованы на сервере)
const currentEntitiesCount = accumulatedEntities.length;
console.log('📊 Автоподгрузка: текущее состояние:', {
накопленоТоваров: currentEntitiesCount,
целевоеКоличество: ITEMS_PER_PAGE,
естьЕщеТовары: hasMoreEntities
});
// Даем время на загрузку цен товаров, если их слишком много загружается
const loadingCount = accumulatedEntities.filter(entity => {
const productForPrice = { id: entity.id, code: entity.code, brand: entity.brand.name };
return isLoadingPrice(productForPrice);
}).length;
// Ждем только если загружается больше 5 товаров одновременно
if (loadingCount > 5) {
console.log('⏳ Автоподгрузка: ждем загрузки цен для', loadingCount, 'товаров (больше 5)');
return;
}
// Если накопили уже много товаров, но мало с предложениями - прекращаем попытки
if (accumulatedEntities.length >= ITEMS_PER_PAGE * 8) { // Увеличили лимит с 4 до 8 страниц
console.log('⚠️ Автоподгрузка: достигли лимита попыток, прекращаем');
return;
}
setIsAutoLoading(true);
try {
console.log('🔄 Автоподгрузка: загружаем следующую страницу PartsIndex...');
const apiParams = convertFiltersToPartsIndexParams;
const paramsString = Object.keys(apiParams).length > 0 ? JSON.stringify(apiParams) : undefined;
const result = await refetchEntities({
catalogId: catalogId as string,
groupId: groupId as string,
lang: 'ru',
limit: PARTSINDEX_PAGE_SIZE,
page: partsIndexPage + 1,
q: searchQuery || undefined,
params: paramsString
});
console.log('✅ Автоподгрузка: страница загружена, результат:', result.data?.partsIndexCatalogEntities?.list?.length || 0);
} catch (error) {
console.error('❌ Автоподгрузка: ошибка загрузки следующей страницы:', error);
} finally {
setIsAutoLoading(false);
}
}, [isAutoLoading, hasMoreEntities, isPartsIndexMode, accumulatedEntities.length, partsIndexPage, refetchEntities, catalogId, groupId, searchQuery]);
// Генерация фильтров для PartsIndex на основе параметров API
const generatePartsIndexFilters = useCallback((): FilterConfig[] => {
@ -216,12 +387,12 @@ export default function Catalog() {
return [];
}
return paramsData.partsIndexCatalogParams.list.map(param => {
return paramsData.partsIndexCatalogParams.list.map((param: any) => {
if (param.type === 'range') {
// Для range фильтров ищем min и max значения
const numericValues = param.values
.map(v => parseFloat(v.value))
.filter(v => !isNaN(v));
.map((v: any) => parseFloat(v.value))
.filter((v: number) => !isNaN(v));
const min = numericValues.length > 0 ? Math.min(...numericValues) : 0;
const max = numericValues.length > 0 ? Math.max(...numericValues) : 100;
@ -230,7 +401,8 @@ export default function Catalog() {
type: 'range' as const,
title: param.name,
min,
max
max,
defaultOpen: false,
};
} else {
// Для dropdown фильтров
@ -238,15 +410,18 @@ export default function Catalog() {
type: 'dropdown' as const,
title: param.name,
options: param.values
.filter(value => value.available) // Показываем только доступные
.map(value => value.title || value.value),
.filter((value: any) => value.available) // Показываем только доступные
.map((value: any) => value.title || value.value),
multi: true,
showAll: true
showAll: true,
defaultOpen: false,
};
}
});
}, [paramsData]);
useEffect(() => {
if (isPartsIndexMode) {
// Для PartsIndex генерируем фильтры на основе параметров API
@ -259,6 +434,91 @@ export default function Catalog() {
}
}, [isPartsIndexMode, generatePartsIndexFilters, paramsLoading]);
// Автоматическая подгрузка товаров с задержкой для загрузки цен
useEffect(() => {
if (!isPartsIndexMode || accumulatedEntities.length === 0 || isAutoLoading) {
return;
}
// Даем время на загрузку цен (3 секунды после последнего изменения)
const timer = setTimeout(() => {
autoLoadMoreEntities();
}, 3000);
return () => clearTimeout(timer);
}, [isPartsIndexMode, accumulatedEntities.length, isAutoLoading]);
// Дополнительный триггер автоподгрузки при изменении количества товаров с предложениями
useEffect(() => {
console.log('🔍 Проверка триггера автоподгрузки:', {
isPartsIndexMode,
entitiesWithOffersLength: entitiesWithOffers.length,
isAutoLoading,
hasMoreEntities,
targetItemsPerPage: ITEMS_PER_PAGE
});
if (!isPartsIndexMode || entitiesWithOffers.length === 0 || isAutoLoading) {
return;
}
// Если товаров с предложениями мало, запускаем автоподгрузку через 1 секунду
if (entitiesWithOffers.length < ITEMS_PER_PAGE && hasMoreEntities) {
console.log('🚀 Запускаем автоподгрузку: товаров', entitiesWithOffers.length, 'из', ITEMS_PER_PAGE);
const timer = setTimeout(() => {
console.log('🚀 Дополнительная автоподгрузка: недостаточно товаров с предложениями');
autoLoadMoreEntities();
}, 1000);
return () => clearTimeout(timer);
} else {
console.log('✅ Автоподгрузка не нужна: товаров достаточно или нет больше данных');
}
}, [isPartsIndexMode, entitiesWithOffers.length, hasMoreEntities, isAutoLoading]);
// Обновляем список товаров при изменении накопленных товаров (серверная фильтрация)
useEffect(() => {
if (!isPartsIndexMode) {
return;
}
// Если фильтры изменяются, не обновляем отображение старых данных
if (isFilterChanging) {
console.log('🔄 Пропускаем обновление entitiesWithOffers - фильтры изменяются');
return;
}
// Все товары уже отфильтрованы на сервере - показываем все накопленные
const entitiesWithOffers = accumulatedEntities;
console.log('📊 Обновляем entitiesWithOffers (серверная фильтрация):', {
накопленоТоваров: accumulatedEntities.length,
отображаемыхТоваров: entitiesWithOffers.length,
целевоеКоличество: ITEMS_PER_PAGE,
isFilterChanging
});
setEntitiesWithOffers(entitiesWithOffers);
// Показываем товары для текущей пользовательской страницы
const startIndex = (currentUserPage - 1) * ITEMS_PER_PAGE;
const endIndex = startIndex + ITEMS_PER_PAGE;
const visibleForCurrentPage = entitiesWithOffers.slice(startIndex, endIndex);
console.log('📊 Обновляем visibleEntities:', {
currentUserPage,
startIndex,
endIndex,
visibleForCurrentPage: visibleForCurrentPage.length,
entitiesWithOffers: entitiesWithOffers.length
});
setVisibleEntities(visibleForCurrentPage);
}, [isPartsIndexMode, accumulatedEntities, currentUserPage, isFilterChanging]);
// Генерируем динамические фильтры для PartsAPI
const generatePartsAPIFilters = useCallback((): FilterConfig[] => {
if (!allArticles.length) return [];
@ -300,7 +560,7 @@ export default function Catalog() {
options: brandsToShow.sort(), // Сортируем по алфавиту для удобства
multi: true,
showAll: true,
defaultOpen: true,
defaultOpen: false,
hasMore: !showAllBrands && sortedBrands.length > MAX_BRANDS_DISPLAY,
onShowMore: () => setShowAllBrands(true)
});
@ -313,7 +573,7 @@ export default function Catalog() {
options: Array.from(productGroups).sort(),
multi: true,
showAll: true,
defaultOpen: true,
defaultOpen: false,
});
}
@ -344,27 +604,94 @@ export default function Catalog() {
// Функция для обновления URL с фильтрами
const updateUrlWithFilters = useCallback((filters: {[key: string]: string[]}, search: string) => {
const query: any = { ...router.query };
// Удаляем старые фильтры из URL
Object.keys(query).forEach(key => {
if (key.startsWith('filter_') || key === 'q') {
delete query[key];
}
});
// Добавляем новые фильтры
Object.entries(filters).forEach(([filterName, values]) => {
if (values.length > 0) {
query[`filter_${filterName}`] = values.length === 1 ? values[0] : values;
}
});
// Добавляем поисковый запрос
if (search.trim()) {
query.q = search;
}
// Обновляем URL без перезагрузки страницы
router.push({
pathname: router.pathname,
query
}, undefined, { shallow: true });
}, [router]);
const handleDesktopFilterChange = (filterTitle: string, value: string | string[]) => {
setSelectedFilters(prev => ({
...prev,
[filterTitle]: Array.isArray(value) ? value : [value]
}));
setSelectedFilters(prev => {
const newFilters = { ...prev };
// Если значение пустое (пустой массив или пустая строка), удаляем фильтр
if (Array.isArray(value) && value.length === 0) {
delete newFilters[filterTitle];
} else if (!value || (typeof value === 'string' && value.trim() === '')) {
delete newFilters[filterTitle];
} else {
// Иначе устанавливаем значение
newFilters[filterTitle] = Array.isArray(value) ? value : [value];
}
// Обновляем URL
updateUrlWithFilters(newFilters, searchQuery);
return newFilters;
});
};
const handleMobileFilterChange = (type: string, value: any) => {
setSelectedFilters(prev => ({
...prev,
[type]: Array.isArray(value) ? value : [value]
}));
setSelectedFilters(prev => {
const newFilters = { ...prev };
// Если значение пустое (пустой массив или пустая строка), удаляем фильтр
if (Array.isArray(value) && value.length === 0) {
delete newFilters[type];
} else if (!value || (typeof value === 'string' && value.trim() === '')) {
delete newFilters[type];
} else {
// Иначе устанавливаем значение
newFilters[type] = Array.isArray(value) ? value : [value];
}
// Обновляем URL
updateUrlWithFilters(newFilters, searchQuery);
return newFilters;
});
};
// Обработчик изменения поискового запроса
const handleSearchChange = useCallback((value: string) => {
setSearchQuery(value);
updateUrlWithFilters(selectedFilters, value);
}, [selectedFilters, updateUrlWithFilters]);
// Функция для сброса всех фильтров
const handleResetFilters = useCallback(() => {
setSearchQuery('');
setSelectedFilters({});
setShowAllBrands(false);
setPartsIndexPage(1); // Сбрасываем страницу PartsIndex на первую
}, []);
// Очищаем URL от фильтров
updateUrlWithFilters({}, '');
}, [updateUrlWithFilters]);
// Фильтрация по поиску и фильтрам для PartsAPI
const filteredArticles = useMemo(() => {
@ -398,9 +725,6 @@ export default function Catalog() {
});
}, [allArticles, searchQuery, selectedFilters]);
// Упрощенная логика - показываем все загруженные товары без клиентской фильтрации
const filteredEntities = visibleEntities;
// Обновляем видимые артикулы при изменении поиска или фильтров для PartsAPI
useEffect(() => {
if (isPartsAPIMode) {
@ -421,16 +745,85 @@ export default function Catalog() {
// При изменении поиска или фильтров сбрасываем пагинацию
setShowEmptyState(false);
// Если изменился поисковый запрос, нужно перезагрузить данные с сервера
// Если изменился поисковый запрос или фильтры, нужно перезагрузить данные с сервера
if (searchQuery.trim() || Object.keys(selectedFilters).length > 0) {
console.log('🔍 Поисковый запрос или фильтры изменились, сбрасываем пагинацию');
// Устанавливаем флаг изменения фильтров
setIsFilterChanging(true);
setPartsIndexPage(1);
setCurrentUserPage(1);
setHasMoreEntities(true);
// refetch будет автоматически вызван при изменении partsIndexPage
setAccumulatedEntities([]);
setEntitiesWithOffers([]);
setEntitiesCache(new Map());
// Вычисляем параметры фильтрации прямо здесь, чтобы избежать зависимости от useMemo
let apiParams: Record<string, any> = {};
if (paramsData?.partsIndexCatalogParams?.list && Object.keys(selectedFilters).length > 0) {
paramsData.partsIndexCatalogParams.list.forEach((param: any) => {
const selectedValues = selectedFilters[param.name];
if (selectedValues && selectedValues.length > 0) {
// Находим соответствующие значения из API данных
const matchingValues = param.values.filter((value: any) =>
selectedValues.includes(value.title || value.value)
);
if (matchingValues.length > 0) {
// Используем ID параметра из API и значения
apiParams[param.id] = matchingValues.map((v: any) => v.value);
}
}
});
}
const paramsString = Object.keys(apiParams).length > 0 ? JSON.stringify(apiParams) : undefined;
console.log('🔄 Запуск refetch с новыми фильтрами:', {
searchQuery,
selectedFilters,
apiParams,
paramsString,
catalogId,
groupId
});
// Также обновляем параметры фильтрации
refetchParams({
catalogId: catalogId as string,
groupId: groupId as string,
lang: 'ru',
q: searchQuery || undefined,
params: paramsString
}).then(result => {
console.log('✅ refetchParams результат:', result);
}).catch(error => {
console.error('❌ refetchParams ошибка:', error);
});
refetchEntities({
catalogId: catalogId as string,
groupId: groupId as string,
lang: 'ru',
limit: PARTSINDEX_PAGE_SIZE,
page: 1,
q: searchQuery || undefined,
params: paramsString
}).then(result => {
console.log('✅ refetchEntities результат:', result.data?.partsIndexCatalogEntities?.list?.length || 0, 'товаров');
}).catch(error => {
console.error('❌ refetchEntities ошибка:', error);
});
} else {
// Если нет активных фильтров, сбрасываем флаг
if (isFilterChanging) {
setIsFilterChanging(false);
}
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isPartsIndexMode, searchQuery, JSON.stringify(selectedFilters)]);
}, [isPartsIndexMode, searchQuery, JSON.stringify(selectedFilters), paramsData]);
// Управляем показом пустого состояния с задержкой
useEffect(() => {
@ -448,26 +841,61 @@ export default function Catalog() {
return () => clearTimeout(timer);
} else if (isPartsIndexMode && !entitiesLoading && !entitiesError) {
// Для PartsIndex показываем пустое состояние если нет товаров
setShowEmptyState(visibleEntities.length === 0);
// Для PartsIndex показываем пустое состояние если нет товаров И данные уже загружены
const hasLoadedData = accumulatedEntities.length > 0 || Boolean(entitiesData?.partsIndexCatalogEntities?.list);
// Показываем пустое состояние если данные загружены и нет видимых товаров
// (товары уже отфильтрованы на сервере, поэтому не нужно ждать загрузки цен)
const shouldShowEmpty = hasLoadedData && visibleEntities.length === 0;
setShowEmptyState(shouldShowEmpty);
console.log('📊 Определяем showEmptyState для PartsIndex (серверная фильтрация):', {
hasLoadedData,
visibleEntitiesLength: visibleEntities.length,
accumulatedEntitiesLength: accumulatedEntities.length,
shouldShowEmpty,
showEmptyState: shouldShowEmpty
});
} else {
setShowEmptyState(false);
}
}, [isPartsAPIMode, articlesLoading, articlesError, visibleProductsCount, allArticles.length,
isPartsIndexMode, entitiesLoading, entitiesError, visibleEntities.length, filteredEntities.length]);
isPartsIndexMode, entitiesLoading, entitiesError, visibleEntities.length, accumulatedEntities.length, entitiesData]);
// Функции для навигации по страницам PartsIndex
// Функции для навигации по пользовательским страницам
const handleNextPage = useCallback(() => {
if (hasMoreEntities && !entitiesLoading) {
setPartsIndexPage(prev => prev + 1);
const maxUserPage = Math.ceil(accumulatedEntities.length / ITEMS_PER_PAGE);
console.log('🔄 Нажата кнопка "Вперед":', {
currentUserPage,
maxUserPage,
accumulatedEntitiesLength: accumulatedEntities.length,
ITEMS_PER_PAGE
});
if (currentUserPage < maxUserPage) {
setCurrentUserPage(prev => {
console.log('✅ Переходим на страницу:', prev + 1);
return prev + 1;
});
} else {
console.log('⚠️ Нельзя перейти вперед: уже на последней странице');
}
}, [hasMoreEntities, entitiesLoading]);
}, [currentUserPage, accumulatedEntities.length]);
const handlePrevPage = useCallback(() => {
if (partsIndexPage > 1 && !entitiesLoading) {
setPartsIndexPage(prev => prev - 1);
console.log('🔄 Нажата кнопка "Назад":', {
currentUserPage,
accumulatedEntitiesLength: accumulatedEntities.length
});
if (currentUserPage > 1) {
setCurrentUserPage(prev => {
const newPage = prev - 1;
console.log('✅ Переходим на страницу:', newPage);
return newPage;
});
} else {
console.log('⚠️ Нельзя перейти назад: уже на первой странице');
}
}, [partsIndexPage, entitiesLoading]);
}, [currentUserPage, accumulatedEntities.length]);
// Функция для загрузки следующей порции товаров по кнопке (только для PartsAPI)
const handleLoadMorePartsAPI = useCallback(async () => {
@ -501,20 +929,46 @@ export default function Catalog() {
return false;
}, [isPartsAPIMode, loadedArticlesCount, filteredArticles.length]);
useEffect(() => {
// Сбросить все состояния при смене каталога или подкатегории
setAccumulatedEntities([]);
setVisibleEntities([]);
setEntitiesWithOffers([]);
setEntitiesCache(new Map());
setCurrentUserPage(1);
setPartsIndexPage(1);
setHasMoreEntities(true);
setShowEmptyState(false);
setIsFilterChanging(false);
setVisibleCount(ITEMS_PER_PAGE);
}, [catalogId, groupId]);
if (filtersLoading) {
return <div className="py-8 text-center">Загрузка фильтров...</div>;
}
// Определяем meta-теги для каталога
const categoryNameDecoded = decodeURIComponent(categoryName as string || 'Каталог');
const metaData = createCategoryMeta(categoryNameDecoded, visibleProductsCount || undefined);
// Генерируем микроразметку для каталога
const breadcrumbSchema = generateBreadcrumbSchema([
{ name: "Главная", url: "https://protek.ru/" },
{ name: "Каталог", url: "https://protek.ru/catalog" },
...(categoryName ? [{ name: categoryNameDecoded, url: `https://protek.ru/catalog?categoryName=${categoryName}` }] : [])
]);
const websiteSchema = generateWebSiteSchema(
"Protek - Каталог автозапчастей",
"https://protek.ru",
"https://protek.ru/search"
);
return (
<>
<Head>
<title>Catalog</title>
<meta name="description" content="Catalog" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<JsonLdScript schema={breadcrumbSchema} />
<JsonLdScript schema={websiteSchema} />
<CatalogInfoHeader
title={
isPartsAPIMode ? decodeURIComponent(categoryName as string || 'Запчасти') :
@ -525,9 +979,7 @@ export default function Catalog() {
isPartsAPIMode ?
(visibilityMap.size === 0 && allArticles.length > 0 ? undefined : visibleProductsCount) :
isPartsIndexMode ?
(searchQuery.trim() || Object.keys(selectedFilters).length > 0 ?
filteredEntities.length :
entitiesData?.partsIndexCatalogEntities?.pagination?.limit || visibleEntities.length) :
entitiesWithOffers.length :
3587
}
productName={
@ -565,35 +1017,35 @@ export default function Catalog() {
</div>
</div>
{isPartsAPIMode ? (
<div className="filters-desktop">
<div className="filters-desktop" style={{ width: '300px', marginRight: '20px', marginBottom: '80px' }}>
<Filters
filters={dynamicFilters}
onFilterChange={handleDesktopFilterChange}
filterValues={selectedFilters}
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
onSearchChange={handleSearchChange}
isLoading={filtersGenerating}
/>
</div>
) : isPartsIndexMode ? (
<div className="filters-desktop">
<div className="filters-desktop" style={{ width: '300px', marginRight: '20px', marginBottom: '80px' }}>
<Filters
filters={catalogFilters}
onFilterChange={handleDesktopFilterChange}
filterValues={selectedFilters}
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
onSearchChange={handleSearchChange}
isLoading={filtersLoading}
/>
</div>
) : (
<div className="filters-desktop">
<div className="filters-desktop" style={{ width: '300px', marginRight: '20px', marginBottom: '80px' }}>
<Filters
filters={catalogFilters}
onFilterChange={handleDesktopFilterChange}
filterValues={selectedFilters}
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
onSearchChange={handleSearchChange}
isLoading={filtersLoading}
/>
</div>
@ -603,7 +1055,7 @@ export default function Catalog() {
onClose={() => setShowFiltersMobile(false)}
filters={isPartsAPIMode ? dynamicFilters : catalogFilters}
searchQuery={searchQuery}
onSearchChange={setSearchQuery}
onSearchChange={handleSearchChange}
filterValues={selectedFilters}
onFilterChange={handleMobileFilterChange}
/>
@ -623,6 +1075,8 @@ export default function Catalog() {
</div>
)}
{/* Сообщение об ошибке */}
{isPartsAPIMode && articlesError && (
<div className="flex justify-center items-center py-8">
@ -672,132 +1126,149 @@ export default function Catalog() {
</>
)}
{/* Показываем индикатор загрузки при изменении фильтров */}
{isPartsIndexMode && isFilterChanging && (
<div className="flex flex-col items-center justify-center py-12">
<LoadingSpinner />
<div className="text-gray-500 text-lg mt-4">Применяем фильтры...</div>
</div>
)}
{/* Отображение товаров PartsIndex */}
{isPartsIndexMode && filteredEntities.length > 0 && (
{isPartsIndexMode && !isFilterChanging && accumulatedEntities.length > 0 && (
<>
{filteredEntities
.map((entity, idx) => {
const productForPrice = { id: entity.id, code: entity.code, brand: entity.brand.name };
const priceData = getPrice(productForPrice);
const isLoadingPriceData = isLoadingPrice(productForPrice);
{accumulatedEntities.slice(0, visibleCount).map((entity, idx) => {
const productForPrice = { id: entity.id, code: entity.code, brand: entity.brand.name };
const priceData = getPrice(productForPrice);
const isLoadingPriceData = isLoadingPrice(productForPrice);
// Fallback cart check via frontend context
const inCartFallback = isItemInCart(entity.id, priceData?.offerKey, entity.code, entity.brand.name);
return {
entity,
idx,
productForPrice,
priceData,
isLoadingPriceData,
hasOffer: priceData !== null || isLoadingPriceData
};
})
.filter(item => item.hasOffer) // Показываем только товары с предложениями или загружающиеся
.map(({ entity, idx, productForPrice, priceData, isLoadingPriceData }) => {
// Определяем цену для отображения
let displayPrice = "Цена по запросу";
let displayCurrency = "RUB";
let priceElement;
// Определяем цену для отображения (все товары уже отфильтрованы на сервере)
let displayPrice = "";
let displayCurrency = "RUB";
let priceElement;
if (isLoadingPriceData) {
priceElement = <PriceSkeleton />;
} else if (priceData && priceData.price) {
displayPrice = `${priceData.price.toLocaleString('ru-RU')}`;
displayCurrency = priceData.currency || "RUB";
}
if (isLoadingPriceData) {
// Показываем скелетон загрузки вместо текста
priceElement = <PriceSkeleton />;
} else if (priceData && priceData.price) {
displayPrice = `${priceData.price.toLocaleString('ru-RU')}`;
displayCurrency = priceData.currency || "RUB";
} else {
// Если нет данных о цене, показываем скелетон (товар должен загрузиться)
priceElement = <PriceSkeleton />;
}
return (
<CatalogProductCard
key={`${entity.id}_${idx}`}
title={entity.originalName || entity.name?.name || 'Товар без названия'}
brand={entity.brand.name}
articleNumber={entity.code}
brandName={entity.brand.name}
image={entity.images?.[0] || ''}
price={isLoadingPriceData ? "" : displayPrice}
priceElement={priceElement}
oldPrice=""
discount=""
currency={displayCurrency}
productId={entity.id}
artId={entity.id}
offerKey={priceData?.offerKey}
onAddToCart={() => {
// Если цена не загружена, загружаем её и добавляем в корзину
if (!priceData && !isLoadingPriceData) {
loadPriceOnDemand(productForPrice);
console.log('🔄 Загружаем цену для:', entity.code, entity.brand.name);
return;
}
return (
<CatalogProductCard
key={`${entity.id}_${idx}`}
title={entity.originalName || entity.name?.name || 'Товар без названия'}
brand={entity.brand.name}
articleNumber={entity.code}
brandName={entity.brand.name}
image={entity.images?.[0] || ''}
price={priceElement ? "" : displayPrice}
priceElement={priceElement}
oldPrice=""
discount=""
currency={displayCurrency}
productId={entity.id}
artId={entity.id}
offerKey={priceData?.offerKey}
isInCart={priceData?.isInCart || inCartFallback}
onAddToCart={async () => {
// Если цена не загружена, загружаем её и добавляем в корзину
if (!priceData && !isLoadingPriceData) {
ensurePriceLoaded(productForPrice);
console.log('🔄 Загружаем цену для:', entity.code, entity.brand.name);
return;
}
// Если цена есть, добавляем в корзину
if (priceData && priceData.price) {
const itemToAdd = {
productId: entity.id,
offerKey: priceData.offerKey,
name: entity.originalName || entity.name?.name || 'Товар без названия',
description: `${entity.brand.name} ${entity.code}`,
brand: entity.brand.name,
article: entity.code,
price: priceData.price,
currency: priceData.currency || 'RUB',
quantity: 1,
deliveryTime: '1-3 дня',
warehouse: 'Parts Index',
supplier: 'Parts Index',
isExternal: true,
image: entity.images?.[0] || '',
};
// Если цена есть, добавляем в корзину
if (priceData && priceData.price) {
const itemToAdd = {
productId: entity.id,
offerKey: priceData.offerKey,
name: entity.originalName || entity.name?.name || 'Товар без названия',
description: `${entity.brand.name} ${entity.code}`,
brand: entity.brand.name,
article: entity.code,
price: priceData.price,
currency: priceData.currency || 'RUB',
quantity: 1,
stock: undefined, // информация о наличии не доступна для PartsIndex
deliveryTime: '1-3 дня',
warehouse: 'Parts Index',
supplier: 'Parts Index',
isExternal: true,
image: entity.images?.[0] || '',
};
addItem(itemToAdd);
const result = await addItem(itemToAdd);
if (result.success) {
// Показываем уведомление
toast.success(`Товар "${entity.brand.name} ${entity.code}" добавлен в корзину за ${priceData.price.toLocaleString('ru-RU')}`);
toast.success(
<div>
<div className="font-semibold" style={{ color: '#fff' }}>Товар добавлен в корзину!</div>
<div className="text-sm" style={{ color: '#fff', opacity: 0.9 }}>{`${entity.brand.name} ${entity.code} за ${priceData.price.toLocaleString('ru-RU')}`}</div>
</div>,
{
duration: 3000,
icon: <CartIcon size={20} color="#fff" />,
}
);
} else {
toast.error('Цена товара еще загружается. Попробуйте снова через несколько секунд.');
toast.error(result.error || 'Ошибка при добавлении товара в корзину');
}
}}
/>
);
})}
} else {
toast.error('Цена товара еще загружается. Попробуйте снова через несколько секунд.');
}
}}
/>
);
})}
{/* Пагинация для PartsIndex */}
<div className="w-layout-hflex pagination">
<button
onClick={handlePrevPage}
disabled={partsIndexPage <= 1 || entitiesLoading}
className="button_strock w-button mr-2"
>
Назад
</button>
<span className="flex items-center px-4 text-gray-600">
Страница {partsIndexPage} {totalPages > partsIndexPage && `из ${totalPages}+`}
</span>
<button
onClick={handleNextPage}
disabled={!hasMoreEntities || entitiesLoading}
className="button_strock w-button ml-2"
>
{entitiesLoading ? 'Загрузка...' : 'Вперед →'}
</button>
</div>
{/* Отладочная информация */}
{isPartsIndexMode && (
<div className="text-xs text-gray-500 mt-4 p-2 bg-gray-100 rounded">
<div>🔍 Отладка PartsIndex:</div>
<div> hasMoreItems: {hasMoreItems ? 'да' : 'нет'}</div>
<div> hasMoreEntities: {hasMoreEntities ? 'да' : 'нет'}</div>
<div> entitiesPage: {entitiesPage}</div>
<div> visibleEntities: {visibleEntities.length}</div>
<div> filteredEntities: {filteredEntities.length}</div>
<div> groupId: {groupId || 'отсутствует'}</div>
<div> isLoadingMore: {isLoadingMore ? 'да' : 'нет'}</div>
<div> entitiesLoading: {entitiesLoading ? 'да' : 'нет'}</div>
<div> catalogId: {catalogId || 'отсутствует'}</div>
<div> Пагинация: {JSON.stringify(entitiesData?.partsIndexCatalogEntities?.pagination)}</div>
{/* Кнопка "Показать еще" */}
{visibleCount < accumulatedEntities.length && (
<div className="w-layout-hflex pagination">
<button
onClick={() => setVisibleCount(c => Math.min(c + ITEMS_PER_PAGE, accumulatedEntities.length))}
className="button_strock w-button"
>
Показать еще
</button>
</div>
)}
{/* Отладочная информация
{isPartsIndexMode && (
<div className="text-xs text-gray-500 mt-4 p-2 bg-gray-100 rounded">
<div>🔍 Отладка PartsIndex (исправленная логика):</div>
<div>• accumulatedEntities: {accumulatedEntities.length}</div>
<div>• entitiesWithOffers: {entitiesWithOffers.length}</div>
<div>• visibleEntities: {visibleEntities.length}</div>
<div>• currentUserPage: {currentUserPage}</div>
<div>• partsIndexPage (API): {partsIndexPage}</div>
<div>• isAutoLoading: {isAutoLoading ? 'да' : 'нет'}</div>
<div>• hasMoreEntities: {hasMoreEntities ? 'да' : 'нет'}</div>
<div>• entitiesLoading: {entitiesLoading ? 'да' : 'нет'}</div>
<div>• groupId: {groupId || 'отсутствует'}</div>
<div>• Target: {ITEMS_PER_PAGE} товаров на страницу</div>
<div>• showEmptyState: {showEmptyState ? 'да' : 'нет'}</div>
<button
onClick={() => {
console.log('🔧 Ручной запуск автоподгрузки');
autoLoadMoreEntities();
}}
className="mt-2 px-3 py-1 bg-blue-500 text-white text-xs rounded"
disabled={isAutoLoading}
>
{isAutoLoading ? 'Загружаем...' : 'Загрузить еще'}
</button>
</div>
)} */}
</>
)}
@ -811,7 +1282,16 @@ export default function Catalog() {
)}
{/* Пустое состояние для PartsIndex */}
{isPartsIndexMode && !entitiesLoading && !entitiesError && showEmptyState && (
{isPartsIndexMode && !entitiesLoading && !entitiesError && (() => {
console.log('🎯 Проверяем пустое состояние PartsIndex:', {
isPartsIndexMode,
entitiesLoading,
entitiesError,
showEmptyState,
visibleEntitiesLength: visibleEntities.length
});
return showEmptyState;
})() && (
<CatalogEmptyState
categoryName={decodeURIComponent(categoryName as string || 'товаров')}
hasFilters={searchQuery.trim() !== '' || Object.keys(selectedFilters).some(key => selectedFilters[key].length > 0)}

View File

@ -1,14 +1,20 @@
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
export default function Checkout() {
const metaConfig = getMetaByPath('/checkout');
return (
<>
<Head>
<title>Checkout</title>
<meta name="description" content="Checkout" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<Header />
{/* Вставь сюда содержимое <body> из checkout.html, преобразовав в JSX. Все пути к картинкам и svg поменяй на /images/... */}
{/* Пример: <img src="/images/logo.svg" ... /> */}

View File

@ -0,0 +1,146 @@
import React from 'react';
import Head from 'next/head';
import CatalogSubscribe from "@/components/CatalogSubscribe";
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import NewsAndPromos from "@/components/index/NewsAndPromos";
import Footer from "@/components/Footer";
import IndexTopMenuNav from "@/components/index/IndexTopMenuNav";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
import JsonLdScript from "@/components/JsonLdScript";
import { generateOrganizationSchema, generateWebSiteSchema, PROTEK_ORGANIZATION } from "@/lib/schema";
export default function Confidentiality() {
const metaData = getMetaByPath('/');
// Добавьте эти строки:
const organizationSchema = generateOrganizationSchema(PROTEK_ORGANIZATION);
const websiteSchema = generateWebSiteSchema(
"Protek - Автозапчасти и аксессуары",
"https://protek.ru",
"https://protek.ru/search"
);
return (
<>
<MetaTags {...metaData} />
<JsonLdScript schema={organizationSchema} />
<JsonLdScript schema={websiteSchema} />
<section className="section-info">
<div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9">
<div className="w-layout-hflex flex-block-7">
<a href="#" className="link-block w-inline-block">
<div>Главная</div>
</a>
<div className="text-block-3"></div>
<a href="#" className="link-block-2 w-inline-block">
<div>Политика конфиденциальности</div>
</a>
</div>
<div className="w-layout-hflex flex-block-8">
<div className="w-layout-hflex flex-block-10">
<h1 className="heading">Политика конфиденциальности</h1>
</div>
</div>
</div>
</div>
</section>
<div className="flex relative gap-8 items-start self-stretch pt-10 pb-20 max-md:p-8 max-sm:gap-5 max-sm:p-5">
<div className="flex relative flex-col gap-8 items-start p-10 bg-white rounded-3xl flex-[1_0_0] max-w-[1580px] mx-auto max-md:p-8 max-sm:gap-5 max-sm:p-5">
<div className="flex relative flex-col gap-5 items-start self-stretch max-sm:gap-4">
<div
layer-name="Объявлен старт продаж электрических насосов"
className="relative self-stretch text-3xl font-bold leading-9 text-gray-950"
>
Объявлен старт продаж электрических насосов
</div>
<div
layer-name="Бренд вывел на рынок сразу широкий ассортимент, уже на старте продаж - более 100 артикулов и включает в себя позиции для брендов-лидеров автомобильного рынка, например: артикул 77WPE080 для Mercedes-Benz S-CLASS (W221, C216), артикул 77WPE096 Land Rover DISCOVERY V (L462) / Jaguar F-PACE (X761), артикул 77WPE014 Audi Q5 (8RB) / Volkswagen TOUAREG (7P5, 7P6)."
className="relative self-stretch text-base leading-6 text-gray-600 max-sm:text-sm"
>
Бренд вывел на рынок сразу широкий ассортимент, уже на старте
продаж - более 100 артикулов и включает в себя позиции для
брендов-лидеров автомобильного рынка, например: артикул 77WPE080
для Mercedes-Benz S-CLASS (W221, C216), артикул 77WPE096 Land
Rover DISCOVERY V (L462) / Jaguar F-PACE (X761), артикул 77WPE014
Audi Q5 (8RB) / Volkswagen TOUAREG (7P5, 7P6).
</div>
</div>
<div className="flex relative flex-col gap-8 items-start self-stretch max-sm:gap-5">
<div
layer-name="Преимущества электрических насосов охлаждающей жидкости MasterKit Electro:"
className="relative self-stretch text-3xl font-medium leading-9 text-gray-950"
>
Преимущества электрических насосов охлаждающей жидкости MasterKit
Electro:
</div>
<div className="flex relative flex-col gap-3.5 items-start self-stretch">
<div className="flex relative gap-10 items-start w-full max-md:gap-5 max-sm:gap-4">
<div className="relative shrink-0 mt-2 w-2 h-2 bg-gray-600 rounded-full" />
<div
layer-name="Отличная производительность за счёт применения компонентов известных мировых брендов."
className="relative text-base leading-6 text-gray-600 flex-[1_0_0] max-sm:text-sm"
>
Отличная производительность за счёт применения компонентов
известных мировых брендов.
</div>
</div>
<div className="flex relative gap-10 items-start w-full max-md:gap-5 max-sm:gap-4">
<div className="relative shrink-0 mt-2 w-2 h-2 bg-gray-600 rounded-full" />
<div
layer-name="Герметичность и устойчивость к коррозии"
className="relative text-base leading-6 text-gray-600 flex-[1_0_0] max-sm:text-sm"
>
Герметичность и устойчивость к коррозии
</div>
</div>
<div className="flex relative gap-10 items-start w-full max-md:gap-5 max-sm:gap-4">
<div className="relative shrink-0 mt-2 w-2 h-2 bg-gray-600 rounded-full" />
<div
layer-name="Высококачественные материалы компонентов, обеспечивающие долгий срок службы"
className="relative text-base leading-6 text-gray-600 flex-[1_0_0] max-sm:text-sm"
>
Высококачественные материалы компонентов, обеспечивающие
долгий срок службы
</div>
</div>
<div className="flex relative gap-10 items-start w-full max-md:gap-5 max-sm:gap-4">
<div className="relative shrink-0 mt-2 w-2 h-2 bg-gray-600 rounded-full" />
<div
layer-name="Широкий ассортимент более 100 артикулов"
className="relative text-base leading-6 text-gray-600 flex-[1_0_0] max-sm:text-sm"
>
Широкий ассортимент более 100 артикулов
</div>
</div>
</div>
<div
layer-name="На электрические насосы системы охлаждения MasterKit Electro предоставляется гарантия 1 год или 30.000 км пробега, в зависимости от того, что наступит раньше. Все новинки уже внесены в каталог подбора продукции и доступны для заказа."
className="relative self-stretch text-base leading-6 text-gray-600 max-sm:text-sm"
>
На электрические насосы системы охлаждения MasterKit Electro
предоставляется гарантия 1 год или 30.000 км пробега, в
зависимости от того, что наступит раньше. Все новинки уже внесены
в каталог подбора продукции и доступны для заказа.
</div>
<div
layer-name="ABig_Button"
data-component-name="ABig_Button"
data-variant-name="Button big=Default"
className="relative gap-2.5 px-10 py-6 text-lg font-medium leading-5 text-center text-white no-underline bg-red-600 rounded-xl transition-all cursor-pointer border-[none] duration-[0.2s] ease-[ease] w-fit max-sm:px-8 max-sm:py-5 max-sm:w-full hover:bg-red-700"
>
Перейти к товару
</div>
</div>
</div>
</div>
<section className="section-3">
<CatalogSubscribe />
</section>
<Footer />
<MobileMenuBottomSection />
</>
);
}

View File

@ -8,17 +8,21 @@ import InfoContacts from "@/components/contacts/InfoContacts";
import MapContacts from "@/components/contacts/MapContacts";
import OrderContacts from "@/components/contacts/OrderContacts";
import LegalContacts from "@/components/contacts/LegalContacts";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
import JsonLdScript from "@/components/JsonLdScript";
import { generateLocalBusinessSchema, PROTEK_LOCAL_BUSINESS } from "@/lib/schema";
const Contacts = () => (
<>
<Head>
<title>Contacts</title>
<meta name="description" content="Contacts" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
const Contacts = () => {
const metaData = getMetaByPath('/contacts');
// Генерируем микроразметку LocalBusiness для страницы контактов
const localBusinessSchema = generateLocalBusinessSchema(PROTEK_LOCAL_BUSINESS);
return (
<>
<MetaTags {...metaData} />
<JsonLdScript schema={localBusinessSchema} />
<InfoContacts />
<section className="main">
<div className="w-layout-blockcontainer container w-container">
@ -38,7 +42,8 @@ const Contacts = () => (
</section>
<Footer />
<MobileMenuBottomSection />
</>
);
</>
);
};
export default Contacts;

View File

@ -1,14 +1,20 @@
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
export default function DetailCategory() {
const metaConfig = getMetaByPath('/detail_category');
return (
<>
<Head>
<title>Detail Category</title>
<meta name="description" content="Detail Category" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<Header />
{/* Вставь сюда содержимое <body> из detail_category.html, преобразовав в JSX. Все пути к картинкам и svg поменяй на /images/... */}
{/* Пример: <img src="/images/logo.svg" ... /> */}

View File

@ -1,14 +1,20 @@
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
export default function DetailProduct() {
const metaConfig = getMetaByPath('/detail_product');
return (
<>
<Head>
<title>Detail Product</title>
<meta name="description" content="Detail Product" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<Header />
{/* Вставь сюда содержимое <body> из detail_product.html, преобразовав в JSX. Все пути к картинкам и svg поменяй на /images/... */}
{/* Пример: <img src="/images/logo.svg" ... /> */}

View File

@ -1,14 +1,20 @@
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
export default function DetailSku() {
const metaConfig = getMetaByPath('/detail_sku');
return (
<>
<Head>
<title>Detail SKU</title>
<meta name="description" content="Detail SKU" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<Header />
{/* Вставь сюда содержимое <body> из detail_sku.html, преобразовав в JSX. Все пути к картинкам и svg поменяй на /images/... */}
{/* Пример: <img src="/images/logo.svg" ... /> */}

View File

@ -1,4 +1,5 @@
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import CatalogSubscribe from "@/components/CatalogSubscribe";
@ -19,6 +20,8 @@ export default function Favorite() {
const [sortBy, setSortBy] = useState<'name' | 'brand' | 'date'>('date');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
const metaConfig = getMetaByPath('/favorite');
// Создаем динамические фильтры на основе данных избранного
const dynamicFilters: FilterConfig[] = useMemo(() => {
const filters: FilterConfig[] = [];
@ -96,13 +99,13 @@ export default function Favorite() {
return (
<>
<Head>
<title>Избранное - Protek Auto</title>
<meta name="description" content="Ваши избранные товары" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<FavoriteInfo />
<section className="main">

59
src/pages/index-old.tsx Normal file
View File

@ -0,0 +1,59 @@
import Head from "next/head";
import Image from "next/image";
import { Geist, Geist_Mono } from "next/font/google";
import styles from "@/styles/Home.module.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import CatalogSubscribe from "@/components/CatalogSubscribe";
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import HeroSlider from "@/components/index/HeroSlider";
import CatalogSection from "@/components/index/CatalogSection";
import AvailableParts from "@/components/index/AvailableParts";
import NewsAndPromos from "@/components/index/NewsAndPromos";
import AboutHelp from "@/components/about/AboutHelp";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
import JsonLdScript from "@/components/JsonLdScript";
import { generateOrganizationSchema, generateWebSiteSchema, PROTEK_ORGANIZATION } from "@/lib/schema";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export default function HomeOld() {
const metaData = getMetaByPath('/');
// Генерируем микроразметку для главной страницы
const organizationSchema = generateOrganizationSchema(PROTEK_ORGANIZATION);
const websiteSchema = generateWebSiteSchema(
"Protek - Автозапчасти и аксессуары",
"https://protek.ru",
"https://protek.ru/search"
);
return (
<>
<MetaTags {...metaData} />
<JsonLdScript schema={organizationSchema} />
<JsonLdScript schema={websiteSchema} />
<HeroSlider />
<CatalogSection />
<div className="w-layout-blockcontainer container w-container">
<AboutHelp />
</div>
<AvailableParts />
<NewsAndPromos />
<section className="section-3">
<CatalogSubscribe />
</section>
<Footer />
<MobileMenuBottomSection />
</>
);
}

View File

@ -1,44 +1,49 @@
import Head from "next/head";
import Image from "next/image";
import { Geist, Geist_Mono } from "next/font/google";
import styles from "@/styles/Home.module.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import React from 'react';
import Head from 'next/head';
import CatalogSubscribe from "@/components/CatalogSubscribe";
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import HeroSlider from "@/components/index/HeroSlider";
import CatalogSection from "@/components/index/CatalogSection";
import AvailableParts from "@/components/index/AvailableParts";
import NewsAndPromos from "@/components/index/NewsAndPromos";
import AboutHelp from "@/components/about/AboutHelp";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
import Footer from "@/components/Footer";
import IndexTopMenuNav from "@/components/index/IndexTopMenuNav";
import ProductOfDaySection from "@/components/index/ProductOfDaySection";
import CategoryNavSection from "@/components/index/CategoryNavSection";
import BrandSelectionSection from "@/components/index/BrandSelectionSection";
import BestPriceSection from "@/components/index/BestPriceSection";
import TopSalesSection from "@/components/index/TopSalesSection";
import PromoImagesSection from "@/components/index/PromoImagesSection";
import NewArrivalsSection from '@/components/index/NewArrivalsSection';
import SupportVinSection from '@/components/index/SupportVinSection';
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
import JsonLdScript from "@/components/JsonLdScript";
import { generateOrganizationSchema, generateWebSiteSchema, PROTEK_ORGANIZATION } from "@/lib/schema";
import HeroSlider from "@/components/index/HeroSlider";
export default function Home() {
const metaData = getMetaByPath('/');
// Генерируем микроразметку для главной страницы
const organizationSchema = generateOrganizationSchema(PROTEK_ORGANIZATION);
const websiteSchema = generateWebSiteSchema(
"Protek - Автозапчасти и аксессуары",
"https://protek.ru",
"https://protek.ru/search"
);
return (
<>
<Head>
<title>Protek</title>
<meta name="description" content="Protek" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<HeroSlider />
<CatalogSection />
<div className="w-layout-blockcontainer container w-container">
<AboutHelp />
</div>
<AvailableParts />
<MetaTags {...metaData} />
<JsonLdScript schema={organizationSchema} />
<JsonLdScript schema={websiteSchema} />
{/* <IndexTopMenuNav /> */}
<ProductOfDaySection />
<CategoryNavSection />
<BrandSelectionSection />
<BestPriceSection />
<TopSalesSection />
<PromoImagesSection />
<NewArrivalsSection />
<SupportVinSection />
<NewsAndPromos />
<section className="section-3">
<CatalogSubscribe />

View File

@ -6,23 +6,19 @@ import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import InfoNewsOpen from "@/components/news-open/InfoNewsOpen";
import ContentNews from "@/components/news-open/ContentNews";
import NewsCard from "@/components/news/NewsCard";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
export default function NewsOpen() {
const metaData = getMetaByPath('/news-open');
return (
<>
<MetaTags {...metaData} />
<Head>
<title>news open</title>
<meta content="news open" property="og:title" />
<meta content="news open" property="twitter:title" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta content="Webflow" name="generator" />
<link href="/css/normalize.css" rel="stylesheet" type="text/css" />
<link href="/css/webflow.css" rel="stylesheet" type="text/css" />
<link href="/css/protekproject.webflow.css" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<InfoNewsOpen />
<section className="main">

View File

@ -1,4 +1,5 @@
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import CatalogSubscribe from "@/components/CatalogSubscribe";
@ -8,16 +9,17 @@ import NewsCard from "@/components/news/NewsCard";
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
export default function News() {
const metaConfig = getMetaByPath('/news');
return (
<>
<Head>
<title>News</title>
<meta name="description" content="News" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<InfoNews />
<section className="main">
<div className="w-layout-blockcontainer container w-container">

View File

@ -1,14 +1,20 @@
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
export default function OrderConfirmation() {
const metaConfig = getMetaByPath('/order-confirmation');
return (
<>
<Head>
<title>Order Confirmation</title>
<meta name="description" content="Order Confirmation" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<Header />
{/* Вставь сюда содержимое <body> из order-confirmation.html, преобразовав в JSX. Все пути к картинкам и svg поменяй на /images/... */}
{/* Пример: <img src="/images/logo.svg" ... /> */}

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react";
import Head from "next/head";
import MetaTags from "../../components/MetaTags";
import { getMetaByPath } from "../../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { useRouter } from "next/router";
@ -30,16 +31,17 @@ export default function PaymentCancelled() {
router.push('/catalog');
};
const metaConfig = getMetaByPath('/payment/cancelled');
return (
<>
<Head>
<title>Оплата отменена - Protekauto</title>
<meta name="description" content="Оплата заказа была отменена" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link href="/images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<Header />

View File

@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react";
import Head from "next/head";
import MetaTags from "../../components/MetaTags";
import { getMetaByPath } from "../../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { useRouter } from "next/router";
@ -40,16 +41,17 @@ export default function PaymentFailed() {
router.push('/catalog');
};
const metaConfig = getMetaByPath('/payment/failed');
return (
<>
<Head>
<title>Ошибка оплаты - Protekauto</title>
<meta name="description" content="Произошла ошибка при оплате заказа" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link href="/images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<Header />

View File

@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import Head from 'next/head';
import MetaTags from "../../components/MetaTags";
import { getMetaByPath } from "../../lib/meta-config";
const InvoicePage: React.FC = () => {
const router = useRouter();
@ -20,12 +21,17 @@ const InvoicePage: React.FC = () => {
router.push('/profile/orders');
};
const metaConfig = getMetaByPath('/payment/invoice');
return (
<>
<Head>
<title>Счёт на оплату - Протек Авто</title>
<meta name="description" content="Счёт на оплату заказа" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<div className="w-layout-vflex" style={{
minHeight: '100vh',

View File

@ -1,11 +1,12 @@
import React, { useEffect, useState } from "react";
import Head from "next/head";
import Footer from "@/components/Footer";
import { useRouter } from "next/router";
import { useMutation, ApolloProvider } from "@apollo/client";
import { gql } from "@apollo/client";
import { apolloClient } from "@/lib/apollo";
import toast from "react-hot-toast";
import MetaTags from "../../components/MetaTags";
import { getMetaByPath } from "../../lib/meta-config";
const CONFIRM_PAYMENT = gql`
mutation ConfirmPayment($orderId: ID!) {
@ -74,17 +75,6 @@ function PaymentSuccessContent() {
return (
<>
<Head>
<title>Оплата прошла успешно - Protekauto</title>
<meta name="description" content="Ваш заказ успешно оплачен" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="/images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<link href="/images/webclip.png" rel="apple-touch-icon" />
</Head>
<div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9">
<div className="w-layout-hflex flex-block-7">
@ -211,9 +201,20 @@ function PaymentSuccessContent() {
}
export default function PaymentSuccess() {
const metaConfig = getMetaByPath('/payment/success');
return (
<ApolloProvider client={apolloClient}>
<PaymentSuccessContent />
</ApolloProvider>
<>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<ApolloProvider client={apolloClient}>
<PaymentSuccessContent />
</ApolloProvider>
</>
);
}

View File

@ -1,4 +1,5 @@
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import CatalogSubscribe from "@/components/CatalogSubscribe";
@ -10,12 +11,17 @@ import DeliveryInfo from "@/components/payments/DeliveryInfo";
import PaymentsCompony from "@/components/payments/PaymentsCompony";
export default function PaymentsMethod() {
const metaConfig = getMetaByPath('/payments-method');
return (
<>
<Head>
<title>Payments Method</title>
<meta name="description" content="Payments Method" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<InfoPayments />
<section className="main">

View File

@ -0,0 +1,177 @@
import React from 'react';
import Head from 'next/head';
const PrivacyPolicy: React.FC = () => {
return (
<>
<Head>
<title>Политика конфиденциальности | ПротекАвто</title>
<meta name="description" content="Политика конфиденциальности интернет-магазина автозапчастей ПротекАвто" />
<meta name="robots" content="index, follow" />
</Head>
<div className="min-h-screen bg-gray-50 py-12">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="bg-white rounded-lg shadow-sm p-8">
<h1 className="text-3xl font-bold text-gray-950 mb-8">
Политика конфиденциальности
</h1>
<div className="prose prose-gray max-w-none space-y-6">
<section>
<h2 className="text-2xl font-semibold text-gray-950 mb-4">
1. Общие положения
</h2>
<p className="text-gray-600 leading-relaxed">
Настоящая Политика конфиденциальности определяет порядок обработки и защиты персональных данных
пользователей интернет-магазина ПротекАвто (далее «Сайт»). Мы уважаем вашу конфиденциальность
и стремимся защитить ваши персональные данные.
</p>
</section>
<section>
<h2 className="text-2xl font-semibold text-gray-950 mb-4">
2. Сбор и использование персональных данных
</h2>
<p className="text-gray-600 leading-relaxed mb-4">
Мы собираем следующие категории персональных данных:
</p>
<ul className="list-disc pl-6 text-gray-600 space-y-2">
<li>Контактная информация (имя, телефон, email)</li>
<li>Данные для доставки (адрес, индекс)</li>
<li>Информация о заказах и покупках</li>
<li>Техническая информация (IP-адрес, браузер, устройство)</li>
</ul>
</section>
<section>
<h2 className="text-2xl font-semibold text-gray-950 mb-4">
3. Файлы cookie
</h2>
<p className="text-gray-600 leading-relaxed mb-4">
Наш сайт использует файлы cookie для улучшения пользовательского опыта. Мы используем следующие типы cookie:
</p>
<div className="space-y-4">
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-semibold text-gray-950 mb-2">Необходимые cookie</h3>
<p className="text-sm text-gray-600">
Обеспечивают базовую функциональность сайта, включая корзину покупок, авторизацию и безопасность.
</p>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-semibold text-gray-950 mb-2">Аналитические cookie</h3>
<p className="text-sm text-gray-600">
Помогают нам понять, как посетители используют сайт, чтобы улучшить его работу.
</p>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-semibold text-gray-950 mb-2">Маркетинговые cookie</h3>
<p className="text-sm text-gray-600">
Используются для показа релевантной рекламы и отслеживания эффективности рекламных кампаний.
</p>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<h3 className="font-semibold text-gray-950 mb-2">Функциональные cookie</h3>
<p className="text-sm text-gray-600">
Обеспечивают расширенную функциональность и персонализацию сайта.
</p>
</div>
</div>
</section>
<section>
<h2 className="text-2xl font-semibold text-gray-950 mb-4">
4. Цели обработки данных
</h2>
<p className="text-gray-600 leading-relaxed mb-4">
Мы обрабатываем ваши персональные данные для следующих целей:
</p>
<ul className="list-disc pl-6 text-gray-600 space-y-2">
<li>Обработка и выполнение заказов</li>
<li>Связь с клиентами по вопросам заказов</li>
<li>Улучшение качества обслуживания</li>
<li>Анализ использования сайта</li>
<li>Маркетинговые коммуникации (с вашего согласия)</li>
</ul>
</section>
<section>
<h2 className="text-2xl font-semibold text-gray-950 mb-4">
5. Передача данных третьим лицам
</h2>
<p className="text-gray-600 leading-relaxed">
Мы не продаем и не передаем ваши персональные данные третьим лицам, за исключением случаев,
необходимых для выполнения наших обязательств перед вами (доставка, оплата) или требований законодательства.
</p>
</section>
<section>
<h2 className="text-2xl font-semibold text-gray-950 mb-4">
6. Защита данных
</h2>
<p className="text-gray-600 leading-relaxed">
Мы применяем современные технические и организационные меры для защиты ваших персональных данных
от несанкционированного доступа, изменения, раскрытия или уничтожения.
</p>
</section>
<section>
<h2 className="text-2xl font-semibold text-gray-950 mb-4">
7. Ваши права
</h2>
<p className="text-gray-600 leading-relaxed mb-4">
Вы имеете право:
</p>
<ul className="list-disc pl-6 text-gray-600 space-y-2">
<li>Получить информацию о обработке ваших данных</li>
<li>Внести изменения в ваши данные</li>
<li>Удалить ваши данные</li>
<li>Ограничить обработку данных</li>
<li>Отозвать согласие на обработку данных</li>
</ul>
</section>
<section>
<h2 className="text-2xl font-semibold text-gray-950 mb-4">
8. Контактная информация
</h2>
<p className="text-gray-600 leading-relaxed">
По вопросам обработки персональных данных вы можете обратиться к нам:
</p>
<div className="bg-gray-50 p-4 rounded-lg mt-4">
<p className="text-gray-600">
<strong>Email:</strong> privacy@protekauto.ru<br />
<strong>Телефон:</strong> +7 (495) 123-45-67<br />
<strong>Адрес:</strong> г. Москва, ул. Примерная, д. 1
</p>
</div>
</section>
<section>
<h2 className="text-2xl font-semibold text-gray-950 mb-4">
9. Изменения в политике
</h2>
<p className="text-gray-600 leading-relaxed">
Мы оставляем за собой право вносить изменения в настоящую Политику конфиденциальности.
Актуальная версия всегда доступна на данной странице.
</p>
</section>
<div className="mt-8 pt-8 border-t border-gray-200">
<p className="text-sm text-gray-500">
Последнее обновление: {new Date().toLocaleDateString('ru-RU')}
</p>
</div>
</div>
</div>
</div>
</div>
</>
);
};
export default PrivacyPolicy;

View File

@ -12,7 +12,8 @@ import LKMenu from '@/components/LKMenu';
import ProfileActsMain from '@/components/profile/ProfileActsMain';
import ProfileInfo from '@/components/profile/ProfileInfo';
import NotificationMane from "@/components/profile/NotificationMane";
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
const ProfileActsPage = () => {
const router = useRouter();
@ -59,13 +60,13 @@ const ProfileActsPage = () => {
}
return (
<>
<Head>
<title>ProfileActs</title>
<meta content="ProfileActs" property="og:title" />
<meta content="ProfileActs" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title={getMetaByPath('/profile-acts').title}
description={getMetaByPath('/profile-acts').description}
keywords={getMetaByPath('/profile-acts').keywords}
ogTitle={getMetaByPath('/profile-acts').ogTitle}
ogDescription={getMetaByPath('/profile-acts').ogDescription}
/>
<ProfileInfo />
<div className="flex flex-col pt-10 pb-16 max-md:px-5">
<div className="flex relative gap-8 items-start self-stretch max-md:gap-5 max-sm:flex-col max-sm:gap-4 justify-center mx-auto max-w-[1580px] w-full h-full">

View File

@ -7,19 +7,17 @@ import LKMenu from '@/components/LKMenu';
import ProfileAddressesMain from '@/components/profile/ProfileAddressesMain';
import ProfileInfo from '@/components/profile/ProfileInfo';
import Head from "next/head";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
const ProfileAddressesPage = () => {
const metaData = getMetaByPath('/profile-addresses');
return (
<div className="page-wrapper">
<Head>
<title>ProfileAddresses</title>
<meta content="ProfileAddresses" property="og:title" />
<meta content="ProfileAddresses" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<ProfileInfo />
<div className="flex flex-col pt-10 pb-16 max-md:px-5">

View File

@ -10,7 +10,8 @@ import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import LKMenu from '@/components/LKMenu';
import ProfileBalanceMain from '@/components/profile/ProfileBalanceMain';
import ProfileInfo from '@/components/profile/ProfileInfo';
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
const ProfileBalancePage = () => {
const router = useRouter();
@ -56,15 +57,17 @@ const ProfileBalancePage = () => {
);
}
const metaConfig = getMetaByPath('/profile-balance');
return (
<div className="page-wrapper">
<Head>
<title>ProfileBalance</title>
<meta content="ProfileBalance" property="og:title" />
<meta content="ProfileBalance" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<ProfileInfo />
<div className="flex flex-col pt-10 pb-16 max-md:px-5">
<div className="flex relative gap-8 items-start self-stretch max-md:gap-5 max-sm:flex-col max-sm:gap-4 justify-center mx-auto max-w-[1580px] w-full h-full">

View File

@ -0,0 +1,34 @@
import React from 'react';
import Head from 'next/head';
import LKMenu from '@/components/LKMenu';
import CookieSettings from '@/components/profile/CookieSettings';
const ProfileCookieSettingsPage: React.FC = () => {
return (
<>
<Head>
<title>Настройки cookies | Личный кабинет | ПротекАвто</title>
<meta name="description" content="Управление настройками файлов cookie в личном кабинете" />
<meta name="robots" content="noindex, nofollow" />
</Head>
<div className="min-h-screen bg-gray-50">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="flex gap-8 max-md:flex-col">
{/* Боковое меню */}
<div className="w-80 max-md:w-full">
<LKMenu />
</div>
{/* Основной контент */}
<div className="flex-1">
<CookieSettings />
</div>
</div>
</div>
</div>
</>
);
};
export default ProfileCookieSettingsPage;

View File

@ -7,19 +7,17 @@ import LKMenu from '@/components/LKMenu';
import ProfileGarageMain from '@/components/profile/ProfileGarageMain';
import ProfileInfo from '@/components/profile/ProfileInfo';
import Head from "next/head";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
const ProfileGaragePage = () => {
const metaData = getMetaByPath('/profile-gar');
return (
<div className="page-wrapper">
<Head>
<title>ProfileGarage</title>
<meta content="ProfileGarage" property="og:title" />
<meta content="ProfileGarage" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<ProfileInfo />
<div className="flex flex-col pt-10 pb-16 max-md:px-5">

View File

@ -7,6 +7,8 @@ import LKMenu from '@/components/LKMenu';
import ProfileHistoryMain from '@/components/profile/ProfileHistoryMain';
import ProfileInfo from '@/components/profile/ProfileInfo';
import Head from "next/head";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
@ -29,19 +31,15 @@ const ProfileHistoryPage = () => {
};
}, []);
const metaData = getMetaByPath('/profile-history');
return (
<>
<Head>
<title>ProfileHistory</title>
<meta content="ProfileHistory" property="og:title" />
<meta content="ProfileHistory" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<div className="page-wrapper h-full flex flex-col flex-1">
<MetaTags {...metaData} />
<div className="page-wrapper">
<ProfileInfo />
<div className="flex flex-col pt-10 pb-16 max-md:px-5 h-full flex-1">
<div className="flex relative gap-8 items-start self-stretch max-md:gap-5 max-sm:flex-col max-sm:gap-4 justify-center mx-auto min-h-[526px] max-w-[1580px] w-full h-full">
<div className="flex flex-col pt-10 pb-16 max-md:px-5">
<div className="flex relative gap-8 items-start self-stretch max-md:gap-5 max-sm:flex-col max-sm:gap-4 justify-center mx-auto max-w-[1580px] w-full h-full">
<LKMenu ref={menuRef} />
<ProfileHistoryMain />
</div>

View File

@ -8,20 +8,18 @@ import LKMenu from '@/components/LKMenu';
import ProfileOrdersMain from '@/components/profile/ProfileOrdersMain';
import ProfileInfo from '@/components/profile/ProfileInfo';
import Head from "next/head";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
const ProfileOrdersPage = () => {
const metaData = getMetaByPath('/profile-orders');
return (
<div className="page-wrapper">
<Head>
<title>ProfileOrders</title>
<meta content="ProfileOrders" property="og:title" />
<meta content="ProfileOrders" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<ProfileInfo />
<div className="flex flex-col pt-10 pb-16 max-md:px-5">

View File

@ -10,7 +10,8 @@ import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import LKMenu from '@/components/LKMenu';
import ProfileRequisitiesMain from '@/components/profile/ProfileRequisitiesMain';
import ProfileInfo from '@/components/profile/ProfileInfo';
import Head from "next/head";
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
const ProfileRequisitiesPage = () => {
const router = useRouter();
@ -55,15 +56,18 @@ const ProfileRequisitiesPage = () => {
</div>
);
}
const metaConfig = getMetaByPath('/profile-req');
return (
<div className="page-wrapper">
<Head>
<title>ProfileRequisities</title>
<meta content="ProfileRequisities" property="og:title" />
<meta content="ProfileRequisities" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags
title={metaConfig.title}
description={metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<ProfileInfo />
<div className="flex flex-col pt-10 pb-16 max-md:px-5">

View File

@ -7,19 +7,17 @@ import LKMenu from '@/components/LKMenu';
import ProfileSettingsMain from '@/components/profile/ProfileSettingsMain';
import ProfileInfo from '@/components/profile/ProfileInfo';
import Head from "next/head";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
const ProfileSettingsPage = () => {
const metaData = getMetaByPath('/profile-set');
return (
<div className="page-wrapper h-full flex flex-col flex-1">
<Head>
<title>ProfileHistory</title>
<meta content="ProfileSettings" property="og:title" />
<meta content="ProfileSettings" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<ProfileInfo />
<div className="flex flex-col pt-10 pb-16 max-md:px-5">

36
src/pages/robots.txt.ts Normal file
View File

@ -0,0 +1,36 @@
import { NextApiRequest, NextApiResponse } from 'next'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const robotsTxt = `User-agent: *
Allow: /
# Запрещаем индексацию служебных страниц
Disallow: /api/
Disallow: /admin/
Disallow: /dashboard/
Disallow: /_next/
Disallow: /static/
Disallow: /test-auth/
# Запрещаем индексацию страниц с параметрами
Disallow: /*?*
# Разрешаем основные страницы
Allow: /
Allow: /catalog
Allow: /about
Allow: /contacts
Allow: /news
Allow: /brands
Allow: /delivery
Allow: /payment
Allow: /wholesale
Allow: /vin
# Указываем карту сайта
Sitemap: https://protekauto.ru/sitemap.xml
`
res.setHeader('Content-Type', 'text/plain')
res.status(200).send(robotsTxt)
}

View File

@ -16,14 +16,29 @@ import MobileMenuBottomSection from '../components/MobileMenuBottomSection';
import { SEARCH_PRODUCT_OFFERS, GET_ANALOG_OFFERS } from "@/lib/graphql";
import { useArticleImage } from "@/hooks/useArticleImage";
import { usePartsIndexEntityInfo } from "@/hooks/usePartsIndex";
import { useCart } from "@/contexts/CartContext";
import MetaTags from "@/components/MetaTags";
import { createProductMeta } from "@/lib/meta-config";
const ANALOGS_CHUNK_SIZE = 5;
const sortOptions = [
"По цене",
"По рейтингу",
"По количеству"
];
// Функция для расчета даты доставки
const calculateDeliveryDate = (deliveryDays: number): string => {
const today = new Date();
const deliveryDate = new Date(today);
deliveryDate.setDate(today.getDate() + deliveryDays);
const months = [
'января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'
];
const day = deliveryDate.getDate();
const month = months[deliveryDate.getMonth()];
const year = deliveryDate.getFullYear();
return `${day} ${month} ${year}`;
};
// Функция для создания динамических фильтров
const createFilters = (result: any, loadedAnalogs: any): FilterConfig[] => {
@ -52,26 +67,40 @@ const createFilters = (result: any, loadedAnalogs: any): FilterConfig[] => {
});
}
// Фильтр по цене
const prices: number[] = [];
// Получаем все доступные предложения для расчета диапазонов
const allAvailableOffers: any[] = [];
// Добавляем основные предложения
result.internalOffers?.forEach((offer: any) => {
if (offer.price > 0) prices.push(offer.price);
allAvailableOffers.push(offer);
});
result.externalOffers?.forEach((offer: any) => {
allAvailableOffers.push(offer);
});
// Добавляем предложения аналогов
Object.values(loadedAnalogs).forEach((analog: any) => {
analog.internalOffers?.forEach((offer: any) => {
allAvailableOffers.push({
...offer,
deliveryDuration: offer.deliveryDays
});
});
analog.externalOffers?.forEach((offer: any) => {
allAvailableOffers.push({
...offer,
deliveryDuration: offer.deliveryTime
});
});
});
// Фильтр по цене - только если есть предложения с разными ценами
const prices: number[] = [];
allAvailableOffers.forEach((offer: any) => {
if (offer.price > 0) prices.push(offer.price);
});
// Добавляем цены аналогов
Object.values(loadedAnalogs).forEach((analog: any) => {
analog.internalOffers?.forEach((offer: any) => {
if (offer.price > 0) prices.push(offer.price);
});
analog.externalOffers?.forEach((offer: any) => {
if (offer.price > 0) prices.push(offer.price);
});
});
if (prices.length > 0) {
if (prices.length > 1) {
const minPrice = Math.min(...prices);
const maxPrice = Math.max(...prices);
@ -85,26 +114,14 @@ const createFilters = (result: any, loadedAnalogs: any): FilterConfig[] => {
}
}
// Фильтр по сроку доставки
// Фильтр по сроку доставки - только если есть предложения с разными сроками
const deliveryDays: number[] = [];
result.internalOffers?.forEach((offer: any) => {
if (offer.deliveryDays && offer.deliveryDays > 0) deliveryDays.push(offer.deliveryDays);
});
result.externalOffers?.forEach((offer: any) => {
if (offer.deliveryTime && offer.deliveryTime > 0) deliveryDays.push(offer.deliveryTime);
allAvailableOffers.forEach((offer: any) => {
const days = offer.deliveryDays || offer.deliveryTime || offer.deliveryDuration;
if (days && days > 0) deliveryDays.push(days);
});
// Добавляем сроки доставки аналогов
Object.values(loadedAnalogs).forEach((analog: any) => {
analog.internalOffers?.forEach((offer: any) => {
if (offer.deliveryDays && offer.deliveryDays > 0) deliveryDays.push(offer.deliveryDays);
});
analog.externalOffers?.forEach((offer: any) => {
if (offer.deliveryTime && offer.deliveryTime > 0) deliveryDays.push(offer.deliveryTime);
});
});
if (deliveryDays.length > 0) {
if (deliveryDays.length > 1) {
const minDays = Math.min(...deliveryDays);
const maxDays = Math.max(...deliveryDays);
@ -118,26 +135,13 @@ const createFilters = (result: any, loadedAnalogs: any): FilterConfig[] => {
}
}
// Фильтр по количеству наличия
// Фильтр по количеству наличия - только если есть предложения с разными количествами
const quantities: number[] = [];
result.internalOffers?.forEach((offer: any) => {
if (offer.quantity && offer.quantity > 0) quantities.push(offer.quantity);
});
result.externalOffers?.forEach((offer: any) => {
allAvailableOffers.forEach((offer: any) => {
if (offer.quantity && offer.quantity > 0) quantities.push(offer.quantity);
});
// Добавляем количества аналогов
Object.values(loadedAnalogs).forEach((analog: any) => {
analog.internalOffers?.forEach((offer: any) => {
if (offer.quantity && offer.quantity > 0) quantities.push(offer.quantity);
});
analog.externalOffers?.forEach((offer: any) => {
if (offer.quantity && offer.quantity > 0) quantities.push(offer.quantity);
});
});
if (quantities.length > 0) {
if (quantities.length > 1) {
const minQuantity = Math.min(...quantities);
const maxQuantity = Math.max(...quantities);
@ -161,49 +165,62 @@ const getBestOffers = (offers: any[]) => {
if (validOffers.length === 0) return [];
const result: { offer: any; type: string }[] = [];
const usedOfferIds = new Set<string>();
// 1. Самая низкая цена (среди всех предложений)
const lowestPriceOffer = [...validOffers].sort((a, b) => a.price - b.price)[0];
if (lowestPriceOffer) {
result.push({ offer: lowestPriceOffer, type: 'Самая низкая цена' });
usedOfferIds.add(`${lowestPriceOffer.articleNumber}-${lowestPriceOffer.price}-${lowestPriceOffer.deliveryDuration}`);
}
// 2. Самый дешевый аналог (только среди аналогов)
// 2. Самый дешевый аналог (только среди аналогов) - всегда показываем если есть аналоги
const analogOffers = validOffers.filter(offer => offer.isAnalog);
if (analogOffers.length > 0) {
const cheapestAnalogOffer = [...analogOffers].sort((a, b) => a.price - b.price)[0];
const analogId = `${cheapestAnalogOffer.articleNumber}-${cheapestAnalogOffer.price}-${cheapestAnalogOffer.deliveryDuration}`;
if (!usedOfferIds.has(analogId)) {
result.push({ offer: cheapestAnalogOffer, type: 'Самый дешевый аналог' });
usedOfferIds.add(analogId);
}
result.push({ offer: cheapestAnalogOffer, type: 'Самый дешевый аналог' });
}
// 3. Самая быстрая доставка (среди всех предложений)
const fastestDeliveryOffer = [...validOffers].sort((a, b) => a.deliveryDuration - b.deliveryDuration)[0];
if (fastestDeliveryOffer) {
const fastestId = `${fastestDeliveryOffer.articleNumber}-${fastestDeliveryOffer.price}-${fastestDeliveryOffer.deliveryDuration}`;
if (!usedOfferIds.has(fastestId)) {
result.push({ offer: fastestDeliveryOffer, type: 'Самая быстрая доставка' });
}
result.push({ offer: fastestDeliveryOffer, type: 'Самая быстрая доставка' });
}
return result;
};
const transformOffersForCard = (offers: any[]) => {
// Убрано: функция сортировки теперь в CoreProductCard
// Функция для проверки наличия товара на складе
const checkProductStock = (result: any): boolean => {
if (!result) return false;
// Используем новые данные stockCalculation если доступны
if (result.stockCalculation) {
return result.stockCalculation.hasAnyStock;
}
// Fallback к старой логике для обратной совместимости
const hasInternalStock = result.internalOffers?.some((offer: any) =>
offer.quantity > 0 && offer.available
);
const hasExternalStock = result.externalOffers?.some((offer: any) =>
offer.quantity > 0
);
return hasInternalStock || hasExternalStock;
};
const transformOffersForCard = (offers: any[], hasStock: boolean = true) => {
return offers.map(offer => {
const isExternal = offer.type === 'external';
const deliveryDays = isExternal ? offer.deliveryTime : offer.deliveryDays;
return {
id: offer.id,
productId: offer.productId,
offerKey: offer.offerKey,
pcs: `${offer.quantity} шт.`,
days: `${isExternal ? offer.deliveryTime : offer.deliveryDays} дн.`,
days: deliveryDays ? calculateDeliveryDate(deliveryDays) : 'Уточняйте',
recommended: !isExternal && offer.available,
price: `${offer.price.toLocaleString('ru-RU')}`,
count: "1",
@ -211,7 +228,8 @@ const transformOffersForCard = (offers: any[]) => {
currency: offer.currency || "RUB",
warehouse: offer.warehouse,
supplier: offer.supplier,
deliveryTime: isExternal ? offer.deliveryTime : offer.deliveryDays,
deliveryTime: deliveryDays,
hasStock, // Добавляем информацию о наличии
};
});
};
@ -219,8 +237,9 @@ const transformOffersForCard = (offers: any[]) => {
export default function SearchResult() {
const router = useRouter();
const { article, brand, q, artId } = router.query;
const { state: cartState } = useCart();
const [sortActive, setSortActive] = useState(0);
// Убрано: глобальная сортировка теперь не используется
const [showFiltersMobile, setShowFiltersMobile] = useState(false);
const [showSortMobile, setShowSortMobile] = useState(false);
const [searchQuery, setSearchQuery] = useState<string>("");
@ -246,10 +265,20 @@ export default function SearchResult() {
setVisibleAnalogsCount(ANALOGS_CHUNK_SIZE);
}, [article, brand]);
// Подготавливаем данные корзины для отправки на backend
const cartItems = cartState.items.map(item => ({
productId: item.productId,
offerKey: item.offerKey,
article: item.article || '',
brand: item.brand || '',
quantity: item.quantity
}));
const { data, loading, error } = useQuery(SEARCH_PRODUCT_OFFERS, {
variables: {
articleNumber: searchQuery,
brand: brandQuery || '' // Используем пустую строку если бренд не указан
brand: brandQuery || '', // Используем пустую строку если бренд не указан
cartItems: cartItems
},
skip: !searchQuery,
errorPolicy: 'all'
@ -374,7 +403,101 @@ export default function SearchResult() {
const hasOffers = result && (result.internalOffers.length > 0 || result.externalOffers.length > 0);
const hasAnalogs = result && result.analogs.length > 0;
const searchResultFilters = createFilters(result, loadedAnalogs);
// Создаем динамические фильтры на основе доступных данных с учетом активных фильтров
const searchResultFilters = useMemo(() => {
const baseFilters = createFilters(result, loadedAnalogs);
// Если нет активных фильтров, возвращаем базовые фильтры
if (!filtersAreActive) {
return baseFilters;
}
// Создаем динамические фильтры с учетом других активных фильтров
return baseFilters.map(filter => {
if (filter.type !== 'range') {
return filter;
}
// Для каждого диапазонного фильтра пересчитываем границы на основе
// предложений, отфильтрованных другими фильтрами (исключая текущий)
let relevantOffers = allOffers;
// Применяем все фильтры кроме текущего
relevantOffers = allOffers.filter(offer => {
// Фильтр по бренду (если это не фильтр производителя)
if (filter.title !== 'Производитель' && selectedBrands.length > 0 && !selectedBrands.includes(offer.brand)) {
return false;
}
// Фильтр по цене (если это не фильтр цены)
if (filter.title !== 'Цена (₽)' && priceRange && (offer.price < priceRange[0] || offer.price > priceRange[1])) {
return false;
}
// Фильтр по сроку доставки (если это не фильтр доставки)
if (filter.title !== 'Срок доставки (дни)' && deliveryRange) {
const deliveryDays = offer.deliveryDuration;
if (deliveryDays < deliveryRange[0] || deliveryDays > deliveryRange[1]) {
return false;
}
}
// Фильтр по количеству (если это не фильтр количества)
if (filter.title !== 'Количество (шт.)' && quantityRange) {
const quantity = offer.quantity;
if (quantity < quantityRange[0] || quantity > quantityRange[1]) {
return false;
}
}
// Фильтр по поисковой строке
if (filterSearchTerm) {
const searchTerm = filterSearchTerm.toLowerCase();
const brandMatch = offer.brand.toLowerCase().includes(searchTerm);
const articleMatch = offer.articleNumber.toLowerCase().includes(searchTerm);
const nameMatch = offer.name.toLowerCase().includes(searchTerm);
if (!brandMatch && !articleMatch && !nameMatch) {
return false;
}
}
return true;
});
// Пересчитываем диапазон на основе отфильтрованных предложений
if (filter.title === 'Цена (₽)') {
const prices = relevantOffers.filter(o => o.price > 0).map(o => o.price);
if (prices.length > 0) {
return {
...filter,
min: Math.floor(Math.min(...prices)),
max: Math.ceil(Math.max(...prices))
};
}
} else if (filter.title === 'Срок доставки (дни)') {
const deliveryDays = relevantOffers
.map(o => o.deliveryDuration)
.filter(d => d && d > 0);
if (deliveryDays.length > 0) {
return {
...filter,
min: Math.min(...deliveryDays),
max: Math.max(...deliveryDays)
};
}
} else if (filter.title === 'Количество (шт.)') {
const quantities = relevantOffers
.map(o => o.quantity)
.filter(q => q && q > 0);
if (quantities.length > 0) {
return {
...filter,
min: Math.min(...quantities),
max: Math.max(...quantities)
};
}
}
return filter;
});
}, [result, loadedAnalogs, filtersAreActive, allOffers, selectedBrands, priceRange, deliveryRange, quantityRange, filterSearchTerm]);
const bestOffersData = getBestOffers(filteredOffers);
@ -404,6 +527,8 @@ export default function SearchResult() {
}
}, [q, article, router.query]);
// Удаляем старую заглушку - теперь обрабатываем все типы поиска
const minPrice = useMemo(() => {
@ -435,62 +560,70 @@ export default function SearchResult() {
);
}
// Создаем динамические meta-теги для товара
const metaData = result ? createProductMeta({
name: result.name,
brand: result.brand,
articleNumber: result.articleNumber,
price: minPrice
}) : {
title: 'Результаты поиска - Protek',
description: 'Результаты поиска автозапчастей. Найдите нужные запчасти среди широкого ассортимента.',
keywords: 'результаты поиска, поиск запчастей, найти запчасти'
};
return (
<>
<Head>
<title>{result ? `${result.brand} ${result.articleNumber} - ${result.name}` : `Результаты поиска`} - Protek</title>
<meta name="description" content={`Лучшие предложения и аналоги для ${result?.name}`} />
<meta content="Search result" property="og:title" />
<meta content="Search result" property="twitter:title" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta content="Webflow" name="generator" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<InfoSearch
brand={result ? result.brand : brandQuery}
articleNumber={result ? result.articleNumber : searchQuery}
name={result ? result.name : "деталь"}
offersCount={result ? result.totalOffers : 0}
minPrice={minPrice}
/>
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-hflex flex-block-84">
{/* <CatalogSortDropdown active={sortActive} onChange={setSortActive} /> */}
<div className="w-layout-hflex flex-block-85" onClick={() => setShowFiltersMobile((v) => !v)}>
<span className="code-embed-9 w-embed">
<svg width="currentwidth" height="currentheight" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 4H14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M10 4H3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M21 12H12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M8 12H3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M21 20H16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M12 20H3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M14 2V6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M8 10V14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M16 18V22" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</span>
<div>Фильтры</div>
</div>
</div>
</div>
</section>
{/* Мобильная панель фильтров */}
<FiltersPanelMobile
filters={searchResultFilters}
open={showFiltersMobile}
onClose={() => setShowFiltersMobile(false)}
searchQuery={filterSearchTerm}
onSearchChange={(value) => handleFilterChange('search', value)}
/>
<MetaTags {...metaData} />
{/* Показываем InfoSearch только если есть результаты */}
{initialOffersExist && (
<InfoSearch
brand={result ? result.brand : brandQuery}
articleNumber={result ? result.articleNumber : searchQuery}
name={result ? result.name : "деталь"}
offersCount={result ? result.totalOffers : 0}
minPrice={minPrice}
/>
)}
{/* Показываем мобильные фильтры только если есть результаты */}
{initialOffersExist && (
<>
<section className="main mobile-only">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-hflex flex-block-84">
{/* Глобальная сортировка убрана - теперь каждый товар сортируется индивидуально */}
<div className="w-layout-hflex flex-block-85" onClick={() => setShowFiltersMobile((v) => !v)}>
<span className="code-embed-9 w-embed">
<svg width="currentwidth" height="currentheight" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 4H14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M10 4H3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M21 12H12" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M8 12H3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M21 20H16" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M12 20H3" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M14 2V6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M8 10V14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M16 18V22" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</span>
<div>Фильтры</div>
</div>
</div>
</div>
</section>
{/* Мобильная панель фильтров */}
<FiltersPanelMobile
filters={searchResultFilters}
open={showFiltersMobile}
onClose={() => setShowFiltersMobile(false)}
searchQuery={filterSearchTerm}
onSearchChange={(value) => handleFilterChange('search', value)}
/>
</>
)}
{/* Лучшие предложения */}
{bestOffersData.length > 0 && (
<section className="section-6">
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-vflex flex-block-36">
{bestOffersData.map(({ offer, type }, index) => (
@ -500,7 +633,7 @@ export default function SearchResult() {
title={`${offer.brand} ${offer.articleNumber}${offer.isAnalog ? ' (аналог)' : ''}`}
description={offer.name}
price={`${offer.price.toLocaleString()}`}
delivery={`${offer.deliveryDuration} ${offer.deliveryDuration === 1 ? 'день' : 'дней'}`}
delivery={offer.deliveryDuration ? calculateDeliveryDate(offer.deliveryDuration) : 'Уточняйте'}
stock={`${offer.quantity} шт.`}
offer={offer}
/>
@ -545,30 +678,34 @@ export default function SearchResult() {
</div>
</section>
)}
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-hflex flex-block-13-copy">
{/* Фильтры для десктопа */}
<div style={{ width: '300px', marginRight: '20px' }}>
<Filters
filters={searchResultFilters}
onFilterChange={handleFilterChange}
filterValues={{
'Производитель': selectedBrands,
'Цена (₽)': priceRange,
'Срок доставки (дни)': deliveryRange,
'Количество (шт.)': quantityRange
}}
searchQuery={filterSearchTerm}
onSearchChange={(value) => handleFilterChange('search', value)}
/>
</div>
{/* Показываем основную секцию с фильтрами только если есть результаты */}
{initialOffersExist && (
<section className="main">
<div className="w-layout-blockcontainer container w-container">
<div className="w-layout-hflex flex-block-13-copy">
{/* Фильтры для десктопа */}
<div style={{ width: '300px', marginRight: '20px', marginBottom: '80px' }}>
<Filters
filters={searchResultFilters}
onFilterChange={handleFilterChange}
filterValues={{
'Производитель': selectedBrands,
'Цена (₽)': priceRange,
'Срок доставки (дни)': deliveryRange,
'Количество (шт.)': quantityRange
}}
searchQuery={filterSearchTerm}
onSearchChange={(value) => handleFilterChange('search', value)}
/>
</div>
{/* Основной товар */}
<div className="w-layout-vflex flex-block-14-copy">
{hasOffers && result && (() => {
const hasMainProductStock = checkProductStock(result);
const mainProductOffers = transformOffersForCard(
filteredOffers.filter(o => !o.isAnalog)
filteredOffers.filter(o => !o.isAnalog),
hasMainProductStock
);
// Не показываем основной товар, если у него нет предложений
@ -576,9 +713,8 @@ export default function SearchResult() {
return null;
}
// Используем фотографию из Parts Index, если она есть, иначе fallback на mainImageUrl
// Используем фотографию только из Parts Index, если она есть
const partsIndexImage = entityInfo?.images?.[0];
const displayImage = partsIndexImage || mainImageUrl;
return (
<>
@ -586,10 +722,11 @@ export default function SearchResult() {
brand={result.brand}
article={result.articleNumber}
name={result.name}
image={displayImage}
{...(partsIndexImage ? { image: partsIndexImage } : {})}
offers={mainProductOffers}
showMoreText={mainProductOffers.length < filteredOffers.filter(o => !o.isAnalog).length ? "Показать еще" : undefined}
partsIndexPowered={!!partsIndexImage}
hasStock={hasMainProductStock}
/>
</>
);
@ -606,12 +743,11 @@ export default function SearchResult() {
return true; // Показываем загружающиеся аналоги
}
const analogOffers = transformOffersForCard(
filteredOffers.filter(o => o.isAnalog && o.articleNumber === analog.articleNumber)
);
// Проверяем, есть ли предложения у аналога
const hasInternalOffers = loadedAnalogData.internalOffers && loadedAnalogData.internalOffers.length > 0;
const hasExternalOffers = loadedAnalogData.externalOffers && loadedAnalogData.externalOffers.length > 0;
// Показываем аналог только если у него есть предложения
return analogOffers.length > 0;
return hasInternalOffers || hasExternalOffers;
});
// Если нет аналогов с предложениями, не показываем секцию
@ -625,11 +761,81 @@ export default function SearchResult() {
const analogKey = `${analog.brand}-${analog.articleNumber}`;
const loadedAnalogData = loadedAnalogs[analogKey];
const analogOffers = loadedAnalogData
? transformOffersForCard(
filteredOffers.filter(o => o.isAnalog && o.articleNumber === analog.articleNumber)
)
: [];
// Если данные аналога загружены, формируем предложения из всех его данных
const analogOffers = loadedAnalogData ? (() => {
const allAnalogOffers: any[] = [];
// Добавляем внутренние предложения
if (loadedAnalogData.internalOffers) {
loadedAnalogData.internalOffers.forEach((offer: any) => {
allAnalogOffers.push({
...offer,
type: 'internal',
brand: loadedAnalogData.brand,
articleNumber: loadedAnalogData.articleNumber,
name: loadedAnalogData.name,
isAnalog: true,
deliveryDuration: offer.deliveryDays
});
});
}
// Добавляем внешние предложения
if (loadedAnalogData.externalOffers) {
loadedAnalogData.externalOffers.forEach((offer: any) => {
allAnalogOffers.push({
...offer,
type: 'external',
brand: offer.brand || loadedAnalogData.brand,
articleNumber: offer.code || loadedAnalogData.articleNumber,
name: offer.name || loadedAnalogData.name,
isAnalog: true,
deliveryDuration: offer.deliveryTime
});
});
}
// Применяем фильтры только если они активны
const filteredAnalogOffers = allAnalogOffers.filter(offer => {
// Фильтр по бренду
if (selectedBrands.length > 0 && !selectedBrands.includes(offer.brand)) {
return false;
}
// Фильтр по цене
if (priceRange && (offer.price < priceRange[0] || offer.price > priceRange[1])) {
return false;
}
// Фильтр по сроку доставки
if (deliveryRange) {
const deliveryDays = offer.deliveryDuration;
if (deliveryDays < deliveryRange[0] || deliveryDays > deliveryRange[1]) {
return false;
}
}
// Фильтр по количеству наличия
if (quantityRange) {
const quantity = offer.quantity;
if (quantity < quantityRange[0] || quantity > quantityRange[1]) {
return false;
}
}
// Фильтр по поисковой строке
if (filterSearchTerm) {
const searchTerm = filterSearchTerm.toLowerCase();
const brandMatch = offer.brand.toLowerCase().includes(searchTerm);
const articleMatch = offer.articleNumber.toLowerCase().includes(searchTerm);
const nameMatch = offer.name.toLowerCase().includes(searchTerm);
if (!brandMatch && !articleMatch && !nameMatch) {
return false;
}
}
return true;
});
return transformOffersForCard(filteredAnalogOffers, checkProductStock(loadedAnalogData));
})() : [];
const hasAnalogStock = loadedAnalogData ? checkProductStock(loadedAnalogData) : true;
return (
<CoreProductCard
@ -640,6 +846,7 @@ export default function SearchResult() {
offers={analogOffers}
isAnalog
isLoadingOffers={!loadedAnalogData}
hasStock={hasAnalogStock}
/>
)
})}
@ -647,20 +854,7 @@ export default function SearchResult() {
{(() => {
// Проверяем, есть ли еще аналоги с предложениями для загрузки
const remainingAnalogs = result.analogs.slice(visibleAnalogsCount);
const hasMoreAnalogsWithOffers = remainingAnalogs.some((analog: any) => {
const analogKey = `${analog.brand}-${analog.articleNumber}`;
const loadedAnalogData = loadedAnalogs[analogKey];
if (!loadedAnalogData) {
return true; // Могут быть предложения у незагруженных аналогов
}
const analogOffers = transformOffersForCard(
filteredOffers.filter(o => o.isAnalog && o.articleNumber === analog.articleNumber)
);
return analogOffers.length > 0;
});
const hasMoreAnalogsWithOffers = remainingAnalogs.length > 0;
return hasMoreAnalogsWithOffers && (
<div className="w-layout-hflex pagination">
@ -681,9 +875,13 @@ export default function SearchResult() {
</div>
</div>
</section>
<section className="section-3">
<CatalogSubscribe />
</section>
)}
{/* Показываем CatalogSubscribe только если есть результаты */}
{initialOffersExist && (
<section className="section-3">
<CatalogSubscribe />
</section>
)}
<Footer />
<MobileMenuBottomSection />
</>

View File

@ -7,6 +7,8 @@ import Footer from '@/components/Footer';
import MobileMenuBottomSection from '@/components/MobileMenuBottomSection';
import { DOC_FIND_OEM, FIND_LAXIMO_VEHICLES_BY_PART_NUMBER } from '@/lib/graphql';
import { LaximoDocFindOEMResult, LaximoVehiclesByPartResult, LaximoVehicleSearchResult } from '@/types/laximo';
import MetaTags from '@/components/MetaTags';
import { getMetaByPath } from '@/lib/meta-config';
type SearchMode = 'parts' | 'vehicles';
@ -98,14 +100,11 @@ const SearchPage = () => {
const hasPartsResults = partsResult && partsResult.details && partsResult.details.length > 0;
const hasVehiclesResults = vehiclesResult && vehiclesResult.totalVehicles > 0;
const metaData = getMetaByPath('/search');
return (
<>
<Head>
<title>Поиск по артикулу {searchQuery} - Protek</title>
<meta name="description" content={`Результаты поиска по артикулу ${searchQuery}`} />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<InfoSearch />
<div className="page-wrapper bg-[#F5F8FB] min-h-screen">
<div className="flex flex-col pt-10 pb-16 max-md:px-5">
@ -200,7 +199,7 @@ const SearchPage = () => {
<div key={detail.detailid || index}>
<button
onClick={() => handlePartDetail(detail)}
className="w-full text-left p-4 hover:bg-gray-50 transition-colors block group"
className="w-full text-left p-4 hover:bg-slate-200 transition-colors block group"
>
<div className="flex w-full items-center gap-2">
<div className="w-1/5 max-md:w-1/3 font-bold text-left truncate" style={{ color: 'rgb(77, 180, 94)' }}>{detail.manufacturer}</div>
@ -253,7 +252,7 @@ const SearchPage = () => {
{vehiclesResult!.catalogs.map((catalog) => (
<tr
key={catalog.catalogCode}
className="hover:bg-gray-50 cursor-pointer transition-colors"
className="hover:bg-slate-200 cursor-pointer transition-colors"
onClick={() => {
router.push(`/search-result?article=${encodeURIComponent(searchQuery)}&brand=${encodeURIComponent(catalog.brand)}`);
}}

85
src/pages/sitemap.xml.ts Normal file
View File

@ -0,0 +1,85 @@
import { NextApiRequest, NextApiResponse } from 'next'
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const baseUrl = 'https://protekauto.ru'
const staticPages = [
{
url: '',
lastModified: new Date().toISOString(),
changeFrequency: 'daily',
priority: 1.0,
},
{
url: '/about',
lastModified: new Date().toISOString(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: '/contacts',
lastModified: new Date().toISOString(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: '/catalog',
lastModified: new Date().toISOString(),
changeFrequency: 'weekly',
priority: 0.9,
},
{
url: '/news',
lastModified: new Date().toISOString(),
changeFrequency: 'weekly',
priority: 0.7,
},
{
url: '/brands',
lastModified: new Date().toISOString(),
changeFrequency: 'monthly',
priority: 0.7,
},
{
url: '/delivery',
lastModified: new Date().toISOString(),
changeFrequency: 'monthly',
priority: 0.6,
},
{
url: '/payment',
lastModified: new Date().toISOString(),
changeFrequency: 'monthly',
priority: 0.6,
},
{
url: '/wholesale',
lastModified: new Date().toISOString(),
changeFrequency: 'monthly',
priority: 0.6,
},
{
url: '/vin',
lastModified: new Date().toISOString(),
changeFrequency: 'monthly',
priority: 0.7,
},
]
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${staticPages
.map(
(page) => ` <url>
<loc>${baseUrl}${page.url}</loc>
<lastmod>${page.lastModified}</lastmod>
<changefreq>${page.changeFrequency}</changefreq>
<priority>${page.priority}</priority>
</url>`
)
.join('\n')}
</urlset>`
res.setHeader('Content-Type', 'application/xml')
res.status(200).send(sitemap)
}

View File

@ -5,23 +5,19 @@ import CatalogSubscribe from "@/components/CatalogSubscribe";
import Footer from "@/components/Footer";
import MobileMenuBottomSection from "@/components/MobileMenuBottomSection";
import Link from 'next/link';
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
export default function ThankYouPage() {
const metaData = getMetaByPath('/thankyoupage');
return (
<>
<MetaTags {...metaData} />
<Head>
<meta charSet="utf-8" />
<title>thankyoupage</title>
<meta content="thankyoupage" property="og:title" />
<meta content="thankyoupage" property="twitter:title" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js" type="text/javascript"></script>
<script type="text/javascript" dangerouslySetInnerHTML={{__html: `WebFont.load({ google: { families: [\"Onest:regular,600,700,800,900:cyrillic-ext,latin\"] }});`}} />
<script type="text/javascript" dangerouslySetInnerHTML={{__html: `!function(o,c){var n=c.documentElement,t=\" w-mod-\";n.className+=t+\"js\",(\"ontouchstart\"in o||o.DocumentTouch&&c instanceof DocumentTouch)&&(n.className+=t+\"touch\")}(window,document);`}} />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<ThankInfo />

View File

@ -175,39 +175,33 @@ const VehicleSearchResultsPage: React.FC<VehicleSearchResultsPageProps> = () =>
<>
<main className="bg-gray-50 min-h-screen">
{/* Breadcrumb */}
<div className="bg-white border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<nav className="flex" aria-label="Breadcrumb">
<ol className="flex items-center space-x-4">
<li>
<Link href="/" className="text-gray-400 hover:text-gray-500">
Главная
</Link>
</li>
<li>
<div className="flex items-center">
<svg className="flex-shrink-0 h-5 w-5 text-gray-300" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true">
<path d="M5.555 17.776l8-16 .894.448-8 16-.894-.448z" />
</svg>
<span className="ml-4 text-sm font-medium text-red-600">
{searchType === 'vin' ? 'Найденные автомобили' : 'Найденные автомобили'}
</span>
</div>
</li>
</ol>
</nav>
<main className="bg-[#F5F8FB] min-h-screen">
{/* Breadcrumb (InfoSearch style) */}
<section className="section-info">
<div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9">
<div className="w-layout-hflex flex-block-7">
<a href="/" className="link-block w-inline-block">
<div>Главная</div>
</a>
<div className="text-block-3"></div>
<a href="#" className="link-block-2 w-inline-block">
<div>Найденные автомобили</div>
</a>
</div>
<div className="w-layout-hflex flex-block-8">
<div className="w-layout-hflex flex-block-10">
<h1 className="heading">{searchType === 'vin' ? 'Поиск по VIN номеру' : 'Поиск по государственному номеру'}</h1>
</div>
</div>
</div>
</div>
</div>
</section>
{/* Search Results Header */}
<div className="bg-white">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="mb-6">
<h1 className="text-3xl font-bold text-gray-900 mb-2">
{searchType === 'vin' ? 'Поиск по VIN номеру' : 'Поиск по государственному номеру'}
</h1>
<div className="flex flex-col items-center pt-10 pb-16 max-md:px-5">
<div className="w-full max-w-[1580px]">
{/* <div className="mb-6">
<p className="text-lg text-gray-600">
Запрос: <span className="font-mono font-bold">{searchQuery}</span>
</p>
@ -216,18 +210,13 @@ const VehicleSearchResultsPage: React.FC<VehicleSearchResultsPageProps> = () =>
Найдено {vehicles.length} автомобилей
</p>
)}
</div>
</div> */}
{/* Loading State */}
{isLoading && (
<div className="flex items-center justify-center py-12">
<div className="flex items-center space-x-3">
<svg className="animate-spin h-8 w-8 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span className="text-lg text-gray-600">Поиск автомобилей...</span>
</div>
<div className="bg-white rounded-2xl shadow p-10 flex flex-col items-center justify-center min-h-[300px]">
<div className="animate-spin rounded-full h-24 w-24 border-b-2 border-red-600 mb-6"></div>
<p className="text-lg text-gray-600">Поиск автомобилей...</p>
</div>
)}
@ -247,9 +236,9 @@ const VehicleSearchResultsPage: React.FC<VehicleSearchResultsPageProps> = () =>
{vehicles[0]?.year && ` (${vehicles[0].year} г.)`}
{vehicles[0]?.engine && `, двигатель: ${vehicles[0].engine}`}
</p>
<p className="text-sm text-green-600 mt-1">
🚀 Переходим сразу к категориям запчастей...
</p>
<p className="text-sm text-green-600 mt-1">
🚀 Переходим сразу к категориям запчастей...
</p>
</div>
</div>
<button
@ -262,209 +251,58 @@ const VehicleSearchResultsPage: React.FC<VehicleSearchResultsPageProps> = () =>
</div>
)}
{/* Results Table */}
{/* Results List (Search-like style, not table) */}
{!isLoading && vehicles.length > 0 && !isRedirecting && (
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<div className="overflow-x-auto">
<table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Бренд
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Название
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Модель
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Год
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Двигатель
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
КПП
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Рынок
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Дата выпуска
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Период производства
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Дополнительно
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{vehicles.map((vehicle, index) => {
console.log('🔍 Отображаем автомобиль в таблице:', {
index,
vehicleid: vehicle.vehicleid,
name: vehicle.name,
brand: vehicle.brand,
catalog: vehicle.catalog,
model: vehicle.model,
year: vehicle.year,
engine: vehicle.engine,
ssd: vehicle.ssd ? vehicle.ssd.substring(0, 30) + '...' : 'отсутствует'
});
return (
<tr
key={vehicle.vehicleid || index}
onClick={() => handleVehicleSelect(vehicle)}
className="hover:bg-gray-50 cursor-pointer transition-colors"
>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
{vehicle.brand}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{vehicle.name}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{vehicle.model}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{(() => {
const year = vehicle.year || vehicle.manufactured || (vehicle.date ? vehicle.date.split('.').pop() : '') || '';
console.log(`🗓️ Год для автомобиля ${vehicle.vehicleid}:`, { year, original_year: vehicle.year, manufactured: vehicle.manufactured, date: vehicle.date });
return year || '-';
})()}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{(() => {
const engine = vehicle.engine || vehicle.engine_info || vehicle.engineno || '';
console.log(`🔧 Двигатель для автомобиля ${vehicle.vehicleid}:`, { engine, original_engine: vehicle.engine, engine_info: vehicle.engine_info, engineno: vehicle.engineno });
return engine || '-';
})()}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{(() => {
const transmission = vehicle.transmission || vehicle.bodytype || '';
console.log(`⚙️ КПП для автомобиля ${vehicle.vehicleid}:`, { transmission, original_transmission: vehicle.transmission, bodytype: vehicle.bodytype });
return transmission || '-';
})()}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{(() => {
const market = vehicle.market || vehicle.destinationregion || vehicle.creationregion || '';
console.log(`🌍 Рынок для автомобиля ${vehicle.vehicleid}:`, { market, original_market: vehicle.market, destinationregion: vehicle.destinationregion, creationregion: vehicle.creationregion });
return market || '-';
})()}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{(() => {
const releaseDate = vehicle.date || vehicle.manufactured || '';
console.log(`📅 Дата выпуска для автомобиля ${vehicle.vehicleid}:`, { releaseDate, date: vehicle.date, manufactured: vehicle.manufactured });
return releaseDate || '-';
})()}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
{(() => {
let prodPeriod = '';
if (vehicle.prodRange) {
prodPeriod = vehicle.prodRange;
} else if (vehicle.prodPeriod) {
prodPeriod = vehicle.prodPeriod;
} else if (vehicle.datefrom && vehicle.dateto) {
prodPeriod = `${vehicle.datefrom} - ${vehicle.dateto}`;
} else if (vehicle.modelyearfrom && vehicle.modelyearto) {
prodPeriod = `${vehicle.modelyearfrom} - ${vehicle.modelyearto}`;
}
console.log(`📈 Период производства для автомобиля ${vehicle.vehicleid}:`, {
prodPeriod,
prodRange: vehicle.prodRange,
original_prodPeriod: vehicle.prodPeriod,
datefrom: vehicle.datefrom,
dateto: vehicle.dateto,
modelyearfrom: vehicle.modelyearfrom,
modelyearto: vehicle.modelyearto
});
return prodPeriod || '-';
})()}
</td>
<td className="px-6 py-4 text-sm text-gray-900">
<div className="space-y-1">
{vehicle.framecolor && (
<div className="text-xs">
<span className="font-medium">Цвет кузова:</span> {vehicle.framecolor}
</div>
)}
{vehicle.trimcolor && (
<div className="text-xs">
<span className="font-medium">Цвет салона:</span> {vehicle.trimcolor}
</div>
)}
{vehicle.engineno && (
<div className="text-xs">
<span className="font-medium">Номер двигателя:</span> {vehicle.engineno}
</div>
)}
{vehicle.engine_info && (
<div className="text-xs max-w-xs truncate" title={vehicle.engine_info}>
<span className="font-medium">Двигатель:</span> {vehicle.engine_info}
</div>
)}
{vehicle.options && (
<div className="text-xs max-w-xs truncate" title={vehicle.options}>
<span className="font-medium">Опции:</span> {vehicle.options}
</div>
)}
{vehicle.description && (
<div className="text-xs max-w-xs truncate" title={vehicle.description}>
<span className="font-medium">Описание:</span> {vehicle.description}
</div>
)}
{vehicle.modification && (
<div className="text-xs max-w-xs truncate" title={vehicle.modification}>
<span className="font-medium">Модификация:</span> {vehicle.modification}
</div>
)}
{vehicle.grade && (
<div className="text-xs">
<span className="font-medium">Класс:</span> {vehicle.grade}
</div>
)}
</div>
</td>
</tr>
);
})}
</tbody>
</table>
<div className="bg-white rounded-2xl shadow p-10">
<div className="flex flex-wrap items-center gap-6 font-bold text-gray-900 text-base mb-2 px-2">
<div className="min-w-[100px] flex-1 break-words">Бренд</div>
<div className="min-w-[120px] flex-1 break-words">Название</div>
<div className="min-w-[120px] flex-1 break-words">Модель</div>
<div className="min-w-[60px] flex-1 break-words">Год</div>
<div className="min-w-[120px] flex-1 break-words">Двигатель</div>
<div className="min-w-[80px] flex-1 break-words">КПП</div>
<div className="min-w-[80px] flex-1 break-words">Рынок</div>
<div className="min-w-[100px] flex-1 break-words">Дата выпуска</div>
<div className="min-w-[140px] flex-1 break-words">Период производства</div>
</div>
<div className="space-y-0">
{vehicles.map((vehicle, index) => (
<div
key={vehicle.vehicleid || index}
className="flex flex-wrap items-center gap-6 bg-white border-b border-gray-200 px-6 py-3 cursor-pointer hover:bg-slate-100 transition-colors max-w-full"
onClick={() => handleVehicleSelect(vehicle)}
style={{ minWidth: 0 }}
>
<div className="font-bold text-gray-900 text-base min-w-[100px] flex-1 break-words">{vehicle.brand}</div>
<div className="text-gray-900 text-base min-w-[120px] flex-1 break-words">{vehicle.name}</div>
<div className="text-gray-900 text-base min-w-[120px] flex-1 break-words">{vehicle.model}</div>
<div className="text-gray-900 text-base min-w-[60px] flex-1 break-words">{vehicle.year || '-'}</div>
<div className="text-gray-900 text-base min-w-[120px] flex-1 break-words">{vehicle.engine || '-'}</div>
<div className="text-gray-900 text-base min-w-[80px] flex-1 break-words">{vehicle.transmission || '-'}</div>
<div className="text-gray-900 text-base min-w-[80px] flex-1 break-words">{vehicle.market || '-'}</div>
<div className="text-gray-900 text-base min-w-[100px] flex-1 break-words">{vehicle.date || vehicle.manufactured || '-'}</div>
<div className="text-gray-900 text-base min-w-[140px] flex-1 break-words">{vehicle.prodRange || vehicle.prodPeriod || ((vehicle.datefrom && vehicle.dateto) ? `${vehicle.datefrom} - ${vehicle.dateto}` : (vehicle.modelyearfrom && vehicle.modelyearto) ? `${vehicle.modelyearfrom} - ${vehicle.modelyearto}` : '-')}</div>
</div>
))}
</div>
</div>
)}
{/* No Results */}
{!isLoading && vehicles.length === 0 && searchQuery && (
<div className="text-center py-12">
<div className="text-yellow-400 mb-4">
<svg className="w-16 h-16 mx-auto" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.728-.833-2.498 0L4.316 14.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
</div>
<h3 className="text-xl font-medium text-gray-900 mb-2">
{searchType === 'vin' ? 'VIN не найден' : 'Госномер не найден'}
<div className="bg-[#eaf0fa] border border-[#b3c6e6] rounded-2xl shadow p-10 text-center">
<svg className="w-16 h-16 mx-auto mb-4" style={{ color: '#0d336c' }} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.172 16.172a4 4 0 015.656 0M9 12h6m-6-4h6m2 5.291A7.962 7.962 0 0112 15c-2.34 0-4.29-1.009-5.824-2.562M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<h3 className="text-xl font-semibold mb-2" style={{ color: '#0d336c' }}>
Автомобили не найдены
</h3>
<p className="text-gray-600 mb-6">
{searchType === 'vin'
? `Автомобиль с VIN номером ${searchQuery} не найден в доступных каталогах`
: `Автомобиль с государственным номером ${searchQuery} не найден в базе данных`
}
<p className="mb-4" style={{ color: '#0d336c' }}>
По запросу <span className="font-mono font-semibold">{searchQuery}</span> автомобили не найдены.
</p>
<p className="text-sm" style={{ color: '#3b5a99' }}>
Попробуйте изменить запрос или проверьте правильность написания.
</p>
<Link
href="/"
className="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
Вернуться на главную
</Link>
</div>
)}

View File

@ -7,6 +7,8 @@ import Footer from '@/components/Footer';
import VehicleSearchSection from '../../components/VehicleSearchSection';
import { GET_LAXIMO_CATALOG_INFO } from '@/lib/graphql';
import { LaximoCatalogInfo } from '@/types/laximo';
import MetaTags from '@/components/MetaTags';
import { getMetaByPath } from '@/lib/meta-config';
const VehicleSearchPage = () => {
const router = useRouter();
@ -69,12 +71,11 @@ const VehicleSearchPage = () => {
const catalogInfo = catalogData.laximoCatalogInfo;
const metaData = getMetaByPath('/vehicle-search');
return (
<>
<Head>
<title>Поиск автомобиля - {catalogInfo.name}</title>
<meta name="description" content={`Поиск автомобилей ${catalogInfo.name} для подбора запчастей`} />
</Head>
<MetaTags {...metaData} />
<Header />
<main className="min-h-screen bg-gray-50">

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useQuery } from '@apollo/client';
import Head from 'next/head';
import Footer from '@/components/Footer';
import Layout from '@/components/Layout';
import VehiclePartsSearchSection from '@/components/VehiclePartsSearchSection';
@ -19,6 +19,8 @@ import KnotParts from '@/components/vin/KnotParts';
import VinQuick from '@/components/vin/VinQuick';
import CatalogSubscribe from '@/components/CatalogSubscribe';
import MobileMenuBottomSection from '@/components/MobileMenuBottomSection';
import MetaTags from '@/components/MetaTags';
import { getMetaByPath } from '@/lib/meta-config';
interface LaximoVehicleInfo {
@ -46,7 +48,7 @@ const VehicleDetailsPage = () => {
if (searchTypeParam === 'categories') {
// В URL categories, но мы используем quickgroups для групп быстрого поиска
defaultSearchType = 'quickgroups';
console.log('🔄 URL содержит searchType=categories, интерпретируем как quickgroups (группы быстрого поиска)');
} else if (searchTypeParam === 'quickgroups') {
defaultSearchType = 'quickgroups';
} else if (searchTypeParam === 'fulltext') {
@ -58,6 +60,7 @@ const VehicleDetailsPage = () => {
const [showKnot, setShowKnot] = useState(false);
const [foundParts, setFoundParts] = useState<any[]>([]);
const [activeTab, setActiveTab] = useState<'uzly' | 'manufacturer'>('uzly');
const [openedPath, setOpenedPath] = useState<string[]>([]);
const [searchState, setSearchState] = useState<{
loading: boolean;
error: any;
@ -71,31 +74,8 @@ const VehicleDetailsPage = () => {
});
const [selectedNode, setSelectedNode] = useState<any | null>(null);
const [selectedQuickGroup, setSelectedQuickGroup] = useState<any | null>(null);
const handleCategoryClick = (e?: React.MouseEvent) => {
if (e) e.preventDefault();
setShowKnot(true);
};
useEffect(() => {
const handler = (e: Event) => {
const target = e.target as HTMLElement;
if (target.classList.contains('link-2')) {
e.preventDefault();
setShowKnot(true);
}
};
document.addEventListener('click', handler);
return () => document.removeEventListener('click', handler);
}, []);
// ====== КОНЕЦ ХУКОВ ======
// Получаем информацию о каталоге
const { data: catalogData } = useQuery<{ laximoCatalogInfo: LaximoCatalogInfo }>(
GET_LAXIMO_CATALOG_INFO,
{
variables: { catalogCode: brand },
skip: !brand
}
);
const [selectedParts, setSelectedParts] = useState<Set<string | number>>(new Set());
const [highlightedPart, setHighlightedPart] = useState<string | number | null>(null);
// Получаем информацию о выбранном автомобиле
const ssdFromQuery = Array.isArray(router.query.ssd) ? router.query.ssd[0] : router.query.ssd;
@ -109,27 +89,21 @@ const VehicleDetailsPage = () => {
const storedSsd = localStorage.getItem(vehicleKey);
if (storedSsd) {
finalSsd = storedSsd;
console.log('🔧 SSD получен из localStorage, длина:', storedSsd.length);
// НЕ ОЧИЩАЕМ SSD сразу, оставляем на случай перезагрузки страницы
// localStorage.removeItem(vehicleKey);
} else {
console.log('⚠️ SSD не найден в localStorage, ключ:', vehicleKey);
console.log('🔍 Все ключи localStorage:', Object.keys(localStorage));
}
} else if (ssdFromQuery && ssdFromQuery.trim() !== '') {
finalSsd = ssdFromQuery;
console.log('🔧 SSD получен из URL');
}
console.log('🔍 Vehicle page params:', {
brand,
vehicleId,
useStorage,
ssdLengthFromUrl,
ssdFromQuery: ssdFromQuery ? `${ssdFromQuery.substring(0, 50)}...` : 'отсутствует',
finalSsd: finalSsd ? `${finalSsd.substring(0, 50)}...` : 'отсутствует',
ssdLength: finalSsd.length
});
// Получаем информацию о каталоге
const { data: catalogData } = useQuery<{ laximoCatalogInfo: LaximoCatalogInfo }>(
GET_LAXIMO_CATALOG_INFO,
{
variables: { catalogCode: brand },
skip: !brand
}
);
const { data: vehicleData, loading: vehicleLoading, error: vehicleError } = useQuery<{ laximoVehicleInfo: LaximoVehicleInfo }>(
GET_LAXIMO_VEHICLE_INFO,
@ -141,11 +115,45 @@ const VehicleDetailsPage = () => {
localized: true
},
skip: !brand || vehicleId === undefined || vehicleId === null,
errorPolicy: 'all'
errorPolicy: 'all',
onCompleted: (data) => {
console.log('🔍 VehicleInfo GraphQL completed:', {
requestedVehicleId: vehicleId,
returnedVehicleId: data?.laximoVehicleInfo?.vehicleid,
vehicleName: data?.laximoVehicleInfo?.name,
ssdUsed: finalSsd?.substring(0, 50) + '...',
fullData: data
});
if (data?.laximoVehicleInfo?.vehicleid !== vehicleId) {
console.log('🚨 ОБНАРУЖЕНО НЕСООТВЕТСТВИЕ VEHICLE ID!');
console.log(`📍 URL vehicleId: ${vehicleId}`);
console.log(`📍 API vehicleId: ${data?.laximoVehicleInfo?.vehicleid}`);
} else {
console.log('✅ Vehicle ID соответствует URL');
}
},
onError: (error) => {
console.error('❌ VehicleInfo GraphQL error:', error);
}
}
);
// Получаем детали выбранного узла, если он выбран
console.log('🔍 [vehicleId].tsx - Проверка условий для GET_LAXIMO_UNIT_DETAILS:', {
selectedNode: selectedNode ? {
unitid: selectedNode.unitid,
name: selectedNode.name,
hasSsd: !!selectedNode.ssd
} : null,
skipCondition: !selectedNode,
catalogCode: selectedNode?.catalogCode || selectedNode?.catalog || brand,
vehicleId: selectedNode?.vehicleId || vehicleId,
unitId: selectedNode?.unitid || selectedNode?.unitId,
ssd: selectedNode?.ssd || finalSsd || '',
finalSsd: finalSsd ? `${finalSsd.substring(0, 50)}...` : 'отсутствует'
});
const {
data: unitDetailsData,
loading: unitDetailsLoading,
@ -163,10 +171,118 @@ const VehicleDetailsPage = () => {
: { catalogCode: '', vehicleId: '', unitId: '', ssd: '' },
skip: !selectedNode,
errorPolicy: 'all',
fetchPolicy: 'no-cache',
notifyOnNetworkStatusChange: true,
onCompleted: (data) => {
console.log('🔍 [vehicleId].tsx - GET_LAXIMO_UNIT_DETAILS completed:', {
detailsCount: data?.laximoUnitDetails?.length || 0,
firstDetail: data?.laximoUnitDetails?.[0],
allDetails: data?.laximoUnitDetails?.map((detail: any) => ({
name: detail.name,
oem: detail.oem,
codeonimage: detail.codeonimage,
attributesCount: detail.attributes?.length || 0
}))
});
},
onError: (error) => {
console.error('❌ [vehicleId].tsx - GET_LAXIMO_UNIT_DETAILS error:', error);
}
}
);
// Автоматическое перенаправление на правильный vehicleId если API вернул другой ID
useEffect(() => {
if (vehicleData?.laximoVehicleInfo && vehicleData.laximoVehicleInfo.vehicleid !== vehicleId) {
const correctVehicleId = vehicleData.laximoVehicleInfo.vehicleid;
console.log(`🔄 Автоматическое перенаправление: ${vehicleId} -> ${correctVehicleId}`);
// Обновляем localStorage с правильным ключом
if (finalSsd && typeof window !== 'undefined') {
const oldKey = `vehicle_ssd_${brand}_${vehicleId}`;
const newKey = `vehicle_ssd_${brand}_${correctVehicleId}`;
// Перемещаем SSD на правильный ключ
localStorage.setItem(newKey, finalSsd);
localStorage.removeItem(oldKey);
console.log(`💾 SSD перемещен: ${oldKey} -> ${newKey}`);
}
// Строим новый URL с правильным vehicleId
const currentParams = new URLSearchParams(window.location.search);
const newUrl = `/vehicle-search/${brand}/${correctVehicleId}?${currentParams.toString()}`;
// Перенаправляем на правильный URL
router.replace(newUrl);
return;
}
}, [vehicleData, vehicleId, brand, finalSsd, router]);
// Следим за изменением quickgroup в URL и обновляем selectedQuickGroup
useEffect(() => {
const quickgroupId = router.query.quickgroup as string;
if (quickgroupId) {
// Используем функциональное обновление состояния для избежания dependency
setSelectedQuickGroup((prev: any) => {
if (prev && prev.quickgroupid === quickgroupId) return prev;
return { quickgroupid: quickgroupId };
});
} else {
setSelectedQuickGroup(null);
}
}, [router.query.quickgroup]);
// Следить за изменением unitid в URL и обновлять selectedNode
useEffect(() => {
const unitid = router.query.unitid as string;
if (unitid) {
// Используем функциональное обновление состояния для избежания dependency
setSelectedNode((prev: any) => {
if (prev && (prev.unitid === unitid || prev.id === unitid)) return prev;
return { unitid };
});
} else {
setSelectedNode(null);
}
}, [router.query.unitid]);
const handleCategoryClick = (e?: React.MouseEvent) => {
if (e) e.preventDefault();
setShowKnot(true);
};
useEffect(() => {
const handler = (e: Event) => {
const target = e.target as HTMLElement;
if (target.classList.contains('link-2')) {
e.preventDefault();
setShowKnot(true);
}
};
document.addEventListener('click', handler);
return () => document.removeEventListener('click', handler);
}, []);
// ====== КОНЕЦ ХУКОВ ======
const unitDetails = unitDetailsData?.laximoUnitDetails || [];
// Детальное логирование данных от API
React.useEffect(() => {
if (unitDetailsData?.laximoUnitDetails) {
console.log('🔍 [vehicleId].tsx - Полные данные unitDetails от API:', {
totalParts: unitDetailsData.laximoUnitDetails.length,
firstPart: unitDetailsData.laximoUnitDetails[0],
allCodeOnImages: unitDetailsData.laximoUnitDetails.map((part: any) => ({
name: part.name,
codeonimage: part.codeonimage,
detailid: part.detailid,
oem: part.oem
}))
});
}
}, [unitDetailsData]);
// Логируем ошибки
if (vehicleError) {
console.error('Vehicle GraphQL error:', vehicleError);
@ -175,9 +291,10 @@ const VehicleDetailsPage = () => {
if (vehicleLoading) {
return (
<>
<Head>
<title>Загрузка автомобиля...</title>
</Head>
<MetaTags
title="Загрузка автомобиля..."
description="Загружаем информацию об автомобиле..."
/>
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f9fafb' }}>
<div className="text-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-red-600 mx-auto"></div>
@ -192,9 +309,10 @@ const VehicleDetailsPage = () => {
if (!catalogData?.laximoCatalogInfo) {
return (
<>
<Head>
<title>Каталог не найден</title>
</Head>
<MetaTags
title="Каталог не найден"
description="Информация о каталоге недоступна"
/>
<main className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">Каталог не найден</h1>
@ -249,27 +367,115 @@ const VehicleDetailsPage = () => {
vehicleInfo = { ...vehicleInfo, vehicleid: fallbackVehicleId };
}
// Логируем, что реально передаём в VinLeftbar
console.log('Передаём в VinLeftbar:', {
catalog: vehicleInfo.catalog,
vehicleid: vehicleInfo.vehicleid,
ssd: vehicleInfo.ssd
});
// Если нет данных автомобиля и есть ошибка, показываем предупреждение
const hasError = vehicleError && !vehicleData?.laximoVehicleInfo;
const catalogInfo = catalogData.laximoCatalogInfo;
// Создаем динамические meta-теги
const vehicleName = vehicleInfo.brand && vehicleInfo.name
? (vehicleInfo.name.indexOf(vehicleInfo.brand) !== 0
? `${vehicleInfo.brand} ${vehicleInfo.name}`
: vehicleInfo.name)
: 'Автомобиль';
const metaData = {
title: `Запчасти для ${vehicleName} - Поиск по каталогу Protek`,
description: `Найдите и купите запчасти для ${vehicleName}. Широкий выбор оригинальных и аналоговых запчастей с быстрой доставкой.`,
keywords: `запчасти ${vehicleName}, ${vehicleInfo.brand} запчасти, автозапчасти, каталог запчастей`,
ogTitle: `Запчасти для ${vehicleName} - Protek`,
ogDescription: `Найдите и купите запчасти для ${vehicleName}. Широкий выбор оригинальных и аналоговых запчастей.`
};
// --- Синхронизация selectedQuickGroup с URL ---
// Функция для открытия VinQuick и добавления quickgroup в URL
const openQuickGroup = (group: any) => {
// Проверяем что group не null и имеет quickgroupid
if (!group || !group.quickgroupid) {
console.warn('⚠️ openQuickGroup: получен null или группа без quickgroupid:', group);
return;
}
setSelectedQuickGroup(group);
router.push(
{ pathname: router.pathname, query: { ...router.query, quickgroup: group.quickgroupid } },
undefined,
{ shallow: true }
);
};
// --- Сброс VinQuick (selectedQuickGroup) и quickgroup в URL ---
const closeQuickGroup = () => {
setSelectedQuickGroup(null);
const { quickgroup, ...rest } = router.query;
if (quickgroup) {
router.push(
{ pathname: router.pathname, query: rest },
undefined,
{ shallow: true }
);
}
};
// --- Синхронизация selectedNode (KnotIn) с URL ---
// Открыть KnotIn и добавить unitid в URL
const openKnot = (node: any) => {
// ОТЛАДКА: Логируем узел который получили
console.log('🔍 [vehicleId].tsx openKnot получил узел:', {
unitId: node.unitid,
unitName: node.name,
hasSsd: !!node.ssd,
nodeSsd: node.ssd ? `${node.ssd.substring(0, 50)}...` : 'отсутствует',
vehicleSsd: vehicleInfo.ssd ? `${vehicleInfo.ssd.substring(0, 50)}...` : 'отсутствует',
ssdLength: node.ssd?.length || 0
});
setSelectedNode(node);
// Сброс состояния выбранных деталей при открытии нового узла
setSelectedParts(new Set());
setHighlightedPart(null);
router.push(
{ pathname: router.pathname, query: { ...router.query, unitid: node.unitid || node.id } },
undefined,
{ shallow: true }
);
};
// Закрыть KnotIn и удалить unitid из URL
const closeKnot = () => {
setSelectedNode(null);
// Сброс состояния выбранных деталей при закрытии узла
setSelectedParts(new Set());
setHighlightedPart(null);
const { unitid, ...rest } = router.query;
router.push(
{ pathname: router.pathname, query: rest },
undefined,
{ shallow: true }
);
};
// Обработчик выбора детали (множественный выбор)
const handlePartSelect = (codeOnImage: string | number | null) => {
if (codeOnImage === null) return; // Игнорируем null значения
setSelectedParts(prev => {
const newSet = new Set(prev);
if (newSet.has(codeOnImage)) {
newSet.delete(codeOnImage); // Убираем если уже выбрана
} else {
newSet.add(codeOnImage); // Добавляем если не выбрана
}
return newSet;
});
};
// Обработчик подсветки детали при наведении
const handlePartHighlight = (codeOnImage: string | number | null) => {
setHighlightedPart(codeOnImage);
};
return (
<>
<Head>
<title>VIN</title>
<meta content="vin" property="og:title" />
<meta content="vin" property="twitter:title" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
{/* ====== ВРЕМЕННЫЙ МАКЕТ ДЛЯ ВЕРСТКИ (начало) ====== */}
<InfoVin
@ -283,6 +489,7 @@ const VehicleDetailsPage = () => {
? vehicleInfo.attributes.map(attr => attr.value).join(' · ')
: ''
}
vehicleAttributes={vehicleInfo.attributes || []}
/>
<div className="w-layout-blockcontainer container-vin w-container">
@ -296,9 +503,28 @@ const VehicleDetailsPage = () => {
setFoundParts(results);
setSearchState({ loading, error, query, isSearching: isSearching || false });
}}
onNodeSelect={setSelectedNode}
onActiveTabChange={(tab) => setActiveTab(tab)}
onQuickGroupSelect={setSelectedQuickGroup}
onNodeSelect={openKnot}
onActiveTabChange={(tab) => {
setActiveTab(tab);
// Сбрасываем состояние при смене вкладки
setSelectedQuickGroup(null);
setSelectedNode(null);
setOpenedPath([]);
// Очищаем URL от параметров quickgroup и unitid
const { quickgroup, unitid, ...rest } = router.query;
if (quickgroup || unitid) {
router.push(
{ pathname: router.pathname, query: rest },
undefined,
{ shallow: true }
);
}
}}
onQuickGroupSelect={openQuickGroup}
activeTab={activeTab}
openedPath={openedPath}
setOpenedPath={setOpenedPath}
onCloseQuickGroup={closeQuickGroup}
/>
{searchState.isSearching ? (
<div className="knot-parts">
@ -332,6 +558,8 @@ const VehicleDetailsPage = () => {
n={idx + 1}
name={detail.name}
oem={detail.oem}
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
/>
))
) : (
@ -356,17 +584,19 @@ const VehicleDetailsPage = () => {
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
ssd={vehicleInfo.ssd}
onBack={() => setSelectedQuickGroup(null)}
onNodeSelect={setSelectedNode}
onBack={closeQuickGroup}
onNodeSelect={openKnot}
/>
) : (
<VinCategory
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
ssd={vehicleInfo.ssd}
onNodeSelect={setSelectedNode}
onNodeSelect={openKnot}
activeTab={activeTab}
onQuickGroupSelect={setSelectedQuickGroup}
onQuickGroupSelect={openQuickGroup}
openedPath={openedPath}
setOpenedPath={setOpenedPath}
/>
)}
</>
@ -375,21 +605,44 @@ const VehicleDetailsPage = () => {
) : (
<div className="w-layout-hflex flex-block-13">
<div className="w-layout-vflex flex-block-14-copy-copy">
<button onClick={() => setSelectedNode(null)} style={{ marginBottom: 16 }}>Назад</button>
{/* <button onClick={() => setSelectedNode(null)} style={{ marginBottom: 16 }}>Назад</button> */}
{/* ОТЛАДКА: Логируем передачу SSD в KnotIn */}
{(() => {
const knotSsd = selectedNode.ssd || vehicleInfo.ssd;
console.log('🔍 [vehicleId].tsx передает в KnotIn:', {
selectedNodeSsd: selectedNode.ssd ? `${selectedNode.ssd.substring(0, 50)}...` : 'отсутствует',
vehicleInfoSsd: vehicleInfo.ssd ? `${vehicleInfo.ssd.substring(0, 50)}...` : 'отсутствует',
finalSsd: knotSsd ? `${knotSsd.substring(0, 50)}...` : 'отсутствует',
unitId: selectedNode.unitid,
unitName: selectedNode.name
});
return null;
})()}
<KnotIn
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
ssd={vehicleInfo.ssd}
ssd={selectedNode.ssd || vehicleInfo.ssd}
unitId={selectedNode.unitid}
unitName={selectedNode.name}
parts={unitDetails}
onPartSelect={handlePartSelect}
onPartsHighlight={handlePartHighlight}
selectedParts={selectedParts}
/>
{unitDetailsLoading ? (
<div style={{ padding: 24, textAlign: 'center' }}>Загружаем детали узла...</div>
) : unitDetailsError ? (
<div style={{ color: 'red', padding: 24 }}>Ошибка загрузки деталей: {unitDetailsError.message}</div>
) : unitDetails.length > 0 ? (
<KnotParts parts={unitDetails} />
<KnotParts
parts={unitDetails}
catalogCode={vehicleInfo.catalog}
vehicleId={vehicleInfo.vehicleid}
highlightedCodeOnImage={highlightedPart}
selectedParts={selectedParts}
onPartSelect={handlePartSelect}
onPartHover={handlePartHighlight}
/>
) : (
<div style={{ padding: 24, textAlign: 'center' }}>Детали не найдены</div>
)}

View File

@ -6,6 +6,8 @@ import Header from '@/components/Header';
import Footer from '@/components/Footer';
import { GET_LAXIMO_CATALOG_INFO, SEARCH_LAXIMO_OEM } from '@/lib/graphql';
import { LaximoCatalogInfo, LaximoOEMResult } from '@/types/laximo';
import MetaTags from '@/components/MetaTags';
import { getMetaByPath } from '@/lib/meta-config';
const InfoPartDetail = ({ brandName, oemNumber }: { brandName: string; oemNumber: string }) => (
<section className="section-info">
@ -121,12 +123,11 @@ const PartDetailPage = () => {
const totalDetails = oemResult?.categories.reduce((total, cat) =>
total + cat.units.reduce((unitTotal, unit) => unitTotal + unit.details.length, 0), 0) || 0;
const metaData = getMetaByPath('/vehicle-search');
return (
<>
<Head>
<title>Деталь {oemNumber} - {catalogInfo?.name || 'Каталог запчастей'}</title>
<meta name="description" content={`Подробная информация о детали ${oemNumber} в каталоге ${catalogInfo?.name}`} />
</Head>
<MetaTags {...metaData} />
<Header />
<div className="bg-[#F5F8FB] min-h-screen w-full">
<InfoPartDetail brandName={catalogInfo?.name || String(brand)} oemNumber={String(oemNumber)} />

View File

@ -0,0 +1,242 @@
import React, { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import { useQuery } from '@apollo/client';
import Head from 'next/head';
import Header from '@/components/Header';
import Footer from '@/components/Footer';
import MobileMenuBottomSection from '@/components/MobileMenuBottomSection';
import { GET_BRANDS_BY_CODE, GET_LAXIMO_CATALOG_INFO } from '@/lib/graphql';
import { LaximoCatalogInfo } from '@/types/laximo';
import MetaTags from '@/components/MetaTags';
import { getMetaByPath } from '@/lib/meta-config';
const InfoBrandSelection = ({
brand,
brandName,
vehicleId,
oemNumber,
detailName
}: {
brand: string;
brandName: string;
vehicleId: string;
oemNumber: string;
detailName?: string;
}) => (
<section className="section-info">
<div className="w-layout-blockcontainer container info w-container">
<div className="w-layout-vflex flex-block-9">
<div className="w-layout-hflex flex-block-7">
<a href="/" className="link-block w-inline-block">
<div>Главная</div>
</a>
<div className="text-block-3"></div>
<a href="#" className="link-block w-inline-block">
<div>Каталог</div>
</a>
<div className="text-block-3"></div>
<a href={`/vehicle-search/${brand}/${vehicleId}`} className="link-block w-inline-block">
<div>{brandName}</div>
</a>
<div className="text-block-3"></div>
<a href="#" className="link-block-2 w-inline-block">
<div>Деталь {oemNumber}</div>
</a>
</div>
<div className="link-block w-inline-block">
<div className="heading">Выберите производителя для {oemNumber}</div>
</div>
</div>
</div>
</section>
);
const BrandSelectionPage = () => {
const router = useRouter();
const { brand, vehicleId, oemNumber, detailName } = router.query;
// Получаем информацию о каталоге
const { data: catalogData, loading: catalogLoading } = useQuery<{ laximoCatalogInfo: LaximoCatalogInfo }>(
GET_LAXIMO_CATALOG_INFO,
{
variables: { catalogCode: brand },
skip: !brand,
errorPolicy: 'all',
}
);
// Получаем список брендов по артикулу
const { data, loading, error } = useQuery(GET_BRANDS_BY_CODE, {
variables: { code: oemNumber },
skip: !oemNumber,
errorPolicy: 'all'
});
if (!brand || vehicleId === undefined || vehicleId === null || !oemNumber) {
return (
<>
<Head>
<title>Выбор производителя</title>
</Head>
<main style={{ minHeight: '100vh', backgroundColor: '#f9fafb' }}>
<div style={{ textAlign: 'center', padding: '4rem 1rem' }}>
<h1 style={{ fontSize: '2rem', fontWeight: 'bold', color: '#1f2937', marginBottom: '1rem' }}>
Неверные параметры
</h1>
<p style={{ color: '#6b7280', marginBottom: '2rem' }}>
Неверные параметры для выбора производителя
</p>
<button
onClick={() => router.back()}
style={{
backgroundColor: '#dc2626',
color: 'white',
padding: '0.75rem 1.5rem',
borderRadius: '0.5rem',
border: 'none',
cursor: 'pointer'
}}
>
Назад
</button>
</div>
</main>
<Footer />
</>
);
}
const catalogInfo = catalogData?.laximoCatalogInfo;
const brandsData = data?.getBrandsByCode;
const brands = brandsData?.brands || [];
const hasError = brandsData?.error || error;
const hasNoBrands = brandsData?.success && brands.length === 0;
const handleBrandSelect = (selectedBrand: string) => {
console.log('🎯 Выбран бренд:', { articleNumber: oemNumber, brand: selectedBrand });
router.push(`/search-result?article=${encodeURIComponent(String(oemNumber))}&brand=${encodeURIComponent(selectedBrand)}`);
};
const handleBack = () => {
router.back();
};
const metaData = getMetaByPath('/vehicle-search');
return (
<>
<MetaTags {...metaData} />
<InfoBrandSelection
brand={String(brand)}
brandName={catalogInfo?.name || String(brand)}
vehicleId={String(vehicleId)}
oemNumber={String(oemNumber)}
detailName={String(detailName || '')}
/>
<div className="page-wrapper bg-[#F5F8FB] min-h-screen">
<div className="mx-auto px-8 max-md:px-5 pt-10 pb-16 ">
{/* Кнопка назад */}
{/* <div className="mb-6">
<button
onClick={handleBack}
className="flex items-center gap-2 text-gray-600 hover:text-gray-900 transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
Назад к деталям
</button>
</div> */}
{/* Обработка ошибок */}
{hasError && !loading && (
<div className="bg-red-50 border border-red-200 rounded-2xl shadow p-10 mb-6">
<div className="flex items-center">
<svg className="w-6 h-6 text-red-600 mr-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div>
<h3 className="text-lg font-medium text-red-800">Ошибка загрузки</h3>
<p className="text-red-700 mt-1">
{brandsData?.error || error?.message || 'Не удалось загрузить список производителей'}
</p>
</div>
</div>
</div>
)}
{/* Загрузка */}
{(catalogLoading || loading) && (
<div className="bg-white rounded-2xl shadow p-10 flex flex-col items-center justify-center min-h-[300px]">
<div className="animate-spin rounded-full h-24 w-24 border-b-2 border-red-600 mb-6"></div>
<p className="text-lg text-gray-600">Загружаем производителей...</p>
</div>
)}
{/* Результаты */}
{!loading && !hasError && (
<div className="space-y-6">
{hasNoBrands ? (
<div className="bg-[#eaf0fa] border border-[#b3c6e6] rounded-2xl shadow p-10 text-center">
<svg className="w-16 h-16 mx-auto mb-4" style={{ color: '#0d336c' }} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 15.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
<h3 className="text-xl font-semibold mb-2" style={{ color: '#0d336c' }}>
Производители не найдены
</h3>
<p className="mb-4" style={{ color: '#0d336c' }}>
По артикулу <span className="font-mono font-semibold">{oemNumber}</span> производители не найдены.
</p>
<p className="text-sm" style={{ color: '#3b5a99' }}>
Попробуйте изменить запрос или обратитесь к нашим менеджерам.
</p>
</div>
) : brands.length > 0 && (
<div className="bg-white rounded-2xl shadow p-10 w-full max-w-[1580px] mx-auto min-h-[500px]">
{/* <div className="border-b border-gray-200 pb-4">
<h2 className="text-xl font-semibold text-gray-900">
Выбор производителя для артикула: {oemNumber}
</h2>
<p className="text-sm text-gray-600 mt-1">
{detailName && <span>Деталь: {detailName} • </span>}
Найдено производителей: <span className="font-medium">{brands.length}</span>
</p>
</div> */}
<div className="divide-y divide-gray-200">
{brands.map((brandItem: any, index: number) => (
<div key={index}>
<button
onClick={() => handleBrandSelect(brandItem.brand)}
className="w-full text-left p-4 hover:bg-gray-50 transition-colors block group"
>
<div className="flex w-full items-center gap-2">
<div className="w-1/5 max-md:w-1/3 font-bold text-left truncate" style={{ color: 'rgb(77, 180, 94)' }}>
{brandItem.brand}
</div>
<div className="w-1/5 max-md:text-center max-md:w-1/3 font-bold text-left truncate group-hover:text-[#EC1C24] transition-colors">
{oemNumber}
</div>
<div className="w-3/5 max-md:w-1/3 text-left truncate">
{brandItem.name && brandItem.name !== brandItem.brand ? brandItem.name : detailName || 'Запчасть'}
</div>
</div>
</button>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
<MobileMenuBottomSection />
<Footer />
</div>
</>
);
};
export default BrandSelectionPage;

View File

@ -6,6 +6,8 @@ import Header from '@/components/Header';
import Footer from '@/components/Footer';
import { FIND_LAXIMO_VEHICLES_BY_PART_NUMBER } from '@/lib/graphql';
import { LaximoVehiclesByPartResult, LaximoVehicleSearchResult } from '@/types/laximo';
import MetaTags from "../components/MetaTags";
import { getMetaByPath } from "../lib/meta-config";
const VehiclesByPartPage = () => {
const router = useRouter();
@ -46,12 +48,15 @@ const VehiclesByPartPage = () => {
router.back();
};
const metaConfig = getMetaByPath('/vehicles-by-part');
if (loading) {
return (
<>
<Head>
<title>Поиск автомобилей по артикулу {cleanPartNumber} - Protek</title>
</Head>
<MetaTags
title="Поиск автомобилей по артикулу - Protek"
description="Поиск автомобилей, в которых используется деталь..."
/>
<main className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-red-600 mx-auto"></div>
@ -66,9 +71,10 @@ const VehiclesByPartPage = () => {
if (error || !data?.laximoFindVehiclesByPartNumber) {
return (
<>
<Head>
<title>Ошибка поиска - Protek</title>
</Head>
<MetaTags
title="Ошибка поиска - Protek"
description="Произошла ошибка при поиске автомобилей по артикулу"
/>
<main className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="text-red-500 mb-4">
@ -100,10 +106,13 @@ const VehiclesByPartPage = () => {
return (
<>
<Head>
<title>Автомобили по артикулу {cleanPartNumber} - Protek</title>
<meta name="description" content={`Найдено ${result.totalVehicles} автомобилей по артикулу ${cleanPartNumber} в ${result.catalogs.length} каталогах`} />
</Head>
<MetaTags
title={cleanPartNumber ? `Автомобили по артикулу ${cleanPartNumber} - Protek` : metaConfig.title}
description={cleanPartNumber ? `Поиск автомобилей, в которых используется деталь с артикулом ${cleanPartNumber}` : metaConfig.description}
keywords={metaConfig.keywords}
ogTitle={metaConfig.ogTitle}
ogDescription={metaConfig.ogDescription}
/>
<Header />
<main className="min-h-screen bg-gray-50">

View File

@ -10,6 +10,8 @@ import VinKnot from "@/components/vin/VinKnot";
import KnotParts from "@/components/vin/KnotParts";
import React, { useState } from "react";
import KnotIn from "@/components/vin/KnotIn";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
export default function Vin() {
@ -34,21 +36,15 @@ export default function Vin() {
return () => document.removeEventListener("click", handler);
}, []);
const metaData = getMetaByPath('/vin-step-2');
return (
<>
<MetaTags {...metaData} />
<Head>
<title>VIN</title>
<meta content="vin" property="og:title" />
<meta content="vin" property="twitter:title" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta content="Webflow" name="generator" />
<link href="/css/normalize.css" rel="stylesheet" type="text/css" />
<link href="/css/webflow.css" rel="stylesheet" type="text/css" />
<link href="/css/protekproject.webflow.css" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<InfoVin />
<section className="main">
@ -56,7 +52,7 @@ export default function Vin() {
<div className="w-layout-hflex flex-block-13">
<div className="w-layout-vflex flex-block-14-copy-copy">
<KnotIn />
<KnotParts />
<KnotParts catalogCode="" vehicleId="" />
</div>

View File

@ -8,6 +8,8 @@ import VinLeftbar from "@/components/vin/VinLeftbar";
import VinCategory from "@/components/vin/VinCategory";
import VinKnot from "@/components/vin/VinKnot";
import React, { useState } from "react";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
export default function Vin() {
const [showKnot, setShowKnot] = useState(false);
@ -31,18 +33,11 @@ export default function Vin() {
return () => document.removeEventListener("click", handler);
}, []);
const metaData = getMetaByPath('/vin');
return (
<>
<Head>
<title>VIN</title>
<meta content="vin" property="og:title" />
<meta content="vin" property="twitter:title" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<InfoVin />
<section className="main">
<div className="w-layout-blockcontainer container-vin w-container">

View File

@ -9,25 +9,16 @@ import WhyWholesale from "@/components/wholesale/WhyWholesale";
import ServiceWholesale from "@/components/wholesale/ServiceWholesale";
import HowToBuy from "@/components/wholesale/HowToBuy";
import Help from "@/components/Help";
import MetaTags from "@/components/MetaTags";
import { getMetaByPath } from "@/lib/meta-config";
export default function Wholesale() {
const metaData = getMetaByPath('/wholesale');
return (
<>
<Head>
<title>wholesale</title>
<meta content="wholesale" property="og:title" />
<meta content="wholesale" property="twitter:title" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta content="Webflow" name="generator" />
<link href="/css/normalize.css" rel="stylesheet" type="text/css" />
<link href="/css/webflow.css" rel="stylesheet" type="text/css" />
<link href="/css/protekproject.webflow.css" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com" rel="preconnect" />
<link href="https://fonts.gstatic.com" rel="preconnect" crossOrigin="anonymous" />
<link href="images/favicon.png" rel="shortcut icon" type="image/x-icon" />
<link href="images/webclip.png" rel="apple-touch-icon" />
</Head>
<MetaTags {...metaData} />
<InfoWholesale />
<section className="main">

View File

@ -85,3 +85,334 @@ input[type=number]::-webkit-outer-spin-button {
input[type=number] {
-moz-appearance: textfield;
}
/* Анимация для cookie consent */
@keyframes slideInFromBottom {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.cookie-consent-enter {
animation: slideInFromBottom 0.3s ease-out;
}
/* Анимации для тултипов */
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes zoomIn {
from {
transform: scale(0.95);
}
to {
transform: scale(1);
}
}
.animate-in {
animation-fill-mode: both;
}
.fade-in-0 {
animation-name: fadeIn;
}
.zoom-in-95 {
animation-name: zoomIn;
}
.duration-200 {
animation-duration: 200ms;
}
/* Стили для кнопок с курсором pointer */
button,
.cursor-pointer,
[role="button"] {
cursor: pointer;
}
/* ===== СОВРЕМЕННЫЕ СТИЛИ ДЛЯ КРАСИВОГО ТУЛТИПА ===== */
.tooltip-detail-modern {
animation: tooltip-modern-fade-in 0.3s cubic-bezier(0.16, 1, 0.3, 1);
filter: drop-shadow(0 25px 50px rgba(0, 0, 0, 0.15));
}
.tooltip-content-modern {
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid #e2e8f0;
border-radius: 16px;
overflow: hidden;
max-width: 420px;
min-width: 280px;
box-shadow:
0 20px 25px -5px rgba(0, 0, 0, 0.1),
0 10px 10px -5px rgba(0, 0, 0, 0.04);
position: relative;
}
.tooltip-arrow {
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
width: 12px;
height: 12px;
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid #e2e8f0;
border-bottom: none;
border-right: none;
transform: translateX(-50%) rotate(45deg);
z-index: 1;
}
.tooltip-header-modern {
background: linear-gradient(135deg, #EC1C24 0%, #DC1C24 100%);
padding: 16px 20px;
display: flex;
align-items: flex-start;
gap: 12px;
position: relative;
}
.tooltip-header-modern::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
pointer-events: none;
}
.tooltip-icon {
color: white;
opacity: 0.9;
flex-shrink: 0;
margin-top: 2px;
}
.tooltip-title-section {
flex: 1;
min-width: 0;
}
.tooltip-title {
margin: 0 0 8px 0;
font-size: 16px;
font-weight: 600;
color: white;
line-height: 1.3;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.tooltip-oem-badge {
display: inline-flex;
align-items: center;
background: rgba(255, 255, 255, 0.15);
border-radius: 6px;
padding: 4px 8px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.tooltip-oem-label {
font-size: 10px;
font-weight: 600;
color: rgba(255, 255, 255, 0.8);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-right: 6px;
}
.tooltip-oem-value {
font-size: 12px;
font-weight: 600;
color: white;
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
}
.tooltip-body-modern {
padding: 20px;
}
.tooltip-section-title {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
font-weight: 600;
color: #374151;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 2px solid #f1f5f9;
}
.tooltip-section-title svg {
color: #EC1C24;
}
.tooltip-attributes-grid {
display: grid;
gap: 8px;
}
.tooltip-attribute-item {
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 10px 12px;
transition: all 0.2s ease;
}
.tooltip-attribute-item:hover {
background: #f1f5f9;
border-color: #cbd5e1;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.tooltip-attribute-key {
font-size: 12px;
font-weight: 600;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 4px;
}
.tooltip-attribute-value {
font-size: 14px;
font-weight: 500;
color: #1e293b;
line-height: 1.4;
}
.tooltip-note-modern {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #e2e8f0;
}
.tooltip-note-text {
background: #fef3c7;
border: 1px solid #fbbf24;
border-radius: 8px;
padding: 12px;
font-size: 13px;
color: #92400e;
line-height: 1.5;
margin-top: 8px;
}
.tooltip-no-data {
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px;
text-align: center;
}
.tooltip-no-data-icon {
color: #94a3b8;
margin-bottom: 12px;
opacity: 0.7;
}
.tooltip-no-data-text {
color: #64748b;
font-size: 14px;
line-height: 1.4;
}
.tooltip-no-data-text div:first-child {
font-weight: 500;
margin-bottom: 2px;
}
.tooltip-no-data-text div:last-child {
opacity: 0.8;
}
@keyframes tooltip-modern-fade-in {
from {
opacity: 0;
transform: scale(0.95) translateY(-10px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
/* Адаптивность для мобильных устройств */
@media (max-width: 480px) {
.tooltip-content-modern {
max-width: 320px;
min-width: 260px;
}
.tooltip-header-modern {
padding: 14px 16px;
}
.tooltip-body-modern {
padding: 16px;
}
.tooltip-title {
font-size: 15px;
}
}
/* Стили для состояния "товар в корзине" */
.button-icon.in-cart {
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.button-icon.in-cart::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(156, 163, 175, 0.2);
pointer-events: none;
border-radius: inherit;
}
.button-icon.in-cart:hover {
opacity: 0.8 !important;
background-color: #6b7280 !important;
transform: scale(0.98);
}
/* Анимация для добавления в корзину */
.button-icon:active {
transform: scale(0.95);
transition: transform 0.1s ease;
}
/* Убеждаемся, что иконка корзины видна в сером состоянии */
.button-icon.in-cart .image-11,
.button-icon.in-cart svg {
filter: brightness(0.7) contrast(1.2);
}

View File

@ -3,11 +3,37 @@
margin-left: 0 !important;
}
.bottom_head{
z-index: 60;
.dropdown-scroll-invisible {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE и Edge */
}
.dropdown-scroll-invisible::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
.top_head{
.dropdown-scroll-invisible {
padding-left: 0 !important;
list-style: none !important;
}
.bottom_head {
background-color: var(--back);
flex-flow: column;
justify-content: center;
align-items: flex-start;
width: 100%;
height: auto;
/* margin-top: -15px; */
padding-left: 60px;
padding-right: 60px;
display: flex;
}
.bottom_head{
z-index: 3000;
}
.top_head {
z-index: 70;
}
@ -19,6 +45,23 @@
display: none !important;
}
.price-in-cart-s1 {
max-width: 140px;
}
.flex-block-39-copy {
max-width: 300px;
min-width: 0;
}
.flex-block-40 {
padding-top: 10px;
}
input.text-block-31 {
background: none !important;
}
@ -154,7 +197,7 @@ input.text-block-31 {
background: #e5e7eb;
}
.form-block, .text-field.w-input {
.form-block {
min-height: 44px !important;
}
@ -229,7 +272,18 @@ header.section-4 {
display: none; /* Chrome, Safari, Opera */
}
.heading-2 {
max-width: 300px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: block;
min-width: 0;
}
.dropdown-toggle.w-dropdown-toggle {
min-width: 0;
}
.mobile-category-overlay {
position: fixed;
@ -311,6 +365,12 @@ input.input-receiver:focus {
box-shadow: none;
}
.flex-block-122 {
width: 100% !important;
}
.button-icon.w-inline-block {
margin-left: auto;
}
.text-block-10 {
display: -webkit-box;
-webkit-line-clamp: 2;
@ -328,6 +388,15 @@ input.input-receiver:focus {
display: block;
}
.image-5-copy {
width: 97px !important;
height: 97px !important;
}
.flex-block-111 {
width: 172px !important;
}
.show-more-btn {
background-color: #ec1c24;
color: #fff;
@ -357,6 +426,25 @@ input.input-receiver:focus {
width: 100% !important;
}
.text-block-7 {
border-radius: var(--_round---small-8);
background-color: var(--green);
color: var(--_fonts---color--white);
padding: 5px;
font-weight: 600;
position: relative;
top: -35px;
height: 30px;
}
.div-block-3 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
align-self: auto;
margin-top: -30px;
display: flex;
}
.sort-item.active {
color: #111;
font-weight: 700;
@ -372,8 +460,15 @@ input.input-receiver:focus {
}
.core-product-s1 {
max-width: 320px ;
.core-product-s1{
width: 100% !important;
}
.heading-10 {
width: auto !important;
min-width: 0 !important;
white-space: nowrap; /* если хотите, чтобы текст не переносился */
}
.flex-block-112 {
@ -414,6 +509,12 @@ input#VinSearchInput {
}
.w-input {
border-radius: 8px !important;
}
.text-block-56, .dropdown-link-3 {
white-space: nowrap;
overflow: hidden;
@ -432,15 +533,35 @@ input#VinSearchInput {
line-height: 1.4em;
}
.heading-9-copy,
.text-block-21-copy {
.text-block-21-copy,
.heading-9-copy {
width: 250px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* .heading-9-copy {
text-align: right;
margin-left: auto;
display: block;
} */
.pcs-search {
color: var(--_fonts---color--black);
font-size: var(--_fonts---font-size--core);
width: 200px;
}
@media (max-width: 767px) {
/* .heading-9-copy {
text-align: left;
display: block;
} */
.w-layout-hflex.flex-block-6 {
flex-direction: column !important;
}
@ -467,11 +588,744 @@ input#VinSearchInput {
}
}
.div-block-19{
padding-left: 20px !important;
}
.dropdown-toggle-card {
align-self: stretch;
margin-bottom: 5px;
margin-left: 0;
margin-right: 0;
padding: 6px 15px;
padding-left: 0 !important;
margin-left: 0 !important;
}
.dropdown-link-3 {
margin-left: 0 !important;
}
.div-block-131 {
max-width: 230px;
}
.dropdown-toggle-3.active{
background-color: var(--background);
font-weight: 700;
}
.dropdown-toggle-card.active {
background-color: var(--background);
}
.dropdown-toggle-3.active {
border-left: 2px solid var(--red);
}
.news-index-block-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex: 1;
max-width: 100%;
margin-top: 0;
margin-bottom: 60px;
}
.news-index-block {
grid-column-gap: 40px;
grid-row-gap: 40px;
flex: 1;
max-width: 100%;
margin-bottom: 100px;
}
@media screen and (max-width: 767px) {
.news-index-block {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.inbt, .news-index-block-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
}
.news-index-block-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex: 1;
max-width: 100%;
margin-top: 0;
margin-bottom: 60px;
}
@media screen and (max-width: 991px) {
.bottom_head {
padding-left: 30px;
padding-right: 30px;
}
}
@media screen and (max-width: 479px) {
.bottom_head {
padding-left: 15px;
padding-right: 15px;
}
}
body {
background-color: var(--background);
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 14px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 18px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 36px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 24px;
--_fonts---font-size--heading-2: 30px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 12px;
font-family: Onest, sans-serif;
}
.heading{
font-size: 28px;
}
.form-block-4,
.flex-block-124,
.flex-block-6-copy
{
overflow: visible !important;
}
a.link-block.w-inline-block,
a.link-block-2.w-inline-block {
font-size: 12px;
}
.core-product-search.carousel-scroll {
display: flex;
flex-wrap: nowrap; /* Не переносить строки */
gap: 16px; /* Отступ между карточками, если нужно */
}
.subscribe{
padding-top: 10px !important;
padding-bottom: 10px !important;
}
.text-block-14, .div-block-9{
max-width: 350px !important;
min-width: 100px !important;
}
.flex-block-18{
row-gap: 40px !important;
}
.menu-button.w--open {
z-index: 2000;
background-color: var(--red);
color: var(--white);
justify-content: center;
align-items: center;
width: 50px;
height: 44px;
padding: 13px 12px;
}
.heading-7 {
z-index: 999;
color: var(--_fonts---color--black);
font-size: var(--_fonts---font-size--heading-3);
width: 200px;
margin-top: 30px;
margin-bottom: 0;
margin-left: 30px;
line-height: 130%;
position: absolute;
}
.heading-7-white {
z-index: 999;
color: var(--_fonts---color--white);
font-size: var(--_fonts---font-size--heading-3);
width: 200px;
margin-top: 30px;
margin-bottom: 0;
margin-left: 30px;
line-height: 130%;
position: absolute;
}
.mobile-menu-buttom-section {
z-index: 1900 !important;
background-color: var(--white);
display: none;
position: fixed;
inset: auto 0% 0%;
}
.knot-parts {
overflow: hidden;
}
.flex-block-14-copy-copy{
margin-bottom: 20px !important;
}
.showall-btn {
width: 100%;
}
.showall-btn:hover {
background: #ec1c24 !important;
color: #fff !important;
}
@media screen and (max-width: 991px) {
.flex-block-108 {
flex-flow: column;
justify-content: space-between;
}
}
@media screen and (max-width: 991px) {
.flex-block-118 {
flex-direction: column !important;
align-items: stretch; /* или center, если нужно по центру */
gap: 20px; /* если нужен отступ между блоками */
}
.flex-block-119 {
width: 100%;
}
}
@media screen and (max-width: 767px) {
.mobile-menu-buttom-section {
display: block;
}
}
@media screen and (max-width: 991px) {
.div-block-128 {
height: 140px;
}
}
@media (max-width: 767px) {
.div-block-128 {
height: 100px;
}
}
.nav-menu-3 {
z-index: 1900;
background-color: #0000;
width: 100%;
height: auto;
display: none;
position: relative;
left: auto;
}
.dropdown-toggle-2 {
z-index: 999;
display: flex;
}
.dropdown-toggle-2.w--open {
z-index: 999998;
}
.div-block-28 {
background-color: var(--background);
height: auto;
margin-top: 60px;
padding-bottom: 60px;
padding-left: 60px;
position: relative;
bottom: auto;
left: 0%;
right: 0%;
}
@media screen and (max-width: 991px) {
.nav-menu-2 {
float: none;
flex-flow: row;
justify-content: flex-start;
align-items: center;
width: 100%;
display: block;
position: relative;
top: 20px;
left: -30px;
right: 0;
}
.div-block-28 {
padding-left: 30px;
}
}
.heading-8-copy {
height: 40px;
}
.flex-block-108 , .flex-block-108-copy, .w-layout-hflex.flex-block-121, .core-product-search{
overflow-x: scroll;
overflow-y: hidden;
}
/* .flex-block-108-copy::-webkit-scrollbar {
display: none;
}
.flex-block-121::-webkit-scrollbar {
display: none;
} */
.flex-block-108 , .flex-block-108-copy, .w-layout-hflex.flex-block-121, .core-product-search {
overflow-x: scroll;
overflow-y: hidden;
scrollbar-width: none; /* Firefox */
}
.flex-block-108::-webkit-scrollbar, .flex-block-108-copy::-webkit-scrollbar, .w-layout-hflex.flex-block-121::-webkit-scrollbar, .core-product-search::-webkit-scrollbar {
display: none; /* Chrome, Safari */
}
.flex-block-44 {
max-width: 33%;
}
.flex-block-44 {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex: 1;
}
.sort-list-s1 {
padding-top: 6px;
padding-bottom: 6px;
}
.show-more-search {
padding: 6px 20px;
}
.flex-block-37 {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.w-layout-vflex.flex-block-40 {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.flex-block-47 {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
justify-content: center !important; /* по центру по горизонтали */
gap: 16px !important;
}
.flex-block-50 {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 16px !important;
width: 100% !important;
}
.flex-block-79 {
display: flex !important;
flex-direction: row !important;
align-items: center !important;
gap: 8px !important;
}
.text-block-21 {
}
.flex-block-45 {
width: 80%;
}
.flex-block-39 {
max-width: 100%;
}
.flex-block-36 {
width: 100%;
}
.flex-block-15-copy {
width: 240px!important;
height: 315px;
min-width: 240px!important;
}
.nameitembp {
flex: 0 auto;
align-self: auto;
height: 60px;
width: 130px;
overflow: hidden;
}
@media screen and (max-width: 991px) {
.flex-block-15-copy {
grid-column-gap: 5px;
grid-row-gap: 5px;
width: 210px !important;
min-width: 210px !important;
padding: 15px;
}
.div-block-3 {
height: 102px !important;
}
.div-block-3.bp-item-info {
height: 90px !important;
}
}
@media screen and (max-width: 479px) {
.flex-block-15-copy {
grid-column-gap: 5px;
grid-row-gap: 5px;
width: 180px !important;
min-width: 180px !important;
padding: 15px;
}
.nameitembp {
height: 36px;
width: 95px;
font-size: 12px;
line-height: 18px;
overflow: hidden;
}
.knot-parts {
min-width: 100%;
}
}
@media (max-width: 767px) {
.flex-block-110 {
flex-direction: row !important;
align-items: flex-start !important;
}
.image-5-copy {
width: 75px !important;
height: 75px !important;
}
}
@media (max-width: 767px) {
.topmenub {
display: none !important;
}
}
@media screen and (max-width: 479px) {
.bestpriceitem {
grid-column-gap: 5px;
grid-row-gap: 5px;
min-width: 160px;
max-width: 190px;
padding: 15px;
}
}
@media screen and (max-width: 1440px) {
.image-27 {
margin-bottom: -212px;
margin-left: 800px;
}
}
@media (max-width: 991px) {
.topnav.w-nav {
margin-left: 0 !important;
}
.code-embed-15.w-embed {
display: none !important;
}
}
.topmenub[style*='#fff'] .link-block-8 {
border: 1px solid #E6EDF6 !important;
transition: border-color 0.2s;
}
.topmenub-white .link-block-8 {
border: 1px solid #E6EDF6 !important;
border-radius: 8px;
transition: border-color 0.2s;
}
.container.info {
padding-top: 5px !important;
padding-bottom: 20px !important;
}
.carousel-row {
display: flex;
align-items: center;
position: relative;
gap: 12px;
width: 100%;
}
.carousel-arrow {
background: none;
border: none;
padding: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
transition: opacity 0.2s;
opacity: 0.85;
z-index: 2;
}
.carousel-arrow:active {
opacity: 0.6;
}
.carousel-arrow[disabled] {
opacity: 0.3;
pointer-events: none;
}
.carousel-arrow-left {
margin-right: 4px;
}
.carousel-arrow-right {
margin-left: 4px;
}
.carousel-scroll {
overflow-x: auto;
overflow-y: visible;
scroll-behavior: smooth;
display: flex;
gap: 24px;
flex: 1 1 auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE и Edge */
}
.carousel-scroll::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
@media (max-width: 991px) {
.carousel-scroll {
gap: 12px;
}
.carousel-row {
gap: 4px;
}
}
@media (max-width: 767px) {
.carousel-arrow {
display: none !important;
}
}
.mobile-only {
display: block;
}
@media (min-width: 768px) {
.mobile-only {
display: none !important;
}
}
.protekauto-logo {
position: fixed;
z-index: 3000;
}
.hide-on-991 {
display: block;
}
@media screen and (max-width: 991px) {
.hide-on-991 {
display: none !important;
}
}
@media (max-width: 767px) {
.flex-block-50 {
flex-direction: column !important;
align-items: flex-start !important;
justify-content: flex-start !important;
gap: 8px !important;
padding-left: 10px !important;
padding-right: 10px !important;
}
}
@media (max-width: 767px) {
.div-block-19 {
display: none !important;
}
}
@media (max-width: 767px) {
.core-product-s1 {
flex-direction: row !important; /* или column, если нужно вертикально */
justify-content: flex-start !important;
align-items: flex-start !important;
}
}
@media (max-width: 767px) {
.mask.w-slider-mask {
height: 100px !important;
min-height: 0 !important;
}
}
@media (max-width: 767px) {
.search-history-dropdown,
.search-results-dropdown,
.dropdown-search,
.dropdown-list-3.w--open {
position: fixed !important;
left: 0 !important;
right: 0 !important;
top: 72px !important; /* подберите под ваш header */
width: 100vw !important;
z-index: 9999 !important;
border-radius: 0 0 16px 16px !important;
margin: 0 !important;
max-width: 100vw !important;
background: white !important;
box-shadow: 0 8px 32px rgba(44,62,80,0.10), 0 1.5px 4px rgba(44,62,80,0.08) !important;
}
}
.pricecartbp {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px; /* или другой нужный вам отступ */
}
.bestpriceitem {
height: 279px;
}
.flex-block-49 {
gap: 15px;
}
.pcs-search-s1,
.sort-item.first {
width: 100px;
}
@media (max-width: 991px) {
.pcs-search-s1,
.sort-item.first {
width: 60px;
}
}
@media (max-width: 479px) {
.pcs-search-s1,
.sort-item.first {
width: 50px;
}
}
.w-layout-vflex.flex-block-36 {
display: flex;
flex-wrap: wrap;
gap: 24px;
justify-content: flex-start;
align-items: stretch;
}
.w-layout-vflex.flex-block-44 {
flex: 1 1 calc(33.333% - 16px);
max-width: calc(33.333% - 16px);
min-width: 0;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
@media (max-width: 991px) {
.w-layout-vflex.flex-block-44 {
flex: 1 1 calc(50% - 12px);
max-width: calc(50% - 12px);
}
}
@media (max-width: 600px) {
.w-layout-vflex.flex-block-44 {
flex: 1 1 100%;
max-width: 100%;
}
}
@media (max-width: 767px) {
.w-layout-vflex.flex-block-36 {
flex-wrap: nowrap;
overflow-x: auto;
gap: 12px;
-webkit-overflow-scrolling: touch;
}
.w-layout-vflex.flex-block-44 {
min-width: 160px;
max-width: 160px;
flex: 0 0 160px;
}
/* .heading-9-copy {
text-align: left !important;
margin-left: 0 !important;
} */
}
@media (max-width: 1200px) {
.pcs-cart-s1 {
display: none !important;
}
}
@media (max-width: 767px) {
.filters-desktop {
display: none !important;
}
}

View File

@ -3,7 +3,7 @@
--back: #0d336c;
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--core: 16px;
--_fonts---font-size--small-font-size: 14px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 18px;
@ -17,7 +17,6 @@
--_button---dark_blue: #0d336c;
--_fonts---color--white: white;
--_button---hover-dark_blue: #00245b;
--_button---black: #000;
--_button---color: white;
--_round---big-20: 20px;
--_fonts---color--light-blue-grey: #8e9aac;
@ -34,6 +33,7 @@
--_fonts---font-size--heading-2: 30px;
--light-grey: #ececec;
--_button---light-blue: #e6edf6;
--_button---black: #000;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 12px;
--light-blue: #dae5ef;
@ -787,8 +787,6 @@
.w-layout-blockcontainer {
max-width: 728px;
}
}
@media screen and (max-width: 767px) {
@ -796,8 +794,6 @@
max-width: none;
}
.w-commerce-commercelayoutcontainer {
flex-direction: column;
align-items: stretch;
@ -828,8 +824,8 @@
}
body {
background-color: var(--background);
--_fonts---font-family: Onest, sans-serif;
color: #333;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
line-height: 20px;
}
@ -937,7 +933,7 @@ body {
.flex-block {
grid-column-gap: 15px;
grid-row-gap: 15px;
display: flex;
display: none;
}
.flex-block-2 {
@ -968,14 +964,14 @@ body {
display: flex;
}
.bottom_head {
.topmenuh {
background-color: var(--back);
flex-flow: column;
justify-content: center;
align-items: flex-start;
width: 100%;
height: auto;
margin-top: -15px;
margin-top: 0;
padding-left: 60px;
padding-right: 60px;
display: flex;
@ -995,7 +991,9 @@ body {
background-color: #fff;
justify-content: center;
align-items: center;
padding: 12px 16px 13px;
width: 50px;
height: 44px;
padding: 13px 12px;
display: flex;
}
@ -1005,15 +1003,14 @@ body {
}
.menu-button.w--open {
z-index: 2000;
z-index: 9999999;
background-color: var(--red);
color: var(--white);
justify-content: center;
align-items: center;
height: 50px;
padding-top: 15px;
padding-bottom: 15px;
left: auto;
width: 50px;
height: 44px;
padding: 13px 12px;
}
.menu-button.w--open:hover {
@ -1114,6 +1111,7 @@ body {
cursor: pointer;
justify-content: flex-start;
align-items: center;
height: 44px;
padding: 6px 15px;
text-decoration: none;
display: flex;
@ -1142,18 +1140,22 @@ body {
.left-arrow {
justify-content: space-between;
align-items: center;
margin-left: -40px;
margin-left: 0;
padding-left: 40px;
display: flex;
position: absolute;
}
.left-arrow:hover {
display: flex;
}
.icon-2 {
color: var(--_button---black);
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
margin: 0;
width: 18px;
height: 18px;
margin: auto;
font-size: 24px;
font-weight: 700;
display: flex;
@ -1168,28 +1170,28 @@ body {
background-color: var(--back);
border: 1px #000;
flex: 1;
min-width: 100%;
max-width: 100%;
height: 480px;
height: 220px;
margin-left: 0;
margin-right: 0;
overflow: hidden;
}
.flex-block-5 {
grid-column-gap: 40px;
grid-row-gap: 40px;
grid-column-gap: 20px;
grid-row-gap: 20px;
flex: 1;
max-width: 100%;
margin-top: 20px;
}
.flex-block-6 {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: wrap;
grid-template: ". ."
". ."
"Area Area"
/ 1fr 1fr;
grid-template: "."
"."
/ 1fr 1fr 1fr 1fr;
grid-auto-columns: 1fr;
align-self: stretch;
display: grid;
@ -1514,7 +1516,7 @@ body {
padding: 5px;
font-weight: 600;
position: relative;
top: -45px;
top: -35px;
}
.text-block-8 {
@ -1537,7 +1539,7 @@ body {
grid-row-gap: 5px;
flex-flow: column;
align-self: auto;
margin-top: -30px;
/* margin-top: -30px; */
display: flex;
}
@ -1557,7 +1559,7 @@ body {
box-sizing: content-box;
object-fit: contain;
width: 100%;
height: 100%;
height: 180px;
overflow: hidden;
}
@ -1885,9 +1887,11 @@ body {
.flex-block-24 {
grid-column-gap: 40px;
grid-row-gap: 40px;
border-radius: var(--_round---big-20);
background-color: var(--white);
flex-flow: column;
flex: 1;
padding-top: 30px;
padding: 30px 40px;
}
.flex-block-25 {
@ -1901,9 +1905,13 @@ body {
text-decoration: none;
}
.link-block-6:hover {
color: var(--_button---primary);
}
.flex-block-26 {
grid-column-gap: 26px;
grid-row-gap: 26px;
grid-column-gap: 22px;
grid-row-gap: 22px;
flex: 1;
min-width: 140px;
}
@ -1942,8 +1950,8 @@ body {
border-radius: var(--_round---supersmall-4);
background-color: var(--white);
color: var(--_fonts---color--grey);
height: 52px;
margin-bottom: 18px;
height: 46px;
margin-bottom: 12px;
padding: 12px 24px;
}
@ -2009,7 +2017,7 @@ body {
align-self: stretch;
align-items: center;
max-width: 100%;
padding: 30px 40px;
padding: 60px 40px;
display: flex;
}
@ -2031,7 +2039,7 @@ body {
.flex-block-30 {
grid-column-gap: 10px;
grid-row-gap: 10px;
max-width: 840px;
max-width: 540px;
}
.flex-block-31 {
@ -2046,22 +2054,23 @@ body {
flex: 1;
justify-content: flex-start;
align-items: flex-start;
min-width: 480px;
height: 260px;
min-width: 240px;
height: 140px;
padding-top: 0;
padding-left: 0;
display: flex;
overflow: hidden;
}
.div-block-12.small {
min-width: 300px;
min-width: 240px;
}
.heading-7 {
z-index: 999;
color: var(--_fonts---color--black);
font-size: var(--_fonts---font-size--heading-3);
width: 200px;
font-size: var(--_fonts---font-size--bigger);
width: 160px;
margin-top: 30px;
margin-bottom: 0;
margin-left: 30px;
@ -2088,13 +2097,15 @@ body {
}
.heading_news {
font-size: var(--_fonts---font-size--heading-3);
font-size: var(--_fonts---font-size--bigger);
margin-top: 0;
margin-bottom: 0;
line-height: 130%;
}
.text-block-20 {
color: var(--_fonts---color--grey);
font-size: var(--_fonts---font-size--small-font-size);
}
.div-block-13 {
@ -2158,12 +2169,18 @@ body {
.slide-nav {
margin-bottom: -55px;
display: none;
}
.right-arrow {
justify-content: flex-end;
align-items: center;
margin-right: -40px;
margin-right: 0;
padding-right: 40px;
display: flex;
}
.right-arrow:hover {
display: flex;
}
@ -2307,8 +2324,7 @@ body {
.text-block-21 {
color: var(--_fonts---color--light-blue-grey);
font-size: var(--_fonts---font-size--small-font-size);
align-self: stretch;
overflow: hidden;
}
.text-block-22 {
@ -2379,13 +2395,7 @@ body {
margin-right: 5px;
}
.flex-block-47 {
grid-column-gap: 15px;
grid-row-gap: 15px;
flex-flow: row;
flex: 1;
align-self: auto;
}
.image-10 {
object-fit: contain;
@ -2465,7 +2475,7 @@ body {
.delivery-time-search {
font-size: var(--_fonts---font-size--core);
width: 160px;
width: 120px;
max-height: 40px;
overflow: hidden;
}
@ -2478,7 +2488,7 @@ body {
}
.price.opencard {
width: 170px;
width: 70px;
}
.add-to-cart-block {
@ -2619,7 +2629,7 @@ body {
color: var(--_fonts---color--black);
font-size: var(--_fonts---font-size--core);
text-align: left;
width: 200px;
width: 60px;
max-height: 40px;
overflow: hidden;
}
@ -2848,7 +2858,6 @@ body {
flex-flow: column;
flex: 1;
display: flex;
}
.text-field-copy {
@ -2867,7 +2876,7 @@ body {
color: var(--_fonts---color--black);
font-size: var(--_fonts---font-size--bigger);
text-align: right;
width: 100px;
margin-top: 0;
margin-bottom: 0;
font-weight: 700;
@ -3115,7 +3124,6 @@ body {
}
.block-name {
max-width: 300px;
flex-flow: column;
flex: 1;
display: flex;
@ -3149,7 +3157,7 @@ body {
.sort-item-brand {
color: var(--_fonts---color--light-blue-grey);
font-size: var(--_fonts---font-size--small-font-size);
width: 120px;
width: 100px;
}
.sort-item-brand.first {
@ -3201,7 +3209,7 @@ body {
align-items: center;
margin-bottom: 20px;
padding-left: 10px;
padding-right: 15px;
padding-right: 10px;
}
.flex-block-62 {
@ -3609,7 +3617,7 @@ body {
}
.code-embed-5 {
width: 30px;
width: 26px;
height: 20px;
}
@ -3701,9 +3709,7 @@ body {
align-items: center;
}
.flex-block-79 {
align-self: stretch;
}
.flex-block-80 {
grid-column-gap: 20px;
@ -3914,7 +3920,6 @@ body {
font-size: var(--_fonts---font-size--small-font-size);
height: 20px;
margin-top: 0;
max-width: 100%;
margin-bottom: 0;
font-weight: 700;
}
@ -3997,7 +4002,7 @@ body {
}
.mobile-menu-buttom-section {
z-index: 1900;
z-index: 9999999;
background-color: var(--white);
display: none;
position: fixed;
@ -4031,7 +4036,6 @@ body {
position: relative;
}
.button-for-mobile-menu-block:hover {
background-color: var(--_button---hover-dark_blue);
}
@ -4108,7 +4112,7 @@ body {
margin-top: 60px;
padding-bottom: 60px;
padding-left: 60px;
position: relative;
position: absolute;
bottom: auto;
left: 0%;
right: 0%;
@ -4222,12 +4226,12 @@ body {
}
.nav-menu-3 {
z-index: 1900;
z-index: 99999999;
background-color: #0000;
width: 100%;
height: auto;
display: none;
position: relative;
position: absolute;
left: auto;
}
@ -4337,7 +4341,7 @@ body {
color: var(--_fonts---color--black);
font-size: var(--_fonts---font-size--bigger);
text-align: right;
max-width: 200px;
max-width: 100px;
margin-top: 0;
margin-bottom: 0;
font-weight: 700;
@ -4478,16 +4482,7 @@ body {
max-width: 100%;
}
.core-product-s1 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: row-reverse;
flex: 1;
justify-content: flex-end;
align-self: stretch;
align-items: center;
max-width: 100%;
}
.flex-block-48-copy {
grid-column-gap: 16px;
@ -4587,7 +4582,6 @@ body {
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
margin-bottom: 80px;
display: flex;
}
@ -4875,9 +4869,10 @@ body {
flex: 0 auto;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
align-items: center;
margin: -20px;
padding: 20px 20px 40px;
padding: 20px;
overflow: hidden;
}
.mask {
@ -4887,14 +4882,25 @@ body {
.div-block-34 {
border: 1px solid var(--light-blue);
background-color: var(--white);
opacity: 0;
color: var(--back);
border-radius: 100px;
width: 60px;
height: 60px;
position: absolute;
flex: none;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
display: flex;
}
.div-block-34:hover {
background-color: var(--_button---primary);
color: var(--white);
border-style: none;
}
.div-block-34.right {
transform: rotate(180deg);
}
.heading-17 {
@ -5015,9 +5021,10 @@ body {
flex: 1;
justify-content: flex-start;
align-items: flex-start;
min-width: 480px;
height: 260px;
min-width: 240px;
height: 140px;
display: flex;
overflow: hidden;
}
.div-block-12-copy.small {
@ -5030,8 +5037,9 @@ body {
flex: 1;
justify-content: flex-start;
align-items: flex-start;
height: 260px;
height: 140px;
display: flex;
overflow: hidden;
}
.div-block-123.small {
@ -5041,7 +5049,7 @@ body {
.heading-7-white {
z-index: 999;
color: var(--_fonts---color--white);
font-size: var(--_fonts---font-size--heading-3);
font-size: var(--_fonts---font-size--bigger);
width: 200px;
margin-top: 30px;
margin-bottom: 0;
@ -5070,18 +5078,28 @@ body {
background-image: none;
justify-content: flex-start;
align-items: flex-start;
min-width: 340px;
min-width: 240px;
height: 140px;
padding-top: 0;
padding-left: 0;
display: flex;
overflow: hidden;
}
.image-22 {
box-sizing: border-box;
object-fit: contain;
object-position: 100% 100%;
max-width: 80%;
height: 100%;
margin-left: auto;
margin-right: 0;
display: block;
position: relative;
top: auto;
bottom: 0;
right: 0;
overflow: clip;
}
.section-5 {
@ -5206,11 +5224,12 @@ body {
}
.news-index-block {
grid-column-gap: 40px;
grid-row-gap: 40px;
grid-column-gap: 20px;
grid-row-gap: 20px;
flex: 1;
max-width: 100%;
margin-bottom: 100px;
margin-top: 20px;
margin-bottom: 60px;
}
.section-6 {
@ -5286,6 +5305,7 @@ body {
}
.div-block-127 {
border: 1px solid var(--_icon---light-blue-grey);
color: var(--_icon---light-blue-grey);
border-radius: 50px;
flex: none;
@ -5299,7 +5319,7 @@ body {
}
.div-block-127:hover {
border-color: var(--_button---primary);
color: var(--_button---primary);
}
@ -5364,8 +5384,9 @@ body {
}
.favcardcat {
border: 1px none var(--_icon---light-blue-grey);
background-color: var(--white);
color: var(--_icon---light-blue-grey);
color: var(--_fonts---color--white);
border-radius: 50px;
flex: none;
justify-content: center;
@ -5380,6 +5401,7 @@ body {
}
.favcardcat:hover {
border-color: var(--_button---primary);
color: var(--_button---primary);
}
@ -5387,4048 +5409,300 @@ body {
flex: none;
}
@media screen and (min-width: 1440px) {
.body {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 16px;
--_fonts---font-size--small-font-size: 14px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 18px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 36px;
--_fonts---black-weight: 800;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 24px;
--_fonts---font-size--heading-2: 30px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 12px;
}
.nav-link {
font-size: var(--_fonts---font-size--small-font-size);
}
.nav-menu {
grid-column-gap: 20px;
grid-row-gap: 20px;
padding-left: 20px;
padding-right: 20px;
display: flex;
}
.flex-block {
grid-column-gap: 10px;
grid-row-gap: 10px;
display: flex;
}
.flex-block-2 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.top_head {
margin-left: 0;
margin-right: 0;
}
/*
.bottom_head {
margin-top: 0;
} */
.flex-block-4 {
grid-column-gap: 40px;
grid-row-gap: 40px;
}
.form-block {
align-self: stretch;
}
.text-field {
padding-top: 14px;
padding-bottom: 14px;
}
.flex-block-12 {
width: 320px;
}
.flex-block-15.end {
display: flex;
}
.flex-block-18 {
grid-column-gap: 60px;
grid-row-gap: 60px;
}
.div-block-9 {
grid-column-gap: 40px;
grid-row-gap: 40px;
justify-content: space-between;
align-items: center;
}
.text-block-14 {
display: block;
}
.flex-block-22 {
grid-column-gap: 40px;
grid-row-gap: 40px;
}
.flex-block-23 {
width: 180px;
}
.flex-block-30 {
max-width: 740px;
}
.news {
min-width: 310px;
}
.core-product-search {
flex-flow: row;
}
.flex-block-47 {
flex: 0 auto;
}
.image-10 {
width: 280px;
height: 180px;
}
.core-product {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
min-width: 270px;
max-width: 320px;
}
.info-block-search {
flex: 0 auto;
}
.flex-block-14-copy {
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-39-copy {
width: 150px;
}
.cart-ditail {
width: 340px;
}
.flex-block-69 {
flex-flow: row;
}
.menu-button-2 {
display: none;
}
.flex-block-18-copy {
grid-column-gap: 60px;
grid-row-gap: 60px;
grid-column-gap: 60px;
grid-row-gap: 60px;
}
.flex-block-78 {
flex: 1;
}
.flex-block-81 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: space-between;
align-items: center;
}
.core-product-copy {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
min-width: 270px;
max-width: 320px;
}
.flex-block-18-copy-copy {
grid-column-gap: 60px;
grid-row-gap: 60px;
}
.section {
background-color: var(--white);
}
.info-block-search-copy {
flex: 0 auto;
}
.mobile-menu-buttom-section {
background-color: var(--white);
}
.button-for-mobile-menu-block {
padding-left: 20px;
padding-right: 20px;
}
.flex-block-89 {
padding-top: 60px;
padding-left: 60px;
}
.image-17 {
width: 420px;
height: 210px;
}
.section-4 {
margin-left: 0;
margin-right: 0;
}
.core-product-search-copy, .core-product-search-s1 {
flex-flow: row;
}
.info-block-search-s1 {
flex: 0 auto;
}
.core-product-s1 {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: stretch;
align-items: flex-start;
min-width: 270px;
}
.core-product-search-s2 {
flex-flow: row;
}
.text-block-25-s1 {
display: none;
}
.flex-block-15-copy.end {
display: flex;
}
.flex-block-14-copy-copy {
justify-content: flex-start;
align-items: flex-start;
}
.core-product-copy-copy {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
min-width: 270px;
max-width: 320px;
}
.flex-block-94 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: flex-start;
align-items: center;
}
.desc-wholesale, .contentnews {
max-width: none;
}
.flex-block-99 {
max-width: 50%;
}
.image-21 {
margin-left: 170px;
}
.div-block-35 {
width: 900px;
margin-top: 40px;
}
.section-5 {
margin-left: 0;
margin-right: 0;
}
.text-block-14-copy {
display: block;
}
.rightbi {
margin-top: 12px;
}
/* .bottom_head {
margin-top: 0;
} */
.vinleftbar {
width: 320px;
}
.knotinfo {
max-width: 320px;
}
.image-26 {
align-self: stretch;
}
.knotin {
flex: 1;
max-width: none;
}
.heading-20 {
font-size: 48px;
}
.flex-block-119 {
width: 520px;
}
.bestpriceitem.end {
display: flex;
}
.flex-block-124 {
width: 540px;
}
.image-27 {
margin-bottom: -218px;
margin-left: 800px;
}
.tabson {
grid-column-gap: 20px;
grid-row-gap: 20px;
display: none;
}
@media screen and (min-width: 1920px) {
.nav-link {
font-size: var(--_fonts---font-size--core);
}
.flex-block {
display: none;
}
.left-arrow {
margin-left: -25px;
}
.icon-2 {
width: 26px;
height: 26px;
margin: auto;
}
.slider {
width: 46px;
}
.flex-block-5 {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-top: 20px;
}
.flex-block-6 {
grid-column-gap: 20px;
grid-row-gap: 20px;
grid-template: "."
"."
/ 1fr 1fr 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
place-items: start stretch;
display: grid;
}
.main {
width: 100%;
}
.flex-block-15.end {
display: flex;
}
.heading-3.sub {
flex: 0 auto;
}
.text-block-14 {
flex: 1;
}
.flex-block-24 {
border-radius: var(--_round---big-20);
background-color: var(--white);
padding: 40px 30px 40px 40px;
}
.select {
height: 48px;
margin-bottom: 14px;
}
.div-block-10 {
justify-content: space-between;
align-items: center;
}
.div-block-12 {
min-width: 240px;
height: 190px;
}
.div-block-12.small {
justify-content: flex-start;
align-items: flex-start;
height: 190px;
}
.right-arrow {
justify-content: flex-end;
align-items: center;
margin-right: -25px;
}
.core-product {
max-width: 320px;
}
.info-block-search {
flex: 1;
}
.flex-block-70 {
max-width: 500px;
}
.flex-block-73 {
max-width: 600px;
}
.flex-block-81 {
justify-content: space-between;
align-items: center;
}
.core-product-copy {
max-width: 320px;
}
.core-product-search-copy {
justify-content: space-between;
align-items: flex-start;
}
.heading-15 {
width: 100%;
}
.info-block-search-copy {
flex: 1;
}
.core-product-search-s1 {
flex-flow: row;
}
.info-block-search-s1 {
flex: 1;
}
.core-product-s1 {
flex-flow: column;
max-width: 320px;
}
.core-product-search-s2 {
flex-flow: row;
}
.flex-block-15-copy.end {
display: flex;
}
.flex-block-14-copy-copy {
flex: 1;
}
.core-product-copy-copy {
max-width: 320px;
}
.flex-block-94 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: flex-start;
align-items: center;
}
.div-block-34 {
justify-content: center;
align-items: center;
width: 48px;
height: 48px;
margin: auto;
}
.heading-17 {
font-size: 64px;
}
.flex-block-99 {
max-width: 45%;
}
.image-21 {
margin-left: 0;
}
.div-block-35 {
width: 1000px;
margin-top: 30px;
}
.flex-block-6-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
grid-template: ". . ."
". Area Area"
/ 1fr 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
place-items: start stretch;
display: flex;
}
.div-block-12-copy {
min-width: 240px;
height: 190px;
}
.div-block-123 {
height: 190px;
}
.div-block-red.small {
min-width: 240px;
height: 190px;
}
.image-22 {
object-fit: contain;
object-position: 100% 100%;
}
.section-info {
background-color: var(--white);
}
.text-block-14-copy {
flex: 1;
}
.thxcontent {
align-self: stretch;
}
.div-block-127 {
border: 1px solid var(--_icon---light-blue-grey);
color: var(--_icon---light-blue-grey);
border-radius: 50px;
justify-content: center;
align-items: center;
width: 48px;
height: 48px;
padding-top: 5px;
display: flex;
}
.headingbi {
justify-content: flex-start;
align-items: center;
}
.rightbi {
margin-top: 12px;
}
.fsfav {
border: 1px solid var(--_icon---light-blue-grey);
color: var(--_icon---light-blue-grey);
border-radius: 50px;
justify-content: center;
align-items: center;
width: 48px;
height: 48px;
padding-top: 5px;
display: flex;
}
.favcardcat {
justify-content: center;
align-items: center;
display: flex;
}
.tabson {
display: none;
}
.indexbrandblock:hover {
color: var(--_button---primary);
}
.arbd {
justify-content: center;
align-items: center;
width: 48px;
height: 48px;
margin: auto;
}
.catnav {
width: 100%;
}
.container-vin {
padding-bottom: 60px;
}
.knotinfo {
max-width: 480px;
}
.knotin {
max-width: none;
}
.brandsortb {
border-radius: var(--_round---big-20);
background-color: var(--white);
padding: 40px 30px 40px 40px;
}
.flex-block-119 {
width: 420px;
}
.bestpriceitem.end {
display: flex;
}
.flex-block-122 {
justify-content: flex-start;
align-items: flex-end;
}
.inbt {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-top: 20px;
}
.flex-block-124 {
background-position: 180px, 0 0;
width: 620px;
}
.select-copy {
height: 48px;
margin-bottom: 14px;
}
.div-block-10-copy {
justify-content: space-between;
align-items: center;
}
.image-27 {
margin-left: 1000px;
}
.code-embed-14 {
width: 28px;
height: 28px;
margin: auto;
}
@media screen and (max-width: 991px) {
.container, .container.nav, .container.info {
padding-left: 0;
padding-right: 0;
}
.container.subscribe, .container.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.top_head, .bottom_head {
padding-left: 30px;
padding-right: 30px;
}
.navbar-2 {
margin-left: 0;
}
.text-block-2 {
display: none;
overflow: visible;
}
.slider {
height: auto;
display: flex;
}
.flex-block-7 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: wrap;
}
.flex-block-10 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
}
.main {
padding-left: 30px;
padding-right: 30px;
}
.flex-block-12 {
display: none;
}
.flex-block-13 {
flex-flow: column;
}
.flex-block-15 {
min-width: 220px;
max-width: 320px;
}
.image-5 {
object-fit: contain;
}
.tabs_block {
justify-content: center;
align-items: flex-start;
display: none;
}
.flex-block-18 {
flex-flow: column;
justify-content: space-between;
align-items: center;
}
.div-block-9 {
flex: 0 auto;
justify-content: flex-start;
align-self: stretch;
align-items: flex-start;
}
.form-block-3 {
align-self: stretch;
}
.flex-block-19 {
width: 460px;
}
.flex-block-22 {
grid-column-gap: 30px;
grid-row-gap: 30px;
flex-flow: wrap;
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
}
.link {
display: none;
}
.text-block-17 {
margin-bottom: 0;
}
.flex-block-23 {
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-28 {
flex: 1;
}
.div-block-11 {
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.div-block-12, .div-block-12.small {
min-width: 300px;
}
.news {
grid-column-gap: 10px;
grid-row-gap: 10px;
min-width: 223px;
padding: 20px;
}
.heading_news {
font-size: var(--_fonts---font-size--core);
}
.div-block-13 {
flex: none;
}
.flex-block-32 {
display: none;
}
.flex-block-33 {
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 10px;
}
.flex-block-35 {
flex-flow: wrap;
}
.heading-8 {
margin-left: 0;
}
.raiting {
display: none;
}
.flex-block-40 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-43 {
flex-flow: column;
}
.flex-block-44 {
align-self: stretch;
}
.flex-block-45 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
}
.image-10 {
object-fit: contain;
}
.core-product {
flex-flow: row-reverse;
}
.sort-list {
padding-right: 210px;
}
.sort-item.price {
text-align: right;
}
.flex-block-14-copy {
justify-content: center;
align-items: flex-start;
}
.image-10-copy {
object-fit: contain;
width: 400px;
height: 280px;
}
.flex-block-56 {
justify-content: center;
align-items: flex-start;
}
.small-img {
object-fit: contain;
}
.core-product-card {
flex-flow: column;
margin-bottom: 20px;
}
.cart-list {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-bottom: 40px;
}
.cart-ditail {
align-self: stretch;
width: auto;
}
.product-list-cart {
padding-bottom: 10px;
}
.brandname {
width: 100px;
}
.sort-item-name {
width: 140px;
}
.sort-item-comments {
width: 120px;
}
.flex-block-67 {
grid-column-gap: 40px;
grid-row-gap: 40px;
flex: 1;
}
.image-14 {
max-width: 700px;
}
.flex-block-68 {
grid-column-gap: 30px;
grid-row-gap: 30px;
align-self: stretch;
}
.flex-block-69 {
grid-column-gap: 40px;
grid-row-gap: 40px;
flex-flow: column-reverse;
}
.flex-block-69-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.flex-block-68-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: wrap;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-70 {
align-self: stretch;
max-width: 340px;
}
.flex-block-71 {
grid-column-gap: 15px;
grid-row-gap: 15px;
flex-flow: column;
}
.flex-block-72 {
flex-flow: column;
}
.flex-block-73 {
flex-flow: row;
align-self: stretch;
max-width: none;
padding: 30px;
}
.flex-block-73-copy-copy {
min-width: 340px;
}
.flex-block-73-copy-copy-red {
min-width: 340px;
max-width: none;
}
.menu-button-2 {
display: none;
position: static;
}
.flex-block-77 {
display: flex;
}
.flex-block-18-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
flex-flow: row;
flex: 1;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
justify-content: space-between;
align-items: center;
display: flex;
}
.flex-block-78 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-80 {
grid-column-gap: 20px;
grid-row-gap: 20px;
align-self: stretch;
}
.flex-block-81 {
justify-content: space-between;
align-items: center;
}
.flex-block-82 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.core-product-copy {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: stretch;
align-items: center;
max-width: 100%;
}
.core-product-search-copy {
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-84 {
grid-column-gap: 30px;
grid-row-gap: 30px;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
display: flex;
}
.flex-block-85 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---normal);
background-color: var(--white);
justify-content: flex-start;
align-items: center;
padding: 10px 20px;
display: flex;
}
.code-embed-9 {
color: var(--_button---primary);
width: 20px;
height: 24px;
}
.code-embed-10 {
color: var(--white);
width: 18px;
height: 18px;
display: block;
}
.flex-block-86 {
grid-column-gap: 10px;
grid-row-gap: 10px;
border-radius: var(--_round---normal);
background-color: var(--_button---hover-dark_blue);
justify-content: flex-start;
align-items: center;
padding: 10px 15px;
display: none;
}
.flex-block-77-copy {
display: flex;
}
.flex-block-18-copy-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
flex-flow: row;
flex: 1;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
justify-content: space-between;
align-items: flex-start;
display: flex;
}
.heading-8-copy {
margin-left: 0;
}
.dropdown-2 {
margin-left: 0;
margin-right: 0;
}
.dropdown-list-2.w--open {
border-radius: var(--_round---small-8);
background-color: var(--white);
box-shadow: 0 2px 5px #0003;
}
.dropdown-toggle-2 {
z-index: 999;
display: flex;
}
.dropdown-toggle-2.w--open {
z-index: 999998;
}
.dropdown-3 {
display: block;
}
.dropdown-list-3 {
z-index: 999999;
}
.section-2, .mobile-menu-bottom, .mobile-menu-bottom.nav, .mobile-menu-bottom.info {
padding-left: 30px;
padding-right: 30px;
}
.mobile-menu-bottom.subscribe, .mobile-menu-bottom.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.n {
display: none;
overflow: visible;
}
.div-block-25 {
width: 20px;
height: 20px;
}
.section-3 {
padding-left: 30px;
padding-right: 30px;
}
.core-product-card-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
flex-flow: column;
margin-bottom: 20px;
}
.nav-menu-2 {
float: none;
flex-flow: row;
justify-content: flex-start;
align-items: center;
width: 100%;
display: block;
position: relative;
top: 20px;
left: -30px;
right: 0;
}
.div-block-28 {
padding-left: 30px;
}
.link-block-7 {
grid-column-gap: 10px;
grid-row-gap: 10px;
border-top-left-radius: var(--_round---small-8);
border-bottom-left-radius: var(--_round---small-8);
color: var(--black);
justify-content: flex-start;
align-items: center;
padding: 15px 18px;
text-decoration: none;
display: flex;
}
.link-block-7:hover {
border-right: 2px solid var(--_button---primary);
background-color: var(--white);
padding-right: 18px;
}
.div-block-29 {
color: var(--_fonts---color--light-blue-grey);
width: 20px;
height: 20px;
}
.text-block-47 {
font-size: var(--_fonts---font-size--bigger);
text-align: left;
flex: 1;
font-weight: 600;
}
.flex-block-88 {
flex: 1;
align-self: auto;
margin-top: 20px;
}
.flex-block-89 {
background-color: var(--white);
flex: 1;
justify-content: flex-start;
align-self: stretch;
align-items: flex-start;
padding-left: 40px;
padding-right: 30px;
}
.heading-16 {
font-size: var(--_fonts---font-size--heading-3);
text-align: left;
}
.link-2 {
font-size: var(--_fonts---font-size--core);
}
.flex-block-91 {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.flex-block-92 {
flex-flow: column;
}
.nav-menu-3 {
left: 30px;
}
.tabs-menu {
max-width: 280px;
}
.text-block-48, .text-block-49 {
font-size: var(--_fonts---font-size--core);
}
.sort-list-card {
padding-right: 80px;
}
.pcs-cart-s1 {
display: none;
}
.flex-block-13-copy {
flex-flow: column;
}
.core-product-s1 {
flex-flow: row-reverse;
}
.sort-list-s1 {
padding-right: 210px;
}
.pcs-search-s1 {
width: 60px;
}
.text-block-25-s1 {
display: none;
}
.flex-block-15-copy {
min-width: 220px;
max-width: 320px;
}
.flex-block-14-copy-copy {
justify-content: center;
align-items: flex-start;
}
.core-product-copy-copy {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: stretch;
align-items: center;
max-width: 100%;
}
.flex-block-94 {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: wrap;
justify-content: flex-start;
align-items: flex-start;
}
.image-18 {
height: 22px;
}
.desc-wholesale {
min-width: 340px;
}
.image-wholesale {
min-width: 340px;
max-width: none;
min-height: 300px;
}
.why-wholesale {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: wrap;
grid-template-rows: auto auto auto auto;
grid-template-columns: 1fr 1fr;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-96 {
grid-column-gap: 20px;
grid-row-gap: 20px;
grid-template: "Area Area Area"
". . ."
". . ."
". . ."
/ 1fr 1fr 1fr;
grid-auto-columns: 1fr;
place-items: center;
display: grid;
}
.service-wholesale-block {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: wrap;
}
.flex-block-71-copy {
grid-column-gap: 15px;
grid-row-gap: 15px;
flex-flow: row;
justify-content: flex-start;
align-items: center;
}
.flex-block-97 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.map-contacts {
min-width: 340px;
max-width: none;
min-height: 300px;
}
.lastnews {
flex-flow: row;
min-width: 320px;
max-width: none;
min-height: 300px;
}
.contentnews {
min-width: 340px;
}
.flex-block-6-copy-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.mask {
height: auto;
}
.heading-17 {
font-size: 44px;
}
.flex-block-99 {
grid-column-gap: 20px;
grid-row-gap: 20px;
max-width: 100%;
margin-top: 20px;
margin-bottom: 20px;
}
.flex-block-101 {
flex-flow: wrap;
display: none;
}
.image-21 {
margin-left: 0;
display: block;
position: static;
}
.div-block-35 {
width: 100%;
margin-top: 0;
position: static;
overflow: visible;
}
.div-block-12-copy, .div-block-12-copy.small, .div-block-123, .div-block-123.small, .div-block-red, .div-block-red.small {
min-width: 300px;
}
.section-info {
padding-left: 30px;
padding-right: 30px;
}
.image-thx {
min-width: 340px;
max-width: none;
min-height: 600px;
}
.div-block-127 {
width: 40px;
height: 40px;
margin-top: 0;
}
.headingbi {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: row;
justify-content: flex-start;
align-items: flex-start;
}
.fsfav, .favcardcat {
width: 40px;
height: 40px;
margin-top: 0;
}
.topnav {
margin-left: 0;
}
.bottom_head {
padding-left: 30px;
padding-right: 30px;
}
.tb, .tb.nav, .tb.info {
padding-left: 0;
padding-right: 0;
}
.tb.subscribe, .tb.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.batd, .batd.nav, .batd.info {
padding-left: 0;
padding-right: 0;
}
.batd.subscribe, .batd.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.catnav {
padding-left: 30px;
padding-right: 30px;
}
.vinleftbar {
display: none;
}
.container-vin, .container-vin.nav, .container-vin.info {
padding-left: 0;
padding-right: 0;
}
.container-vin.subscribe, .container-vin.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.bestpriceitem {
min-width: 220px;
max-width: 320px;
}
.flex-block-123 {
flex-flow: row;
}
.image-5-copy {
object-fit: contain;
}
.container-copy, .container-copy.nav, .container-copy.info {
padding-left: 0;
padding-right: 0;
}
.container-copy.subscribe, .container-copy.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.code-embed-15 {
margin-top: 20px;
position: absolute;
}
@media screen and (max-width: 767px) {
.body {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.container {
padding-top: 20px;
}
.container.info {
padding-top: 15px;
padding-bottom: 15px;
}
.container.footer {
padding-bottom: 90px;
}
.brand {
min-width: 140px;
}
.phone {
display: block;
}
.flex-block {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.flex-block-2 {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-top: 3px;
}
.top_head {
margin-top: -15px;
}
.menu-button {
padding: 12px;
}
.navbar-2 {
height: auto;
top: 72px;
}
.left-arrow {
display: none;
}
.flex-block-5 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.flex-block-6 {
grid-template: "."
"."
"Area"
"."
"."
/ 1fr;
grid-auto-flow: column;
}
.flex-block-7 {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.flex-block-8 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-9, .flex-block-10 {
grid-column-gap: 5px;
grid-row-gap: 5px;
}
.heading {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.link-block-3 {
width: 30px;
height: 30px;
}
.form-block {
align-self: stretch;
height: auto;
min-height: auto;
}
.text-field {
min-height: auto;
margin-right: 10px;
padding-left: 10px;
padding-right: 50px;
}
.flex-block-13 {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
}
.flex-block-14 {
grid-column-gap: 40px;
grid-row-gap: 40px;
align-self: stretch;
}
.text-block-6, .tabs_block {
display: none;
}
.tab_c {
flex: none;
}
.flex-block-18 {
flex-flow: column;
justify-content: space-between;
align-items: center;
}
.heading-3.sub {
align-self: stretch;
}
.div-block-9 {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
align-self: stretch;
}
.text-block-14 {
align-self: stretch;
display: block;
}
.form-block-3 {
align-self: stretch;
}
.flex-block-19 {
flex-flow: wrap;
flex: 1;
justify-content: center;
align-self: stretch;
align-items: center;
width: 100%;
}
.link-block-5 {
padding: 14px 20px;
}
.flex-block-22 {
flex-flow: wrap;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-23 {
justify-content: flex-start;
align-items: center;
}
.container2 {
padding: 20px 30px;
}
.flex-block-31 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.news {
min-width: 244px;
}
.right-arrow {
display: none;
}
.flex-block-36 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: row;
}
.flex-block-37 {
padding: 0;
}
.raiting {
display: none;
}
.flex-block-40 {
flex-flow: column;
align-self: auto;
padding-left: 0;
padding-right: 0;
}
.div-block-16 {
display: none;
}
.flex-block-43 {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: row;
justify-content: space-between;
align-self: auto;
align-items: center;
}
.flex-block-44 {
border-radius: var(--_round---normal);
background-color: var(--white);
flex: 1;
align-self: stretch;
padding: 20px 15px;
}
.flex-block-45 {
flex-flow: row;
}
.core-product-search {
align-self: stretch;
}
.image-10 {
object-fit: contain;
}
.product-item-search {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: row;
justify-content: space-between;
align-items: flex-start;
padding-top: 10px;
padding-bottom: 10px;
}
.info-block-search {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
}
.price {
text-align: right;
width: 110px;
}
.add-to-cart-block {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.text-block-25 {
display: none;
}
.sort-list {
padding-right: 30px;
}
.sort-item {
width: 90px;
}
.sort-item.first {
width: 55px;
}
.flex-block-49 {
grid-column-gap: 30px;
grid-row-gap: 30px;
}
.show-more-search {
align-self: auto;
}
.heading-11 {
align-self: stretch;
max-width: 100%;
}
.product-item-card {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-52 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
}
.flex-block-53 {
align-self: stretch;
}
.cart-item {
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-39-copy {
width: 150px;
}
.heading-9-copy-copy {
text-align: left;
flex: 1;
width: auto;
}
.form-block-copy {
align-self: stretch;
max-width: none;
}
.block-detail {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: column;
flex: 1;
}
.favorite-item {
flex-flow: column;
}
.brandname {
flex: 0 auto;
align-self: stretch;
width: auto;
}
.block-name {
align-self: stretch;
}
.productname_f {
align-self: stretch;
width: auto;
}
.sort-item-brand {
width: 100px;
}
.flex-block-61 {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.sort-item-name {
width: 90px;
}
.sort-item-comments {
width: 80px;
}
.heading-list {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.cart-item-check {
flex-flow: column;
}
.text-block-35 {
width: auto;
height: auto;
}
.product-list-cart-check, .menu-category {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.tab-menu-category, .tab-menu-category-activ {
padding-left: 10px;
padding-right: 10px;
}
.image-14 {
max-width: 480px;
}
.flex-block-69-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.flex-block-70 {
max-width: 240px;
padding-left: 22px;
padding-right: 22px;
}
.flex-block-73 {
padding-left: 30px;
padding-right: 30px;
}
.code-embed-4 {
width: 30px;
height: 30px;
}
.pcs-info {
color: var(--_fonts---color--white);
font-size: 10px;
font-weight: 400;
}
.flex-block-76 {
display: none;
}
.flex-block-18-copy {
grid-column-gap: 40px;
grid-row-gap: 40px;
flex-flow: column;
justify-content: space-between;
align-items: center;
}
.phone-copy {
display: block;
}
.flex-block-78 {
justify-content: space-between;
align-items: center;
}
.flex-block-81 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: row;
justify-content: space-between;
align-items: center;
}
.core-product-copy {
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
}
.core-product-search-copy {
flex-flow: row;
align-self: stretch;
max-width: 100%;
height: 340px;
display: flex;
overflow: scroll;
}
.raiting-copy, .pcs-copy {
display: none;
}
.item-recommend-copy {
display: block;
}
.flex-block-83 {
flex-flow: column;
}
.flex-block-84 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: space-between;
align-items: flex-start;
width: 100%;
}
.flex-block-85 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---small-8);
background-color: var(--white);
justify-content: flex-start;
align-items: center;
padding: 8px 12px;
display: flex;
}
.code-embed-9 {
color: var(--_button---primary);
width: 16px;
height: 16px;
}
.image-15 {
max-width: 200px;
}
.code-embed-10 {
color: var(--white);
width: 12px;
height: 16px;
}
.flex-block-86 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---small-8);
background-color: var(--_button---hover-dark_blue);
justify-content: flex-start;
align-items: center;
padding: 10px 15px;
}
.flex-block-18-copy-copy {
grid-column-gap: 40px;
grid-row-gap: 40px;
flex-flow: column;
}
.link-block-4-copy {
flex: 0 auto;
}
.heading-8-copy {
color: var(--_fonts---color--light-blue-grey);
font-size: var(--_fonts---font-size--small-font-size);
margin-left: 0;
margin-right: 0;
font-weight: 400;
}
.dropdown-2 {
margin-left: 0;
margin-right: 0;
}
.dropdown-list-2 {
background-color: var(--white);
box-shadow: 0 2px 5px #0003;
}
.dropdown-list-2.w--open {
border-radius: var(--_round---small-8);
}
.heading-9-copy {
margin-top: 0;
}
.info-block-search-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
}
.heading-9-copy-copy {
font-size: var(--_fonts---font-size--small-font-size);
line-height: 18px;
}
.section-2 {
padding-left: 15px;
padding-right: 15px;
}
.mobile-block {
flex: 0 auto;
width: auto;
}
.flex-block-87 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex: 1;
}
.mobile-menu-bottom {
margin-left: 0;
margin-right: 0;
padding-left: 15px;
padding-right: 15px;
}
.mobile-menu-bottom.nav, .mobile-menu-bottom.info {
padding-left: 15px;
padding-right: 15px;
}
.mobile-menu-bottom.subscribe, .mobile-menu-bottom.footer {
padding: 40px 15px;
}
.name-mobile-menu-item {
display: block;
}
.button-for-mobile-menu-block {
grid-column-gap: 2px;
grid-row-gap: 2px;
background-color: var(--_fonts---color--white);
color: var(--_button---light-blue-grey);
flex-flow: column;
width: 70px;
}
.button-for-mobile-menu-block:hover {
background-color: var(--_button---light-blue);
}
.section-3 {
padding-left: 15px;
padding-right: 15px;
}
.nav-menu-3 {
display: none;
}
.flex-block-93 {
align-self: auto;
min-height: 48px;
}
.sort-list-card {
grid-column-gap: 0px;
grid-row-gap: 0px;
padding-left: 18px;
padding-right: 18px;
}
.flex-block-49-copy {
grid-column-gap: 28px;
grid-row-gap: 28px;
}
.price-in-cart-s1 {
text-align: left;
flex: 1;
width: auto;
}
.price-1-pcs-cart-s1 {
text-align: left;
}
.add-to-cart-block-s2 {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.core-product-search-copy, .core-product-search-s1 {
align-self: stretch;
}
.product-item-search-s1 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: row;
justify-content: space-between;
align-items: flex-start;
padding-top: 10px;
padding-bottom: 10px;
}
.info-block-search-s1 {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.price-s1 {
text-align: right;
width: 110px;
}
.flex-block-14-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
align-self: stretch;
}
.flex-block-13-copy {
flex-flow: column;
}
.sort-list-s1 {
padding-right: 30px;
}
.core-product-search-s2 {
align-self: stretch;
}
.text-block-25-s1, .pcs-re-s1 {
display: none;
}
.add-to-cart-block-s1 {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.flex-block-14-copy-copy {
margin-bottom: 0;
}
.add-to-cart-block-copy {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.service-wholesale-block {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.lastnews {
flex-flow: column;
}
.heading-17 {
font-size: 36px;
}
.container2-copy {
padding: 20px 30px 75px;
}
.image-24 {
max-width: 140px;
}
.text-block-14-copy {
align-self: stretch;
display: block;
}
.news-index-block {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.section-6 {
padding-left: 30px;
padding-right: 30px;
}
.image-thx {
min-height: 500px;
}
.div-block-127 {
justify-content: center;
align-items: center;
margin-top: 4px;
}
.flex-block-bi {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.heading-bi {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.fsfav, .favcardcat {
justify-content: center;
align-items: center;
margin-top: 4px;
}
.topnav {
height: auto;
top: 72px;
}
.searcj {
align-self: stretch;
height: auto;
min-height: auto;
}
.tb {
padding-top: 20px;
}
.tb.info {
padding-top: 15px;
padding-bottom: 15px;
}
.tb.footer {
padding-bottom: 90px;
}
.batd {
padding-top: 20px;
}
.batd.info {
padding-top: 15px;
padding-bottom: 15px;
}
.batd.footer {
padding-bottom: 90px;
}
.container-vin {
padding-top: 20px;
}
.container-vin.info {
padding-top: 15px;
padding-bottom: 15px;
}
.container-vin.footer {
padding-bottom: 90px;
}
.inbt, .news-index-block-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.container-copy {
padding-top: 20px;
}
.container-copy.info {
padding-top: 15px;
padding-bottom: 15px;
}
.container-copy.footer {
padding-bottom: 90px;
}
.mobile-menu-buttom-section {
display: block;
}
.topnav {
background-color: #0000;
flex-flow: column;
margin-left: 220px;
margin-right: 0;
display: block;
position: static;
}
@media screen and (max-width: 479px) {
body {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.container {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.container.nav {
padding-left: 0;
padding-right: 0;
}
.container.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.container.subscribe {
padding: 40px 0;
}
.container.footer {
padding: 40px 0 90px;
}
.brand {
width: 140px;
padding-left: 0;
}
.phone {
font-size: var(--_fonts---font-size--core);
display: block;
}
.icon_messenger {
max-width: 30px;
}
.flex-block, .flex-block-2 {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.top_head, .bottom_head {
padding-left: 15px;
padding-right: 15px;
}
.flex-block-4 {
padding-top: 15px;
padding-bottom: 15px;
}
.menu-button {
height: auto;
padding: 10px 12px 12px;
}
.menu-button.w--open {
justify-content: center;
align-items: center;
width: 54px;
height: 48px;
padding-left: 15px;
}
.navbar-2 {
margin-left: 0;
top: 58px;
}
.flex-block-7 {
grid-column-gap: 2px;
grid-row-gap: 2px;
display: flex;
}
.flex-block-8 {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.flex-block-9, .flex-block-10 {
grid-column-gap: 5px;
grid-row-gap: 5px;
}
.heading {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
line-height: 36px;
}
.text-block-4 {
line-height: 140%;
}
.flex-block-11 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: wrap;
justify-content: flex-start;
align-items: flex-start;
}
.text-block-5 {
margin-top: 3px;
margin-bottom: 0;
}
.main {
padding-left: 15px;
padding-right: 15px;
}
.flex-block-13 {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
max-width: 100%;
}
.flex-block-14 {
grid-column-gap: 20px;
grid-row-gap: 20px;
max-width: 100%;
margin-bottom: 40px;
}
.text-block-6 {
display: none;
}
.flex-block-15 {
grid-column-gap: 5px;
grid-row-gap: 5px;
min-width: 140px;
max-width: 190px;
padding: 15px;
}
.text-block-7 {
padding-top: 3px;
padding-bottom: 3px;
font-size: 12px;
top: -15px;
}
.flex-block-16 {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: column;
}
.div-block-3 {
margin-top: -20px;
}
.text-block-10 {
max-height: 36px;
font-size: 12px;
line-height: 18px;
overflow: hidden;
}
.image-5 {
width: 100%;
height: 100%;
min-height: auto;
}
.div-block-4 {
padding-left: 0;
padding-right: 0;
}
.button_strock {
padding: 12px 25px;
}
.div-block-7 {
width: 20px;
height: 20px;
}
.flex-block-18 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.div-block-9 {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.form-3 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
}
.text-field-3 {
padding-top: 20px;
padding-bottom: 20px;
}
.submit-button {
align-self: stretch;
padding: 15px 30px;
}
.text-block-15 {
text-align: left;
}
.flex-block-19 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: flex-start;
align-items: center;
}
.link-block-5 {
padding-top: 10px;
padding-bottom: 10px;
}
.flex-block-20 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.text-block-16 {
font-size: 12px;
}
.flex-block-22 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: wrap;
justify-content: flex-start;
align-self: stretch;
align-items: flex-start;
margin-top: 20px;
margin-bottom: 20px;
}
.text-block-17 {
font-size: var(--_fonts---font-size--core);
}
.flex-block-24 {
grid-column-gap: 30px;
grid-row-gap: 30px;
padding-top: 0;
overflow: hidden;
}
.flex-block-26 {
min-width: 90px;
}
.flex-block-28 {
min-width: 290px;
padding: 20px;
}
.select {
margin-bottom: 10px;
}
.container2 {
padding-left: 15px;
padding-right: 15px;
}
.div-block-11 {
padding: 40px 30px;
}
.heading-6 {
font-size: var(--_fonts---font-size--heading-2);
}
.div-block-12 {
justify-content: flex-start;
align-items: flex-start;
min-width: 280px;
height: auto;
overflow: hidden;
}
.div-block-12.small {
min-width: 280px;
}
.news {
grid-column-gap: 10px;
grid-row-gap: 10px;
min-width: 250px;
padding: 20px;
}
.heading_news {
line-height: 28px;
}
.text-block-20 {
line-height: 18px;
}
.div-block-13 {
flex: none;
}
.flex-block-33 {
margin-top: 10px;
}
.flex-block-36 {
grid-column-gap: 10px;
grid-row-gap: 10px;
scrollbar-width: none;
flex-flow: row;
overflow: scroll;
}
.heading-8 {
margin-left: 20px;
margin-right: 20px;
}
.flex-block-37 {
grid-column-gap: 10px;
grid-row-gap: 10px;
padding-top: 10px;
}
.image-8 {
width: 16px;
height: 16px;
}
.raiting {
flex: 0 auto;
width: auto;
}
.flex-block-40 {
grid-column-gap: 10px;
grid-row-gap: 10px;
font-size: var(--_fonts---font-size--bigger);
}
.div-block-16 {
display: none;
}
.flex-block-43 {
flex-flow: row;
}
.flex-block-44 {
grid-column-gap: 0px;
grid-row-gap: 0px;
border-radius: var(--_round---normal);
background-color: var(--white);
align-self: stretch;
min-width: 160px;
padding-top: 10px;
}
.heading-9 {
margin-top: 0;
}
.text-block-21 {
line-height: 140%;
}
.flex-block-45 {
display: flex;
}
.core-product-search {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.flex-block-47 {
grid-column-gap: 10px;
grid-row-gap: 10px;
align-self: stretch;
}
.image-10 {
object-fit: contain;
}
.div-block-20 {
align-self: auto;
display: block;
}
.core-product {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column-reverse wrap;
justify-content: flex-start;
align-items: flex-start;
}
.product-item-search {
padding-left: 10px;
padding-right: 10px;
}
.product-list-search {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.info-block-search {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
}
.pcs-search {
width: 50px;
}
.price {
text-align: left;
width: 80px;
}
.add-to-cart-block {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
justify-content: space-between;
align-self: stretch;
place-items: flex-start start;
display: grid;
}
.info-block-product-card-search, .item-recommend {
display: none;
}
.flex-block-48 {
max-width: 100%;
}
.sort-list {
grid-column-gap: 0px;
grid-row-gap: 0px;
padding-left: 18px;
padding-right: 18px;
}
.sort-item, .sort-item.first {
width: auto;
}
.flex-block-49 {
grid-column-gap: 28px;
grid-row-gap: 28px;
}
.show-more-search {
justify-content: flex-start;
align-items: flex-start;
}
.text-block-27 {
font-size: var(--_fonts---font-size--core);
line-height: 140%;
}
.pcs-card {
text-align: left;
}
.product-item-card {
align-self: stretch;
}
.flex-block-51 {
padding-bottom: 2px;
overflow: scroll;
}
.flex-block-14-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
max-width: 100%;
margin-bottom: 40px;
overflow: scroll;
}
.image-10-copy {
width: 270px;
}
.flex-block-56 {
justify-content: space-between;
align-items: flex-start;
max-width: 290px;
}
.small-img {
width: 60px;
height: 45px;
}
.cart-list {
max-width: 100%;
}
.cart-item {
grid-column-gap: 10px;
grid-row-gap: 10px;
padding-left: 10px;
padding-right: 10px;
}
.heading-9-copy {
align-self: stretch;
}
.flex-block-39-copy {
flex: 1;
justify-content: flex-start;
align-items: flex-start;
width: auto;
}
.heading-9-copy-copy {
text-align: left;
flex: 0 auto;
}
.text-block-21-copy-copy {
font-size: var(--_fonts---font-size--small-font-size);
text-align: left;
}
.flex-block-39-copy-copy {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: column;
flex: 1;
justify-content: space-between;
align-items: flex-start;
display: flex;
}
.form-block-copy {
align-self: stretch;
max-width: none;
}
.control-element {
width: 60px;
margin-left: auto;
}
.multi-control {
grid-column-gap: 20px;
grid-row-gap: 20px;
padding-left: 10px;
padding-right: 10px;
}
.cart-detail-info {
padding-left: 20px;
padding-right: 20px;
}
.comments_f {
align-self: stretch;
max-width: none;
}
.flex-block-61 {
display: none;
}
.product-list-cart-check {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.menu-category {
display: none;
}
.image-14 {
max-width: 280px;
}
.flex-block-68 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.text-block-36 {
flex: 1;
}
.flex-block-70 {
max-width: none;
}
.flex-block-75 {
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-73-copy-copy, .flex-block-73-copy-copy-red {
min-width: 260px;
}
.code-embed-4 {
width: 24px;
height: 24px;
}
.code-embed-5 {
width: 24px;
height: 18px;
}
.flex-block-76 {
display: none;
}
.flex-block-18-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: space-between;
align-items: flex-start;
}
.phone-copy {
display: none;
}
.flex-block-78 {
justify-content: space-between;
align-items: center;
}
.flex-block-81 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: row;
justify-content: space-between;
align-items: center;
}
.core-product-copy {
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
}
.core-product-search-copy {
flex-flow: row;
align-self: stretch;
max-width: 100%;
height: 340px;
display: flex;
overflow: scroll;
}
.raiting-copy, .pcs-copy {
display: none;
}
.item-recommend-copy {
display: block;
}
.flex-block-83 {
flex-flow: column;
}
.flex-block-84 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: space-between;
align-items: flex-start;
width: 100%;
}
.flex-block-85 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---small-8);
background-color: var(--white);
justify-content: flex-start;
align-items: center;
padding: 8px 12px;
display: flex;
}
.code-embed-9 {
color: var(--_button---primary);
width: 16px;
height: 16px;
}
.image-15 {
max-width: 200px;
}
.code-embed-10 {
color: var(--white);
width: 12px;
height: 16px;
}
.flex-block-86 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---small-8);
background-color: var(--_button---hover-dark_blue);
justify-content: flex-start;
align-items: center;
padding: 10px 15px;
}
.flex-block-18-copy-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.link-block-4-copy {
flex: 0 auto;
}
.heading-8-copy {
color: var(--_fonts---color--light-blue-grey);
font-size: var(--_fonts---font-size--small-font-size);
margin-left: 0;
margin-right: 0;
font-weight: 400;
}
.dropdown-2 {
margin-left: 0;
margin-right: 0;
}
.dropdown-list-2 {
background-color: var(--white);
box-shadow: 0 2px 5px #0003;
}
.dropdown-list-2.w--open {
border-radius: var(--_round---small-8);
}
.heading-9-copy {
margin-top: 0;
}
.info-block-search-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
}
.heading-9-copy-copy {
font-size: var(--_fonts---font-size--small-font-size);
line-height: 18px;
}
.section-2 {
padding-left: 15px;
padding-right: 15px;
}
.mobile-block {
flex: 0 auto;
width: auto;
}
.flex-block-87 {
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.mobile-menu-bottom {
margin-left: 0;
margin-right: 0;
padding-left: 15px;
padding-right: 15px;
}
.mobile-menu-bottom.nav, .mobile-menu-bottom.info {
padding-left: 15px;
padding-right: 15px;
}
.mobile-menu-bottom.subscribe, .mobile-menu-bottom.footer {
padding: 40px 15px;
}
.name-mobile-menu-item {
display: block;
}
.button-for-mobile-menu-block {
grid-column-gap: 0px;
grid-row-gap: 0px;
width: 60px;
padding-bottom: 5px;
}
.section-3 {
padding-left: 15px;
padding-right: 15px;
}
.nav-menu-3 {
display: none;
}
.flex-block-93 {
margin-left: 0;
}
.sort-list-card {
grid-column-gap: 0px;
grid-row-gap: 0px;
padding-left: 18px;
padding-right: 18px;
}
.flex-block-49-copy {
grid-column-gap: 28px;
grid-row-gap: 28px;
}
.price-in-cart-s1 {
text-align: left;
min-width: 120px;
}
.delivery-cart-s1 {
font-size: var(--_fonts---font-size--small-font-size);
line-height: 18px;
}
.price-1-pcs-cart-s1 {
text-align: left;
}
.pcs-cart-s1 {
margin-left: auto;
margin-right: 0;
display: flex;
}
.add-to-cart-block-s2 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
justify-content: space-between;
align-self: stretch;
place-items: flex-start start;
display: flex;
}
.product-item-search-s1 {
padding-left: 10px;
padding-right: 10px;
}
.info-block-search-s1 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: row;
justify-content: space-between;
align-self: auto;
align-items: center;
}
.info-block-product-card-search-s1 {
display: none;
}
.price-s1 {
text-align: left;
width: 80px;
}
.flex-block-14-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-bottom: 40px;
}
.flex-block-13-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
max-width: 100%;
}
.core-product-s1 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column-reverse wrap;
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-48-copy {
max-width: 100%;
}
.sort-list-s1 {
grid-column-gap: 0px;
grid-row-gap: 0px;
padding-left: 18px;
padding-right: 18px;
}
.pcs-search-s1 {
width: 50px;
}
.add-to-cart-block-s1 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
justify-content: space-between;
align-self: stretch;
place-items: flex-start start;
display: flex;
}
.flex-block-15-copy {
grid-column-gap: 5px;
grid-row-gap: 5px;
min-width: 135px;
max-width: 190px;
padding: 15px;
}
.flex-block-14-copy-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
max-width: 100%;
margin-bottom: 0;
overflow: scroll;
}
.add-to-cart-block-copy {
grid-column-gap: 5px;
grid-row-gap: 5px;
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: row;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
justify-content: space-between;
align-self: stretch;
place-items: center start;
display: flex;
}
.control-element-copy {
margin-left: auto;
}
.core-product-copy-copy {
flex-flow: column;
justify-content: flex-start;
align-items: center;
}
.flex-block-94 {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.div-block-31 {
padding: 12px 15px;
}
.image-18 {
height: 18px;
}
.desc-wholesale {
min-width: 260px;
padding: 30px 20px;
}
.flex-block-95 {
grid-template-rows: auto auto auto auto auto;
grid-template-columns: 1fr;
}
.image-wholesale {
min-width: 260px;
}
.why-wholesale {
grid-template-rows: auto auto auto auto auto auto auto auto;
grid-template-columns: 1fr;
}
.flex-block-96 {
flex-flow: column;
grid-template-rows: auto auto auto auto auto auto;
grid-template-columns: 1fr;
justify-content: flex-start;
align-items: flex-start;
padding-left: 20px;
padding-right: 20px;
display: flex;
}
.div-block-33 {
transform: rotate(90deg);
}
.flex-block-71-copy {
flex-flow: wrap;
}
.text-block-36-copy {
flex: 0 auto;
}
.map-contacts, .lastnews {
min-width: 260px;
}
.contentnews {
min-width: 260px;
padding: 30px 20px;
}
.submit-button-s {
padding: 15px 30px;
}
.flex-block-6-copy-copy {
margin-left: -16px;
margin-right: -16px;
padding-left: 16px;
padding-right: 16px;
}
.heading-17 {
text-align: center;
font-size: 24px;
}
.text-block-51 {
display: none;
}
.flex-block-99 {
margin-bottom: 0;
}
.flex-block-100 {
padding-left: 20px;
padding-right: 20px;
}
.flex-block-101 {
display: none;
}
.container2-copy {
padding-left: 15px;
padding-right: 15px;
}
.div-block-12-copy, .div-block-123, .div-block-red.small {
min-width: 280px;
height: auto;
overflow: hidden;
}
.image-22 {
margin-right: -50px;
}
.section-info {
padding-left: 15px;
padding-right: 15px;
}
.catc {
flex: 0 auto;
}
.image-23 {
width: 18px;
}
.text-block-4-copy {
line-height: 140%;
}
.image-thx {
min-width: 260px;
min-height: 300px;
}
.flex-block-104 {
flex-flow: column;
}
.button_strock-s {
padding: 12px 25px;
}
.txtpthx {
flex: 0 auto;
}
.div-block-127 {
margin-top: 0;
}
.headingbi {
grid-column-gap: 5px;
grid-row-gap: 5px;
}
.rightbi {
flex-flow: wrap;
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-bi {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.heading-bi {
font-size: var(--_fonts---font-size--heading-3);
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
line-height: 130%;
}
.fsfav {
margin-top: 0;
}
.favcardcat {
width: 30px;
height: 30px;
margin-top: 0;
}
.topnav {
margin-left: 0;
top: 58px;
}
.bottom_head {
padding-left: 15px;
padding-right: 15px;
}
.tb {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.tb.nav {
padding-left: 0;
padding-right: 0;
}
.tb.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.tb.subscribe {
padding: 40px 0;
}
.tb.footer {
padding: 40px 0 90px;
}
.batd {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.batd.nav {
padding-left: 0;
padding-right: 0;
}
.batd.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.batd.subscribe {
padding: 40px 0;
}
.batd.footer {
padding: 40px 0 90px;
}
.saletag {
padding-top: 3px;
padding-bottom: 3px;
font-size: 12px;
top: -15px;
}
.catnav {
padding-left: 15px;
padding-right: 15px;
}
.div-block-131 {
min-width: 120px;
min-height: 140px;
}
.text-block-57 {
font-size: var(--_fonts---font-size--small-font-size);
line-height: 16px;
}
.container-vin {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.container-vin.nav {
padding-left: 0;
padding-right: 0;
}
.container-vin.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.container-vin.subscribe {
padding: 40px 0;
}
.container-vin.footer {
padding: 40px 0 90px;
}
.knot-img {
min-width: 120px;
min-height: 140px;
padding: 20px;
}
.knotinfo {
max-width: 320px;
min-height: 140px;
}
.heading-19 {
font-size: 18px;
line-height: 28px;
}
.flex-block-115 {
flex-flow: column;
}
.knotin {
max-width: 320px;
min-height: 140px;
}
.knot-parts {
grid-column-gap: 15px;
grid-row-gap: 15px;
min-width: 120px;
min-height: 140px;
padding: 20px;
}
.knotlistitem {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.brandsortb {
grid-column-gap: 30px;
grid-row-gap: 30px;
padding-top: 0;
overflow: hidden;
}
.bestpriceitem {
grid-column-gap: 5px;
grid-row-gap: 5px;
min-width: 135px;
max-width: 190px;
padding: 15px;
}
.pricecartbp {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: column;
}
.nameitembp {
max-height: 36px;
font-size: 12px;
line-height: 18px;
overflow: hidden;
}
.saletagbp {
padding-top: 3px;
padding-bottom: 3px;
font-size: 12px;
top: -15px;
}
.imgitembp {
padding-left: 0;
padding-right: 0;
}
.submit-button-copy {
align-self: stretch;
padding: 15px 30px;
}
.supportheading {
font-size: var(--_fonts---font-size--heading-2);
}
.image-5-copy {
width: 100%;
height: 100%;
min-height: auto;
}
.select-copy {
margin-bottom: 10px;
}
.container-copy {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.container-copy.nav {
padding-left: 0;
padding-right: 0;
}
.container-copy.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.container-copy.subscribe {
padding: 40px 0;
}
.container-copy.footer {
padding: 40px 0 90px;
}
.searcj {
border-radius: var(--_round---small-8);
flex: 1;
align-self: stretch;
height: 44px;
margin-bottom: 0;
padding: 0;
}
#w-node-bc394713-4b8e-44e3-8ddf-3edc1c31a743-3b3232bc {
justify-self: stretch;
.searcj.big {
padding-top: 5px;
padding-bottom: 5px;
}
#w-node-_8908a890-8c8f-e12c-999f-08d5da3bcc01-3b3232bc {
grid-area: Area;
.topmenub {
flex-flow: column;
justify-content: center;
align-items: flex-start;
width: 100%;
height: auto;
margin-top: 0;
padding-left: 60px;
padding-right: 60px;
display: flex;
}
@media screen and (min-width: 1920px) {
#w-node-_8908a890-8c8f-e12c-999f-08d5da3bcc01-3b3232bc {
grid-area: Area;
}
.tb {
justify-content: space-between;
align-items: center;
width: 100%;
min-width: auto;
max-width: 1580px;
margin-left: auto;
margin-right: auto;
padding-top: 40px;
display: block;
}
@media screen and (max-width: 991px) {
#w-node-_2eb32dc9-d3cb-cbbb-db81-4205d4e11f12-659ca3b9 {
grid-area: span 1 / span 3 / span 1 / span 3;
}
.tb.nav {
height: auto;
padding-top: 15px;
padding-bottom: 15px;
}
@media screen and (max-width: 479px) {
#w-node-_35f55517-cbe0-9ee3-13bb-a3ed00029bba-00029ba8, #w-node-_35f55517-cbe0-9ee3-13bb-a3ed00029bc7-00029ba8 {
justify-self: stretch;
}
.button-for-mobile-menu-block {
grid-column-gap: 0px;
grid-row-gap: 0px;
width: 60px;
padding-bottom: 5px;
}
.tb.info {
width: 100%;
padding-top: 30px;
padding-bottom: 30px;
}
.tb.subscribe {
padding-top: 40px;
padding-bottom: 40px;
}
.tb.footer {
background-color: var(--back);
padding-top: 50px;
padding-bottom: 50px;
}
.link-block-8 {
border-radius: var(--_round---supersmall-4);
background-color: var(--white);
color: #000;
font-size: var(--_fonts---font-size--core);
flex: none;
padding: 4px 8px;
text-decoration: none;
}
.link-block-8.green {
background-color: var(--green);
color: var(--_fonts---color--white);
}
.link-block-8.orange {
color: var(--_fonts---color--white);
background-color: #ff5f00;
}
.flex-block-107 {
grid-column-gap: 10px;
grid-row-gap: 10px;
overflow: hidden;
}
.batd {
justify-content: space-between;
align-items: center;
width: 100%;
min-width: auto;
max-width: 1580px;
margin-left: auto;
margin-right: auto;
display: block;
}
.batd.nav {
height: auto;
padding-top: 0;
}
.batd.info {
width: 100%;
padding-top: 30px;
padding-bottom: 30px;
}
.batd.subscribe {
padding-top: 40px;
padding-bottom: 40px;
}
.batd.footer {
background-color: var(--back);
padding-top: 50px;
padding-bottom: 50px;
}
.div-block-128 {
background-image: url('/images/bannertop.jpg');
background-position: 0%;
background-repeat: no-repeat;
background-size: cover;
width: 100%;
height: 100%;
}
.div-block-129 {
border-radius: var(--_round---big-20);
background-color: var(--white);
flex-flow: column;
flex: none;
justify-content: flex-start;
align-items: flex-start;
width: 330px;
height: 220px;
padding: 30px;
display: flex;
}
.flex-block-108 {
grid-column-gap: 18px;
grid-row-gap: 18px;
justify-content: space-between;
align-items: flex-start;
overflow: scroll;
}
.heading-18 {
font-size: var(--_fonts---font-size--core);
margin-top: 0;
margin-bottom: 0;
font-weight: 900;
}
.flex-block-109 {
grid-column-gap: 16px;
grid-row-gap: 16px;
flex: 0 auto;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
justify-content: space-between;
align-self: stretch;
align-items: center;
height: 30px;
padding-top: 0;
padding-bottom: 0;
display: flex;
}
.saletag {
border-radius: var(--_round---small-8);
background-color: var(--green);
color: var(--_fonts---color--white);
padding: 4px 5px;
font-size: 12px;
font-weight: 600;
}
.flex-block-110 {
justify-content: flex-start;
align-self: stretch;
align-items: center;
}
.flex-block-111 {
grid-column-gap: 6px;
grid-row-gap: 6px;
}
.arbd {
border: 1px solid var(--light-blue);
background-color: var(--white);
opacity: 0;
color: var(--back);
border-radius: 100px;
flex: none;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
display: flex;
}
.arbd:hover {
background-color: var(--_button---primary);
color: var(--white);
border-style: none;
}
.arbd.right {
transform: rotate(180deg);
}
.catnav {
flex-flow: column;
justify-content: flex-start;
align-items: center;
width: 100%;
padding-top: 40px;
padding-left: 60px;
padding-right: 60px;
display: flex;
}
.ci1 {
border-radius: var(--_round---big-20);
background-color: var(--white);
background-image: url('/images/catalog_item.png');
background-position: 100% 100%;
background-repeat: no-repeat;
background-size: auto;
flex: 1;
min-width: 160px;
height: 180px;
display: flex;
}
.text-block-54 {
color: var(--_fonts---color--black);
flex: 1;
padding-top: 20px;
padding-left: 20px;
padding-right: 20px;
font-weight: 700;
line-height: 18px;
}
.image-25 {
margin: auto 0 0 auto;
display: block;
position: relative;
bottom: 0;
right: 36px;
}
.text-block-55 {
color: var(--_fonts---color--light-blue-grey);
}
.flex-block-112 {
grid-column-gap: 10px;
grid-row-gap: 10px;
color: var(--red);
margin-top: 10px;
}
.flex-block-113 {
@ -9486,7 +5760,7 @@ body {
display: flex;
}
.dropdown-toggle-3, .dropdown-toggle-card {
.dropdown-toggle-3 {
border-top-right-radius: var(--_round---normal);
border-bottom-right-radius: var(--_round---normal);
border-left: 2px solid #0000;
@ -10071,8 +6345,8 @@ body {
grid-column-gap: 30px;
grid-row-gap: 30px;
border-radius: var(--_round---big-20);
/* background-image: url('/images/carvin.png'), linear-gradient(45deg, #1b283b, #0d336c);
background-position: 150px, 0 0; */
background-image: url('/images/carvin.png'), linear-gradient(45deg, #1b283b, #0d336c);
background-position: 150px, 0 0;
background-repeat: no-repeat, repeat;
background-size: auto, auto;
justify-content: center;
@ -10238,3 +6512,4083 @@ body {
align-items: center;
margin-left: 30px;
}
.flex-block-108-copy {
grid-column-gap: 18px;
grid-row-gap: 18px;
justify-content: space-between;
align-items: flex-start;
overflow: scroll;
}
@media screen and (min-width: 1440px) {
.body {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 16px;
--_fonts---font-size--small-font-size: 14px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 18px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 36px;
--_fonts---black-weight: 800;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 24px;
--_fonts---font-size--heading-2: 30px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 12px;
}
.nav-link {
font-size: var(--_fonts---font-size--small-font-size);
}
.nav-menu {
grid-column-gap: 20px;
grid-row-gap: 20px;
padding-left: 20px;
padding-right: 20px;
display: flex;
}
.flex-block {
grid-column-gap: 10px;
grid-row-gap: 10px;
display: flex;
}
.flex-block-2 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.top_head {
margin-left: 0;
margin-right: 0;
}
.topmenuh {
margin-top: 0;
}
.flex-block-4 {
grid-column-gap: 40px;
grid-row-gap: 40px;
}
.form-block {
align-self: stretch;
}
.text-field {
padding-top: 14px;
padding-bottom: 14px;
}
.flex-block-12 {
width: 320px;
}
.flex-block-15.end {
display: flex;
}
.flex-block-18 {
grid-column-gap: 60px;
grid-row-gap: 60px;
}
.div-block-9 {
grid-column-gap: 40px;
grid-row-gap: 40px;
justify-content: space-between;
align-items: center;
}
.text-block-14 {
display: block;
}
.flex-block-22 {
grid-column-gap: 40px;
grid-row-gap: 40px;
}
.flex-block-23 {
width: 180px;
}
.flex-block-30 {
max-width: 740px;
}
.news {
min-width: 310px;
}
.core-product-search {
flex-flow: row;
}
.flex-block-47 {
flex: 0 auto;
}
.image-10 {
width: 280px;
height: 180px;
}
.core-product {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
min-width: 270px;
max-width: 320px;
}
.info-block-search {
flex: 0 auto;
}
.flex-block-14-copy {
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-39-copy {
width: 200px;
}
.cart-ditail {
width: 340px;
}
.flex-block-69 {
flex-flow: row;
}
.menu-button-2 {
display: none;
}
.flex-block-18-copy {
grid-column-gap: 60px;
grid-row-gap: 60px;
grid-column-gap: 60px;
grid-row-gap: 60px;
}
.flex-block-78 {
flex: 1;
}
.flex-block-81 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: space-between;
align-items: center;
}
.core-product-copy {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
min-width: 270px;
max-width: 320px;
}
.flex-block-18-copy-copy {
grid-column-gap: 60px;
grid-row-gap: 60px;
}
.section {
background-color: var(--white);
}
.info-block-search-copy {
flex: 0 auto;
}
.mobile-menu-buttom-section {
background-color: var(--white);
}
.button-for-mobile-menu-block {
padding-left: 20px;
padding-right: 20px;
}
.flex-block-89 {
padding-top: 60px;
padding-left: 60px;
}
.image-17 {
width: 420px;
height: 210px;
}
.section-4 {
margin-left: 0;
margin-right: 0;
}
.core-product-search-copy, .core-product-search-s1 {
flex-flow: row;
}
.info-block-search-s1 {
flex: 0 auto;
}
.core-product-search-s2 {
flex-flow: row;
}
.text-block-25-s1 {
display: none;
}
.flex-block-15-copy.end {
display: flex;
}
.flex-block-14-copy-copy {
justify-content: flex-start;
align-items: flex-start;
}
.core-product-copy-copy {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
min-width: 270px;
max-width: 320px;
}
.flex-block-94 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: flex-start;
align-items: center;
}
.desc-wholesale, .contentnews {
max-width: none;
}
.flex-block-99 {
max-width: 50%;
}
.image-21 {
margin-left: 170px;
}
.div-block-35 {
width: 900px;
margin-top: 40px;
}
.section-5 {
margin-left: 0;
margin-right: 0;
}
.text-block-14-copy {
display: block;
}
.rightbi {
margin-top: 12px;
}
.topmenub {
margin-top: 0;
}
.ci1:hover {
background-color: var(--light-blue);
}
.vinleftbar {
width: 320px;
}
.knotinfo {
max-width: 320px;
}
.image-26 {
align-self: stretch;
}
.knotin {
flex: 1;
max-width: none;
}
.flex-block-118 {
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.heading-20 {
font-size: 48px;
}
.flex-block-119 {
width: 480px;
}
.bestpriceitem.end {
display: flex;
}
.flex-block-121 {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.ci2:hover, .ci3:hover, .ci4:hover, .ci5:hover, .ci6:hover, .ci7:hover, .ci8:hover, .ci9:hover {
background-color: var(--light-blue);
}
.flex-block-124 {
width: 540px;
}
.image-27 {
margin-bottom: -212px;
margin-left: 800px;
}
}
@media screen and (min-width: 1920px) {
.nav-link {
font-size: var(--_fonts---font-size--core);
}
.flex-block {
display: none;
}
.left-arrow {
margin-left: -25px;
}
.icon-2 {
width: 26px;
height: 26px;
margin: auto;
}
.slider {
width: 46px;
}
.flex-block-5 {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-top: 20px;
}
.flex-block-6 {
grid-column-gap: 20px;
grid-row-gap: 20px;
grid-template: "."
"."
/ 1fr 1fr 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
place-items: start stretch;
display: grid;
}
.main {
width: 100%;
}
.flex-block-15.end {
display: flex;
}
.heading-3.sub {
flex: 0 auto;
}
.text-block-14 {
flex: 1;
}
.flex-block-24 {
border-radius: var(--_round---big-20);
background-color: var(--white);
padding: 40px 30px 40px 40px;
}
.select {
height: 48px;
margin-bottom: 14px;
}
.div-block-10 {
justify-content: space-between;
align-items: center;
}
.div-block-12 {
min-width: 240px;
height: 190px;
}
.div-block-12.small {
justify-content: flex-start;
align-items: flex-start;
height: 190px;
}
.right-arrow {
justify-content: flex-end;
align-items: center;
margin-right: -25px;
}
.core-product {
max-width: 320px;
}
.info-block-search {
flex: 1;
}
.flex-block-70 {
max-width: 500px;
}
.flex-block-73 {
max-width: 600px;
}
.flex-block-81 {
justify-content: space-between;
align-items: center;
}
.core-product-copy {
max-width: 320px;
}
.core-product-search-copy {
justify-content: space-between;
align-items: flex-start;
}
.heading-15 {
width: 100%;
}
.info-block-search-copy {
flex: 1;
}
.core-product-search-s1 {
flex-flow: row;
}
.info-block-search-s1 {
flex: 1;
}
.core-product-search-s2 {
flex-flow: row;
}
.flex-block-15-copy.end {
display: flex;
}
.flex-block-14-copy-copy {
flex: 1;
}
.core-product-copy-copy {
max-width: 320px;
}
.flex-block-94 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: flex-start;
align-items: center;
}
.div-block-34 {
justify-content: center;
align-items: center;
width: 48px;
height: 48px;
margin: auto;
}
.heading-17 {
font-size: 64px;
}
.flex-block-99 {
max-width: 45%;
}
.image-21 {
margin-left: 0;
}
.div-block-35 {
width: 1000px;
margin-top: 30px;
}
.flex-block-6-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
grid-template: ". . ."
". Area Area"
/ 1fr 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
place-items: start stretch;
display: flex;
}
.div-block-12-copy {
min-width: 240px;
height: 190px;
}
.div-block-123 {
height: 190px;
}
.div-block-red.small {
min-width: 240px;
height: 190px;
}
.image-22 {
object-fit: contain;
object-position: 100% 100%;
}
.section-info {
background-color: var(--white);
}
.text-block-14-copy {
flex: 1;
}
.thxcontent {
align-self: stretch;
}
.div-block-127 {
border: 1px solid var(--_icon---light-blue-grey);
color: var(--_icon---light-blue-grey);
border-radius: 50px;
justify-content: center;
align-items: center;
width: 48px;
height: 48px;
padding-top: 5px;
display: flex;
}
.headingbi {
justify-content: flex-start;
align-items: center;
}
.rightbi {
margin-top: 12px;
}
.fsfav {
border: 1px solid var(--_icon---light-blue-grey);
color: var(--_icon---light-blue-grey);
border-radius: 50px;
justify-content: center;
align-items: center;
width: 48px;
height: 48px;
padding-top: 5px;
display: flex;
}
.favcardcat {
justify-content: center;
align-items: center;
display: flex;
}
.tabson {
display: none;
}
.indexbrandblock:hover {
color: var(--_button---primary);
}
.arbd {
justify-content: center;
align-items: center;
width: 48px;
height: 48px;
margin: auto;
}
.catnav {
width: 100%;
}
.container-vin {
padding-bottom: 60px;
}
.knotinfo {
max-width: 480px;
}
.knotin {
max-width: none;
}
.brandsortb {
border-radius: var(--_round---big-20);
background-color: var(--white);
padding: 40px 30px 40px 40px;
}
.flex-block-119 {
width: 420px;
}
.bestpriceitem.end {
display: flex;
}
.flex-block-122 {
justify-content: flex-start;
align-items: flex-end;
}
.inbt {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-top: 20px;
}
.flex-block-124 {
background-position: 180px, 0 0;
width: 620px;
}
.select-copy {
height: 48px;
margin-bottom: 14px;
}
.div-block-10-copy {
justify-content: space-between;
align-items: center;
}
.image-27 {
margin-left: 1000px;
}
}
@media screen and (max-width: 991px) {
.container, .container.nav, .container.info {
padding-left: 0;
padding-right: 0;
}
.container.subscribe, .container.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.top_head, .topmenuh {
padding-left: 30px;
padding-right: 30px;
}
.navbar-2 {
margin-left: 0;
}
.text-block-2 {
display: none;
overflow: visible;
}
.slider {
align-self: stretch;
height: auto;
display: flex;
}
.flex-block-7 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: wrap;
}
.flex-block-10 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
}
.main {
padding-left: 30px;
padding-right: 30px;
}
.flex-block-12 {
display: none;
}
.flex-block-13 {
flex-flow: column;
}
.flex-block-15 {
min-width: 220px;
max-width: 320px;
}
.image-5 {
object-fit: contain;
}
.tabs_block {
justify-content: center;
align-items: flex-start;
display: none;
}
.flex-block-18 {
flex-flow: column;
justify-content: space-between;
align-items: center;
}
.div-block-9 {
flex: 0 auto;
justify-content: flex-start;
align-self: stretch;
align-items: flex-start;
}
.form-block-3 {
align-self: stretch;
}
.flex-block-19 {
width: 460px;
}
.flex-block-22 {
grid-column-gap: 30px;
grid-row-gap: 30px;
flex-flow: wrap;
justify-content: flex-start;
align-self: auto;
align-items: flex-start;
}
.link {
display: none;
}
.text-block-17 {
margin-bottom: 0;
}
.flex-block-23 {
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-28 {
flex: 1;
}
.div-block-11 {
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
padding-top: 40px;
padding-bottom: 40px;
}
.div-block-12, .div-block-12.small {
min-width: 300px;
}
.news {
grid-column-gap: 10px;
grid-row-gap: 10px;
min-width: 223px;
padding: 20px;
}
.heading_news {
font-size: var(--_fonts---font-size--core);
}
.div-block-13 {
flex: none;
}
.flex-block-32 {
display: none;
}
.flex-block-33 {
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 10px;
}
.flex-block-35 {
flex-flow: wrap;
}
.heading-8 {
margin-left: 0;
}
.raiting {
display: none;
}
.flex-block-40 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-43 {
flex-flow: column;
}
.flex-block-44 {
align-self: stretch;
}
.flex-block-45 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
}
.image-10 {
object-fit: contain;
}
.core-product {
flex-flow: row-reverse;
}
.sort-list {
padding-right: 210px;
}
.sort-item.price {
text-align: right;
}
.flex-block-14-copy {
justify-content: center;
align-items: flex-start;
}
.image-10-copy {
object-fit: contain;
width: 400px;
height: 280px;
}
.flex-block-56 {
justify-content: center;
align-items: flex-start;
}
.small-img {
object-fit: contain;
}
.core-product-card {
flex-flow: column;
margin-bottom: 20px;
}
.cart-list {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-bottom: 40px;
}
.cart-ditail {
align-self: stretch;
width: auto;
}
.product-list-cart {
padding-bottom: 10px;
}
.brandname {
width: 100px;
}
.sort-item-name {
width: 140px;
}
.sort-item-comments {
width: 120px;
}
.flex-block-67 {
grid-column-gap: 40px;
grid-row-gap: 40px;
flex: 1;
}
.image-14 {
max-width: 700px;
}
.flex-block-68 {
grid-column-gap: 30px;
grid-row-gap: 30px;
align-self: stretch;
}
.flex-block-69 {
grid-column-gap: 40px;
grid-row-gap: 40px;
flex-flow: column-reverse;
}
.flex-block-69-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.flex-block-68-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: wrap;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-70 {
align-self: stretch;
max-width: 340px;
}
.flex-block-71 {
grid-column-gap: 15px;
grid-row-gap: 15px;
flex-flow: column;
}
.flex-block-72 {
flex-flow: column;
}
.flex-block-73 {
flex-flow: row;
align-self: stretch;
max-width: none;
padding: 30px;
}
.flex-block-73-copy-copy {
min-width: 340px;
}
.flex-block-73-copy-copy-red {
min-width: 340px;
max-width: none;
}
.menu-button-2 {
display: none;
position: static;
}
.flex-block-77 {
display: flex;
}
.flex-block-18-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
flex-flow: row;
flex: 1;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
justify-content: space-between;
align-items: center;
display: flex;
}
.flex-block-78 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-80 {
grid-column-gap: 20px;
grid-row-gap: 20px;
align-self: stretch;
}
.flex-block-81 {
justify-content: space-between;
align-items: center;
}
.flex-block-82 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.core-product-copy {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: stretch;
align-items: center;
max-width: 100%;
}
.core-product-search-copy {
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-84 {
grid-column-gap: 30px;
grid-row-gap: 30px;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
display: flex;
}
.flex-block-85 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---normal);
background-color: var(--white);
justify-content: flex-start;
align-items: center;
padding: 10px 20px;
display: flex;
}
.code-embed-9 {
color: var(--_button---primary);
width: 20px;
height: 24px;
}
.code-embed-10 {
color: var(--white);
width: 18px;
height: 18px;
display: block;
}
.flex-block-86 {
grid-column-gap: 10px;
grid-row-gap: 10px;
border-radius: var(--_round---normal);
background-color: var(--_button---hover-dark_blue);
justify-content: flex-start;
align-items: center;
padding: 10px 15px;
display: none;
}
.flex-block-77-copy {
display: flex;
}
.flex-block-18-copy-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
flex-flow: row;
flex: 1;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
justify-content: space-between;
align-items: flex-start;
display: flex;
}
.heading-8-copy {
margin-left: 0;
}
.dropdown-2 {
margin-left: 0;
margin-right: 0;
}
.dropdown-list-2.w--open {
border-radius: var(--_round---small-8);
background-color: var(--white);
box-shadow: 0 2px 5px #0003;
}
.dropdown-toggle-2 {
z-index: 999;
display: flex;
}
.dropdown-toggle-2.w--open {
z-index: 999998;
}
.dropdown-3 {
display: block;
}
.dropdown-list-3 {
z-index: 999999;
}
.section-2, .mobile-menu-bottom, .mobile-menu-bottom.nav, .mobile-menu-bottom.info {
padding-left: 30px;
padding-right: 30px;
}
.mobile-menu-bottom.subscribe, .mobile-menu-bottom.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.n {
display: none;
overflow: visible;
}
.div-block-25 {
width: 20px;
height: 20px;
}
.section-3 {
padding-left: 30px;
padding-right: 30px;
}
.core-product-card-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
flex-flow: column;
margin-bottom: 20px;
}
.nav-menu-2 {
float: none;
flex-flow: row;
justify-content: flex-start;
align-items: center;
width: 100%;
display: block;
position: relative;
top: 20px;
left: -30px;
right: 0;
}
.div-block-28 {
padding-left: 30px;
}
.link-block-7 {
grid-column-gap: 10px;
grid-row-gap: 10px;
border-top-left-radius: var(--_round---small-8);
border-bottom-left-radius: var(--_round---small-8);
color: var(--black);
justify-content: flex-start;
align-items: center;
padding: 15px 18px;
text-decoration: none;
display: flex;
}
.link-block-7:hover {
border-right: 2px solid var(--_button---primary);
background-color: var(--white);
padding-right: 18px;
}
.div-block-29 {
color: var(--_fonts---color--light-blue-grey);
width: 20px;
height: 20px;
}
.text-block-47 {
font-size: var(--_fonts---font-size--bigger);
text-align: left;
flex: 1;
font-weight: 600;
}
.flex-block-88 {
flex: 1;
align-self: auto;
margin-top: 20px;
}
.flex-block-89 {
background-color: var(--white);
flex: 1;
justify-content: flex-start;
align-self: stretch;
align-items: flex-start;
padding-left: 40px;
padding-right: 30px;
}
.heading-16 {
font-size: var(--_fonts---font-size--heading-3);
text-align: left;
}
.link-2 {
font-size: var(--_fonts---font-size--core);
}
.flex-block-91 {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.flex-block-92 {
flex-flow: column;
}
.nav-menu-3 {
left: 30px;
}
.tabs-menu {
max-width: 280px;
}
.text-block-48, .text-block-49 {
font-size: var(--_fonts---font-size--core);
}
.sort-list-card {
padding-right: 80px;
}
.pcs-cart-s1 {
display: none;
}
.flex-block-13-copy {
flex-flow: column;
}
.sort-list-s1 {
padding-right: 210px;
}
.pcs-search-s1 {
width: 60px;
}
.text-block-25-s1 {
display: none;
}
.flex-block-15-copy {
min-width: 220px;
max-width: 320px;
}
.flex-block-14-copy-copy {
justify-content: center;
align-items: flex-start;
}
.core-product-copy-copy {
flex-flow: column;
flex: 1;
justify-content: flex-start;
align-self: stretch;
align-items: center;
max-width: 100%;
}
.flex-block-94 {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: wrap;
justify-content: flex-start;
align-items: flex-start;
}
.image-18 {
height: 22px;
}
.desc-wholesale {
min-width: 340px;
}
.image-wholesale {
min-width: 340px;
max-width: none;
min-height: 300px;
}
.why-wholesale {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: wrap;
grid-template-rows: auto auto auto auto;
grid-template-columns: 1fr 1fr;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-96 {
grid-column-gap: 20px;
grid-row-gap: 20px;
grid-template: "Area Area Area"
". . ."
". . ."
". . ."
/ 1fr 1fr 1fr;
grid-auto-columns: 1fr;
place-items: center;
display: grid;
}
.service-wholesale-block {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: wrap;
}
.flex-block-71-copy {
grid-column-gap: 15px;
grid-row-gap: 15px;
flex-flow: row;
justify-content: flex-start;
align-items: center;
}
.flex-block-97 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.map-contacts {
min-width: 340px;
max-width: none;
min-height: 300px;
}
.lastnews {
flex-flow: row;
min-width: 320px;
max-width: none;
min-height: 300px;
}
.contentnews {
min-width: 340px;
}
.flex-block-6-copy-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.mask {
height: auto;
}
.heading-17 {
font-size: 44px;
}
.flex-block-99 {
grid-column-gap: 20px;
grid-row-gap: 20px;
max-width: 100%;
margin-top: 20px;
margin-bottom: 20px;
}
.flex-block-101 {
flex-flow: wrap;
display: none;
}
.image-21 {
margin-left: 0;
display: block;
position: static;
}
.div-block-35 {
width: 100%;
margin-top: 0;
position: static;
overflow: visible;
}
.div-block-12-copy, .div-block-12-copy.small, .div-block-123, .div-block-123.small, .div-block-red, .div-block-red.small {
min-width: 300px;
}
.section-info {
padding-left: 30px;
padding-right: 30px;
}
.image-thx {
min-width: 340px;
max-width: none;
min-height: 600px;
}
.div-block-127 {
width: 40px;
height: 40px;
margin-top: 0;
}
.headingbi {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: row;
justify-content: flex-start;
align-items: flex-start;
}
.fsfav, .favcardcat {
width: 40px;
height: 40px;
margin-top: 0;
}
.code-embed-15 {
width: 160px;
}
.topnav {
margin-left: 190px;
}
.topmenub {
padding-left: 30px;
padding-right: 30px;
}
.tb, .tb.nav, .tb.info {
padding-left: 0;
padding-right: 0;
}
.tb.subscribe, .tb.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.batd, .batd.nav, .batd.info {
padding-left: 0;
padding-right: 0;
}
.batd.subscribe, .batd.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.div-block-128 {
background-position: 0%;
}
.catnav {
padding-left: 30px;
padding-right: 30px;
}
.vinleftbar {
display: none;
}
.container-vin, .container-vin.nav, .container-vin.info {
padding-left: 0;
padding-right: 0;
}
.container-vin.subscribe, .container-vin.footer {
padding-top: 40px;
padding-bottom: 40px;
}
.bestpriceitem {
min-width: 220px;
max-width: 320px;
}
.flex-block-123 {
flex-flow: row;
}
.supportheading {
font-size: 30px;
}
.image-5-copy {
object-fit: contain;
}
.image-27 {
margin-bottom: -280px;
margin-left: 530px;
}
.container-copy, .container-copy.nav, .container-copy.info {
padding-left: 0;
padding-right: 0;
}
.container-copy.subscribe, .container-copy.footer {
padding-top: 40px;
padding-bottom: 40px;
}
}
@media screen and (max-width: 767px) {
.body {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.container {
padding-top: 20px;
}
.container.info {
padding-top: 15px;
padding-bottom: 15px;
}
.container.footer {
padding-bottom: 90px;
}
.brand {
min-width: 140px;
}
.phone {
display: block;
}
.flex-block {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.flex-block-2 {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-top: 3px;
}
.top_head {
margin-top: -15px;
}
.menu-button {
padding: 12px;
}
.navbar-2 {
height: auto;
top: 72px;
}
.left-arrow {
display: none;
}
.flex-block-5 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.flex-block-6 {
grid-template: "."
"."
"Area"
"."
"."
/ 1fr;
grid-auto-flow: column;
}
.flex-block-7 {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.flex-block-8 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-9, .flex-block-10 {
grid-column-gap: 5px;
grid-row-gap: 5px;
}
.heading {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.link-block-3 {
width: 30px;
height: 30px;
}
.form-block {
align-self: stretch;
height: auto;
min-height: auto;
}
.text-field {
min-height: auto;
margin-right: 10px;
padding-left: 10px;
padding-right: 50px;
}
.flex-block-13 {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
}
.flex-block-14 {
grid-column-gap: 40px;
grid-row-gap: 40px;
align-self: stretch;
}
.text-block-6, .tabs_block {
display: none;
}
.tab_c {
flex: none;
}
.flex-block-18 {
flex-flow: column;
justify-content: space-between;
align-items: center;
}
.heading-3.sub {
align-self: stretch;
}
.div-block-9 {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
align-self: stretch;
}
.text-block-14 {
align-self: stretch;
display: block;
}
.form-block-3 {
align-self: stretch;
}
.flex-block-19 {
flex-flow: wrap;
flex: 1;
justify-content: center;
align-self: stretch;
align-items: center;
width: 100%;
}
.link-block-5 {
padding: 14px 20px;
}
.flex-block-22 {
flex-flow: wrap;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-23 {
justify-content: flex-start;
align-items: center;
}
.flex-block-26 {
min-width: 120px;
}
.container2 {
padding: 20px 30px;
}
.flex-block-31 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.news {
min-width: 244px;
}
.right-arrow {
display: none;
}
.flex-block-36 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: row;
}
.flex-block-37 {
padding: 0;
}
.raiting {
display: none;
}
.flex-block-40 {
flex-flow: column;
align-self: auto;
padding-left: 0;
padding-right: 0;
}
.div-block-16 {
display: none;
}
.flex-block-43 {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: row;
justify-content: space-between;
align-self: auto;
align-items: center;
}
.flex-block-44 {
border-radius: var(--_round---normal);
background-color: var(--white);
flex: 1;
align-self: stretch;
padding: 20px 15px;
}
.flex-block-45 {
flex-flow: row;
}
.core-product-search {
align-self: stretch;
}
.image-10 {
object-fit: contain;
}
.product-item-search {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: row;
justify-content: space-between;
align-items: flex-start;
padding-top: 10px;
padding-bottom: 10px;
}
.info-block-search {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
}
.price {
text-align: right;
width: 110px;
}
.add-to-cart-block {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.text-block-25 {
display: none;
}
.sort-list {
padding-right: 30px;
}
.sort-item {
width: 90px;
}
.sort-item.first {
width: 55px;
}
.flex-block-49 {
grid-column-gap: 30px;
grid-row-gap: 30px;
}
.show-more-search {
align-self: auto;
}
.heading-11 {
align-self: stretch;
max-width: 100%;
}
.product-item-card {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-52 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
}
.flex-block-53 {
align-self: stretch;
}
.cart-item {
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.flex-block-39-copy {
width: 200px;
}
.heading-9-copy-copy {
text-align: left;
flex: 1;
width: auto;
}
.form-block-copy {
align-self: stretch;
max-width: none;
}
.block-detail {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: column;
flex: 1;
}
.favorite-item {
flex-flow: column;
}
.brandname {
flex: 0 auto;
align-self: stretch;
width: auto;
}
.block-name {
align-self: stretch;
}
.productname_f {
align-self: stretch;
width: auto;
}
.sort-item-brand {
width: 100px;
}
.flex-block-61 {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.sort-item-name {
width: 90px;
}
.sort-item-comments {
width: 80px;
}
.heading-list {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.cart-item-check {
flex-flow: column;
}
.text-block-35 {
width: auto;
height: auto;
}
.product-list-cart-check, .menu-category {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.tab-menu-category, .tab-menu-category-activ {
padding-left: 10px;
padding-right: 10px;
}
.image-14 {
max-width: 480px;
}
.flex-block-69-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.flex-block-70 {
max-width: 240px;
padding-left: 22px;
padding-right: 22px;
}
.flex-block-73 {
padding-left: 30px;
padding-right: 30px;
}
.code-embed-4 {
width: 30px;
height: 30px;
}
.pcs-info {
color: var(--_fonts---color--white);
font-size: 10px;
font-weight: 400;
}
.flex-block-76 {
display: none;
}
.flex-block-18-copy {
grid-column-gap: 40px;
grid-row-gap: 40px;
flex-flow: column;
justify-content: space-between;
align-items: center;
}
.phone-copy {
display: block;
}
.flex-block-78 {
flex-flow: row;
justify-content: space-between;
align-items: flex-start;
display: none;
}
.flex-block-80 {
display: none;
}
.flex-block-81 {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.flex-block-82 {
grid-column-gap: 30px;
grid-row-gap: 30px;
justify-content: flex-start;
align-items: center;
}
.sort-item-brand-copy {
width: 50px;
}
.flex-block-84 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: space-between;
align-items: flex-start;
width: 100%;
}
.flex-block-85 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---normal);
background-color: var(--white);
justify-content: flex-start;
align-items: center;
padding: 10px 20px;
display: flex;
}
.code-embed-9 {
color: var(--_button---primary);
width: 18px;
height: 18px;
}
.flex-block-77-copy {
justify-content: flex-start;
align-items: center;
}
.flex-block-18-copy-copy {
grid-column-gap: 40px;
grid-row-gap: 40px;
flex-flow: column;
}
.heading-8-copy {
font-size: var(--_fonts---font-size--small-font-size);
align-self: stretch;
padding-left: 0;
padding-right: 0;
font-weight: 400;
line-height: 20px;
display: block;
overflow: hidden;
}
.dropdown-2 {
margin-left: 0;
margin-right: 0;
}
.heading-9-copy {
font-size: var(--_fonts---font-size--bigger);
}
.info-block-search-copy {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.mobile-block {
display: flex;
}
.flex-block-87 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex: 1;
}
.mobile-menu-bottom {
padding-top: 0;
padding-bottom: 0;
box-shadow: 0 0 5px #0003;
}
.mobile-menu-bottom.info {
padding-top: 20px;
padding-bottom: 20px;
}
.mobile-menu-buttom-section {
display: block;
}
.name-mobile-menu-item {
color: var(--black);
font-size: var(--_fonts---font-size--small-font-size);
font-weight: 400;
}
.button-for-mobile-menu-block {
grid-column-gap: 2px;
grid-row-gap: 2px;
background-color: var(--_fonts---color--white);
color: var(--_button---light-blue-grey);
flex-flow: column;
width: 70px;
}
.button-for-mobile-menu-block:hover {
background-color: var(--_button---light-blue);
}
.icon_favorite {
color: var(--_button---light-blue-grey);
}
.block-for-moble-menu-icon {
width: 30px;
height: 30px;
}
.div-block-25 {
width: 20px;
height: 20px;
display: block;
}
.info-satus {
background-color: var(--green);
color: var(--_fonts---color--white);
font-size: 10px;
font-weight: 400;
}
.flex-block-93 {
align-self: auto;
min-height: 48px;
}
.sort-list-card {
padding-right: 30px;
display: none;
}
.flex-block-49-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
}
.price-in-cart-s1 {
text-align: left;
flex: 1;
width: auto;
}
.price-1-pcs-cart-s1 {
text-align: left;
}
.add-to-cart-block-s2 {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.core-product-search-copy, .core-product-search-s1 {
align-self: stretch;
}
.product-item-search-s1 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: row;
justify-content: space-between;
align-items: flex-start;
padding-top: 10px;
padding-bottom: 10px;
}
.info-block-search-s1 {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.price-s1 {
text-align: right;
width: 110px;
}
.flex-block-14-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
align-self: stretch;
}
.flex-block-13-copy {
flex-flow: column;
}
.sort-list-s1 {
padding-right: 30px;
}
.core-product-search-s2 {
align-self: stretch;
}
.text-block-25-s1, .pcs-re-s1 {
display: none;
}
.add-to-cart-block-s1 {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.flex-block-14-copy-copy {
margin-bottom: 0;
}
.add-to-cart-block-copy {
justify-content: space-between;
align-self: stretch;
align-items: center;
}
.service-wholesale-block {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.lastnews {
flex-flow: column;
}
.heading-17 {
font-size: 36px;
}
.container2-copy {
padding: 20px 30px 75px;
}
.image-24 {
max-width: 140px;
}
.text-block-14-copy {
align-self: stretch;
display: block;
}
.news-index-block {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.section-6 {
padding-left: 30px;
padding-right: 30px;
}
.image-thx {
min-height: 500px;
}
.div-block-127 {
justify-content: center;
align-items: center;
margin-top: 4px;
}
.flex-block-bi {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.heading-bi {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.fsfav, .favcardcat {
justify-content: center;
align-items: center;
margin-top: 4px;
}
.topnav {
height: auto;
top: 72px;
}
.searcj {
align-self: stretch;
height: auto;
min-height: auto;
}
.tb {
padding-top: 20px;
}
.tb.info {
padding-top: 15px;
padding-bottom: 15px;
}
.tb.footer {
padding-bottom: 90px;
}
.batd {
padding-top: 20px;
}
.batd.info {
padding-top: 15px;
padding-bottom: 15px;
}
.batd.footer {
padding-bottom: 90px;
}
.div-block-129 {
justify-content: flex-start;
align-self: stretch;
align-items: center;
}
.flex-block-108 {
flex-flow: column;
justify-content: space-between;
align-items: center;
}
.container-vin {
padding-top: 20px;
}
.container-vin.info {
padding-top: 15px;
padding-bottom: 15px;
}
.container-vin.footer {
padding-bottom: 90px;
}
.inbt {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.flex-block-123 {
flex-flow: column;
}
.flex-block-124 {
flex: 1;
}
.image-27 {
margin-left: 330px;
}
.news-index-block-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.container-copy {
padding-top: 20px;
}
.container-copy.info {
padding-top: 15px;
padding-bottom: 15px;
}
.container-copy.footer {
padding-bottom: 90px;
}
}
@media screen and (max-width: 479px) {
body {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
}
.container {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.container.nav {
padding-left: 0;
padding-right: 0;
}
.container.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.container.subscribe {
padding: 40px 0;
}
.container.footer {
padding: 40px 0 90px;
}
.brand {
width: 140px;
padding-left: 0;
}
.phone {
font-size: var(--_fonts---font-size--core);
display: block;
}
.icon_messenger {
max-width: 30px;
}
.flex-block, .flex-block-2 {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.top_head, .topmenuh {
padding-left: 15px;
padding-right: 15px;
}
.flex-block-4 {
padding-top: 15px;
padding-bottom: 15px;
}
.menu-button {
height: auto;
padding: 10px 12px 12px;
}
.menu-button.w--open {
justify-content: center;
align-items: center;
width: 54px;
height: 48px;
padding-left: 15px;
}
.navbar-2 {
margin-left: 0;
top: 58px;
}
.flex-block-7 {
grid-column-gap: 2px;
grid-row-gap: 2px;
display: flex;
}
.flex-block-8 {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.flex-block-9, .flex-block-10 {
grid-column-gap: 5px;
grid-row-gap: 5px;
}
.heading {
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
line-height: 36px;
}
.text-block-4 {
line-height: 140%;
}
.flex-block-11 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: wrap;
justify-content: flex-start;
align-items: flex-start;
}
.text-block-5 {
margin-top: 3px;
margin-bottom: 0;
}
.main {
padding-left: 15px;
padding-right: 15px;
}
.flex-block-13 {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
max-width: 100%;
}
.flex-block-14 {
grid-column-gap: 20px;
grid-row-gap: 20px;
max-width: 100%;
margin-bottom: 40px;
}
.text-block-6 {
display: none;
}
.flex-block-15 {
grid-column-gap: 5px;
grid-row-gap: 5px;
min-width: 140px;
max-width: 190px;
padding: 15px;
}
.text-block-7 {
padding-top: 3px;
padding-bottom: 3px;
font-size: 12px;
top: -15px;
}
.flex-block-16 {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: column;
}
.div-block-3 {
margin-top: -20px;
}
.text-block-10 {
max-height: 36px;
font-size: 12px;
line-height: 18px;
overflow: hidden;
}
.image-5 {
width: 100%;
height: 100%;
min-height: auto;
}
.div-block-4 {
padding-left: 0;
padding-right: 0;
}
.button_strock {
padding: 12px 25px;
}
.div-block-7 {
width: 20px;
height: 20px;
}
.flex-block-18 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.div-block-9 {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.form-3 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
}
.text-field-3 {
padding-top: 20px;
padding-bottom: 20px;
}
.submit-button {
align-self: stretch;
padding: 15px 30px;
}
.text-block-15 {
text-align: left;
}
.flex-block-19 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: flex-start;
align-items: center;
}
.link-block-5 {
padding-top: 10px;
padding-bottom: 10px;
}
.flex-block-20 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.text-block-16 {
font-size: 12px;
}
.flex-block-22 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: wrap;
justify-content: flex-start;
align-self: stretch;
align-items: flex-start;
margin-top: 20px;
margin-bottom: 20px;
}
.text-block-17 {
font-size: var(--_fonts---font-size--core);
}
.flex-block-24 {
grid-column-gap: 30px;
grid-row-gap: 30px;
padding-top: 0;
overflow: hidden;
}
.flex-block-26 {
min-width: 90px;
}
.flex-block-28 {
min-width: 290px;
padding: 20px;
}
.select {
margin-bottom: 10px;
}
.container2 {
padding-left: 15px;
padding-right: 15px;
}
.div-block-11 {
padding: 40px 30px;
}
.heading-6 {
font-size: var(--_fonts---font-size--heading-2);
}
.div-block-12 {
justify-content: flex-start;
align-items: flex-start;
min-width: 280px;
height: auto;
overflow: hidden;
}
.div-block-12.small {
min-width: 280px;
}
.news {
grid-column-gap: 10px;
grid-row-gap: 10px;
min-width: 250px;
padding: 20px;
}
.heading_news {
line-height: 20px;
}
.text-block-20 {
line-height: 18px;
}
.div-block-13 {
flex: none;
}
.flex-block-33 {
margin-top: 10px;
}
.flex-block-36 {
grid-column-gap: 10px;
grid-row-gap: 10px;
scrollbar-width: none;
flex-flow: row;
overflow: scroll;
}
.heading-8 {
margin-left: 20px;
margin-right: 20px;
}
.flex-block-37 {
grid-column-gap: 10px;
grid-row-gap: 10px;
padding-top: 10px;
}
.image-8 {
width: 16px;
height: 16px;
}
.raiting {
flex: 0 auto;
width: auto;
}
.flex-block-40 {
grid-column-gap: 10px;
grid-row-gap: 10px;
font-size: var(--_fonts---font-size--bigger);
}
.div-block-16 {
display: none;
}
.flex-block-43 {
flex-flow: row;
}
.flex-block-44 {
grid-column-gap: 0px;
grid-row-gap: 0px;
border-radius: var(--_round---normal);
background-color: var(--white);
align-self: stretch;
min-width: 160px;
padding-top: 10px;
}
.heading-9 {
margin-top: 0;
}
/* .text-block-21 {
line-height: 140%;
} */
.flex-block-45 {
display: flex;
}
.core-product-search {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.flex-block-47 {
grid-column-gap: 10px;
grid-row-gap: 10px;
align-self: stretch;
}
.image-10 {
object-fit: contain;
}
.div-block-20 {
align-self: auto;
display: block;
}
.core-product {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column-reverse wrap;
justify-content: flex-start;
align-items: flex-start;
}
.product-item-search {
padding-left: 10px;
padding-right: 10px;
}
.product-list-search {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.info-block-search {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
}
.pcs-search {
width: 50px;
}
.price {
text-align: left;
width: 80px;
}
.add-to-cart-block {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
justify-content: space-between;
align-self: stretch;
place-items: flex-start start;
display: grid;
}
.info-block-product-card-search, .item-recommend {
display: none;
}
.flex-block-48 {
max-width: 100%;
}
.sort-list {
grid-column-gap: 0px;
grid-row-gap: 0px;
padding-left: 18px;
padding-right: 18px;
}
.sort-item, .sort-item.first {
width: auto;
}
.flex-block-49 {
grid-column-gap: 28px;
grid-row-gap: 28px;
}
.show-more-search {
justify-content: flex-start;
align-items: flex-start;
}
.text-block-27 {
font-size: var(--_fonts---font-size--core);
line-height: 140%;
}
.pcs-card {
text-align: left;
}
.product-item-card {
align-self: stretch;
}
.flex-block-51 {
padding-bottom: 2px;
overflow: scroll;
}
.flex-block-14-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
max-width: 100%;
margin-bottom: 40px;
overflow: scroll;
}
.image-10-copy {
width: 270px;
}
.flex-block-56 {
justify-content: space-between;
align-items: flex-start;
max-width: 290px;
}
.small-img {
width: 60px;
height: 45px;
}
.cart-list {
max-width: 100%;
}
.cart-item {
grid-column-gap: 10px;
grid-row-gap: 10px;
padding-left: 10px;
padding-right: 10px;
}
.heading-9-copy {
align-self: stretch;
}
.flex-block-39-copy {
flex: 1;
justify-content: flex-start;
align-items: flex-start;
width: auto;
}
.heading-9-copy-copy {
text-align: left;
flex: 0 auto;
}
.text-block-21-copy-copy {
font-size: var(--_fonts---font-size--small-font-size);
text-align: left;
}
.flex-block-39-copy-copy {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: column;
flex: 1;
justify-content: space-between;
align-items: flex-start;
display: flex;
}
.form-block-copy {
align-self: stretch;
max-width: none;
}
.control-element {
width: 60px;
margin-left: auto;
}
.multi-control {
grid-column-gap: 20px;
grid-row-gap: 20px;
padding-left: 10px;
padding-right: 10px;
}
.cart-detail-info {
padding-left: 20px;
padding-right: 20px;
}
.comments_f {
align-self: stretch;
max-width: none;
}
.flex-block-61 {
display: none;
}
.product-list-cart-check {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.menu-category {
display: none;
}
.image-14 {
max-width: 280px;
}
.flex-block-68 {
grid-column-gap: 20px;
grid-row-gap: 20px;
}
.text-block-36 {
flex: 1;
}
.flex-block-70 {
max-width: none;
}
.flex-block-75 {
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-73-copy-copy, .flex-block-73-copy-copy-red {
min-width: 260px;
}
.code-embed-4 {
width: 24px;
height: 24px;
}
.code-embed-5 {
width: 24px;
height: 18px;
}
.flex-block-76 {
display: none;
}
.flex-block-18-copy {
grid-column-gap: 30px;
grid-row-gap: 30px;
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: space-between;
align-items: flex-start;
}
.phone-copy {
display: none;
}
.flex-block-78 {
justify-content: space-between;
align-items: center;
}
.flex-block-81 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: row;
justify-content: space-between;
align-items: center;
}
.core-product-copy {
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
}
.core-product-search-copy {
flex-flow: row;
align-self: stretch;
max-width: 100%;
height: 340px;
display: flex;
overflow: scroll;
}
.raiting-copy, .pcs-copy {
display: none;
}
.item-recommend-copy {
display: block;
}
.flex-block-83 {
flex-flow: column;
}
.flex-block-84 {
grid-column-gap: 20px;
grid-row-gap: 20px;
justify-content: space-between;
align-items: flex-start;
width: 100%;
}
.flex-block-85 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---small-8);
background-color: var(--white);
justify-content: flex-start;
align-items: center;
padding: 8px 12px;
display: flex;
}
.code-embed-9 {
color: var(--_button---primary);
width: 16px;
height: 16px;
}
.image-15 {
max-width: 200px;
}
.code-embed-10 {
color: var(--white);
width: 12px;
height: 16px;
}
.flex-block-86 {
grid-column-gap: 5px;
grid-row-gap: 5px;
border-radius: var(--_round---small-8);
background-color: var(--_button---hover-dark_blue);
justify-content: flex-start;
align-items: center;
padding: 10px 15px;
}
.flex-block-18-copy-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
}
.link-block-4-copy {
flex: 0 auto;
}
.heading-8-copy {
color: var(--_fonts---color--light-blue-grey);
font-size: var(--_fonts---font-size--small-font-size);
margin-left: 0;
margin-right: 0;
font-weight: 400;
}
.dropdown-2 {
margin-left: 0;
margin-right: 0;
}
.dropdown-list-2 {
background-color: var(--white);
box-shadow: 0 2px 5px #0003;
}
.dropdown-list-2.w--open {
border-radius: var(--_round---small-8);
}
.heading-9-copy {
margin-top: 0;
}
.info-block-search-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column;
justify-content: space-between;
align-self: stretch;
align-items: flex-start;
}
.heading-9-copy-copy {
font-size: var(--_fonts---font-size--small-font-size);
line-height: 18px;
}
.section-2 {
padding-left: 15px;
padding-right: 15px;
}
.mobile-block {
flex: 0 auto;
width: auto;
}
.flex-block-87 {
grid-column-gap: 0px;
grid-row-gap: 0px;
}
.mobile-menu-bottom {
margin-left: 0;
margin-right: 0;
padding-left: 15px;
padding-right: 15px;
}
.mobile-menu-bottom.nav, .mobile-menu-bottom.info {
padding-left: 15px;
padding-right: 15px;
}
.mobile-menu-bottom.subscribe, .mobile-menu-bottom.footer {
padding: 40px 15px;
}
.name-mobile-menu-item {
display: block;
}
.button-for-mobile-menu-block {
grid-column-gap: 0px;
grid-row-gap: 0px;
width: 60px;
padding-bottom: 5px;
}
.icon-setting {
border: 0 #000;
}
.section-3 {
padding-left: 15px;
padding-right: 15px;
}
.nav-menu-3 {
display: none;
}
.flex-block-93 {
margin-left: 0;
}
.sort-list-card {
grid-column-gap: 0px;
grid-row-gap: 0px;
padding-left: 18px;
padding-right: 18px;
}
.flex-block-49-copy {
grid-column-gap: 28px;
grid-row-gap: 28px;
}
.price-in-cart-s1 {
text-align: left;
min-width: 120px;
}
.delivery-cart-s1 {
font-size: var(--_fonts---font-size--small-font-size);
line-height: 18px;
}
.price-1-pcs-cart-s1 {
text-align: left;
}
.pcs-cart-s1 {
margin-left: auto;
margin-right: 0;
display: flex;
}
.add-to-cart-block-s2 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
justify-content: space-between;
align-self: stretch;
place-items: flex-start start;
display: flex;
}
.product-item-search-s1 {
padding-left: 10px;
padding-right: 10px;
}
.info-block-search-s1 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: row;
justify-content: space-between;
align-self: auto;
align-items: center;
}
.info-block-product-card-search-s1 {
display: none;
}
.price-s1 {
text-align: left;
width: 80px;
}
.flex-block-14-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
margin-bottom: 40px;
}
.flex-block-13-copy {
grid-column-gap: 20px;
grid-row-gap: 20px;
flex-flow: column;
max-width: 100%;
}
.flex-block-48-copy {
max-width: 100%;
}
.sort-list-s1 {
grid-column-gap: 0px;
grid-row-gap: 0px;
padding-left: 18px;
padding-right: 18px;
}
.pcs-search-s1 {
width: 50px;
}
.add-to-cart-block-s1 {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
justify-content: space-between;
align-self: stretch;
place-items: flex-start start;
display: flex;
}
/* .flex-block-15-copy {
grid-column-gap: 5px;
grid-row-gap: 5px;
min-width: 135px;
max-width: 190px;
padding: 15px;
} */
.flex-block-14-copy-copy {
grid-column-gap: 10px;
grid-row-gap: 10px;
max-width: 100%;
margin-bottom: 0;
overflow: scroll;
}
.add-to-cart-block-copy {
grid-column-gap: 5px;
grid-row-gap: 5px;
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: row;
grid-template-rows: auto auto;
grid-template-columns: 1fr 1fr;
grid-auto-columns: 1fr;
grid-auto-flow: row;
justify-content: space-between;
align-self: stretch;
place-items: center start;
display: flex;
}
.control-element-copy {
margin-left: auto;
}
.core-product-copy-copy {
flex-flow: column;
justify-content: flex-start;
align-items: center;
}
.flex-block-94 {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.div-block-31 {
padding: 12px 15px;
}
.image-18 {
height: 18px;
}
.desc-wholesale {
min-width: 260px;
padding: 30px 20px;
}
.flex-block-95 {
grid-template-rows: auto auto auto auto auto;
grid-template-columns: 1fr;
}
.image-wholesale {
min-width: 260px;
}
.why-wholesale {
grid-template-rows: auto auto auto auto auto auto auto auto;
grid-template-columns: 1fr;
}
.flex-block-96 {
flex-flow: column;
grid-template-rows: auto auto auto auto auto auto;
grid-template-columns: 1fr;
justify-content: flex-start;
align-items: flex-start;
padding-left: 20px;
padding-right: 20px;
display: flex;
}
.div-block-33 {
transform: rotate(90deg);
}
.flex-block-71-copy {
flex-flow: wrap;
}
.text-block-36-copy {
flex: 0 auto;
}
.map-contacts, .lastnews {
min-width: 260px;
}
.contentnews {
min-width: 260px;
padding: 30px 20px;
}
.submit-button-s {
padding: 15px 30px;
}
.flex-block-6-copy-copy {
margin-left: -16px;
margin-right: -16px;
padding-left: 16px;
padding-right: 16px;
}
.heading-17 {
text-align: center;
font-size: 24px;
}
.text-block-51 {
display: none;
}
.flex-block-99 {
margin-bottom: 0;
}
.flex-block-100 {
padding-left: 20px;
padding-right: 20px;
}
.flex-block-101 {
display: none;
}
.container2-copy {
padding-left: 15px;
padding-right: 15px;
}
.div-block-12-copy, .div-block-123, .div-block-red.small {
min-width: 280px;
height: auto;
overflow: hidden;
}
.image-22 {
margin-right: -50px;
}
.section-info {
padding-left: 15px;
padding-right: 15px;
}
.catc {
flex: 0 auto;
}
.image-23 {
width: 18px;
}
.text-block-4-copy {
line-height: 140%;
}
.image-thx {
min-width: 260px;
min-height: 300px;
}
.flex-block-104 {
flex-flow: column;
}
.button_strock-s {
padding: 12px 25px;
}
.txtpthx {
flex: 0 auto;
}
.div-block-127 {
margin-top: 0;
}
.headingbi {
grid-column-gap: 5px;
grid-row-gap: 5px;
}
.rightbi {
flex-flow: wrap;
justify-content: flex-start;
align-items: flex-start;
}
.flex-block-bi {
grid-column-gap: 15px;
grid-row-gap: 15px;
}
.heading-bi {
font-size: var(--_fonts---font-size--heading-3);
--_fonts---font-family: Onest, sans-serif;
--_fonts---color--dark-blue: #0d336c;
--_fonts---font-size--core: 14px;
--_fonts---font-size--small-font-size: 12px;
--_fonts---color--black: #000;
--_fonts---font-size--bigger: 16px;
--_fonts---color--white: white;
--_fonts---color--light-blue-grey: #8e9aac;
--_fonts---h1: 30px;
--_fonts---color--light-blue: #b7cae2;
--_fonts---color--grey: #747474;
--_fonts---font-size--heading-3: 20px;
--_fonts---font-size--heading-2: 24px;
--_fonts---color--green: #4db45e;
--_fonts---font-size--supersmall: 10px;
line-height: 130%;
}
.fsfav {
margin-top: 0;
}
.favcardcat {
border-style: none;
width: 30px;
height: 30px;
margin-top: 0;
}
.code-embed-15 {
display: none;
}
.topnav {
margin-left: 0;
top: 58px;
}
.topmenub {
padding-left: 15px;
padding-right: 15px;
}
.tb {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.tb.nav {
padding-left: 0;
padding-right: 0;
}
.tb.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.tb.subscribe {
padding: 40px 0;
}
.tb.footer {
padding: 40px 0 90px;
}
.batd {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.batd.nav {
padding-left: 0;
padding-right: 0;
}
.batd.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.batd.subscribe {
padding: 40px 0;
}
.batd.footer {
padding: 40px 0 90px;
}
.div-block-129 {
justify-content: flex-start;
align-items: flex-start;
width: 100%;
height: auto;
}
.flex-block-109 {
margin-bottom: 10px;
}
.saletag {
padding-top: 3px;
padding-bottom: 3px;
font-size: 12px;
top: -15px;
}
.flex-block-110 {
grid-column-gap: 10px;
grid-row-gap: 10px;
flex-flow: column-reverse;
justify-content: flex-start;
align-items: flex-start;
margin-bottom: 20px;
}
.catnav {
padding-left: 15px;
padding-right: 15px;
}
.div-block-131 {
min-width: 120px;
min-height: 140px;
}
.text-block-57 {
font-size: var(--_fonts---font-size--small-font-size);
line-height: 16px;
}
.container-vin {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.container-vin.nav {
padding-left: 0;
padding-right: 0;
}
.container-vin.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.container-vin.subscribe {
padding: 40px 0;
}
.container-vin.footer {
padding: 40px 0 90px;
}
.knot-img {
min-width: 120px;
min-height: 140px;
padding: 20px;
}
.knotinfo {
max-width: 320px;
min-height: 140px;
}
.heading-19 {
font-size: 18px;
line-height: 28px;
}
.flex-block-115 {
flex-flow: column;
}
.knotin {
max-width: 320px;
min-height: 140px;
}
.knot-parts {
grid-column-gap: 15px;
grid-row-gap: 15px;
min-width: 120px;
min-height: 140px;
padding: 20px;
}
.knotlistitem {
grid-column-gap: 5px;
grid-row-gap: 5px;
flex-flow: column;
justify-content: space-between;
align-items: flex-start;
}
.brandsortb {
grid-column-gap: 30px;
grid-row-gap: 30px;
overflow: hidden;
}
.bestpriceitem {
grid-column-gap: 5px;
grid-row-gap: 5px;
min-width: 135px;
max-width: 190px;
padding: 15px;
}
.pricecartbp {
grid-column-gap: 0px;
grid-row-gap: 0px;
flex-flow: column;
}
.nameitembp {
max-height: 36px;
font-size: 12px;
line-height: 18px;
overflow: hidden;
}
.saletagbp {
padding-top: 3px;
padding-bottom: 3px;
font-size: 12px;
top: -15px;
}
.imgitembp {
padding-left: 0;
padding-right: 0;
}
.submit-button-copy {
align-self: auto;
padding: 15px 25px;
}
.supportheading {
font-size: var(--_fonts---font-size--heading-2);
width: 220px;
}
.image-5-copy {
width: 60%;
height: 100%;
min-height: auto;
}
.select-copy {
margin-bottom: 10px;
}
.image-27 {
display: none;
}
.container-copy {
max-width: 100%;
margin-left: 0;
margin-right: 0;
padding-top: 20px;
padding-left: 0;
padding-right: 0;
}
.container-copy.nav {
padding-left: 0;
padding-right: 0;
}
.container-copy.info {
margin-left: 0;
margin-right: 0;
padding-left: 0;
padding-right: 0;
}
.container-copy.subscribe {
padding: 40px 0;
}
.container-copy.footer {
padding: 40px 0 90px;
}
}
#w-node-_5428604d-3026-96c9-8306-ab3c3dd9acb7-3b3232bc {
justify-self: stretch;
}
@media screen and (max-width: 991px) {
#w-node-_2eb32dc9-d3cb-cbbb-db81-4205d4e11f12-659ca3b9 {
grid-area: span 1 / span 3 / span 1 / span 3;
}
}
@media screen and (max-width: 479px) {
#w-node-_35f55517-cbe0-9ee3-13bb-a3ed00029bba-00029ba8, #w-node-_35f55517-cbe0-9ee3-13bb-a3ed00029bc7-00029ba8 {
justify-self: stretch;
}
}

View File

@ -10,6 +10,7 @@ export interface CartItem {
originalPrice?: number
currency: string
quantity: number
stock?: string | number // количество товара в наличии на складе
deliveryTime?: string
deliveryDate?: string
warehouse?: string
@ -52,7 +53,7 @@ export interface CartState {
export interface CartContextType {
state: CartState
addItem: (item: Omit<CartItem, 'id' | 'selected' | 'favorite'>) => void
addItem: (item: Omit<CartItem, 'id' | 'selected' | 'favorite'>) => Promise<{ success: boolean; error?: string }>
removeItem: (id: string) => void
updateQuantity: (id: string, quantity: number) => void
toggleSelect: (id: string) => void
@ -64,4 +65,6 @@ export interface CartContextType {
removeSelected: () => void
updateDelivery: (delivery: Partial<DeliveryInfo>) => void
clearCart: () => void
clearError: () => void
isInCart: (productId?: string, offerKey?: string, article?: string, brand?: string) => boolean
}

12
src/types/index.ts Normal file
View File

@ -0,0 +1,12 @@
// Навигационные категории
export interface NavigationCategory {
id: string
partsIndexCatalogId: string
partsIndexGroupId: string | null
name: string
catalogName: string
groupName: string | null
icon: string | null
sortOrder: number
isHidden: boolean
}

View File

@ -88,7 +88,6 @@ export interface LaximoVehicleSearchResult {
// Дополнительные атрибуты из документации Laximo
grade?: string
transmission?: string
doors?: string
creationregion?: string
destinationregion?: string
date?: string
@ -97,12 +96,6 @@ export interface LaximoVehicleSearchResult {
trimcolor?: string
datefrom?: string
dateto?: string
frame?: string
frames?: string
framefrom?: string
frameto?: string
engine1?: string
engine2?: string
engine_info?: string
engineno?: string
options?: string
@ -112,8 +105,10 @@ export interface LaximoVehicleSearchResult {
market?: string
prodRange?: string
prodPeriod?: string
carpet_color?: string
seat_combination_code?: string
// Дополнительные атрибуты (могут приходить в виде массива attributes)
sales_code?: string
attributes: LaximoVehicleAttribute[]
}
export interface LaximoVehicleInfo {
@ -154,6 +149,7 @@ export interface LaximoUnit {
description?: string
imageurl?: string
largeimageurl?: string
ssd?: string // 🎯 ДОБАВЛЕНИЕ: SSD для узла
details?: LaximoDetail[]
attributes?: LaximoDetailAttribute[]
}
@ -279,6 +275,7 @@ export interface LaximoUnitInfo {
description?: string
imageurl?: string
largeimageurl?: string
ssd?: string // 🎯 ДОБАВЛЕНИЕ: SSD для узла
attributes?: LaximoDetailAttribute[]
}

View File

@ -5,6 +5,7 @@ FRONTEND_PORT=3000
NEXT_PUBLIC_CMS_GRAPHQL_URL=https://cms.protekauto.ru/api/graphql
NEXT_PUBLIC_UPLOAD_URL=https://cms.protekauto.ru/upload
NEXT_PUBLIC_MAINTENANCE_MODE=true
NEXT_PUBLIC_PARTSAPI_URL=https://api.parts-index.com
# Build Configuration
NODE_ENV=production

View File

@ -9,7 +9,7 @@ async function testPartsIndexAPI() {
// Получаем каталоги
console.log('\n📦 Получаем список каталогов...');
const catalogsResponse = await fetch('https://api.parts-index.com/v1/catalogs?lang=ru', {
const catalogsResponse = await fetch(process.env.PARTSAPI_URL+"/v1/catalogs?lang=ru", {
headers: {
'Accept': 'application/json',
},
@ -31,7 +31,7 @@ async function testPartsIndexAPI() {
console.log(`\n🎯 Получаем группы для каталога "${firstCatalog.name}"...`);
const groupsResponse = await fetch(
`https://api.parts-index.com/v1/catalogs/${firstCatalog.id}/groups?lang=ru`,
`${process.env.PARTSAPI_URL}/v1/catalogs/${firstCatalog.id}/groups?lang=ru`,
{
headers: {
'Accept': 'application/json',

21
user_input.py Normal file
View File

@ -0,0 +1,21 @@
def main():
while True:
print("\n" + "="*50)
user_input = input("Please provide feedback or next task (type 'stop' to exit): ").strip()
if user_input.lower() == 'stop':
print("Exiting task loop. Thank you!")
break
elif user_input.lower() == '':
print("Please provide some input or type 'stop' to exit.")
continue
else:
print(f"\nReceived input: {user_input}")
print("Processing your request...")
# Here the main process would handle the user's input
return user_input
if __name__ == "__main__":
result = main()
if result and result.lower() != 'stop':
print(f"Next task received: {result}")