feedback panel(not centered on screen)

This commit is contained in:
aurinex
2025-07-07 21:01:46 +05:00
parent 7bbdedf6ef
commit eb6cc40f22
9 changed files with 1424 additions and 162 deletions

861
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,14 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.2.0",
"@mui/material": "^7.2.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react-dom": "^19.1.0",
"react-imask": "^7.6.1",
"react-router-dom": "^7.6.3"
},
"devDependencies": {
"@eslint/js": "^9.29.0",

View File

@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@ -1,33 +1,14 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { Routes, Route } from 'react-router-dom'
import MainPage from './pages/MainPage.tsx'
import Header from './components/Header.tsx'
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
<Header />
<Routes>
<Route path="/" element={<MainPage />} />
</Routes>
</>
)
}

417
src/components/Feedback.tsx Normal file
View File

@ -0,0 +1,417 @@
import React, { useState } from 'react';
import {
Dialog,
TextField,
Button,
IconButton,
Typography,
Box,
Radio,
RadioGroup,
FormControlLabel,
FormControl,
InputAdornment,
InputLabel,
OutlinedInput
} from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import { IMaskInput } from 'react-imask';
interface CustomProps {
onChange: (event: { target: { name: string; value: string } }) => void;
name: string;
}
// Компонент маски для телефона
const TextMaskCustom = React.forwardRef<HTMLInputElement, CustomProps>(
function TextMaskCustom(props, ref) {
const { onChange, ...other } = props;
return (
<IMaskInput
{...other}
mask="+7 (___) ___-__-__"
definitions={{
'_': /[0-9]/,
}}
inputRef={ref}
onAccept={(value: any) => onChange({ target: { name: props.name, value } })}
overwrite
unmask={false}
lazy={false}
placeholderChar="_"
/>
);
},
);
interface FeedbackProps {
open: boolean;
onClose: () => void;
}
const Feedback: React.FC<FeedbackProps> = ({ open, onClose }) => {
const [name, setName] = useState('');
const [phone, setPhone] = useState('+7');
const [country, setCountry] = useState('Европа');
const [budget, setBudget] = useState('до 3 млн');
const [description, setDescription] = useState('');
const [agreeToPolicy, setAgreeToPolicy] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
// логика отправки формы
console.log({ name, phone, country, budget, description, agreeToPolicy });
onClose();
};
const textFieldSx = {
'& .MuiOutlinedInput-root': {
'&.Mui-focused fieldset': {
borderColor: '#c22d1a',
},
'&:hover fieldset': {
borderColor: '#c22d1a',
},
},
'& .MuiOutlinedInput-root, fieldset': {
borderRadius: '1.5vw',
fontSize: '1.25vw'
},
'& .MuiInputLabel-root': {
fontSize: '1.25vw',
transform: 'translate(1.5vw, 1.1vw) scale(1)',
'&.MuiInputLabel-shrink': {
transform: 'translate(1.5vw, -0.6vw) scale(0.75)',
},
'&.Mui-focused': {
color: '#c22d1a',
}
},
'& .MuiInputBase-input': {
fontSize: '1.25vw',
padding: '1vw 1.5vw',
},
};
const handlePhoneChange = (event: React.ChangeEvent<HTMLInputElement>) => {
// Убедимся, что +7 всегда присутствует
if (event.target.value.startsWith('+7')) {
setPhone(event.target.value);
}
};
return (
<Box
// sx={{
// position: 'fixed',
// top: 0,
// left: 0,
// width: '100%',
// height: '100%',
// display: open ? 'flex' : 'none',
// alignItems: 'center',
// justifyContent: 'center',
// zIndex: 1300, // высокий z-index как у диалога
// bgcolor: 'rgba(0, 0, 0, 0.5)', // затемнение фона
// }}
sx={{
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
display: open ? 'flex' : 'none',
zIndex: 1300, // высокий z-index как у диалога
}}
onClick={onClose} // закрытие при клике на фон
>
<Box
sx={{
position: 'relative',
bgcolor: 'background.paper',
maxWidth: '52vw',
borderRadius: '1.5vw',
p: '6vw',
overflow: 'hidden',
boxShadow: 24, // тень как у диалога
}}
onClick={(e) => e.stopPropagation()} // предотвращаем закрытие при клике на контент
>
<IconButton
onClick={onClose}
sx={{
position: 'absolute',
right: '1vw',
top: '1vw',
color: (theme) => theme.palette.grey[500],
}}
>
<CloseIcon />
</IconButton>
<Box sx={{ textAlign: 'center', mb: '1vw', mt: '1vw' }}>
<Typography variant="h5" fontWeight="bold" sx={{ fontSize: '1.9vw' }}>
Оставьте заявку
</Typography>
</Box>
<Box sx={{ mb: '2vw' }}>
<Typography variant="body1" sx={{ textAlign: 'center', fontSize: '1.25vw' }}>
И наш менеджер свяжется с вами для уточнения деталей заказа
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: '1vw' }}>
<TextField
fullWidth
label="Ваше имя"
value={name}
onChange={(e) => setName(e.target.value)}
sx={{
mb: '1vw',
...textFieldSx,
}}
/>
{/* Поле с телефоном как на скриншоте */}
<FormControl
fullWidth
variant="outlined"
sx={{
mb: '1vw',
...textFieldSx,
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: '#c22d1a',
},
}}
>
<InputLabel
htmlFor="phone-input"
shrink
sx={{
color: 'rgba(0, 0, 0, 0.6)',
'&.Mui-focused': {
color: '#c22d1a',
},
}}
>
Ваш телефон*
</InputLabel>
<OutlinedInput
id="phone-input"
value={phone}
onChange={handlePhoneChange}
inputComponent={TextMaskCustom as any}
label="Ваш телефон*"
notched
sx={{
borderColor: '#c22d1a',
'& input': {
paddingLeft: '0',
},
}}
/>
</FormControl>
<Box sx={{ mb: '1vw' }}>
<Typography variant="body2" sx={{ mb: '0.5vw', fontSize: '1.1vw' }}>
Из какой страны привезти автомобиль?
</Typography>
<FormControl component="fieldset" sx={{ width: '100%' }}>
<RadioGroup
row
value={country}
onChange={(e) => setCountry(e.target.value)}
>
<FormControlLabel
value="Европа"
control={
<Radio
sx={{
color: '#c22d1a',
'&.Mui-checked': { color: '#c22d1a' },
'& .MuiSvgIcon-root': { fontSize: '1.5vw' },
padding: '0.5vw'
}}
/>
}
label={<Typography sx={{ fontSize: '1.1vw' }}>Европа</Typography>}
sx={{ marginRight: '1.5vw', ml: '0vw' }}
/>
<FormControlLabel
value="США"
control={
<Radio
sx={{
color: '#c22d1a',
'&.Mui-checked': { color: '#c22d1a' },
'& .MuiSvgIcon-root': { fontSize: '1.5vw' },
padding: '0.5vw'
}}
/>
}
label={<Typography sx={{ fontSize: '1.1vw' }}>США</Typography>}
sx={{ marginRight: '1.5vw' }}
/>
<FormControlLabel
value="Китай"
control={
<Radio
sx={{
color: '#c22d1a',
'&.Mui-checked': { color: '#c22d1a' },
'& .MuiSvgIcon-root': { fontSize: '1.5vw' },
padding: '0.5vw'
}}
/>
}
label={<Typography sx={{ fontSize: '1.1vw' }}>Китай</Typography>}
sx={{ marginRight: '1.5vw' }}
/>
<FormControlLabel
value="Корея"
control={
<Radio
sx={{
color: '#c22d1a',
'&.Mui-checked': { color: '#c22d1a' },
'& .MuiSvgIcon-root': { fontSize: '1.5vw' },
padding: '0.5vw'
}}
/>
}
label={<Typography sx={{ fontSize: '1.1vw' }}>Корея</Typography>}
/>
</RadioGroup>
</FormControl>
</Box>
<Box sx={{ mb: '1vw' }}>
<Typography variant="body2" sx={{ mb: '0.5vw', fontSize: '1.1vw' }}>
Какой у вас бюджет на автомобиль?
</Typography>
<FormControl component="fieldset" sx={{ width: '100%' }}>
<RadioGroup
row
value={budget}
onChange={(e) => setBudget(e.target.value)}
>
<FormControlLabel
value="до 3 млн"
control={
<Radio
sx={{
color: '#c22d1a',
'&.Mui-checked': { color: '#c22d1a' },
'& .MuiSvgIcon-root': { fontSize: '1.5vw' },
padding: '0.5vw'
}}
/>
}
label={<Typography sx={{ fontSize: '1.1vw' }}>до 3 млн</Typography>}
sx={{ marginRight: '1.5vw', ml: '0vw' }}
/>
<FormControlLabel
value="до 5 млн"
control={
<Radio
sx={{
color: '#c22d1a',
'&.Mui-checked': { color: '#c22d1a' },
'& .MuiSvgIcon-root': { fontSize: '1.5vw' },
padding: '0.5vw'
}}
/>
}
label={<Typography sx={{ fontSize: '1.1vw' }}>до 5 млн</Typography>}
sx={{ marginRight: '1.5vw' }}
/>
<FormControlLabel
value="5-10 млн"
control={
<Radio
sx={{
color: '#c22d1a',
'&.Mui-checked': { color: '#c22d1a' },
'& .MuiSvgIcon-root': { fontSize: '1.5vw' },
padding: '0.5vw'
}}
/>
}
label={<Typography sx={{ fontSize: '1.1vw' }}>5-10 млн</Typography>}
sx={{ marginRight: '1.5vw' }}
/>
<FormControlLabel
value="более 10 млн"
control={
<Radio
sx={{
color: '#c22d1a',
'&.Mui-checked': { color: '#c22d1a' },
'& .MuiSvgIcon-root': { fontSize: '1.5vw' },
padding: '0.5vw'
}}
/>
}
label={<Typography sx={{ fontSize: '1.1vw' }}>более 10 млн</Typography>}
/>
</RadioGroup>
</FormControl>
</Box>
<TextField
fullWidth
multiline
rows={4}
placeholder="Укажите какой автомобиль вам нужен ?"
value={description}
onChange={(e) => setDescription(e.target.value)}
sx={{
mb: '1.7vw',
...textFieldSx
}}
/>
<Button
type="submit"
variant="contained"
fullWidth
sx={{
bgcolor: '#c22d1a',
color: 'white',
py: '0.9vw',
borderRadius: '1vw',
fontSize: '1.25vw',
'&:hover': { bgcolor: '#a42517' }
}}
endIcon={<ArrowForwardIcon sx={{ fontSize: '1.4vw' }} />}
>
Отправить
</Button>
<Box sx={{ mt: '1vw', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<Typography variant="body2" sx={{ fontSize: '1vw' }}>
Я подтверждаю, что ознакомлен{' '}
<Typography
component="span"
sx={{
color: '#c22d1a',
ml: '0.5vw',
cursor: 'pointer',
textDecoration: 'underline',
fontSize: '1vw'
}}
>
с политикой конфиденциальности
</Typography>
</Typography>
</Box>
</Box>
</Box>
</Box>
</Box>
);
};
export default Feedback;

