@@ -0,0 +1,5 @@ + + + + + @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -0,0 +1,5 @@ + + + + + @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + @@ -0,0 +1,5 @@ + + + + + @@ -0,0 +1,4 @@ + + + + @@ -0,0 +1,20 @@ + + + + + + + + + + + Timeless - Luxury Watches + + + +
+ + @@ -0,0 +1,33 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import styles from './Footer.module.css'; + +const Footer = () => { + return ( + <> +
+ + + ); +}; + +export default Footer; @@ -0,0 +1,72 @@ +.line { + width: 100%; + height: 1px; + background-color: #ddd; + margin: 40px 0 20px 0; +} + +.footer { + display: flex; + justify-content: space-around; + align-items: flex-start; + padding: 40px 50px; + background-color: #f9f9f9; +} + +.footerLogo .logo { + width: 200px; +} + +.footerText ul { + list-style: none; + padding: 0; +} + +.footerText ul li { + margin-bottom: 10px; +} + +.footerText ul li a { + text-decoration: none; + color: #333; + font-size: 14px; + transition: color 0.3s; +} + +.footerText ul li a:hover { + color: #007bff; +} + +.social { + display: flex; + flex-direction: column; + gap: 10px; +} + +.social a { + text-decoration: none; + color: #333; + font-size: 14px; + font-weight: 600; + transition: color 0.3s; +} + +.social a:hover { + color: #007bff; +} + +@media (max-width: 768px) { + .footer { + flex-direction: column; + gap: 30px; + padding: 30px 20px; + } + + .footerLogo { + text-align: center; + } + + .social { + align-items: center; + } +} @@ -0,0 +1,39 @@ +import React from 'react'; +import { Link, useLocation } from 'react-router-dom'; +import { useBasket } from '../../../context/BasketContext'; +import styles from './Navigation.module.css'; + +const Navigation = () => { + const location = useLocation(); + const { getTotalItems } = useBasket(); + const totalItems = getTotalItems(); + + const isActive = (path) => location.pathname === path ? styles.active : ''; + + return ( + + ); +}; + +export default Navigation; @@ -0,0 +1,101 @@ +.cap { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 50px; + background-color: #fff; + border-bottom: 1px solid #eee; +} + +.brand a { + text-decoration: none; + color: #000; + font-size: 24px; + font-weight: 700; +} + +.inputContainer { + flex: 1; + max-width: 400px; + margin: 0 30px; +} + +.input { + width: 500px; + padding: 10px 15px; + border: 2px solid #000000; + border-radius: 5px; + outline: none; + font-size: 14px; +} + +.input:focus { + border-color: #000; +} + +.icons { + display: flex; + gap: 70px; + align-items: center; + position: relative; +} + +.icon { + cursor: pointer; + transition: opacity 0.3s; +} + +.icon:hover { + opacity: 0.7; +} + +.badge { + position: absolute; + top: -8px; + right: -8px; + background-color: #ff4444; + color: white; + border-radius: 50%; + padding: 2px 6px; + font-size: 12px; + font-weight: bold; +} + +.registration { + margin-left: 20px; +} + +.registration a { + text-decoration: none; + color: #000; + font-size: 14px; + font-weight: 500; + padding: 8px 16px; + border: 1px solid #000; + border-radius: 5px; + transition: all 0.3s; +} + +.registration a:hover { + background-color: #000; + color: #fff; +} + +.active { + font-weight: bold; + color: #007bff; +} + +@media (max-width: 768px) { + .cap { + flex-wrap: wrap; + padding: 15px 20px; + } + + .inputContainer { + order: 3; + width: 100%; + max-width: 100%; + margin: 10px 0 0 0; + } +} @@ -0,0 +1,37 @@ +import React from 'react'; +import { useBasket } from '../../../context/BasketContext'; +import styles from './ProductCard.module.css'; + +const ProductCard = ({ product }) => { + const { addToBasket } = useBasket(); + + const handleAddToBasket = () => { + addToBasket(product); + alert(`${product.name} добавлен в корзину!`); + }; + + return ( +
+ {product.name} +
+
+

{product.name}

+
+

{product.brand}

+
+

{product.description}

+
+

${product.price.toLocaleString()}

+ +
+
+ ); +}; + +export default ProductCard; @@ -0,0 +1,88 @@ +.card { + background: white; + border-radius: 10px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + overflow: hidden; + transition: transform 0.3s, box-shadow 0.3s; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; +} + +.card:hover { + transform: translateY(-5px); + box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15); + +} + +.image { + width: 150px; + height: 250px; + object-fit: cover; + +} + +.info { + padding: 20px; + display: flex; + flex-direction: column; + flex: 1; +} + +.name { + font-size: 20px; + font-weight: 600; + margin-bottom: 10px; +} + +.name p { + margin: 0; +} + +.brand { + margin: 5px 0; + color: #666; + font-size: 14px; +} + +.description { + margin: 10px 0; + min-height: 60px; + flex: 1; +} + +.description p { + font-size: 13px; + color: #888; + line-height: 1.4; + margin: 0; +} + +.price { + font-size: 24px; + font-weight: 700; + color: #667eea; + margin: 15px 0; +} + +.button { + width: 100%; + padding: 12px; + background-color: #667eea; + color: white; + border: none; + border-radius: 5px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background-color 0.3s; +} + +.button:hover { + background-color: #5568d3; +} + +.button:active { + transform: scale(0.98); +} @@ -0,0 +1,53 @@ +import React from 'react'; +import styles from './About.module.css'; + +const About = () => { + return ( +
+
+

О нас

+ +
+
+
+

Timeless — искусство вечного времени

+

+ Компания Timeless создаёт элегантные наручные часы, сочетающие в себе безупречный дизайн, + высокое качество и точность. Мы верим, что время — это не просто цифры на циферблате, + а отражение стиля, статуса и индивидуальности. +

+ +

Наша философия

+
    +
  • + Традиции и инновации — в каждой модели гармонично сочетаются классические + элементы и современные технологии. +
  • +
  • + Долговечность — мы используем только премиальные материалы, чтобы часы служили + вам десятилетиями. +
  • +
  • + Уникальный стиль — от минималистичных моделей до сложных механизмов с автоподзаводом. +
  • +
+ +

Почему выбирают Timeless?

+
    +
  • Швейцарское качество — точность и надёжность в каждой детали.
  • +
  • Ручная сборка — внимание к мелочам, которое делает часы произведением искусства.
  • +
  • Эксклюзивные коллекции — ограниченные серии для истинных ценителей.
  • +
+ +

+ Timeless — не просто часы, это наследие, которое вы передадите следующим поколениям. +

+
+
+
+
+
+ ); +}; + +export default About; @@ -0,0 +1,100 @@ +.container { + min-height: 70vh; + padding: 40px 50px; +} + +.main { + max-width: 900px; + margin: 0 auto; +} + +.title { + text-align: center; + font-size: 42px; + margin-bottom: 40px; + color: #333; +} + +.userInfoPanel { + background: white; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + padding: 40px; +} + +.userInfoContent { + line-height: 1.8; +} + +.content h2 { + font-size: 28px; + margin-bottom: 20px; + color: #667eea; +} + +.content h3 { + font-size: 22px; + margin-top: 30px; + margin-bottom: 15px; + color: #333; +} + +.content p { + margin-bottom: 20px; + color: #555; + font-size: 16px; +} + +.content ul { + margin: 20px 0; + padding-left: 20px; +} + +.content ul li { + margin-bottom: 15px; + color: #555; + font-size: 16px; +} + +.checkList { + list-style: none !important; + padding-left: 0 !important; +} + +.checkList li { + padding-left: 30px; + position: relative; +} + +.conclusion { + margin-top: 30px; + font-size: 18px; + font-weight: 600; + text-align: center; + color: #667eea; + padding: 20px; + background: #f0f4ff; + border-radius: 5px; +} + +@media (max-width: 768px) { + .container { + padding: 20px; + } + + .userInfoPanel { + padding: 30px 20px; + } + + .title { + font-size: 32px; + } + + .content h2 { + font-size: 24px; + } + + .content h3 { + font-size: 20px; + } +} @@ -0,0 +1,77 @@ +import React from 'react'; +import { useBasket } from '../../../context/BasketContext'; +import { useUser } from '../../../context/UserContext'; +import styles from './Basket.module.css'; + +const Basket = () => { + const { basketItems, updateQuantity, removeFromBasket, getTotalPrice } = useBasket(); + const { user } = useUser(); + + if (basketItems.length === 0) { + return ( +
+
+

Корзина

+
+ empty basket +

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

+
+
+
+ ); + } + + return ( +
+
+
+
+

Корзина

+ + {user && ( +
+
+
+ profile +
{user.fullName || user.email || 'Имя пользователя'}
+
+
+ Пункт выдачи:
+ Ул. Московская 231к3 +
+
+
+ )} +
+ +
+
+ {basketItems.map(item => ( +
+ {item.name} +
+

{item.name}

+

{item.brand}

+

${item.price.toLocaleString()}

+
+ + {item.quantity} + +
+ +
+
+ ))} +
+
+

Итого: ${getTotalPrice().toLocaleString()}

+ +
+
+
+ ); +}; + +export default Basket; @@ -0,0 +1,205 @@ +.container { + min-height: 70vh; + padding: 20px 50px; +} + +.favoritesHeader { + text-align: center; + margin-bottom: 20px; +} + +.iconLove { + opacity: 0.3; +} + +.main { + margin-bottom: 30px; +} + +.title { + text-align: center; + font-size: 36px; + margin-bottom: 30px; +} + +.userInfoPanel { + background: #f9f9f9; + border-radius: 10px; + padding: 20px; + margin-bottom: 30px; +} + +.userInfoContent { + display: flex; + justify-content: space-between; + align-items: center; +} + +.userProfile { + display: flex; + align-items: center; + gap: 15px; +} + +.username { + font-weight: 600; + font-size: 18px; +} + +.pickupPoint { + text-align: right; + color: #666; + font-size: 14px; +} + +.emptyBasket { + text-align: center; + padding: 60px 20px; +} + +.emptyBasket p { + margin-top: 20px; + font-size: 18px; + color: #666; +} + +.basketMain { + padding: 20px 0; +} + +.basketGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.basketItem { + background: white; + border-radius: 10px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + overflow: hidden; + display: flex; + align-items:center ; +} + +.itemImage { + width: 130px; + height: 200px; + object-fit: cover; +} + +.itemInfo { + padding: 20px; +} + +.itemInfo h3 { + margin: 0 0 10px 0; + font-size: 18px; +} + +.itemInfo p { + margin: 5px 0; + color: #666; +} + +.price { + font-size: 20px; + font-weight: 700; + color: #667eea; + margin: 10px 0 !important; +} + +.quantityControl { + display: flex; + align-items: center; + gap: 15px; + margin: 15px 0; +} + +.quantityControl button { + width: 30px; + height: 30px; + border: 1px solid #ddd; + background: white; + border-radius: 5px; + cursor: pointer; + font-size: 18px; + transition: all 0.3s; +} + +.quantityControl button:hover { + background: #667eea; + color: white; + border-color: #667eea; +} + +.quantityControl span { + font-size: 16px; + font-weight: 600; + min-width: 30px; + text-align: center; +} + +.removeBtn { + width: 100%; + padding: 10px; + background: #ff4444; + color: white; + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + transition: background 0.3s; +} + +.removeBtn:hover { + background: #cc0000; +} + +.totalSection { + text-align: center; + padding: 30px; + background: #f9f9f9; + border-radius: 10px; +} + +.totalSection h2 { + margin-bottom: 20px; + color: #667eea; +} + +.checkoutBtn { + padding: 15px 40px; + background: #667eea; + color: white; + border: none; + border-radius: 5px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.3s; +} + +.checkoutBtn:hover { + background: #5568d3; +} + +@media (max-width: 768px) { + .container { + padding: 20px; + } + + .basketGrid { + grid-template-columns: 1fr; + } + + .userInfoContent { + flex-direction: column; + gap: 15px; + } + + .pickupPoint { + text-align: center; + } +} @@ -0,0 +1,100 @@ +import React from 'react'; +import styles from './FAQ.module.css'; + +const FAQ = () => { + const faqData = [ + { + category: '1. О продуктах', + questions: [ + { + q: 'Какие материалы используются в часах Timeless?', + a: 'Мы используем высококачественную нержавеющую сталь 316L, сапфировые стёкла, итальянскую кожу и швейцарские механизмы.' + }, + { + q: 'Есть ли водозащита?', + a: 'Да, большинство моделей имеют водонепроницаемость от 50м (5ATM) до 300м (дайверские часы).' + }, + { + q: 'Где производятся часы?', + a: 'Дизайн разрабатывается в Европе, сборка осуществляется на швейцарских и японских мануфактурах.' + } + ] + }, + { + category: '2. Заказ и доставка', + questions: [ + { + q: 'Как оформить заказ?', + a: 'Выберите модель → добавьте в корзину → укажите адрес → оплатите удобным способом (карта, Apple/Google Pay, криптовалюта).' + }, + { + q: 'Какие способы доставки доступны?', + a: 'Курьерская доставка (1–3 дня). Самовывоз из фирменных бутиков. Международная доставка (DHL/FedEx, сроки уточняйте).' + }, + { + q: 'Можно ли изменить/отменить заказ?', + a: 'Да, в течение 24 часов после оформления (напишите нам на support@timeless.com).' + } + ] + }, + { + category: '3. Оплата', + questions: [ + { + q: 'Какие платежи принимаются?', + a: 'VISA, Mastercard, PayPal, банковские переводы, BTC/ETH (для VIP-клиентов).' + }, + { + q: 'Безопасна ли оплата на сайте?', + a: 'Да, мы используем шифрование SSL и PCI DSS-совместимые платёжные шлюзы.' + } + ] + }, + { + category: '4. Гарантия и возврат', + questions: [ + { + q: 'Есть ли гарантия?', + a: 'Да, 2 года на механизмы и 1 год на аксессуары (брак, заводские дефекты).' + }, + { + q: 'Как вернуть часы?', + a: 'При наличии чека и целой упаковки — в течение 14 дней. Подробнее в разделе «Возврат».' + } + ] + }, + { + category: '5. Уход за часами', + questions: [ + { + q: 'Как чистить часы?', + a: 'Кожаный ремешок: сухая мягкая ткань. Корпус из стали: мыльный раствор (без погружения!). Избегайте магнитов, ударов и резких перепадов температур.' + } + ] + } + ]; + + return ( +
+
+

FAQ

+ +
+ {faqData.map((section, idx) => ( +
+

{section.category}

+ {section.questions.map((item, qIdx) => ( +
+

Q: {item.q}

+

A: {item.a}

+
+ ))} +
+ ))} +
+
+
+ ); +}; + +export default FAQ; @@ -0,0 +1,82 @@ +.container { + min-height: 70vh; + padding: 40px 50px; +} + +.main { + max-width: 900px; + margin: 0 auto; +} + +.title { + text-align: center; + font-size: 42px; + margin-bottom: 40px; + color: #333; +} + +.faqPanel { + background: white; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + padding: 40px; +} + +.faqSection { + margin-bottom: 40px; +} + +.faqSection:last-child { + margin-bottom: 0; +} + +.faqSection h2 { + font-size: 24px; + color: #667eea; + margin-bottom: 20px; + padding-bottom: 10px; + border-bottom: 2px solid #667eea; +} + +.faqItem { + margin-bottom: 25px; + padding: 20px; + background: #f9f9f9; + border-radius: 5px; +} + +.faqItem:last-child { + margin-bottom: 0; +} + +.faqItem h3 { + font-size: 16px; + color: #333; + margin-bottom: 10px; + font-weight: 600; +} + +.faqItem p { + font-size: 15px; + color: #555; + line-height: 1.6; + margin: 0; +} + +@media (max-width: 768px) { + .container { + padding: 20px; + } + + .faqPanel { + padding: 30px 20px; + } + + .title { + font-size: 32px; + } + + .faqSection h2 { + font-size: 20px; + } +} @@ -0,0 +1,32 @@ +import React from 'react'; +import { products } from '../../../utils/products'; +import ProductCard from '../../common/ProductCard/ProductCard'; +import styles from './Home.module.css'; + +const Home = () => { + return ( +
+
+
+

+ Время стильно +
+ Точно +
+ Эксклюзивно +

+
+
+
+

Продукция

+
+ {products.map(product => ( + + ))} +
+
+
+ ); +}; + +export default Home; @@ -0,0 +1,51 @@ +.homePage { + min-height: 80vh; +} + +.header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 80px 50px; + text-align: center; +} + +.slogan h1 { + font-size: 48px; + font-weight: 700; + line-height: 1.3; + margin: 0; +} + +.main { + padding: 40px 50px; +} + +.main h4 { + font-size: 32px; + margin-bottom: 30px; + text-align: center; +} + +.items { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 30px; +} + +@media (max-width: 768px) { + .header { + padding: 40px 20px; + } + + .slogan h1 { + font-size: 32px; + } + + .main { + padding: 30px 20px; + } + + .items { + grid-template-columns: 1fr; + } +} @@ -0,0 +1,86 @@ +import React from 'react'; +import styles from './Policy.module.css'; + +const Policy = () => { + return ( +
+
+

Политика и Конфиденциальность

+ +
+
+

1. Сбор информации

+

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

+
+ +
+

2. Использование информации

+

Собранная информация используется для:

+
    +
  • Обработки и выполнения ваших заказов
  • +
  • Улучшения качества обслуживания клиентов
  • +
  • Отправки периодических email-рассылок о новых продуктах и специальных предложениях
  • +
  • Персонализации вашего опыта использования сайта
  • +
+
+ +
+

3. Защита информации

+

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

+
+ +
+

4. Использование файлов cookie

+

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

+
+ +
+

5. Раскрытие информации третьим лицам

+

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

+
+ +
+

6. Ваши права

+

Вы имеете право:

+
    +
  • Запросить доступ к вашим персональным данным
  • +
  • Запросить исправление неточных данных
  • +
  • Запросить удаление ваших данных
  • +
  • Отказаться от получения маркетинговых материалов
  • +
+
+ +
+

7. Контактная информация

+

+ Если у вас есть вопросы относительно нашей политики конфиденциальности, вы можете связаться с нами: +

+

Email: privacy@timeless.com

+

Телефон: +7 (800) 555-35-35

+
+ +

+ Последнее обновление: 24 декабря 2025 г. +

+
+
+
+ ); +}; + +export default Policy; @@ -0,0 +1,98 @@ +.container { + min-height: 70vh; + padding: 40px 50px; +} + +.main { + max-width: 900px; + margin: 0 auto; +} + +.title { + text-align: center; + font-size: 42px; + margin-bottom: 40px; + color: #333; +} + +.policyPanel { + background: white; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + padding: 40px; +} + +.policySection { + margin-bottom: 35px; +} + +.policySection:last-of-type { + margin-bottom: 20px; +} + +.policySection h2 { + font-size: 22px; + color: #667eea; + margin-bottom: 15px; +} + +.policySection p { + font-size: 16px; + color: #555; + line-height: 1.7; + margin-bottom: 15px; +} + +.policySection ul { + margin: 15px 0; + padding-left: 30px; +} + +.policySection ul li { + margin-bottom: 10px; + color: #555; + font-size: 16px; + line-height: 1.6; +} + +.policySection a { + color: #667eea; + text-decoration: none; +} + +.policySection a:hover { + text-decoration: underline; +} + +.updateDate { + text-align: center; + font-style: italic; + color: #888; + font-size: 14px; + margin-top: 30px; + padding-top: 20px; + border-top: 1px solid #eee; +} + +@media (max-width: 768px) { + .container { + padding: 20px; + } + + .policyPanel { + padding: 30px 20px; + } + + .title { + font-size: 32px; + } + + .policySection h2 { + font-size: 20px; + } + + .policySection p, + .policySection ul li { + font-size: 15px; + } +} @@ -0,0 +1,166 @@ +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { useUser } from '../../../context/UserContext'; +import { useNavigate } from 'react-router-dom'; +import styles from './Profile.module.css'; + +const Profile = () => { + const { user, updateUser, isAuthenticated, logout } = useUser(); + const navigate = useNavigate(); + const { register, handleSubmit, formState: { errors } } = useForm({ + defaultValues: { + firstName: user?.firstName || '', + lastName: user?.lastName || '', + gender: user?.gender || '', + cardNumber: user?.cardNumber || '', + cardExpiry: user?.cardExpiry || '', + cardCVV: user?.cardCVV || '' + } + }); + + React.useEffect(() => { + if (!isAuthenticated) { + navigate('/registration'); + } + }, [isAuthenticated, navigate]); + + const onSubmit = (data) => { + updateUser(data); + alert('Профиль обновлен!'); + }; + + const handleLogout = () => { + logout(); + navigate('/'); + }; + + if (!isAuthenticated) { + return null; + } + + return ( +
+
+
+

Профиль

+ profile + +
+
+
+ + {errors.firstName && {errors.firstName.message}} +
+ +
+ + {errors.lastName && {errors.lastName.message}} +
+ +
+ + +
+ {errors.gender && {errors.gender.message}} + + +
+
+
+ +
+
+

Ваши реквизиты

+
+
+ + {errors.cardNumber && {errors.cardNumber.message}} +
+ +
+
+ + {errors.cardExpiry && {errors.cardExpiry.message}} +
+ +
+ + {errors.cardCVV && {errors.cardCVV.message}} +
+
+
+
+
+ + +
+
+ ); +}; + +export default Profile; @@ -0,0 +1,192 @@ +.profilePage { + min-height: 70vh; + padding: 40px 50px; +} + +.main { + max-width: 1200px; + margin: 0 auto; + display: flex; + flex-direction: column; + gap: 40px; +} + +.profileInfo { + background: white; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + padding: 40px; + text-align: center; +} + +.profileInfo h3 { + font-size: 32px; + margin-bottom: 20px; +} + +.icon { + margin-bottom: 30px; +} + +.form { + width: 100%; +} + +.inputContainer { + display: flex; + flex-direction: column; + gap: 20px; + max-width: 400px; + margin: 0 auto; +} + +.formGroup { + display: flex; + flex-direction: column; + gap: 5px; +} + +.input { + width: 100%; + padding: 15px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + outline: none; + transition: border-color 0.3s; + box-sizing: border-box; +} + +.input:focus { + border-color: #667eea; +} + +.input.error { + border-color: #ff4444; +} + +.errorText { + color: #ff4444; + font-size: 12px; + text-align: left; +} + +.genderGroup { + display: flex; + gap: 30px; + justify-content: center; +} + +.genderGroup label { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; +} + +.genderGroup input[type="radio"] { + width: 20px; + height: 20px; + cursor: pointer; +} + +.saveButton { + width: 100%; + padding: 15px; + background: #667eea; + color: white; + border: none; + border-radius: 5px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.3s; +} + +.saveButton:hover { + background: #5568d3; +} + +.paymentInfo { + background: white; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + padding: 40px; +} + +.cart h4 { + font-size: 24px; + margin-bottom: 20px; + text-align: center; +} + +.inputCart1 { + width: 100%; + padding: 15px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + outline: none; + margin-bottom: 15px; + transition: border-color 0.3s; +} + +.inputCart1:focus { + border-color: #667eea; +} + +.cardDetails { + display: flex; + gap: 15px; +} + +.inputCart2 { + width: 100%; + padding: 15px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + outline: none; + transition: border-color 0.3s; +} + +.inputCart2:focus { + border-color: #667eea; +} + +.inputCart2.error, +.inputCart1.error { + border-color: #ff4444; +} + +.logoutButton { + padding: 15px 40px; + background: #ff4444; + color: white; + border: none; + border-radius: 5px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.3s; + align-self: center; +} + +.logoutButton:hover { + background: #cc0000; +} + +@media (max-width: 768px) { + .profilePage { + padding: 20px; + } + + .profileInfo, + .paymentInfo { + padding: 30px 20px; + } + + .cardDetails { + flex-direction: column; + } +} @@ -0,0 +1,116 @@ +import React from 'react'; +import { useForm } from 'react-hook-form'; +import { useNavigate } from 'react-router-dom'; +import { useUser } from '../../../context/UserContext'; +import styles from './Registration.module.css'; + +const Registration = () => { + const { register: registerForm, handleSubmit, formState: { errors }, watch } = useForm(); + const { register } = useUser(); + const navigate = useNavigate(); + + const onSubmit = (data) => { + const userData = { + fullName: data.fullName, + email: data.email, + phone: data.phone, + password: data.password + }; + register(userData); + alert('Регистрация успешна!'); + navigate('/profile'); + }; + + return ( +
+
+

Регистрация

+
+
+ + {errors.fullName && {errors.fullName.message}} +
+ +
+ + {errors.email && {errors.email.message}} +
+ +
+ + {errors.phone && {errors.phone.message}} +
+ +
+ + {errors.password && {errors.password.message}} +
+ +
+ + value === watch('password') || 'Пароли не совпадают' + })} + /> + {errors.confirmPassword && {errors.confirmPassword.message}} +
+ + +
+
+
+ ); +}; + +export default Registration; @@ -0,0 +1,93 @@ +.registrationPage { + min-height: 70vh; + display: flex; + justify-content: center; + align-items: center; + padding: 40px 20px; +} + +.main { + background: white; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + padding: 40px; + max-width: 500px; + width: 100%; +} + +.main h4 { + font-size: 32px; + text-align: center; + margin-bottom: 30px; + color: #333; +} + +.form { + display: flex; + flex-direction: column; + gap: 20px; +} + +.formGroup { + display: flex; + flex-direction: column; + gap: 5px; +} + +.input { + width: 100%; + padding: 15px; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + outline: none; + transition: border-color 0.3s; + box-sizing: border-box; +} + +.input:focus { + border-color: #667eea; +} + +.input.error { + border-color: #ff4444; +} + +.errorText { + color: #ff4444; + font-size: 12px; + margin-top: 5px; +} + +.saveButton { + width: 100%; + padding: 15px; + background: #667eea; + color: white; + border: none; + border-radius: 5px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: background 0.3s; + margin-top: 10px; +} + +.saveButton:hover { + background: #5568d3; +} + +.saveButton:disabled { + background: #ccc; + cursor: not-allowed; +} + +@media (max-width: 768px) { + .main { + padding: 30px 20px; + } + + .main h4 { + font-size: 24px; + } +} @@ -0,0 +1,80 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; + +const BasketContext = createContext(); + +export const useBasket = () => { + const context = useContext(BasketContext); + if (!context) { + throw new Error('useBasket must be used within BasketProvider'); + } + return context; +}; + +export const BasketProvider = ({ children }) => { + const [basketItems, setBasketItems] = useState(() => { + const saved = localStorage.getItem('basket'); + return saved ? JSON.parse(saved) : []; + }); + + useEffect(() => { + localStorage.setItem('basket', JSON.stringify(basketItems)); + }, [basketItems]); + + const addToBasket = (product) => { + setBasketItems(prev => { + const existing = prev.find(item => item.id === product.id); + if (existing) { + return prev.map(item => + item.id === product.id + ? { ...item, quantity: item.quantity + 1 } + : item + ); + } + return [...prev, { ...product, quantity: 1 }]; + }); + }; + + const removeFromBasket = (productId) => { + setBasketItems(prev => prev.filter(item => item.id !== productId)); + }; + + const updateQuantity = (productId, quantity) => { + if (quantity <= 0) { + removeFromBasket(productId); + return; + } + setBasketItems(prev => + prev.map(item => + item.id === productId ? { ...item, quantity } : item + ) + ); + }; + + const clearBasket = () => { + setBasketItems([]); + }; + + const getTotalPrice = () => { + return basketItems.reduce((total, item) => total + (item.price * item.quantity), 0); + }; + + const getTotalItems = () => { + return basketItems.reduce((total, item) => total + item.quantity, 0); + }; + + return ( + + {children} + + ); +}; @@ -0,0 +1,67 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; + +const UserContext = createContext(); + +export const useUser = () => { + const context = useContext(UserContext); + if (!context) { + throw new Error('useUser must be used within UserProvider'); + } + return context; +}; + +export const UserProvider = ({ children }) => { + const [user, setUser] = useState(() => { + const saved = localStorage.getItem('user'); + return saved ? JSON.parse(saved) : null; + }); + + const [isAuthenticated, setIsAuthenticated] = useState(!!user); + + useEffect(() => { + if (user) { + localStorage.setItem('user', JSON.stringify(user)); + setIsAuthenticated(true); + } else { + localStorage.removeItem('user'); + setIsAuthenticated(false); + } + }, [user]); + + const login = (userData) => { + setUser(userData); + }; + + const logout = () => { + setUser(null); + }; + + const updateUser = (updates) => { + setUser(prev => prev ? { ...prev, ...updates } : null); + }; + + const register = (userData) => { + const newUser = { + ...userData, + id: Date.now(), + createdAt: new Date().toISOString() + }; + setUser(newUser); + return newUser; + }; + + return ( + + {children} + + ); +}; @@ -0,0 +1,83 @@ +export const products = [ + { + id: 1, + image: "Collection Moon Phase.svg", + name: "Collection Moon Phase", + brand: "Longines Elegant", + description: "- это швейцарский часовой бренд, входящий в Swatch Group...", + price: 10000, + link: "collection-moon-phase" + }, + { + id: 2, + image: "Conquest.svg", + name: "Conquest", + brand: "Longines", + description: "- это швейцарский часовой бренд с богатой историей, входящий...", + price: 15500, + link: "conquest" + }, + { + id: 3, + image: "Defy Classic.svg", + name: "Defy Classic", + brand: "Zenith", + description: "- это швейцарский бренд, известный своими высокоточными...", + price: 11500, + link: "defy-classic" + }, + { + id: 4, + image: "Hampden.svg", + name: "Hampden", + brand: "Hampden", + description: "- это бренд, возрожденный в наше время...", + price: 14500, + link: "hampden" + }, + { + id: 5, + image: "Marvin.svg", + name: "Marvin", + brand: "Marvin", + description: "- это швейцарский часовой бренд с богатой историей, основанный...", + price: 25500, + link: "marvin" + }, + { + id: 6, + image: "Oyster Perpetual.svg", + name: "Oyster Perpetual", + brand: "Rolex", + description: "- это классическая модель Rolex, известная своей...", + price: 10500, + link: "oyster-perpetual" + }, + { + id: 7, + image: "Paul Hewitt.svg", + name: "Paul Hewitt", + brand: "Sailor Line", + description: "- это немецкий бренд, известный своими часами и аксессуарами...", + price: 15500, + link: "paul-hewitt" + }, + { + id: 8, + image: "Sky-Dweller.svg", + name: "Sky-Dweller", + brand: "Rolex", + description: "- это один из самых сложных и техничных...", + price: 25500, + link: "sky-dweller" + }, + { + id: 9, + image: "Speedmaster.svg", + name: "Speedmaster", + brand: "Omega", + description: "- это швейцарский бренд класса люкс...", + price: 19500, + link: "speedmaster" + } +]; @@ -0,0 +1,37 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.App { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.main-content { + flex: 1; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +a { + text-decoration: none; + color: inherit; +} + +button { + font-family: inherit; +} @@ -0,0 +1,42 @@ +import React from 'react'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { BasketProvider } from './context/BasketContext'; +import { UserProvider } from './context/UserContext'; +import Navigation from './components/common/Navigation/Navigation'; +import Footer from './components/common/Footer/Footer'; +import Home from './components/pages/Home/Home'; +import About from './components/pages/About/About'; +import Basket from './components/pages/Basket/Basket'; +import FAQ from './components/pages/FAQ/FAQ'; +import Profile from './components/pages/Profile/Profile'; +import Registration from './components/pages/Registration/Registration'; +import Policy from './components/pages/Policy/Policy'; +import './App.css'; + +function App() { + return ( + + + +
+ +
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+
+
+
+
+ ); +} + +export default App; @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* @@ -0,0 +1,70 @@ +# Getting Started with Create React App + +This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). + +## Available Scripts + +In the project directory, you can run: + +### `npm start` + +Runs the app in the development mode.\ +Open [http://localhost:3000](http://localhost:3000) to view it in your browser. + +The page will reload when you make changes.\ +You may also see any lint errors in the console. + +### `npm test` + +Launches the test runner in the interactive watch mode.\ +See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. + +### `npm run build` + +Builds the app for production to the `build` folder.\ +It correctly bundles React in production mode and optimizes the build for the best performance. + +The build is minified and the filenames include the hashes.\ +Your app is ready to be deployed! + +See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. + +### `npm run eject` + +**Note: this is a one-way operation. Once you `eject`, you can't go back!** + +If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. + +Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. + +You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. + +## Learn More + +You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). + +To learn React, check out the [React documentation](https://reactjs.org/). + +### Code Splitting + +This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) + +### Analyzing the Bundle Size + +This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) + +### Making a Progressive Web App + +This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) + +### Advanced Configuration + +This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) + +### Deployment + +This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) + +### `npm run build` fails to minify + +This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) @@ -0,0 +1,41 @@ +{ + "name": "my-app", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.1", + "@testing-library/user-event": "^13.5.0", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-hook-form": "^7.49.2", + "react-router-dom": "^6.21.1", + "react-scripts": "5.0.1", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +}