add new pages
BIN
src/assets/emoji/1.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
src/assets/emoji/2.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/assets/emoji/3.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
src/assets/emoji/4.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
src/assets/emoji/5.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/emoji/china.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
src/assets/emoji/korea.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/emoji/united-states.png
Normal file
After Width: | Height: | Size: 24 KiB |
@ -12,6 +12,8 @@ import {
|
|||||||
InputLabel,
|
InputLabel,
|
||||||
OutlinedInput,
|
OutlinedInput,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
import ArrowForwardIcon from "@mui/icons-material/ArrowForward";
|
||||||
@ -53,7 +55,11 @@ interface FeedbackProps {
|
|||||||
handleScrollToAnchor?: (anchor: string) => void;
|
handleScrollToAnchor?: (anchor: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor }) => {
|
const Feedback: React.FC<FeedbackProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
handleScrollToAnchor,
|
||||||
|
}) => {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [phone, setPhone] = useState("+7");
|
const [phone, setPhone] = useState("+7");
|
||||||
const [country, setCountry] = useState("Европа");
|
const [country, setCountry] = useState("Европа");
|
||||||
@ -64,9 +70,7 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
window.history.pushState(null, '', '#feedback');
|
window.history.pushState(null, "", "#feedback");
|
||||||
} else if (!open && !isMobile && handleScrollToAnchor) {
|
|
||||||
handleScrollToAnchor('#main');
|
|
||||||
}
|
}
|
||||||
}, [open, handleScrollToAnchor]);
|
}, [open, handleScrollToAnchor]);
|
||||||
|
|
||||||
@ -79,10 +83,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log({ name, phone, country, budget, description, agreeToPolicy });
|
console.log({ name, phone, country, budget, description, agreeToPolicy });
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
if (handleScrollToAnchor) {
|
if (handleScrollToAnchor) {
|
||||||
handleScrollToAnchor('#main');
|
handleScrollToAnchor("#main");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -98,15 +102,17 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
"& .MuiOutlinedInput-root, fieldset": {
|
"& .MuiOutlinedInput-root, fieldset": {
|
||||||
borderRadius: isMobile ? "3vw" : "1.5vw",
|
borderRadius: isMobile ? "3vw" : "1.5vw",
|
||||||
fontSize: isMobile ? "2.8vw" : "1.25vw",
|
fontSize: isMobile ? "2.8vw" : "1.25vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
},
|
},
|
||||||
"& .MuiInputLabel-root": {
|
"& .MuiInputLabel-root": {
|
||||||
fontSize: isMobile ? "2.8vw" : "1.25vw",
|
fontSize: isMobile ? "2.8vw" : "1.25vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
transform: isMobile
|
transform: isMobile
|
||||||
? "translate(2.5vw, 3.1vw) scale(1)"
|
? "translate(2.5vw, 2.1vw) scale(1)"
|
||||||
: "translate(1vw, 1.1vw) scale(1)",
|
: "translate(1vw, 1.1vw) scale(1)",
|
||||||
"&.MuiInputLabel-shrink": {
|
"&.MuiInputLabel-shrink": {
|
||||||
transform: isMobile
|
transform: isMobile
|
||||||
? "translate(3vw, -1.6vw) scale(0.75)"
|
? "translate(3.7vw, -1.6vw) scale(0.75)"
|
||||||
: "translate(0.9vw, -0.8vw) scale(0.75)",
|
: "translate(0.9vw, -0.8vw) scale(0.75)",
|
||||||
},
|
},
|
||||||
"&.Mui-focused": {
|
"&.Mui-focused": {
|
||||||
@ -114,59 +120,45 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"& .MuiInputBase-input": {
|
"& .MuiInputBase-input": {
|
||||||
fontSize: isMobile ? "2.8vw" : "1.25vw",
|
fontSize: isMobile ? "2.5vw" : "1.25vw",
|
||||||
padding: isMobile ? "3vw 2.5vw" : "1vw 1.5vw",
|
padding: isMobile ? "2vw 2.5vw" : "1vw 1.5vw",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePhoneChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handlePhoneChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
// Убедимся, что +7 всегда присутствует
|
|
||||||
if (event.target.value.startsWith("+7")) {
|
if (event.target.value.startsWith("+7")) {
|
||||||
setPhone(event.target.value);
|
setPhone(event.target.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Dialog
|
||||||
sx={{
|
open={open}
|
||||||
position: "absolute",
|
onClose={onClose}
|
||||||
top: 0,
|
maxWidth="md"
|
||||||
left: 0,
|
fullWidth
|
||||||
width: "100%",
|
PaperProps={{
|
||||||
height: "100%",
|
sx: {
|
||||||
display: open ? "flex" : "none",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
zIndex: 1300, // высокий z-index как у диалога
|
|
||||||
bgcolor: "rgba(0, 0, 0, 0.5)", // затемнение фона
|
|
||||||
color: "black",
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
|
||||||
onClose();
|
|
||||||
if (handleScrollToAnchor) handleScrollToAnchor('#main');
|
|
||||||
}}
|
|
||||||
id="feedback"
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: "relative",
|
|
||||||
bgcolor: "background.paper",
|
|
||||||
borderRadius: isMobile ? "3vw" : "1.5vw",
|
borderRadius: isMobile ? "3vw" : "1.5vw",
|
||||||
p: isMobile ? "6vw" : "4vw",
|
margin: "auto",
|
||||||
maxWidth: isMobile ? "70vw" : "40vw",
|
maxWidth: isMobile ? "85vw" : "38vw",
|
||||||
maxHeight: "75vh",
|
position: "fixed",
|
||||||
overflow: "hidden",
|
top: "50%",
|
||||||
boxShadow: 24, // тень как у диалога
|
left: "50%",
|
||||||
margin: "auto", // для дополнительной центровки
|
transform: "translate(-50%, -50%)",
|
||||||
|
overflowY: "hidden",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent
|
||||||
|
sx={{
|
||||||
|
padding: isMobile ? "4vw" : "3vw",
|
||||||
|
overflowY: "hidden",
|
||||||
}}
|
}}
|
||||||
onClick={(e) => e.stopPropagation()} // предотвращаем закрытие при клике на контент
|
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={onClose}
|
||||||
onClose();
|
|
||||||
if (handleScrollToAnchor) handleScrollToAnchor('#main');
|
|
||||||
}}
|
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right: "1vw",
|
right: "1vw",
|
||||||
@ -181,20 +173,24 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
<Typography
|
<Typography
|
||||||
variant="h5"
|
variant="h5"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
sx={{ fontSize: isMobile ? "5vw" : "1.9vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "5vw" : "1.9vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Оставьте заявку
|
Оставьте заявку
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{}}>
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
fontSize: isMobile ? "3.5vw" : "1.25vw",
|
fontSize: isMobile ? "3.5vw" : "1.25vw",
|
||||||
maxWidth: "65vw",
|
maxWidth: "100%",
|
||||||
margin: "auto",
|
margin: "auto",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
И наш менеджер свяжется с вами для уточнения деталей заказа
|
И наш менеджер свяжется с вами для уточнения деталей заказа
|
||||||
@ -216,7 +212,6 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Поле с телефоном как на скриншоте */}
|
|
||||||
<FormControl
|
<FormControl
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@ -244,7 +239,11 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
id="phone-input"
|
id="phone-input"
|
||||||
value={phone}
|
value={phone}
|
||||||
onChange={handlePhoneChange}
|
onChange={handlePhoneChange}
|
||||||
inputComponent={TextMaskCustom as unknown as React.ComponentType<import('@mui/material').InputBaseComponentProps>}
|
inputComponent={
|
||||||
|
TextMaskCustom as unknown as React.ComponentType<
|
||||||
|
import("@mui/material").InputBaseComponentProps
|
||||||
|
>
|
||||||
|
}
|
||||||
label="Ваш телефон*"
|
label="Ваш телефон*"
|
||||||
notched
|
notched
|
||||||
sx={{
|
sx={{
|
||||||
@ -256,10 +255,13 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<Box sx={{ mb: isMobile ? "3vw" : "0.5vw" }}>
|
<Box sx={{ mb: isMobile ? "2vw" : "0.5vw" }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ mb: "0.5vw", fontSize: isMobile ? "3.5vw" : "1.1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "3vw" : "1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Из какой страны привезти автомобиль?
|
Из какой страны привезти автомобиль?
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -285,7 +287,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ fontSize: isMobile ? "3vw" : "1.1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "3vw" : "1.1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Европа
|
Европа
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -311,7 +316,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ fontSize: isMobile ? "3vw" : "1.1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "3vw" : "1.1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
США
|
США
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -337,7 +345,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ fontSize: isMobile ? "3vw" : "1.1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "3vw" : "1.1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Китай
|
Китай
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -363,7 +374,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ fontSize: isMobile ? "3vw" : "1.1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "3vw" : "1.1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Корея
|
Корея
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -380,7 +394,11 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
<Box sx={{ mb: isMobile ? "2.5vw" : "1vw" }}>
|
<Box sx={{ mb: isMobile ? "2.5vw" : "1vw" }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ mb: "0.5vw", fontSize: isMobile ? "3.5vw" : "1.1vw" }}
|
sx={{
|
||||||
|
mb: "0.5vw",
|
||||||
|
fontSize: isMobile ? "3vw" : "1.1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Какой у вас бюджет на автомобиль?
|
Какой у вас бюджет на автомобиль?
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -406,7 +424,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ fontSize: isMobile ? "3vw" : "1.1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "3vw" : "1.1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
до 3 млн
|
до 3 млн
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -417,7 +438,7 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="до 5 млн"
|
value="3-5 млн"
|
||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
sx={{
|
sx={{
|
||||||
@ -432,7 +453,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ fontSize: isMobile ? "3vw" : "1.1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "3vw" : "1.1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
3-5 млн
|
3-5 млн
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -458,7 +482,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ fontSize: isMobile ? "3vw" : "1.1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "3vw" : "1.1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
5-10 млн
|
5-10 млн
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -469,7 +496,7 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="более 10 млн"
|
value="10+ млн"
|
||||||
control={
|
control={
|
||||||
<Radio
|
<Radio
|
||||||
sx={{
|
sx={{
|
||||||
@ -484,7 +511,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
}
|
}
|
||||||
label={
|
label={
|
||||||
<Typography
|
<Typography
|
||||||
sx={{ fontSize: isMobile ? "3vw" : "1.1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "3vw" : "1.1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
10+ млн
|
10+ млн
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -524,6 +554,7 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
py: "0.9vw",
|
py: "0.9vw",
|
||||||
borderRadius: isMobile ? "3vw" : "1vw",
|
borderRadius: isMobile ? "3vw" : "1vw",
|
||||||
fontSize: isMobile ? "3.5vw" : "1.25vw",
|
fontSize: isMobile ? "3.5vw" : "1.25vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
"&:hover": { bgcolor: "#a42517" },
|
"&:hover": { bgcolor: "#a42517" },
|
||||||
textTransform: "none",
|
textTransform: "none",
|
||||||
}}
|
}}
|
||||||
@ -538,10 +569,7 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
mt: isMobile ? "2vw" : "1vw",
|
mt: isMobile ? "1vw" : "0.5vw",
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
@ -566,7 +594,10 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
label={
|
label={
|
||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ fontSize: isMobile ? "2.5vw" : "1vw" }}
|
sx={{
|
||||||
|
fontSize: isMobile ? "2.5vw" : "1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Я подтверждаю, что ознакомлен{" "}
|
Я подтверждаю, что ознакомлен{" "}
|
||||||
<Typography
|
<Typography
|
||||||
@ -576,6 +607,7 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
textDecoration: "underline",
|
textDecoration: "underline",
|
||||||
fontSize: isMobile ? "2.5vw" : "1vw",
|
fontSize: isMobile ? "2.5vw" : "1vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
с политикой конфиденциальности
|
с политикой конфиденциальности
|
||||||
@ -587,8 +619,8 @@ const Feedback: React.FC<FeedbackProps> = ({ open, onClose, handleScrollToAnchor
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</DialogContent>
|
||||||
</Box>
|
</Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ const Header = () => {
|
|||||||
const [scrolled, setScrolled] = useState(false);
|
const [scrolled, setScrolled] = useState(false);
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ title: "О нас", anchor: "#about" },
|
{ title: "О нас", anchor: "#about-us" },
|
||||||
{ title: "Калькулятор", anchor: "#calculator" },
|
{ title: "Калькулятор", anchor: "#calculator" },
|
||||||
{ title: "Отзывы", anchor: "#reviews" },
|
{ title: "Отзывы", anchor: "#reviews" },
|
||||||
{ title: "Контакты", anchor: "#contacts" },
|
{ title: "Контакты", anchor: "#contacts" },
|
||||||
@ -29,7 +29,7 @@ const Header = () => {
|
|||||||
{ title: "Команда", anchor: "#team" },
|
{ title: "Команда", anchor: "#team" },
|
||||||
{ title: "Доставленные авто", anchor: "#delivered" },
|
{ title: "Доставленные авто", anchor: "#delivered" },
|
||||||
{ title: "Этапы работы", anchor: "#stages" },
|
{ title: "Этапы работы", anchor: "#stages" },
|
||||||
{ title: " ", anchor: "#main"},
|
{ title: " ", anchor: "#main" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Отслеживание скролла
|
// Отслеживание скролла
|
||||||
@ -48,21 +48,23 @@ const Header = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleDrawer = (open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
const toggleDrawer =
|
||||||
if (
|
(open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
|
||||||
event.type === "keydown" &&
|
if (
|
||||||
((event as React.KeyboardEvent).key === "Tab" || (event as React.KeyboardEvent).key === "Shift")
|
event.type === "keydown" &&
|
||||||
) {
|
((event as React.KeyboardEvent).key === "Tab" ||
|
||||||
return;
|
(event as React.KeyboardEvent).key === "Shift")
|
||||||
}
|
) {
|
||||||
setDrawerOpen(open);
|
return;
|
||||||
};
|
}
|
||||||
|
setDrawerOpen(open);
|
||||||
// Используем глобальную функцию из utils
|
|
||||||
const handleScrollToAnchor = (anchor: string) => {
|
|
||||||
scrollToAnchor(anchor, setDrawerOpen, isMobile);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Используем глобальную функцию из utils
|
||||||
|
const handleScrollToAnchor = (anchor: string) => {
|
||||||
|
scrollToAnchor(anchor, setDrawerOpen, isMobile);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -98,8 +100,7 @@ const Header = () => {
|
|||||||
"&:hover": { scale: 1.1 },
|
"&:hover": { scale: 1.1 },
|
||||||
transition: "all 0.4s ease",
|
transition: "all 0.4s ease",
|
||||||
}}
|
}}
|
||||||
>
|
></Box>
|
||||||
</Box>
|
|
||||||
|
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
<Box
|
<Box
|
||||||
@ -154,19 +155,15 @@ const Header = () => {
|
|||||||
<Box sx={{ height: isMobile ? "15vw" : "10vw" }} />
|
<Box sx={{ height: isMobile ? "15vw" : "10vw" }} />
|
||||||
|
|
||||||
{/* Мобильное меню */}
|
{/* Мобильное меню */}
|
||||||
<Drawer
|
<Drawer anchor="right" open={drawerOpen} onClose={toggleDrawer(false)}>
|
||||||
anchor="right"
|
|
||||||
open={drawerOpen}
|
|
||||||
onClose={toggleDrawer(false)}
|
|
||||||
>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{ width: "58vw", bgcolor: "#2D2D2D", height: "100%" }}
|
sx={{ width: "58vw", bgcolor: "#2D2D2D", height: "100%" }}
|
||||||
role="presentation"
|
role="presentation"
|
||||||
>
|
>
|
||||||
<List>
|
<List>
|
||||||
{menuItems.map((item, index) => (
|
{menuItems.map((item, index) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => handleScrollToAnchor(item.anchor)}
|
onClick={() => handleScrollToAnchor(item.anchor)}
|
||||||
sx={{ cursor: "pointer" }}
|
sx={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
@ -188,7 +185,11 @@ const Header = () => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<Feedback open={feedbackOpen} onClose={() => setFeedbackOpen(false)} handleScrollToAnchor={handleScrollToAnchor} />
|
<Feedback
|
||||||
|
open={feedbackOpen}
|
||||||
|
onClose={() => setFeedbackOpen(false)}
|
||||||
|
handleScrollToAnchor={handleScrollToAnchor}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
90
src/pages/AboutUsPage.tsx
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
|
|
||||||
|
function AboutUsPage() {
|
||||||
|
const { isMobile } = useResponsive();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
id="about-us"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "white",
|
||||||
|
color: "black",
|
||||||
|
padding: "5vw",
|
||||||
|
marginTop: "5vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: "80vw",
|
||||||
|
margin: "0 auto",
|
||||||
|
textAlign: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "8vw" : "4rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
КТО <span style={{ color: "#C27664" }}>МЫ?</span>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "7vw" : "3.5rem",
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
АВТОБРО
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "4vw" : "1.5rem",
|
||||||
|
marginBottom: "2rem",
|
||||||
|
lineHeight: 1.6,
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Мы предлагаем широкий выбор автомобилей на любой вкус и бюджет. Мы
|
||||||
|
привозим новые машины под заказ напрямую от производителей, помогая
|
||||||
|
клиентам получить желаемый автомобиль в нужной комплектации.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "4vw" : "1.5rem",
|
||||||
|
marginBottom: "2rem",
|
||||||
|
lineHeight: 1.6,
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Также у нас есть проверенные б/у автомобили с прозрачной историей и
|
||||||
|
готовые варианты из наличия – вы можете уехать на новом авто уже в
|
||||||
|
день покупки.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "4vw" : "1.5rem",
|
||||||
|
lineHeight: 1.6,
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Наш автосалон работает для тех, кто ценит надежность, выгодные условия
|
||||||
|
и индивидуальный подход. Оставьте заявку, и мы подберем для вас
|
||||||
|
идеальный вариант!
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AboutUsPage;
|
550
src/pages/CarsPage.tsx
Normal file
@ -0,0 +1,550 @@
|
|||||||
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
|
import Feedback from "../components/Feedback";
|
||||||
|
import { fetchCars, getImageUrl, type Car } from "../utils/api";
|
||||||
|
import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft";
|
||||||
|
import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
|
||||||
|
|
||||||
|
function CarsPage() {
|
||||||
|
const { isMobile } = useResponsive();
|
||||||
|
const [cars, setCars] = useState<Car[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const scrollTrackRef = useRef<HTMLDivElement>(null);
|
||||||
|
const scrollThumbRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
|
const [thumbWidth, setThumbWidth] = useState(30); // в процентах
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadCars = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetchCars();
|
||||||
|
setCars(response.cars);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Ошибка при загрузке данных:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadCars();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Обновление размера ползунка скролл-бара
|
||||||
|
useEffect(() => {
|
||||||
|
const updateThumbWidth = () => {
|
||||||
|
if (scrollContainerRef.current && scrollTrackRef.current) {
|
||||||
|
const { scrollWidth, clientWidth } = scrollContainerRef.current;
|
||||||
|
const thumbWidthPercent = (clientWidth / scrollWidth) * 100;
|
||||||
|
setThumbWidth(Math.max(10, thumbWidthPercent)); // Минимальная ширина 10%
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
updateThumbWidth();
|
||||||
|
window.addEventListener("resize", updateThumbWidth);
|
||||||
|
return () => window.removeEventListener("resize", updateThumbWidth);
|
||||||
|
}, [cars]);
|
||||||
|
|
||||||
|
// Синхронизация положения скролл-бара со скроллом контейнера
|
||||||
|
useEffect(() => {
|
||||||
|
const syncScrollThumb = () => {
|
||||||
|
if (
|
||||||
|
scrollContainerRef.current &&
|
||||||
|
scrollTrackRef.current &&
|
||||||
|
scrollThumbRef.current
|
||||||
|
) {
|
||||||
|
const { scrollLeft, scrollWidth, clientWidth } =
|
||||||
|
scrollContainerRef.current;
|
||||||
|
const trackWidth = scrollTrackRef.current.clientWidth;
|
||||||
|
const scrollPercent = scrollLeft / (scrollWidth - clientWidth);
|
||||||
|
|
||||||
|
// Учитываем отступы 0.5vw с обеих сторон
|
||||||
|
const minOffset =
|
||||||
|
parseFloat(getComputedStyle(document.documentElement).fontSize) *
|
||||||
|
(isMobile ? 0.25 : 0.5);
|
||||||
|
const maxOffset =
|
||||||
|
trackWidth - (trackWidth * thumbWidth) / 100 - minOffset;
|
||||||
|
|
||||||
|
// Расчет позиции с учетом отступов
|
||||||
|
const thumbLeft = minOffset + scrollPercent * (maxOffset - minOffset);
|
||||||
|
scrollThumbRef.current.style.left = `${thumbLeft}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const scrollContainer = scrollContainerRef.current;
|
||||||
|
if (scrollContainer) {
|
||||||
|
scrollContainer.addEventListener("scroll", syncScrollThumb);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (scrollContainer) {
|
||||||
|
scrollContainer.removeEventListener("scroll", syncScrollThumb);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [thumbWidth]);
|
||||||
|
|
||||||
|
// Обработчики перетаскивания скролл-бара
|
||||||
|
const handleDragStart = (e: React.MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsDragging(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Добавьте эти обработчики для сенсорных событий
|
||||||
|
const handleTouchStart = (e: React.TouchEvent) => {
|
||||||
|
e.preventDefault(); // Предотвращаем стандартное поведение
|
||||||
|
setIsDragging(true);
|
||||||
|
|
||||||
|
// Сохраняем текущую позицию касания
|
||||||
|
if (e.touches.length > 0) {
|
||||||
|
const touch = e.touches[0];
|
||||||
|
handleDragMove(touch); // Переиспользуем функцию для перетаскивания
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragMove = (e: MouseEvent | Touch) => {
|
||||||
|
if (!isDragging || !scrollTrackRef.current || !scrollContainerRef.current)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const trackRect = scrollTrackRef.current.getBoundingClientRect();
|
||||||
|
const thumbWidthPx = trackRect.width * (thumbWidth / 100);
|
||||||
|
const trackWidth = trackRect.width - thumbWidthPx;
|
||||||
|
|
||||||
|
// Используем clientX, которое есть как у MouseEvent, так и у Touch
|
||||||
|
let clickPosition =
|
||||||
|
(e.clientX - trackRect.left - thumbWidthPx / 2) / trackWidth;
|
||||||
|
clickPosition = Math.max(0, Math.min(1, clickPosition));
|
||||||
|
|
||||||
|
// Устанавливаем скролл контейнера
|
||||||
|
const { scrollWidth, clientWidth } = scrollContainerRef.current;
|
||||||
|
scrollContainerRef.current.scrollLeft =
|
||||||
|
clickPosition * (scrollWidth - clientWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragEnd = () => {
|
||||||
|
setIsDragging(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обновите функцию handleDragMove для работы как с мышью, так и с сенсорными событиями
|
||||||
|
const handleTouchMove = (e: TouchEvent) => {
|
||||||
|
e.preventDefault(); // Предотвращаем скроллинг страницы
|
||||||
|
|
||||||
|
if (isDragging && e.touches.length > 0) {
|
||||||
|
const touch = e.touches[0];
|
||||||
|
handleDragMove(touch); // Переиспользуем функцию для перетаскивания
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTouchEnd = (e: TouchEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsDragging(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обновите useEffect для добавления обработчиков сенсорных событий
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDragging) {
|
||||||
|
// Вместо простого скрытия overflow, сохраняем ширину до блокировки
|
||||||
|
const scrollbarWidth =
|
||||||
|
window.innerWidth - document.documentElement.clientWidth;
|
||||||
|
|
||||||
|
// Блокируем скролл страницы
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
|
||||||
|
// Добавляем padding-right для компенсации исчезнувшего скроллбара
|
||||||
|
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
||||||
|
|
||||||
|
window.addEventListener("mousemove", handleDragMove);
|
||||||
|
window.addEventListener("mouseup", handleDragEnd);
|
||||||
|
window.addEventListener("touchmove", handleTouchMove, { passive: false });
|
||||||
|
window.addEventListener("touchend", handleTouchEnd, { passive: false });
|
||||||
|
} else {
|
||||||
|
// Восстанавливаем скроллинг и убираем padding
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
document.body.style.paddingRight = "";
|
||||||
|
|
||||||
|
window.removeEventListener("mousemove", handleDragMove);
|
||||||
|
window.removeEventListener("mouseup", handleDragEnd);
|
||||||
|
window.removeEventListener("touchmove", handleTouchMove);
|
||||||
|
window.removeEventListener("touchend", handleTouchEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Не забываем сбросить стили при размонтировании
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
document.body.style.paddingRight = "";
|
||||||
|
|
||||||
|
window.removeEventListener("mousemove", handleDragMove);
|
||||||
|
window.removeEventListener("mouseup", handleDragEnd);
|
||||||
|
window.removeEventListener("touchmove", handleTouchMove);
|
||||||
|
window.removeEventListener("touchend", handleTouchEnd);
|
||||||
|
};
|
||||||
|
}, [isDragging]);
|
||||||
|
|
||||||
|
// Обработчик клика по треку скролл-бара
|
||||||
|
const handleTrackClick = (e: React.MouseEvent) => {
|
||||||
|
if (!scrollTrackRef.current || !scrollContainerRef.current) return;
|
||||||
|
|
||||||
|
const trackRect = scrollTrackRef.current.getBoundingClientRect();
|
||||||
|
const thumbWidthPx = trackRect.width * (thumbWidth / 100);
|
||||||
|
const trackWidth = trackRect.width - thumbWidthPx;
|
||||||
|
|
||||||
|
// Рассчитываем положение клика относительно трека с учетом ширины ползунка
|
||||||
|
let clickPosition =
|
||||||
|
(e.clientX - trackRect.left - thumbWidthPx / 2) / trackWidth;
|
||||||
|
clickPosition = Math.max(0, Math.min(1, clickPosition));
|
||||||
|
|
||||||
|
// Устанавливаем скролл контейнера
|
||||||
|
const { scrollWidth, clientWidth } = scrollContainerRef.current;
|
||||||
|
scrollContainerRef.current.scrollLeft =
|
||||||
|
clickPosition * (scrollWidth - clientWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenFeedback = () => {
|
||||||
|
setFeedbackOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseFeedback = () => {
|
||||||
|
setFeedbackOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для прокрутки влево
|
||||||
|
const scrollLeft = () => {
|
||||||
|
if (scrollContainerRef.current) {
|
||||||
|
// Найти текущую видимую карточку
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
const scrollPosition = container.scrollLeft;
|
||||||
|
const cardWidth = isMobile ? 80 : 25;
|
||||||
|
const gap = 2;
|
||||||
|
const vwToPx = (vw: number) => (window.innerWidth * vw) / 100;
|
||||||
|
|
||||||
|
// Рассчитать индекс предыдущей карточки
|
||||||
|
const currentIndex = Math.round(scrollPosition / vwToPx(cardWidth + gap));
|
||||||
|
const prevIndex = Math.max(0, currentIndex - 1);
|
||||||
|
|
||||||
|
// Прокрутить к предыдущей карточке
|
||||||
|
if (container.children[prevIndex]) {
|
||||||
|
container.children[prevIndex].scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
inline: "start",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Добавляем дополнительный сдвиг на 0.5vw для ПК версии
|
||||||
|
if (!isMobile) {
|
||||||
|
setTimeout(() => {
|
||||||
|
container.scrollLeft -= vwToPx(0.5);
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Функция для прокрутки вправо
|
||||||
|
const scrollRight = () => {
|
||||||
|
if (scrollContainerRef.current) {
|
||||||
|
// Найти текущую видимую карточку
|
||||||
|
const container = scrollContainerRef.current;
|
||||||
|
const scrollPosition = container.scrollLeft;
|
||||||
|
const cardWidth = isMobile ? 80 : 25;
|
||||||
|
const gap = 2;
|
||||||
|
const vwToPx = (vw: number) => (window.innerWidth * vw) / 100;
|
||||||
|
|
||||||
|
// Рассчитать индекс следующей карточки
|
||||||
|
const currentIndex = Math.round(scrollPosition / vwToPx(cardWidth + gap));
|
||||||
|
const nextIndex = Math.min(cars.length - 1, currentIndex + 1);
|
||||||
|
|
||||||
|
// Прокрутить к следующей карточке
|
||||||
|
if (container.children[nextIndex]) {
|
||||||
|
container.children[nextIndex].scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
inline: "start",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isMobile && nextIndex !== cars.length - 1) {
|
||||||
|
setTimeout(() => {
|
||||||
|
container.scrollLeft -= vwToPx(0.5);
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
id="available"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "white",
|
||||||
|
color: "black",
|
||||||
|
padding: isMobile ? "10vw 5vw" : "5vw",
|
||||||
|
scrollMarginTop: isMobile ? "15vw" : "10vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: "75vw",
|
||||||
|
margin: "0 auto",
|
||||||
|
textAlign: "left",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "8vw" : "4vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: "2vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
АВТОМОБИЛИ <span style={{ color: "#C27664" }}>В НАЛИЧИИ</span>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<Typography>Загрузка...</Typography>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Контейнер для карточек с горизонтальным скроллом */}
|
||||||
|
<Box
|
||||||
|
ref={scrollContainerRef}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "2vw",
|
||||||
|
overflowX: "auto",
|
||||||
|
scrollbarWidth: "none",
|
||||||
|
"&::-webkit-scrollbar": {
|
||||||
|
display: "none",
|
||||||
|
},
|
||||||
|
pb: "1vw",
|
||||||
|
maxWidth: isMobile ? "81vw" : "53vw",
|
||||||
|
scrollBehavior: "smooth",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cars.map((car, index) => (
|
||||||
|
<Box
|
||||||
|
key={car.id}
|
||||||
|
sx={{
|
||||||
|
width: isMobile ? "80vw" : "25vw",
|
||||||
|
minWidth: isMobile ? "80vw" : "25vw",
|
||||||
|
marginLeft: index === 0 ? "0.5vw" : 0,
|
||||||
|
bgcolor: "white",
|
||||||
|
border: isMobile ? "1px solid #C27664" : "none",
|
||||||
|
borderRadius: "2vw",
|
||||||
|
overflow: "hidden",
|
||||||
|
boxShadow: isMobile
|
||||||
|
? "none"
|
||||||
|
: "-0.5vw 0.5vw 2vw rgba(0, 0, 0, 0.25)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: isMobile ? "50vw" : "15vw",
|
||||||
|
backgroundImage: `url(${getImageUrl(car.image)})`,
|
||||||
|
backgroundSize: "cover",
|
||||||
|
backgroundPosition: "center",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Box sx={{ p: "0.5vw 1.5vw 1.5vw 1.5vw" }}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "4vw" : "1.8vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{car.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "3.5vw" : "1vw",
|
||||||
|
color: "#666",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Год выпуска {car.year}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "3.5vw" : "1vw",
|
||||||
|
color: "#666",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Пробег {car.mileage.toLocaleString()} км.
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
width: "100%",
|
||||||
|
height: "0.2vw",
|
||||||
|
backgroundColor: "rgba(220, 220, 220, 1)",
|
||||||
|
borderRadius: "1vw",
|
||||||
|
mt: "0.5vw",
|
||||||
|
}}
|
||||||
|
></Box>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "4.5vw" : "1.5vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{car.price.toLocaleString()}₽
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
position: "relative",
|
||||||
|
top: isMobile ? "3.7vw" : "-14vw",
|
||||||
|
left: "0",
|
||||||
|
right: "0",
|
||||||
|
bottom: "0",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: isMobile ? "75vw" : "60vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KeyboardDoubleArrowLeftIcon
|
||||||
|
onClick={scrollLeft}
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "10vw" : "5vw",
|
||||||
|
cursor: "pointer",
|
||||||
|
color: "#C27664",
|
||||||
|
transition: "transform 0.2s",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "scale(1.2)",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
transform: "scale(0.9)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<KeyboardDoubleArrowRightIcon
|
||||||
|
onClick={scrollRight}
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "10vw" : "5vw",
|
||||||
|
cursor: "pointer",
|
||||||
|
color: "#C27664",
|
||||||
|
transition: "transform 0.2s",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "scale(1.2)",
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
transform: "scale(0.9)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Индикатор скролла */}
|
||||||
|
<Box
|
||||||
|
ref={scrollTrackRef}
|
||||||
|
onClick={handleTrackClick}
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: isMobile ? "6vw" : "2vw",
|
||||||
|
bgcolor: "#C27664",
|
||||||
|
borderRadius: "3vw",
|
||||||
|
mb: "1vw",
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
cursor: "pointer",
|
||||||
|
boxShadow: "-0.5vw 0.5vw 1vw rgba(0, 0, 0, 0.25)",
|
||||||
|
mt: "-4vw ",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
ref={scrollThumbRef}
|
||||||
|
onMouseDown={handleDragStart}
|
||||||
|
onTouchStart={handleTouchStart}
|
||||||
|
sx={{
|
||||||
|
width: `${thumbWidth}%`,
|
||||||
|
height: isMobile ? "4vw" : "1vw",
|
||||||
|
bgcolor: "#fff",
|
||||||
|
borderRadius: "3vw",
|
||||||
|
position: "absolute",
|
||||||
|
left: isMobile ? "1vw" : "0.5vw",
|
||||||
|
top: isMobile ? "1vw" : "0.5vw",
|
||||||
|
cursor: "grab",
|
||||||
|
boxShadow: "-0.5vw 0.5vw 1vw rgba(0, 0, 0, 0.25)",
|
||||||
|
"&:active": {
|
||||||
|
cursor: "grabbing",
|
||||||
|
},
|
||||||
|
transition: isDragging ? "none" : "left 0.1s ease",
|
||||||
|
userSelect: "none", // Предотвращает выделение текста
|
||||||
|
WebkitUserDrag: "none", // Предотвращает перетаскивание в WebKit
|
||||||
|
WebkitTouchCallout: "none", // Отключает контекстное меню при длительном нажатии
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Кнопки */}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: "2vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
component="a"
|
||||||
|
href="https://t.me/your_telegram_channel"
|
||||||
|
target="_blank"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "#C27664",
|
||||||
|
color: "#fff",
|
||||||
|
borderRadius: isMobile ? "3vw" : "1.5vw",
|
||||||
|
padding: isMobile ? "3vw 5vw" : "1vw 2vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "3.5vw" : "1.2vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
cursor: "pointer",
|
||||||
|
textDecoration: "none",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Наличие в Telegram
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
onClick={handleOpenFeedback}
|
||||||
|
sx={{
|
||||||
|
bgcolor: "#fff",
|
||||||
|
color: "#C27664",
|
||||||
|
border: "2px solid #C27664",
|
||||||
|
borderRadius: isMobile ? "3vw" : "1.5vw",
|
||||||
|
padding: isMobile ? "3vw 5vw" : "1vw 2vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "3.5vw" : "1.2vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Оставить заявку
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Feedback
|
||||||
|
open={feedbackOpen}
|
||||||
|
onClose={handleCloseFeedback}
|
||||||
|
handleScrollToAnchor={() => {}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CarsPage;
|
151
src/pages/DeliveryPage.tsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
|
import usa from "../assets/emoji/united-states.png";
|
||||||
|
import china from "../assets/emoji/china.png";
|
||||||
|
import korea from "../assets/emoji/korea.png";
|
||||||
|
import Feedback from "../components/Feedback";
|
||||||
|
import { scrollToAnchor } from "../utils/scrollUtils";
|
||||||
|
|
||||||
|
function DeliveryPage() {
|
||||||
|
const { isMobile } = useResponsive();
|
||||||
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
||||||
|
const setDrawerOpen = () => {};
|
||||||
|
|
||||||
|
const handleOpenFeedback = () => {
|
||||||
|
setFeedbackOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseFeedback = () => {
|
||||||
|
setFeedbackOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScrollToAnchor = (anchor: string) => {
|
||||||
|
scrollToAnchor(anchor, setDrawerOpen, isMobile);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
id="about-us"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "white",
|
||||||
|
color: "black",
|
||||||
|
padding: "5vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: "80vw",
|
||||||
|
margin: "0 auto",
|
||||||
|
textAlign: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "10vw" : "4vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: "1vw",
|
||||||
|
maxWidth: "50vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Мы доставляем автомобили из{" "}
|
||||||
|
<span style={{ color: "#C27664" }}>США, Китая и Кореи</span>
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: "flex", gap: "2vw" }}>
|
||||||
|
<img
|
||||||
|
src={usa}
|
||||||
|
alt="usa"
|
||||||
|
style={{ width: "10vw", height: "9vw", pointerEvents: "none" }}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={china}
|
||||||
|
alt="china"
|
||||||
|
style={{ width: "10vw", height: "9vw", pointerEvents: "none" }}
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={korea}
|
||||||
|
alt="korea"
|
||||||
|
style={{ width: "10vw", height: "9vw", pointerEvents: "none" }}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: "80vw",
|
||||||
|
width: "29vw",
|
||||||
|
height: "23vw",
|
||||||
|
bgcolor: "transparent",
|
||||||
|
position: "relative",
|
||||||
|
top: "-29vw",
|
||||||
|
left: "56vw",
|
||||||
|
borderRadius: "3vw",
|
||||||
|
border: "0.3vw solid #C27664",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: "2vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "#C27664",
|
||||||
|
mt: "3vw",
|
||||||
|
ml: "2.5vw",
|
||||||
|
maxWidth: "80%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Не знаете какой авто выбрать?
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: "1.5vw",
|
||||||
|
fontWeight: "light",
|
||||||
|
color: "#C27664",
|
||||||
|
ml: "2.5vw",
|
||||||
|
maxWidth: "60%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Подберите авто прямо сейчас!
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
onClick={handleOpenFeedback}
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: "1.5vw",
|
||||||
|
color: "white",
|
||||||
|
bgcolor: "#C27664",
|
||||||
|
borderRadius: "3vw",
|
||||||
|
maxWidth: "60%",
|
||||||
|
cursor: "pointer",
|
||||||
|
left: "50%",
|
||||||
|
top: "70%",
|
||||||
|
position: "absolute",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
padding: "1vw 3vw 1vw 3vw",
|
||||||
|
width: "18vw",
|
||||||
|
textAlign: "center",
|
||||||
|
"&:hover": {
|
||||||
|
bgcolor: "#945B4D",
|
||||||
|
transform: "translateX(-50%) translateY(-5%)",
|
||||||
|
},
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Подобрать авто
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Feedback
|
||||||
|
open={feedbackOpen}
|
||||||
|
onClose={handleCloseFeedback}
|
||||||
|
handleScrollToAnchor={handleScrollToAnchor}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DeliveryPage;
|
@ -3,10 +3,14 @@ import { Button, Box, Typography, IconButton } from "@mui/material";
|
|||||||
import Feedback from "../components/Feedback";
|
import Feedback from "../components/Feedback";
|
||||||
import car from "../../src/assets/icon/car.png";
|
import car from "../../src/assets/icon/car.png";
|
||||||
import { useResponsive } from "../theme/useResponsive";
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
import TelegramIcon from "@mui/icons-material/Telegram";
|
||||||
import VkIcon from '@mui/icons-material/Facebook';
|
import VkIcon from "@mui/icons-material/Facebook";
|
||||||
import WhatsAppIcon from '@mui/icons-material/WhatsApp';
|
import WhatsAppIcon from "@mui/icons-material/WhatsApp";
|
||||||
import { scrollToAnchor } from "../utils/scrollUtils";
|
import { scrollToAnchor } from "../utils/scrollUtils";
|
||||||
|
import AboutUsPage from "./AboutUsPage";
|
||||||
|
import StagesPage from "./StagesPage";
|
||||||
|
import CarsPage from "./CarsPage";
|
||||||
|
import DeliveryPage from "./DeliveryPage";
|
||||||
|
|
||||||
function MainPage() {
|
function MainPage() {
|
||||||
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
||||||
@ -26,172 +30,196 @@ function MainPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ bgcolor: "#2D2D2D", color: "white", userSelect: "none" }} id="main">
|
<Box sx={{ bgcolor: "#2D2D2D", color: "white", userSelect: "none" }}>
|
||||||
<Box
|
<Box
|
||||||
id="title"
|
id="main"
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: "left",
|
bgcolor: "transparent",
|
||||||
width: "35vw",
|
height: "100%",
|
||||||
ml: "5vw",
|
width: "100%",
|
||||||
height: "30vw",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontFamily: "Unbounded",
|
|
||||||
fontSize: "9vw",
|
|
||||||
fontWeight: "bold",
|
|
||||||
position: "relative",
|
|
||||||
zIndex: 1,
|
|
||||||
left: "3vw",
|
|
||||||
background:
|
|
||||||
"linear-gradient(90deg, rgb(255, 255, 255), rgb(105, 105, 105))",
|
|
||||||
WebkitBackgroundClip: "text",
|
|
||||||
WebkitTextFillColor: "transparent",
|
|
||||||
backgroundClip: "text",
|
|
||||||
color: "transparent",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
АВТО
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontFamily: "Unbounded",
|
|
||||||
fontSize: "9vw",
|
|
||||||
fontWeight: "bold",
|
|
||||||
position: "relative",
|
|
||||||
mt: "-5.5vw",
|
|
||||||
zIndex: 1,
|
|
||||||
right: "2vw",
|
|
||||||
background:
|
|
||||||
"linear-gradient(90deg, rgb(255, 255, 255), rgb(0, 0, 0))",
|
|
||||||
WebkitBackgroundClip: "text",
|
|
||||||
WebkitTextFillColor: "transparent",
|
|
||||||
backgroundClip: "text",
|
|
||||||
color: "transparent",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
БРО
|
|
||||||
</Typography>
|
|
||||||
<img
|
|
||||||
src={car}
|
|
||||||
alt="logo"
|
|
||||||
style={{
|
|
||||||
width: "60vw",
|
|
||||||
height: "32vw",
|
|
||||||
position: "relative",
|
|
||||||
top: "-19vw",
|
|
||||||
left: "12vw",
|
|
||||||
zIndex: 2,
|
|
||||||
pointerEvents: "none",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
|
id="title"
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
textAlign: "left",
|
||||||
right: "4.5vw",
|
width: "35vw",
|
||||||
top: isMobile ? "18.3vw" : "13.3vw",
|
ml: "5vw",
|
||||||
display: "flex",
|
height: "30vw",
|
||||||
flexDirection: "column",
|
|
||||||
gap: "1vw",
|
|
||||||
zIndex: 1,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Typography
|
||||||
onClick={() => handleScrollToAnchor("#calculator")}
|
|
||||||
variant="contained"
|
|
||||||
sx={{
|
sx={{
|
||||||
bgcolor: "#C27664",
|
|
||||||
color: "white",
|
|
||||||
fontSize: "1.5vw",
|
|
||||||
padding: "2.2vw 2vw",
|
|
||||||
textTransform: "none",
|
|
||||||
borderRadius: "3vw",
|
|
||||||
fontWeight: "bold",
|
|
||||||
fontFamily: "Unbounded",
|
fontFamily: "Unbounded",
|
||||||
"&:hover": { bgcolor: "#945B4D" },
|
fontSize: "9vw",
|
||||||
transition: "all 0.3s ease",
|
fontWeight: "bold",
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 1,
|
||||||
|
left: "3vw",
|
||||||
|
background:
|
||||||
|
"linear-gradient(90deg, rgb(255, 255, 255), rgb(105, 105, 105))",
|
||||||
|
WebkitBackgroundClip: "text",
|
||||||
|
WebkitTextFillColor: "transparent",
|
||||||
|
backgroundClip: "text",
|
||||||
|
color: "transparent",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Рассчитать стоимость
|
АВТО
|
||||||
</Button>
|
</Typography>
|
||||||
|
<Typography
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={handleOpenFeedback}
|
|
||||||
sx={{
|
sx={{
|
||||||
color: "#C27664",
|
|
||||||
fontSize: "1.5vw",
|
|
||||||
padding: "2.2vw 2vw",
|
|
||||||
textTransform: "none",
|
|
||||||
borderRadius: "3vw",
|
|
||||||
fontWeight: "bold",
|
|
||||||
fontFamily: "Unbounded",
|
fontFamily: "Unbounded",
|
||||||
borderColor: "#C27664",
|
fontSize: "9vw",
|
||||||
"&:hover": { borderColor: "#945B4D", bgcolor: "transparent", color: "#945B4D" },
|
fontWeight: "bold",
|
||||||
transition: "all 0.3s ease",
|
position: "relative",
|
||||||
|
mt: "-5.5vw",
|
||||||
|
zIndex: 1,
|
||||||
|
right: "2vw",
|
||||||
|
background:
|
||||||
|
"linear-gradient(90deg, rgb(255, 255, 255), rgb(0, 0, 0))",
|
||||||
|
WebkitBackgroundClip: "text",
|
||||||
|
WebkitTextFillColor: "transparent",
|
||||||
|
backgroundClip: "text",
|
||||||
|
color: "transparent",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Подобрать авто
|
БРО
|
||||||
</Button>
|
</Typography>
|
||||||
</Box>
|
<img
|
||||||
<Box id="contacts" sx={{
|
src={car}
|
||||||
// position: "absolute",
|
alt="logo"
|
||||||
// top: isMobile ? "18.3vw" : "13.3vw",
|
style={{
|
||||||
// right: "4.5vw",
|
width: "60vw",
|
||||||
// width: "100%",
|
height: "32vw",
|
||||||
// display: "flex",
|
position: "relative",
|
||||||
// flexDirection: "column",
|
top: "-19vw",
|
||||||
// gap: isMobile ? "1vw" : "2vw",
|
left: "12vw",
|
||||||
|
zIndex: 2,
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
position: "absolute",
|
<Box
|
||||||
right: "1vw",
|
sx={{
|
||||||
top: isMobile ? "20.3vw" : "15.3vw",
|
position: "absolute",
|
||||||
display: "flex",
|
right: "4.5vw",
|
||||||
flexDirection: "column",
|
top: isMobile ? "18.3vw" : "13.3vw",
|
||||||
gap: "1vw",
|
display: "flex",
|
||||||
zIndex: 1,
|
flexDirection: "column",
|
||||||
}}>
|
gap: "1vw",
|
||||||
<IconButton
|
zIndex: 1,
|
||||||
sx={{
|
}}
|
||||||
color: "#C27664",
|
>
|
||||||
|
<Button
|
||||||
|
onClick={() => handleScrollToAnchor("#calculator")}
|
||||||
|
variant="contained"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "#C27664",
|
||||||
|
color: "white",
|
||||||
|
fontSize: "1.5vw",
|
||||||
|
padding: "2.2vw 2vw",
|
||||||
|
textTransform: "none",
|
||||||
|
borderRadius: "3vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
"&:hover": { bgcolor: "#945B4D" },
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Рассчитать стоимость
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={handleOpenFeedback}
|
||||||
|
sx={{
|
||||||
|
color: "#C27664",
|
||||||
|
fontSize: "1.5vw",
|
||||||
|
padding: "2.2vw 2vw",
|
||||||
|
textTransform: "none",
|
||||||
|
borderRadius: "3vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
borderColor: "#C27664",
|
||||||
|
"&:hover": {
|
||||||
|
borderColor: "#945B4D",
|
||||||
|
bgcolor: "transparent",
|
||||||
|
color: "#945B4D",
|
||||||
|
},
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Подобрать авто
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
id="contacts"
|
||||||
|
sx={{
|
||||||
|
// position: "absolute",
|
||||||
|
// top: isMobile ? "18.3vw" : "13.3vw",
|
||||||
|
// right: "4.5vw",
|
||||||
|
// width: "100%",
|
||||||
|
// display: "flex",
|
||||||
|
// flexDirection: "column",
|
||||||
|
// gap: isMobile ? "1vw" : "2vw",
|
||||||
|
|
||||||
|
position: "absolute",
|
||||||
|
right: "1vw",
|
||||||
|
top: isMobile ? "20.3vw" : "15.3vw",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "1vw",
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
sx={{
|
||||||
|
color: "#C27664",
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
width: "3vw",
|
width: "3vw",
|
||||||
height: "3vw",
|
height: "3vw",
|
||||||
"&:hover": { color: "#945B4D", borderColor: "#945B4D" }
|
"&:hover": { color: "#945B4D", borderColor: "#945B4D" },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<VkIcon sx={{ fontSize: "3vw" }} />
|
<VkIcon sx={{ fontSize: "3vw" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
color: "#C27664",
|
color: "#C27664",
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
width: "3vw",
|
width: "3vw",
|
||||||
height: "3vw",
|
height: "3vw",
|
||||||
"&:hover": { color: "#945B4D", borderColor: "#945B4D" }
|
"&:hover": { color: "#945B4D", borderColor: "#945B4D" },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TelegramIcon sx={{ fontSize: "3vw" }} />
|
<TelegramIcon sx={{ fontSize: "3vw" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
sx={{
|
sx={{
|
||||||
color: "#C27664",
|
color: "#C27664",
|
||||||
borderRadius: "50%",
|
borderRadius: "50%",
|
||||||
width: "3vw",
|
width: "3vw",
|
||||||
height: "3vw",
|
height: "3vw",
|
||||||
"&:hover": { color: "#945B4D", borderColor: "#945B4D" }
|
"&:hover": { color: "#945B4D", borderColor: "#945B4D" },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WhatsAppIcon sx={{ fontSize: "3vw" }} />
|
<WhatsAppIcon sx={{ fontSize: "3vw" }} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Feedback open={feedbackOpen} onClose={handleCloseFeedback} handleScrollToAnchor={handleScrollToAnchor} />
|
<Feedback
|
||||||
|
open={feedbackOpen}
|
||||||
|
onClose={handleCloseFeedback}
|
||||||
|
handleScrollToAnchor={handleScrollToAnchor}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
<AboutUsPage />
|
||||||
|
<StagesPage />
|
||||||
|
<CarsPage />
|
||||||
|
<DeliveryPage />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
449
src/pages/StagesPage.tsx
Normal file
@ -0,0 +1,449 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import { useResponsive } from "../theme/useResponsive";
|
||||||
|
import Feedback from "../components/Feedback";
|
||||||
|
import emoji1 from "../assets/emoji/1.png";
|
||||||
|
import emoji2 from "../assets/emoji/2.png";
|
||||||
|
import emoji3 from "../assets/emoji/3.png";
|
||||||
|
import emoji4 from "../assets/emoji/4.png";
|
||||||
|
import emoji5 from "../assets/emoji/5.png";
|
||||||
|
|
||||||
|
function StagesPage() {
|
||||||
|
const { isMobile } = useResponsive();
|
||||||
|
const [feedbackOpen, setFeedbackOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleOpenFeedback = () => {
|
||||||
|
setFeedbackOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseFeedback = () => {
|
||||||
|
setFeedbackOpen(false);
|
||||||
|
const circle = document.querySelector(".slider-circle") as HTMLElement;
|
||||||
|
if (circle) {
|
||||||
|
// Удаляем inline-стиль вместо его установки
|
||||||
|
circle.style.removeProperty("left");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const stageCards = [
|
||||||
|
{
|
||||||
|
number: "1",
|
||||||
|
emoji: emoji1,
|
||||||
|
title: "Знакомство с компанией и предварительный договор",
|
||||||
|
items: [
|
||||||
|
"Консультация",
|
||||||
|
"Подбор автомобиля",
|
||||||
|
"предварительный расчет стоимости",
|
||||||
|
"Заключение предварительного договора и внесение депозита 10000₽ (100 000 руб — входит в стоимость автомобиля)",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "2",
|
||||||
|
emoji: emoji2,
|
||||||
|
title: "Подбор, выездная диагностика и выкуп автомобиля",
|
||||||
|
items: [
|
||||||
|
"Поиск - подбираем авто по вашим параметрам.",
|
||||||
|
"Проверка – выездная экспертиза с отчетом.",
|
||||||
|
"Покупка – безопасный выкуп.",
|
||||||
|
"Договор – фиксируем цену (авто + доставка + таможня), предоплата 30-70%.",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "3",
|
||||||
|
emoji: emoji3,
|
||||||
|
title: "Логистика",
|
||||||
|
items: [
|
||||||
|
"Привезем автомобиль в установленные сроки: Китай от 30 дней, Европа от 30 дней, США от 60 дней, Корея от 45 дней",
|
||||||
|
"Выплатим компенсацию за каждый день задержки",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "4",
|
||||||
|
emoji: emoji4,
|
||||||
|
title: "Таможенная очистка и получение эПТС",
|
||||||
|
items: [
|
||||||
|
"В зависимости от страны экспорта таможенная очистка проходит в разных таможенных пунктах (благодаря нашему опыту мы всегда делаем предварительные расчеты, где выгоднее для клиента проводить таможенное оформление)",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
number: "5",
|
||||||
|
emoji: emoji5,
|
||||||
|
title: "Вручение",
|
||||||
|
items: [
|
||||||
|
"Автомобиль вручается с полным комплектом документов в нашем автосалоне в Москве",
|
||||||
|
"Собственный парк эвакуаторов позволяет организовать доставку автомобиля с полным комплектом документов до двери вашего дома",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
id="stages"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "white",
|
||||||
|
color: "black",
|
||||||
|
padding: isMobile ? "10vw 5vw" : "5vw",
|
||||||
|
minHeight: "100vh",
|
||||||
|
scrollMarginTop: isMobile ? "15vw" : "10vw",
|
||||||
|
position: "relative", // Добавляем позиционирование для родителя
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
maxWidth: "75vw",
|
||||||
|
margin: "0 auto",
|
||||||
|
textAlign: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
component="h2"
|
||||||
|
id="stages"
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "8vw" : "4vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: "5vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
ЭТАПЫ <span style={{ color: "#C27664" }}>РАБОТЫ</span>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{isMobile ? (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "3vw",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stageCards.map((card, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#ffffff",
|
||||||
|
borderRadius: "3vw",
|
||||||
|
padding: "3vw",
|
||||||
|
color: "#000000",
|
||||||
|
boxShadow: "0px 4px 20px rgba(0, 0, 0, 0.1)",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: "2vw",
|
||||||
|
gap: "1vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={card.emoji}
|
||||||
|
alt={`Этап ${card.number}`}
|
||||||
|
style={{
|
||||||
|
width: isMobile ? "10vw" : "5vw",
|
||||||
|
height: "auto",
|
||||||
|
marginRight: "1vw",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "4vw" : "1.8vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
lineHeight: 1.2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{card.number}. {card.title}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
{card.items.map((item, idx) => (
|
||||||
|
<Box
|
||||||
|
key={idx}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
marginBottom: "1vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "3.5vw" : "1.5vw",
|
||||||
|
lineHeight: 1.3,
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
• {item}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Box
|
||||||
|
onClick={(e) => {
|
||||||
|
handleOpenFeedback();
|
||||||
|
const circle = e.currentTarget.querySelector(
|
||||||
|
".slider-circle"
|
||||||
|
) as HTMLElement;
|
||||||
|
if (circle) {
|
||||||
|
circle.style.left = "18.2vw";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
background: "linear-gradient(to bottom, #C27664, #f7c6bc)",
|
||||||
|
borderRadius: isMobile ? "4.5vw" : "1.5vw",
|
||||||
|
color: "#ffffff",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: isMobile ? "70vw" : "24vw",
|
||||||
|
maxWidth: isMobile ? "70vw" : "24vw",
|
||||||
|
height: isMobile ? "85vw" : "29vw",
|
||||||
|
maxHeight: isMobile ? "85vw" : "29vw",
|
||||||
|
boxShadow: isMobile
|
||||||
|
? "-1.5vw 1.5vw 2vw rgba(0, 0, 0, 0.3), inset 1.5vw 1.5vw 1.7vw rgba(255, 255, 255, 0.3)"
|
||||||
|
: "-0.5vw 0.5vw 2vw rgba(0, 0, 0, 0.3), inset 0.5vw 0.5vw 0.7vw rgba(255, 255, 255, 0.3)",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
"&:hover": {
|
||||||
|
boxShadow:
|
||||||
|
"-0.5vw 0.5vw 2.5vw rgba(0, 0, 0, 0.4), inset 0.5vw 0.5vw 0.7vw rgba(255, 255, 255, 0.3)",
|
||||||
|
"& .slider-circle": {
|
||||||
|
left: "18.2vw",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ padding: "2vw" }}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: isMobile ? "6vw" : "2vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
mt: "2vw",
|
||||||
|
ml: "2vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Есть вопросы?
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: isMobile ? "5.2vw" : "1.2vw",
|
||||||
|
marginBottom: "2vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
ml: "2vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Оставьте заявку прямо сейчас
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: isMobile ? "5vw" : "2vw",
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 1)",
|
||||||
|
borderRadius: isMobile ? "5vw" : "1vw",
|
||||||
|
position: "relative",
|
||||||
|
mt: isMobile ? "46vw" : "15vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className="slider-circle"
|
||||||
|
sx={{
|
||||||
|
width: isMobile ? "4vw" : "1.5vw",
|
||||||
|
height: isMobile ? "4vw" : "1.5vw",
|
||||||
|
backgroundColor: "#9A5B4C",
|
||||||
|
borderRadius: "50%",
|
||||||
|
position: "absolute",
|
||||||
|
left: isMobile ? "0.5vw" : "0.25vw",
|
||||||
|
top: isMobile ? "0.5vw" : "0.25vw",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: "2vw",
|
||||||
|
justifyContent: "center",
|
||||||
|
maxWidth: "75vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stageCards.map((card, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "white",
|
||||||
|
borderRadius: "1.5vw",
|
||||||
|
padding: "2vw",
|
||||||
|
color: "#000000",
|
||||||
|
boxShadow: "-0.5vw 0.5vw 2vw rgba(0, 0, 0, 0.3)",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "20vw",
|
||||||
|
marginBottom: "2vw",
|
||||||
|
height: "25vw",
|
||||||
|
maxWidth: "20vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: "1.5vw",
|
||||||
|
gap: "1vw",
|
||||||
|
flexDirection: "column",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={card.emoji}
|
||||||
|
alt={`Этап ${card.number}`}
|
||||||
|
style={{
|
||||||
|
width: "3vw",
|
||||||
|
height: "auto",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: "1.2vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
lineHeight: 1.2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{card.number}. {card.title}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
{card.items.map((item, idx) => (
|
||||||
|
<Box
|
||||||
|
key={idx}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
marginBottom: "0.8vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "1vw",
|
||||||
|
lineHeight: 1.3,
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
• {item}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Box
|
||||||
|
onClick={(e) => {
|
||||||
|
handleOpenFeedback();
|
||||||
|
const circle = e.currentTarget.querySelector(
|
||||||
|
".slider-circle"
|
||||||
|
) as HTMLElement;
|
||||||
|
if (circle) {
|
||||||
|
circle.style.left = "18.2vw";
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
background: "linear-gradient(to bottom, #C27664, #f7c6bc)",
|
||||||
|
borderRadius: "1.5vw",
|
||||||
|
color: "#ffffff",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
width: "24vw",
|
||||||
|
maxWidth: "24vw",
|
||||||
|
height: "29vw",
|
||||||
|
maxHeight: "29vw",
|
||||||
|
boxShadow:
|
||||||
|
"-0.5vw 0.5vw 2vw rgba(0, 0, 0, 0.3), inset 0.5vw 0.5vw 0.7vw rgba(255, 255, 255, 0.3)",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
"&:hover": {
|
||||||
|
boxShadow:
|
||||||
|
"-0.5vw 0.5vw 2.5vw rgba(0, 0, 0, 0.4), inset 0.5vw 0.5vw 0.7vw rgba(255, 255, 255, 0.3)",
|
||||||
|
"& .slider-circle": {
|
||||||
|
left: "18.2vw",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ padding: "2vw" }}>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
fontSize: "2vw",
|
||||||
|
fontWeight: "bold",
|
||||||
|
marginBottom: "1.5vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Есть вопросы?
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "1.2vw",
|
||||||
|
marginBottom: "2vw",
|
||||||
|
fontFamily: "Unbounded",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Оставьте заявку прямо сейчас
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "2vw",
|
||||||
|
backgroundColor: "rgba(255, 255, 255, 1)",
|
||||||
|
borderRadius: "1vw",
|
||||||
|
position: "relative",
|
||||||
|
mt: "15vw",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
className="slider-circle"
|
||||||
|
sx={{
|
||||||
|
width: "1.5vw",
|
||||||
|
height: "1.5vw",
|
||||||
|
backgroundColor: "#9A5B4C",
|
||||||
|
borderRadius: "50%",
|
||||||
|
position: "absolute",
|
||||||
|
left: "0.25vw",
|
||||||
|
top: "0.25vw",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Feedback
|
||||||
|
open={feedbackOpen}
|
||||||
|
onClose={handleCloseFeedback}
|
||||||
|
handleScrollToAnchor={() => {}} // Если нужен пустой обработчик для совместимости
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StagesPage;
|
131
src/utils/api.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// utils/api.ts
|
||||||
|
|
||||||
|
// Типы данных
|
||||||
|
export interface Car {
|
||||||
|
id: number;
|
||||||
|
make: string;
|
||||||
|
model: string;
|
||||||
|
year: number;
|
||||||
|
mileage: number;
|
||||||
|
price: number;
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CarsResponse {
|
||||||
|
cars: Car[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CarResponse {
|
||||||
|
car: Car;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Базовый URL API
|
||||||
|
const API_BASE_URL = 'http://localhost:8000';
|
||||||
|
|
||||||
|
// Общая функция для обработки ошибок
|
||||||
|
const handleApiError = (error: any): never => {
|
||||||
|
console.error('API Error:', error);
|
||||||
|
throw new Error(error?.message || 'Произошла ошибка при запросе к API');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Получение списка автомобилей
|
||||||
|
export const fetchCars = async (skip = 0, limit = 100): Promise<CarsResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/cars?skip=${skip}&limit=${limit}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Получение одного автомобиля по ID
|
||||||
|
export const fetchCarById = async (carId: number): Promise<CarResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/cars/${carId}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Добавление нового автомобиля
|
||||||
|
export const createCar = async (carData: Omit<Car, 'id'>, image?: File): Promise<CarResponse> => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('car_data', JSON.stringify(carData));
|
||||||
|
|
||||||
|
if (image) {
|
||||||
|
formData.append('image', image);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/cars`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обновление автомобиля
|
||||||
|
export const updateCar = async (
|
||||||
|
carId: number,
|
||||||
|
carData: Partial<Omit<Car, 'id'>>,
|
||||||
|
image?: File
|
||||||
|
): Promise<CarResponse> => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('car_data', JSON.stringify(carData));
|
||||||
|
|
||||||
|
if (image) {
|
||||||
|
formData.append('image', image);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/cars/${carId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Удаление автомобиля
|
||||||
|
export const deleteCar = async (carId: number): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/cars/${carId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Ошибка HTTP: ${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return handleApiError(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Вспомогательная функция для формирования полного URL для изображения
|
||||||
|
export const getImageUrl = (imagePath: string | null): string => {
|
||||||
|
if (!imagePath) return '/placeholder.jpg';
|
||||||
|
return `${API_BASE_URL}${imagePath}`;
|
||||||
|
};
|
@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Плавная прокрутка к указанному якорю
|
* Плавная прокрутка к указанному якорю с учетом высоты хедера
|
||||||
* @param anchor - идентификатор якоря, например "#about"
|
* @param anchor - идентификатор якоря, например "#about"
|
||||||
* @param setDrawerOpen - опциональная функция для закрытия мобильного меню
|
* @param setDrawerOpen - опциональная функция для закрытия мобильного меню
|
||||||
* @param isMobile - флаг мобильного устройства
|
* @param isMobile - флаг мобильного устройства
|
||||||
@ -11,11 +11,53 @@ export const scrollToAnchor = (
|
|||||||
): void => {
|
): void => {
|
||||||
const element = document.querySelector(anchor);
|
const element = document.querySelector(anchor);
|
||||||
if (element) {
|
if (element) {
|
||||||
element.scrollIntoView({ behavior: "smooth" });
|
// Получаем высоту хедера (в соответствии с размерами в Header.tsx)
|
||||||
|
const headerHeight = isMobile ? window.innerWidth * 0.15 : window.innerWidth * 0.10; // 15vw или 10vw
|
||||||
|
|
||||||
|
// Получаем позицию элемента относительно верха страницы
|
||||||
|
const elementPosition = element.getBoundingClientRect().top + window.scrollY;
|
||||||
|
|
||||||
|
// Целевая позиция с учетом высоты хедера
|
||||||
|
const targetPosition = elementPosition - headerHeight;
|
||||||
|
|
||||||
// Обновляем URL с хешем без перезагрузки страницы
|
// Обновляем URL с хешем без перезагрузки страницы
|
||||||
window.history.pushState(null, '', anchor);
|
window.history.pushState(null, '', anchor);
|
||||||
|
|
||||||
|
// Реализация плавного скролла с анимацией
|
||||||
|
smoothScrollTo(targetPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMobile && setDrawerOpen) {
|
if (isMobile && setDrawerOpen) {
|
||||||
setDrawerOpen(false);
|
setDrawerOpen(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция для плавного скролла с анимацией
|
||||||
|
* @param targetPosition - конечная позиция скролла
|
||||||
|
*/
|
||||||
|
function smoothScrollTo(targetPosition: number, duration: number = 500) {
|
||||||
|
const startPosition = window.scrollY;
|
||||||
|
const distance = targetPosition - startPosition;
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
function step(currentTime: number) {
|
||||||
|
const elapsedTime = currentTime - startTime;
|
||||||
|
|
||||||
|
if (elapsedTime >= duration) {
|
||||||
|
window.scrollTo(0, targetPosition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция плавности - easeInOutQuad
|
||||||
|
const progress = elapsedTime / duration;
|
||||||
|
const easeProgress = progress < 0.5
|
||||||
|
? 2 * progress * progress
|
||||||
|
: 1 - Math.pow(-2 * progress + 2, 2) / 2;
|
||||||
|
|
||||||
|
window.scrollTo(0, startPosition + distance * easeProgress);
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
}
|