107
src/components/Header.tsx Normal file
View File

@ -0,0 +1,107 @@
import React from 'react';
import {
AppBar,
Toolbar,
Typography,
Button,
Box,
Container,
IconButton,
Stack
} from '@mui/material';
import { styled } from '@mui/material/styles';
import PhoneIcon from '@mui/icons-material/Phone';
import AccessTimeIcon from '@mui/icons-material/AccessTime';
import TelegramIcon from '@mui/icons-material/Telegram';
import WhatsAppIcon from '@mui/icons-material/WhatsApp';
// Стилизованная кнопка для "Оставить заявку"
const RequestButton = styled(Button)(({ theme }) => ({
backgroundColor: '#c22d1a',
color: 'white',
borderRadius: '4px',
padding: '10px 20px',
'&:hover': {
backgroundColor: '#a42517',
},
}));
// Стилизованный логотип
const Logo = styled('img')({
height: '40px',
marginRight: '10px',
});
const Header = () => {
return (
<>
<AppBar position="static" color="default" elevation={0} sx={{ backgroundColor: 'white' }}>
<Container maxWidth="xl">
<Toolbar disableGutters>
{/* Логотип и текст */}
<Box sx={{ display: 'flex', alignItems: 'center', flexGrow: 1 }}>
<Logo src="/logo.svg" alt="Логотип" />
<Typography variant="body2" color="text.primary" sx={{ maxWidth: 250, lineHeight: 1.2 }}>
Автомобили из Европы, США, Китая, и Кореи
</Typography>
</Box>
{/* Время работы */}
<Box sx={{ display: 'flex', alignItems: 'center', mx: 2 }}>
<AccessTimeIcon sx={{ mr: 1, color: 'primary.main' }} />
<Typography variant="body2">
Ежедневно: с 9:00 до 20:00
</Typography>
</Box>
{/* Номер телефона */}
<Box sx={{ display: 'flex', alignItems: 'center', mx: 2 }}>
<PhoneIcon sx={{ mr: 1, color: 'primary.main' }} />
<Box>
<Typography variant="body2" color="text.secondary">
Номер телефона
</Typography>
<Typography variant="body1" fontWeight="bold">
+7 927 853 3979
</Typography>
</Box>
</Box>
{/* Кнопка заявки */}
<RequestButton variant="contained">
Оставить заявку
</RequestButton>
{/* Социальные сети */}
<Stack direction="row" spacing={1} sx={{ ml: 2 }}>
<IconButton sx={{ border: '1px solid #e0e0e0' }}>
<img src="/vk-icon.svg" alt="VK" width="24" height="24" />
</IconButton>
<IconButton sx={{ border: '1px solid #e0e0e0' }}>
<WhatsAppIcon color="success" />
</IconButton>
<IconButton sx={{ border: '1px solid #e0e0e0' }}>
<TelegramIcon color="primary" />
</IconButton>
</Stack>
</Toolbar>
</Container>
</AppBar>
{/* Навигационное меню */}
<Box sx={{ borderBottom: 1, borderColor: 'divider', backgroundColor: 'white' }}>
<Container maxWidth="xl">
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
{['О нас', 'В наличии', 'Этапы работы', 'Отзывы', 'Калькулятор', 'Команда', 'Доставленные авто', 'Контакты'].map((item) => (
<Button key={item} sx={{ color: 'text.primary', py: 1.5 }}>
{item}
</Button>
))}
</Box>
</Container>
</Box>
</>
);
};
export default Header;

