Centralize and update contact information across app

Introduced a centralized contacts configuration in src/config/contacts.js, added a useContacts hook, and a reusable ContactInfo component. Updated Header, Footer, Home, Office, Services, About, Objects, and Apartament pages to use the new contact data source. Added documentation in CONTACTS_CONFIG.md and included the AlmaVid logo asset.
This commit is contained in:
Madara0330E
2025-07-16 23:16:00 +05:00
parent 2af795f819
commit 4adbf791ea
20 changed files with 2007 additions and 838 deletions

View File

@@ -0,0 +1,78 @@
import { CONTACTS } from "../../config/contacts";
import "./ContactInfo.scss";
function ContactInfo({ variant = "default" }) {
if (variant === "inline") {
return (
<div className="contact-info-inline">
<span className="contact-info__phone">
<a href={CONTACTS.phone.link}>{CONTACTS.phone.display}</a>
</span>
<span className="contact-info__email">
<a href={CONTACTS.email.link}>{CONTACTS.email.display}</a>
</span>
</div>
);
}
if (variant === "full") {
return (
<div className="contact-info-full">
<div className="contact-info__item">
<span className="contact-info__label">Телефон:</span>
<a href={CONTACTS.phone.link} className="contact-info__value">
{CONTACTS.phone.display}
</a>
</div>
<div className="contact-info__item">
<span className="contact-info__label">E-mail:</span>
<a href={CONTACTS.email.link} className="contact-info__value">
{CONTACTS.email.display}
</a>
</div>
<div className="contact-info__item">
<span className="contact-info__label">Адрес:</span>
<span className="contact-info__value">
{CONTACTS.address.main.full}
</span>
</div>
<div className="contact-info__item">
<span className="contact-info__label">Соц.сети:</span>
<div className="contact-info__social">
<a
href={CONTACTS.social.vk.url}
className="contact-info__social-link"
>
{CONTACTS.social.vk.name}
</a>
<a
href={CONTACTS.social.telegram.url}
className="contact-info__social-link"
>
{CONTACTS.social.telegram.name}
</a>
<a
href={CONTACTS.social.instagram.url}
className="contact-info__social-link"
>
{CONTACTS.social.instagram.name}
</a>
</div>
</div>
</div>
);
}
return (
<div className="contact-info">
<a href={CONTACTS.phone.link} className="contact-info__phone">
{CONTACTS.phone.display}
</a>
<a href={CONTACTS.email.link} className="contact-info__email">
{CONTACTS.email.display}
</a>
</div>
);
}
export { ContactInfo };

View File

@@ -0,0 +1,72 @@
.contact-info {
display: flex;
gap: 1rem;
align-items: center;
&__phone,
&__email {
text-decoration: none;
color: inherit;
font-weight: 600;
transition: color 0.3s ease;
&:hover {
color: #007bff;
}
}
}
.contact-info-inline {
display: flex;
gap: 2rem;
align-items: center;
.contact-info__phone,
.contact-info__email {
a {
text-decoration: none;
color: inherit;
font-weight: 600;
transition: color 0.3s ease;
&:hover {
color: #007bff;
}
}
}
}
.contact-info-full {
.contact-info__item {
margin-bottom: 1rem;
.contact-info__label {
font-weight: bold;
margin-right: 0.5rem;
}
.contact-info__value {
text-decoration: none;
color: inherit;
&:hover {
color: #007bff;
}
}
}
.contact-info__social {
display: flex;
gap: 1rem;
&-link {
text-decoration: none;
color: inherit;
transition: color 0.3s ease;
&:hover {
color: #007bff;
}
}
}
}

View File

@@ -1,38 +1,58 @@
import './Footer.scss';
import { Link, useNavigate } from 'react-router-dom';
import "./Footer.scss";
import { Link, useNavigate } from "react-router-dom";
import { CONTACTS } from "../../config/contacts";
function Footer() {
const navigate = useNavigate();
const navigate = useNavigate();
const handleClick = () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
const handleClick = () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
};
const handlePageChange = (path) => {
handleClick();
navigate(path);
};
const handlePageChange = (path) => {
handleClick();
navigate(path);
};
return (
<footer className="footer">
<Link to="/" className="footer__logo font-inter-regular"
onClick={() => handlePageChange('/')}>OOO "ALMA-VID"</Link>
<div className="footer-info font-inter-bold">
<Link to="/objects" onClick={() => handlePageChange('/objects')}>Объекты</Link>
<Link to="/services" onClick={() => handlePageChange('/services')}>Услуги</Link>
<Link to="/about" onClick={() => handlePageChange('/about')}>О компании</Link>
<Link to="/office" onClick={() => handlePageChange('/office')}>Контакты</Link>
</div>
<div className="footer-socials">
<a className="footer-socials__tg" href="#"></a>
<a className="footer-socials__inst" href="#"></a>
<a className="footer-socials__vk" href="#"></a>
</div>
</footer>
);
return (
<footer className="footer">
<Link
to="/"
className="footer__logo font-inter-regular"
onClick={() => handlePageChange("/")}
>
OOO "ALMA-VID"
</Link>
<div className="footer-info font-inter-bold">
<Link to="/objects" onClick={() => handlePageChange("/objects")}>
Объекты
</Link>
<Link to="/services" onClick={() => handlePageChange("/services")}>
Услуги
</Link>
<Link to="/about" onClick={() => handlePageChange("/about")}>
О компании
</Link>
<Link to="/office" onClick={() => handlePageChange("/office")}>
Контакты
</Link>
</div>
<div className="footer-socials">
<a
className="footer-socials__tg"
href={CONTACTS.social.telegram.url}
></a>
<a
className="footer-socials__inst"
href={CONTACTS.social.instagram.url}
></a>
<a className="footer-socials__vk" href={CONTACTS.social.vk.url}></a>
</div>
</footer>
);
}
export { Footer };

View File

@@ -1,89 +1,213 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect } from "react";
import './Form.scss';
import PencilIcon from '../../assets/images/icons/pencil.svg';
import "./Form.scss";
import PencilIcon from "../../assets/images/icons/pencil.svg";
import { API_CONFIG } from "../../config/contacts";
function Form({ scrolledThreshold }) {
const [name, setName] = useState('');
const [phone, setPhone] = useState('');
const [isVisible, setVisible] = useState(false);
const [name, setName] = useState("");
const [phone, setPhone] = useState("");
const [isVisible, setVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [submitStatus, setSubmitStatus] = useState(null); // 'success', 'error', null
const checkVisible = () => {
const scrolled = document.documentElement.scrollTop;
if (scrolled > scrolledThreshold){ // вы можете настроить это значение
if(!isVisible) setVisible(true);
} else{
if(isVisible) setVisible(false);
}
};
const checkVisible = () => {
const scrolled = document.documentElement.scrollTop;
if (scrolled > scrolledThreshold) {
// вы можете настроить это значение
if (!isVisible) setVisible(true);
} else {
if (isVisible) setVisible(false);
}
};
useEffect(() => {
window.addEventListener('scroll', checkVisible);
return () => window.removeEventListener('scroll', checkVisible);
});
useEffect(() => {
window.addEventListener("scroll", checkVisible);
return () => window.removeEventListener("scroll", checkVisible);
});
const handleSubmit = (e) => {
e.preventDefault();
// в этой функции можно добавить логику для отправки формы на сервер
console.log(`${name}, ваш номер телефона: ${phone}`);
setName('');
setPhone('');
};
// Автоматически скрываем сообщение об успехе через 5 секунд
useEffect(() => {
if (submitStatus === "success") {
const timer = setTimeout(() => {
setSubmitStatus(null);
}, 5000);
return () => clearTimeout(timer);
}
}, [submitStatus]);
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
// Функция для форматирования номера телефона
const formatPhoneNumber = (value) => {
// Убираем все нецифровые символы
const phoneNumber = value.replace(/\D/g, "");
// Ограничиваем длину номера
if (phoneNumber.length > 11) {
return phone; // возвращаем предыдущее значение, если превышена длина
}
return (
<section className="consultation-form font-inter-regular">
<div className="consultation-form__info form-info">
<h2 className="form-info__title font-inter-bold">
Решили купить или продать квартиру?
<span>Закажите бесплатную консультацию</span>
</h2>
<form onSubmit={handleSubmit}>
<p className="form__title">Заполните форму ниже</p>
<p>Мы позвоним вам в ближайшее время</p>
<div>
<input
className="font-inter-regular"
type="text"
value={name}
placeholder="Ваше имя"
onChange={(e) => setName(e.target.value)}
/>
<input
className="font-inter-regular"
type="tel"
value={phone}
placeholder="+7(800)555-35-35"
onChange={(e) => setPhone(e.target.value)}
/>
<button
className="form-btn font-inter-regular"
type="submit"
>
Записаться на консультацию
</button>
</div>
<p>
Заполняя форму, вы соглашаетесь с политикой
конфиденциальности
</p>
</form>
<div className="que-form">
<div className={`arrow ${isVisible ? 'arrow__visible' : ''}`} onClick={scrollToTop}></div>
<p className="font-inter-bold">
У вас остались вопросы? <span>Напишите нам, мы онлайн!</span>
</p>
<img className="que__img" src={PencilIcon} alt="pencil" />
</div>
// Форматируем номер
if (phoneNumber.length === 0) return "";
if (phoneNumber.length <= 1) return `+7`;
if (phoneNumber.length <= 4) return `+7(${phoneNumber.slice(1)}`;
if (phoneNumber.length <= 7)
return `+7(${phoneNumber.slice(1, 4)})${phoneNumber.slice(4)}`;
if (phoneNumber.length <= 9)
return `+7(${phoneNumber.slice(1, 4)})${phoneNumber.slice(
4,
7
)}-${phoneNumber.slice(7)}`;
return `+7(${phoneNumber.slice(1, 4)})${phoneNumber.slice(
4,
7
)}-${phoneNumber.slice(7, 9)}-${phoneNumber.slice(9, 11)}`;
};
const handlePhoneChange = (e) => {
const formattedPhone = formatPhoneNumber(e.target.value);
setPhone(formattedPhone);
};
const handleSubmit = async (e) => {
e.preventDefault();
// Проверяем, что поля заполнены
if (!name.trim() || !phone.trim()) {
setSubmitStatus("error");
return;
}
setIsLoading(true);
setSubmitStatus(null);
try {
const response = await fetch(
API_CONFIG.getFullURL(API_CONFIG.endpoints.feedback),
{
method: "POST",
headers: {
"Content-Type": "application/json",
accept: "*/*",
},
body: JSON.stringify({
firstName: name.trim(),
phoneNumber: phone.trim().replace(/\D/g, ""), // убираем все нецифровые символы
}),
}
);
if (response.ok) {
setSubmitStatus("success");
setName("");
setPhone("");
console.log("Заявка успешно отправлена");
} else {
throw new Error("Ошибка при отправке заявки");
}
} catch (error) {
console.error("Ошибка при отправке формы:", error);
setSubmitStatus("error");
} finally {
setIsLoading(false);
}
};
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
return (
<section className="consultation-form font-inter-regular">
<div className="consultation-form__info form-info">
<h2 className="form-info__title font-inter-bold">
Решили купить или продать квартиру?
<span>Закажите бесплатную консультацию</span>
</h2>
<form onSubmit={handleSubmit}>
<p className="form__title">Заполните форму ниже</p>
<p>Мы позвоним вам в ближайшее время</p>
{submitStatus === "success" && (
<div
style={{
color: "green",
marginBottom: "15px",
padding: "10px",
backgroundColor: "#d4edda",
border: "1px solid #c3e6cb",
borderRadius: "4px",
}}
>
Спасибо! Ваша заявка отправлена. Мы свяжемся с вами в ближайшее
время.
</div>
</section>
);
)}
{submitStatus === "error" && (
<div
style={{
color: "red",
marginBottom: "15px",
padding: "10px",
backgroundColor: "#f8d7da",
border: "1px solid #f5c6cb",
borderRadius: "4px",
}}
>
Ошибка при отправке. Пожалуйста, проверьте данные и попробуйте
снова.
</div>
)}
<div>
<input
className="font-inter-regular"
type="text"
value={name}
placeholder="Ваше имя"
onChange={(e) => setName(e.target.value)}
required
disabled={isLoading}
/>
<input
className="font-inter-regular"
type="tel"
value={phone}
placeholder="+7(800)555-35-35"
onChange={handlePhoneChange}
required
disabled={isLoading}
/>
<button
className="form-btn font-inter-regular"
type="submit"
disabled={isLoading || !name.trim() || !phone.trim()}
style={{
opacity: isLoading ? 0.7 : 1,
cursor: isLoading ? "not-allowed" : "pointer",
}}
>
{isLoading ? "Отправляем..." : "Записаться на консультацию"}
</button>
</div>
<p>Заполняя форму, вы соглашаетесь с политикой конфиденциальности</p>
</form>
<div className="que-form">
<div
className={`arrow ${isVisible ? "arrow__visible" : ""}`}
onClick={scrollToTop}
></div>
<p className="font-inter-bold">
У вас остались вопросы? <span>Напишите нам, мы онлайн!</span>
</p>
<img className="que__img" src={PencilIcon} alt="pencil" />
</div>
</div>
</section>
);
}
export { Form };

View File

@@ -1,86 +1,117 @@
import './Header.scss';
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import "./Header.scss";
import React, { useState } from "react";
import { Link, useLocation } from "react-router-dom";
import { CONTACTS } from "../../config/contacts";
function Header() {
const [isOpen, setIsOpen] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const handleToggle = () => {
setIsOpen(!isOpen);
};
const handleToggle = () => {
setIsOpen(!isOpen);
};
const location = useLocation();
const isGradientBackground = location.pathname === '/apartament';
const isHomePage = location.pathname === '/';
return (
<header className={`header font-inter-bold ${isGradientBackground ? 'header_gradient' : ''}`}>
<Link className="header__logo" to="/"></Link>
{isHomePage && <div className="header__text"></div>}
<div className="burgerMenu">
<a href="tel:+73512170074" className="header-info__call">8(351)217-00-74</a>
<div className="burger-item">
<input id="toggle" type="checkbox" checked={isOpen} onChange={handleToggle}></input>
<label for="toggle" className={`hamburger ${isOpen ? 'open' : ''}`}>
<div class="top-bun"></div>
<div class="meat"></div>
<div class="bottom-bun"></div>
</label>
<div class="nav">
<div class="nav-wrapper">
<nav className="nav-container">
<Link to="/objects" className="header__link">
Объекты
</Link>
<Link to="/services" className="header__link">
Услуги
</Link>
<Link to="/about" className="header__link">
О компании
</Link>
<Link to="/office" className="header__link" href="#">
Наш офис
</Link>
<p>Челябинск</p>
<div className="header-socials__links">
<a className="header-socials__tg" href="#"></a>
<a className="header-socials__inst" href="#"></a>
<a className="header-socials__vk" href="#"></a>
</div>
</nav>
</div>
</div>
</div>
</div>
<nav className="header__nav">
const location = useLocation();
const isGradientBackground = location.pathname === "/apartament";
const isHomePage = location.pathname === "/";
return (
<header
className={`header font-inter-bold ${
isGradientBackground ? "header_gradient" : ""
}`}
>
<Link className="header__logo" to="/"></Link>
{isHomePage && <div className="header__text"></div>}
<div className="burgerMenu">
<a href={CONTACTS.phone.link} className="header-info__call">
{CONTACTS.phone.display}
</a>
<div className="burger-item">
<input
id="toggle"
type="checkbox"
checked={isOpen}
onChange={handleToggle}
></input>
<label for="toggle" className={`hamburger ${isOpen ? "open" : ""}`}>
<div class="top-bun"></div>
<div class="meat"></div>
<div class="bottom-bun"></div>
</label>
<div class="nav">
<div class="nav-wrapper">
<nav className="nav-container">
<Link to="/objects" className="header__link">
Объекты
Объекты
</Link>
<Link to="/services" className="header__link">
Услуги
Услуги
</Link>
<Link to="/about" className="header__link">
О компании
О компании
</Link>
<Link to="/office" className="header__link" href="#">
Наш офис
Наш офис
</Link>
</nav>
<div className="header-info">
<a href="tel:+73512170074" className="header-info__call">8(351)217-00-74</a>
<button className="header-info__btn" type="button">
Обратный звонок
</button>
</div>
<div className="header-socials">
<span className="header-socials__location">Челябинск</span>
<p>{CONTACTS.address.main.city}</p>
<div className="header-socials__links">
<a className="header-socials__tg" href="#"></a>
<a className="header-socials__inst" href="#"></a>
<a className="header-socials__vk" href="#"></a>
<a
className="header-socials__tg"
href={CONTACTS.social.telegram.url}
></a>
<a
className="header-socials__inst"
href={CONTACTS.social.instagram.url}
></a>
<a
className="header-socials__vk"
href={CONTACTS.social.vk.url}
></a>
</div>
</nav>
</div>
</header>
);
</div>
</div>
</div>
<nav className="header__nav">
<Link to="/objects" className="header__link">
Объекты
</Link>
<Link to="/services" className="header__link">
Услуги
</Link>
<Link to="/about" className="header__link">
О компании
</Link>
<Link to="/office" className="header__link" href="#">
Наш офис
</Link>
</nav>
<div className="header-info">
<a href={CONTACTS.phone.link} className="header-info__call">
{CONTACTS.phone.display}
</a>
<button className="header-info__btn" type="button">
Обратный звонок
</button>
</div>
<div className="header-socials">
<span className="header-socials__location">
{CONTACTS.address.main.city}
</span>
<div className="header-socials__links">
<a
className="header-socials__tg"
href={CONTACTS.social.telegram.url}
></a>
<a
className="header-socials__inst"
href={CONTACTS.social.instagram.url}
></a>
<a className="header-socials__vk" href={CONTACTS.social.vk.url}></a>
</div>
</div>
</header>
);
}
export { Header };

View File

@@ -1,388 +1,391 @@
@import '../../styles/vars.scss';
@import "../../styles/vars.scss";
a {
color: white;
text-decoration: none;
color: white;
text-decoration: none;
}
.header {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr);
padding: 45px;
color: white;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr);
padding: 45px;
color: white;
}
.header a:hover {
text-decoration: underline;
text-decoration: underline;
}
.header_gradient {
background: linear-gradient(180deg, #061A25 0%, rgba(18, 114, 170, 0.7) 150%);
background: linear-gradient(180deg, #061a25 0%, rgba(18, 114, 170, 0.7) 150%);
}
.header__logo {
grid-row: 1/3;
background-image: url('../../assets/images/logo/logo-png.png');
width: 302px;
height: 69px;
background-size: cover;
grid-row: 1/3;
background-image: url("../../assets/images/logo/AlmaVid-Logo.svg");
width: 320px;
height: 69px;
background-size: cover;
}
.header__nav {
display: flex;
align-items: center;
justify-content: center;
column-gap: 47px;
font-size: 17px;
line-height: 29px;
display: flex;
align-items: center;
justify-content: center;
column-gap: 47px;
font-size: 17px;
line-height: 29px;
}
.header-info {
display: flex;
grid-column: 3/3;
align-items: center;
justify-self: end;
column-gap: 23.5px;
display: flex;
grid-column: 3/3;
align-items: center;
justify-self: end;
column-gap: 23.5px;
}
.header-info__btn {
height: 38px;
width: 150px;
font-weight: 700;
font-size: 15px;
color: black;
background-color: rgb(255, 255, 255, 0.8);
border-radius: 16px;
border: none;
cursor: pointer;
height: 38px;
width: 150px;
font-weight: 700;
font-size: 15px;
color: black;
background-color: rgb(255, 255, 255, 0.8);
border-radius: 16px;
border: none;
cursor: pointer;
}
.header-info__btn:hover {
background-color: rgb(255, 255, 255, 0.9);
background-color: rgb(255, 255, 255, 0.9);
}
.header-socials {
grid-row: 2/2;
grid-column: 3/3;
display: flex;
align-items: center;
column-gap: 47px;
justify-self: end;
margin-right: 29px;
margin-top: 10px;
grid-row: 2/2;
grid-column: 3/3;
display: flex;
align-items: center;
column-gap: 47px;
justify-self: end;
margin-right: 29px;
margin-top: 10px;
}
.header-socials__links {
display: flex;
align-items: center;
display: flex;
align-items: center;
}
.header-socials__tg {
background-image: url(../../assets/images/icons/telegram.svg);
width: 22.5px;
height: 19px;
background-size: cover;
background-image: url(../../assets/images/icons/telegram.svg);
width: 22.5px;
height: 19px;
background-size: cover;
}
// ширину и высоту увеличили на 1px
.header-socials__inst {
background-image: url(../../assets/images/icons/inst.svg);
width: 42px;
height: 24px;
background-size: cover;
background-image: url(../../assets/images/icons/inst.svg);
width: 42px;
height: 24px;
background-size: cover;
}
// ширину уменьшили на 1px
.header-socials__vk {
background-image: url(../../assets/images/icons/vk.svg);
width: 32.5px;
height: 34px;
background-size: cover;
background-image: url(../../assets/images/icons/vk.svg);
width: 32.5px;
height: 34px;
background-size: cover;
}
@media (min-width:1800px) {
.header__logo {
width: 372px;
height: 85px;
}
@media (min-width: 1800px) {
.header__logo {
width: 400px;
height: 85px;
}
.header__nav,
.header-info__call,
.header-info__btn,
.header-socials__location {
font-size: 21px;
}
.header__nav,
.header-info__call,
.header-info__btn,
.header-socials__location {
font-size: 21px;
}
.header-info__btn {
width: 210px;
}
.header-info__btn {
width: 210px;
}
.header-socials__tg {
width: 28.5px;
height: 24px;
}
.header-socials__tg {
width: 28.5px;
height: 24px;
}
.header-socials__inst {
width: 47px;
height: 27px;
}
.header-socials__inst {
width: 47px;
height: 27px;
}
.header-socials__vk {
width: 35.5px;
height: 37px;
}
.header-socials__vk {
width: 35.5px;
height: 37px;
}
}
@media (max-width: 1480px) {
.header__nav {
column-gap: 31px;
font-size: 16px;
}
.header__nav {
column-gap: 31px;
font-size: 16px;
}
}
@media (max-width: $desktopWidth) {
.header {
padding: 36px;
}
.header {
padding: 36px;
}
.header__logo {
width: 290px;
height: 62px;
}
.header__logo {
width: 270px;
height: 62px;
}
.header-info,
.header-info__btn {
font-size: 14px;
}
.header-info,
.header-info__btn {
font-size: 14px;
}
.header-info__btn {
height: 35px;
width: 137px;
}
.header-info__btn {
height: 35px;
width: 137px;
}
.header__nav {
column-gap: 17px;
font-size: 14px;
}
.header__nav {
column-gap: 17px;
font-size: 14px;
}
.header-socials {
font-size: 14px;
}
.header-socials {
font-size: 14px;
}
}
@media (max-width: $laptopWidth) {
.header {
grid-template-columns: auto;
padding: 18px;
}
.header {
grid-template-columns: auto;
padding: 18px;
}
.header__nav {
column-gap: 14px;
font-size: 11px;
}
.header__nav {
column-gap: 14px;
font-size: 11px;
}
.header__logo {
width: 200px;
height: 46px;
}
.header__logo {
width: 220px;
height: 46px;
}
.header-info {
font-size: 11px;
}
.header-info {
font-size: 11px;
}
.header-info__btn {
font-size: 13px;
}
.header-info__btn {
font-size: 13px;
}
.header-socials {
margin-top: 5px;
font-size: 11px;
}
.header-socials {
margin-top: 5px;
font-size: 11px;
}
}
@media (max-width: 780px) {
.header__logo {
width: 210px;
height: 42px;
}
}
h1 {
text-align: center;
letter-spacing: 1px;
word-spacing: 0.15em;
font-size: 3em;
line-height: 1.2;
transform: translateY(52%);
text-align: center;
letter-spacing: 1px;
word-spacing: 0.15em;
font-size: 3em;
line-height: 1.2;
transform: translateY(52%);
}
#toggle {
display: none;
display: none;
}
/**
Hamburger
**/
.hamburger {
position: absolute;
top: 4em;
right: 7%;
margin-left: -2em;
margin-top: -45px;
width: 2em;
height: 45px;
z-index: 5;
position: absolute;
top: 4em;
right: 7%;
margin-left: -2em;
margin-top: -45px;
width: 2em;
height: 45px;
z-index: 5;
}
.open {
position: fixed;
position: fixed;
}
.hamburger div {
position: relative;
width: 3.1em;
height: 5px;
border-radius: 3px;
background-color: white;
margin-top: 8px;
transition: all 0.3s ease-in-out;
position: relative;
width: 3.1em;
height: 5px;
border-radius: 3px;
background-color: white;
margin-top: 8px;
transition: all 0.3s ease-in-out;
}
/**
Nav Styles
**/
.nav {
position: fixed;
width: 100%;
height: 100%;
background-color: #17628C;
top: -100%;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
transition: all 0.3s ease-in-out;
transform: scale(0);
z-index: 1;
position: fixed;
width: 100%;
height: 100%;
background-color: #17628c;
top: -100%;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
transition: all 0.3s ease-in-out;
transform: scale(0);
z-index: 1;
}
.nav-wrapper {
position: relative;
overflow: hidden;
overflow-y: auto;
height: 100%;
position: relative;
overflow: hidden;
overflow-y: auto;
height: 100%;
}
.nav-container {
display: flex;
flex-direction: column;
text-align: left;
margin-left: 25%;
margin-top: 15%;
display: flex;
flex-direction: column;
text-align: left;
margin-left: 25%;
margin-top: 15%;
}
.nav-container a,
.nav-container p {
position: relative;
text-decoration: none;
color: white;
font-size: 2em;
display: inline-block;
margin-top: 1.25em;
margin-bottom: 0;
transition: color 0.2s ease-in-out;
letter-spacing: 1px;
position: relative;
text-decoration: none;
color: white;
font-size: 2em;
display: inline-block;
margin-top: 1.25em;
margin-bottom: 0;
transition: color 0.2s ease-in-out;
letter-spacing: 1px;
}
.nav-container a:hover {
text-decoration: none;
text-decoration: none;
}
.nav-container .header__link:before {
content: '';
height: 0;
position: absolute;
width: 0.25em;
background-color: white;
left: -0.5em;
transition: all 0.2s ease-in-out;
content: "";
height: 0;
position: absolute;
width: 0.25em;
background-color: white;
left: -0.5em;
transition: all 0.2s ease-in-out;
}
.nav-container a:hover {
color: white;
color: white;
}
.nav-container a:hover:before {
height: 100%;
height: 100%;
}
/**
Animations
**/
#toggle:checked+.hamburger .top-bun {
transform: rotate(-45deg);
margin-top: 25px;
#toggle:checked + .hamburger .top-bun {
transform: rotate(-45deg);
margin-top: 25px;
}
#toggle:checked+.hamburger .bottom-bun {
opacity: 0;
transform: rotate(45deg);
#toggle:checked + .hamburger .bottom-bun {
opacity: 0;
transform: rotate(45deg);
}
#toggle:checked+.hamburger .meat {
transform: rotate(45deg);
margin-top: -7px;
#toggle:checked + .hamburger .meat {
transform: rotate(45deg);
margin-top: -7px;
}
#toggle:checked+.hamburger+.nav {
top: 0;
transform: scale(1);
#toggle:checked + .hamburger + .nav {
top: 0;
transform: scale(1);
}
@media (min-width: 769px) {
.burgerMenu {
display: none;
}
.burgerMenu {
display: none;
}
.header__text {
display: none;
}
.header__text {
display: none;
}
}
@media (max-width: $tabletWidth) {
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
.burgerMenu {
display: flex;
justify-content: flex-end;
}
.burgerMenu {
display: flex;
justify-content: flex-end;
}
.header-info__call {
margin-right: 100px;
}
.header-info__call {
margin-right: 100px;
}
.header__logo {
// grid-row: 1/3;
background-image: url('../../assets/images/logo/AlvaMid-Logo-Small.svg');
width: 50px;
height: 50px;
}
.header__logo {
// grid-row: 1/3;
background-image: url("../../assets/images/logo/AlvaMid-Logo-Small.svg");
width: 50px;
height: 50px;
}
.header__nav,
.header-info,
.header-socials {
display: none;
}
.header__nav,
.header-info,
.header-socials {
display: none;
}
.header__text {
position: absolute;
background-image: url('../../assets/images/logo/textLogo.svg');
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 113px;
background-size: contain;
background-repeat: no-repeat;
}
}
.header__text {
position: absolute;
background-image: url("../../assets/images/logo/textLogo.svg");
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
width: 300px;
height: 113px;
background-size: contain;
background-repeat: no-repeat;
}
}

View File

@@ -66,6 +66,55 @@
.item__image {
border-top-right-radius: 15px;
border-top-left-radius: 15px;
width: 100%;
height: 250px;
object-fit: cover;
object-position: center;
display: block;
@media (max-width: 1300px) {
height: 230px;
}
@media (max-width: 1200px) {
height: 210px;
}
@media (max-width: 1100px) {
height: 190px;
}
@media (max-width: $laptopWidth) {
height: 170px;
}
@media (max-width: $tabletWidth) {
height: 300px;
}
@media (max-width: 740px) {
height: 280px;
}
@media (max-width: 690px) {
height: 250px;
}
@media (max-width: 635px) {
height: 220px;
}
@media (max-width: 570px) {
height: 190px;
}
@media (max-width: $mobileWidth) {
height: 210px;
}
@media (max-width: 420px) {
height: 190px;
}
}
.item__info {

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import Slider from 'react-slick';
import { Link } from 'react-router-dom';
import { useMediaQuery } from 'react-responsive';
@@ -7,6 +7,7 @@ import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
import './SliderObjects.scss';
import { Object } from '../Object/Object';
import { API_CONFIG } from '../../config/contacts';
import objectPicOne from '../../assets/images/apartaments/image-44.jpg';
import objectPicTwo from '../../assets/images/apartaments/image-45.jpg';
@@ -25,10 +26,40 @@ const PrevArrow = ({ onClick }) => {
};
const SliderComponent = () => {
const [objects, setObjects] = useState([]);
const [loading, setLoading] = useState(true);
const isMobileResolution = useMediaQuery({ maxWidth: 768 });
// Загрузка данных с API
useEffect(() => {
const fetchObjects = async () => {
try {
const response = await fetch(API_CONFIG.getFullURL(API_CONFIG.endpoints.rental), {
method: 'GET',
headers: {
'accept': '*/*'
}
});
if (response.ok) {
const data = await response.json();
setObjects(data);
}
} catch (error) {
console.error('Ошибка при загрузке объектов:', error);
// В случае ошибки оставляем пустой массив, компонент не сломается
} finally {
setLoading(false);
}
};
fetchObjects();
}, []);
const settings = {
dots: false,
infinite: true,
dots: objects.length > (isMobileResolution ? 1 : 3), // Показываем точки если объектов больше чем влезает
infinite: objects.length > (isMobileResolution ? 1 : 3), // Бесконечная прокрутка только если объектов достаточно
speed: 500,
slidesToShow: isMobileResolution ? 1 : 3,
slidesToScroll: isMobileResolution ? 1 : 3,
@@ -37,46 +68,49 @@ const SliderComponent = () => {
prevArrow: <PrevArrow />,
};
// Если загружаем данные, показываем статические объекты как fallback
if (loading || objects.length === 0) {
return (
<div className="slider-objects">
<Slider {...{...settings, dots: false, infinite: true}}>
<Link className="objects-link" to="/apartament">
<Object
image={objectPicOne}
price="1 234 567 ₽"
desc="1-комн. кв. 34 м"
address="Ул. Луначарского, Ленинский район"
/>
</Link>
<Object
image={objectPicTwo}
price="1 234 567 ₽"
desc="1-комн. кв. 34 м"
address="Ул. Зари, Вагонка"
/>
<Object
image={objectPicThree}
price="1 234 567 ₽"
desc="1-комн. кв. 34 м"
address="Ул. Солнечная, Заречный район"
/>
</Slider>
</div>
);
}
return (
<div className="slider-objects">
<Slider {...settings}>
<Link className="objects-link" to="/apartament">
<Object
image={objectPicOne}
price="1 234 567 ₽"
desc="1-комн. кв. 34 м"
address="Ул. Луначарского, Ленинский район"
/></Link>
<Object
image={objectPicTwo}
price="1 234 567 ₽"
desc="1-комн. кв. 34 м"
address="Ул. Зари, Вагонка"
/>
<Object
image={objectPicThree}
price="1 234 567 ₽"
desc="1-комн. кв. 34 м"
address="Ул. Солнечная, Заречный район"
/>
<Object
image={objectPicOne}
price="1 234 567 ₽"
desc="1-комн. кв. 34 м"
address="Ул. Луначарского, Ленинский район"
/>
<Object
image={objectPicTwo}
price="1 234 567 ₽"
desc="1-комн. кв. 34 м"
address="Ул. Зари, Вагонка"
/>
<Object
image={objectPicThree}
price="1 234 567 ₽"
desc="1-комн. кв. 34 м"
address="Ул. Солнечная, Заречный район"
/>
{objects.map((object) => (
<Link key={object.id} className="objects-link" to={`/apartament/${object.id}`}>
<Object
image={object.photoUrl}
price={object.price}
desc={object.title}
address={object.address}
/>
</Link>
))}
</Slider>
</div>
);