Добавлено модальное окно выбора города с сохранением выбора в localStorage. Обновлена логика управления состоянием для выбора города в компоненте Home, улучшено взаимодействие с пользователем при загрузке страницы.
This commit is contained in:
134
app/components/CitySelectModal.tsx
Normal file
134
app/components/CitySelectModal.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { MapPin, X } from 'lucide-react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
|
interface CitySelectModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSelectCity: (city: 'Москва' | 'Чебоксары') => void;
|
||||||
|
currentCity: 'Москва' | 'Чебоксары';
|
||||||
|
}
|
||||||
|
|
||||||
|
const CitySelectModal = ({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onSelectCity,
|
||||||
|
currentCity,
|
||||||
|
}: CitySelectModalProps) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
const overlayVariants = {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: { opacity: 1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const modalVariants = {
|
||||||
|
hidden: { opacity: 0, y: 50, scale: 0.95 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
scale: 1,
|
||||||
|
transition: { duration: 0.3, ease: [0.16, 1, 0.3, 1] },
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
opacity: 0,
|
||||||
|
y: 50,
|
||||||
|
scale: 0.95,
|
||||||
|
transition: { duration: 0.2 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{isOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
exit="hidden"
|
||||||
|
variants={overlayVariants}
|
||||||
|
className="absolute inset-0 bg-black/50"
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
exit="exit"
|
||||||
|
variants={modalVariants}
|
||||||
|
className="bg-white rounded-xl shadow-xl p-6 w-full max-w-md z-10 mx-4"
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900">
|
||||||
|
Выберите город
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="w-8 h-8 flex items-center justify-center rounded-full bg-gray-100 hover:bg-gray-200 text-gray-600"
|
||||||
|
>
|
||||||
|
<X className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-gray-600 mb-6">
|
||||||
|
Пожалуйста, выберите ваш город для получения актуальной информации
|
||||||
|
о наших услугах и контактах
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-3 mb-6">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
onSelectCity('Москва');
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
className={`w-full py-3 px-4 rounded-lg text-left flex items-center ${
|
||||||
|
currentCity === 'Москва'
|
||||||
|
? 'bg-blue-50 text-blue-700 border border-blue-200'
|
||||||
|
: 'text-gray-700 hover:bg-gray-100 border border-gray-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<MapPin className="h-5 w-5 mr-3 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">Москва</span>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
ул. Космонавта Волкова, д. 29к1
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
onSelectCity('Чебоксары');
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
className={`w-full py-3 px-4 rounded-lg text-left flex items-center ${
|
||||||
|
currentCity === 'Чебоксары'
|
||||||
|
? 'bg-blue-50 text-blue-700 border border-blue-200'
|
||||||
|
: 'text-gray-700 hover:bg-gray-100 border border-gray-200'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<MapPin className="h-5 w-5 mr-3 flex-shrink-0" />
|
||||||
|
<div>
|
||||||
|
<span className="font-medium">Чебоксары</span>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
ул. Зои Яковлевой, д. 54
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
className="w-full bg-blue-700 hover:bg-blue-800 text-white"
|
||||||
|
>
|
||||||
|
Продолжить
|
||||||
|
</Button>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CitySelectModal;
|
32
app/page.tsx
32
app/page.tsx
@ -13,12 +13,35 @@ import Contacts from './components/Contacts';
|
|||||||
import ContactForm from './components/ContactForm';
|
import ContactForm from './components/ContactForm';
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
import Loader from './components/Loader';
|
import Loader from './components/Loader';
|
||||||
|
import CitySelectModal from './components/CitySelectModal';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [selectedCity, setSelectedCity] = useState<'Москва' | 'Чебоксары'>(
|
const [selectedCity, setSelectedCity] = useState<'Москва' | 'Чебоксары'>(
|
||||||
'Москва'
|
'Москва'
|
||||||
);
|
);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [isCityModalOpen, setIsCityModalOpen] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Проверяем, выбирал ли пользователь город ранее
|
||||||
|
const savedCity = localStorage.getItem('selectedCity');
|
||||||
|
if (savedCity) {
|
||||||
|
setSelectedCity(savedCity as 'Москва' | 'Чебоксары');
|
||||||
|
} else {
|
||||||
|
// Если город не выбран, показываем модальное окно после загрузки
|
||||||
|
// Устанавливаем таймер, чтобы сначала загрузка прошла, а потом показалось модальное окно
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setIsCityModalOpen(true);
|
||||||
|
}, 2300); // Чуть больше времени, чем таймер загрузки
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Сохраняем выбор города в localStorage
|
||||||
|
const handleCityChange = (city: 'Москва' | 'Чебоксары') => {
|
||||||
|
setSelectedCity(city);
|
||||||
|
localStorage.setItem('selectedCity', city);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Имитация загрузки ресурсов
|
// Имитация загрузки ресурсов
|
||||||
@ -44,7 +67,7 @@ export default function Home() {
|
|||||||
>
|
>
|
||||||
<Header
|
<Header
|
||||||
selectedCity={selectedCity}
|
selectedCity={selectedCity}
|
||||||
onCityChange={setSelectedCity}
|
onCityChange={handleCityChange}
|
||||||
/>
|
/>
|
||||||
<main className="flex-1">
|
<main className="flex-1">
|
||||||
<Hero selectedCity={selectedCity} />
|
<Hero selectedCity={selectedCity} />
|
||||||
@ -60,6 +83,13 @@ export default function Home() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
|
<CitySelectModal
|
||||||
|
isOpen={isCityModalOpen}
|
||||||
|
onClose={() => setIsCityModalOpen(false)}
|
||||||
|
onSelectCity={handleCityChange}
|
||||||
|
currentCity={selectedCity}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user