View File

@ -1,68 +1,5 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
padding: 0;
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
background-color: #505050;
}

View File

@ -2,9 +2,12 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { BrowserRouter } from 'react-router-dom'
createRoot(document.getElementById('root')!).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
)

46
src/pages/MainPage.tsx Normal file
View File

@ -0,0 +1,46 @@
import React, { useState } from 'react'
import { Button, Container, Box, Typography } from '@mui/material'
import Feedback from '../components/Feedback'
function MainPage() {
const [feedbackOpen, setFeedbackOpen] = useState(false);
const handleOpenFeedback = () => {
setFeedbackOpen(true);
};
const handleCloseFeedback = () => {
setFeedbackOpen(false);
};
return (
<Container maxWidth="xl">
<Box sx={{ mt: 4, textAlign: 'center' }}>
<Typography variant="h3" component="h1" gutterBottom>
Главная страница
</Typography>
<Typography variant="body1" paragraph>
Это главная страница приложения
</Typography>
<Button
variant="contained"
onClick={handleOpenFeedback}
sx={{
bgcolor: '#c22d1a',
color: 'white',
py: 1.5,
px: 3,
'&:hover': { bgcolor: '#a42517' }
}}
>
Оставить заявку
</Button>
</Box>
<Feedback open={feedbackOpen} onClose={handleCloseFeedback} />
</Container>
)
}
export default MainPage