'use client'; import { useState, useCallback, useEffect } from 'react'; import { format } from 'date-fns'; import { ru } from 'date-fns/locale'; type PosterModalProps = { data: { id: number; img: string; title: string; likes: number; desc: string; price: number; date: string; }; isOpen: boolean; onClose: () => void; }; export default function PosterModal({ data, isOpen, onClose }: PosterModalProps) { const [liked, setLiked] = useState(false); const [likesCount, setLikesCount] = useState(data.likes); const [loading, setLoading] = useState(false); const [paymentLoading, setPaymentLoading] = useState(false); const [message, setMessage] = useState<{text: string, type: 'success' | 'error' | 'info' | null}>({ text: '', type: null }); // Блокировка прокрутки страницы при открытом модальном окне useEffect(() => { if (isOpen) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } return () => { document.body.style.overflow = ''; }; }, [isOpen]); // Обработка клавиши Escape useEffect(() => { const handleEscKey = (e: KeyboardEvent) => { if (e.key === 'Escape' && isOpen) { onClose(); } }; window.addEventListener('keydown', handleEscKey); return () => { window.removeEventListener('keydown', handleEscKey); }; }, [isOpen, onClose]); // Функция для форматирования даты const formatDate = (dateStr: string) => { const date = new Date(dateStr); return format(date, "d MMMM yyyy 'г. в' HH:mm", { locale: ru }); }; // Функция обработки лайков const handleLike = useCallback(async () => { if (loading) return; try { setLoading(true); // Получаем JWT токен из cookie const token = document.cookie .split('; ') .find(row => row.startsWith('JWT_token=')) ?.split('=')[1]; if (!token) { setMessage({ text: 'Необходимо авторизоваться для оценки мероприятия', type: 'error' }); setTimeout(() => setMessage({text: '', type: null}), 3000); return; } const formData = new FormData(); formData.append('poster_id', String(data.id)); formData.append('like', liked ? '-1' : '1'); const response = await fetch('http://localhost:8000/setlike', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData, credentials: 'include' }); const responseData = await response.json(); if (response.ok) { setLikesCount(responseData.poster.like); setMessage({ text: responseData.message, type: 'success' }); if (responseData.message !== "Вы уже оценили этот постер." && responseData.message !== "Вы еще не ставили лайк этому постеру.") { setLiked(!liked); } } else { setMessage({ text: responseData.detail || 'Произошла ошибка при оценке мероприятия', type: 'error' }); } setTimeout(() => setMessage({text: '', type: null}), 3000); } catch (error) { console.error('Ошибка при отправке лайка:', error); setMessage({ text: 'Не удалось обработать запрос. Попробуйте позже.', type: 'error' }); setTimeout(() => setMessage({text: '', type: null}), 3000); } finally { setLoading(false); } }, [data.id, liked, loading]); // Функция для обработки платежа const handlePayment = useCallback(async () => { if (paymentLoading) return; try { setPaymentLoading(true); // Получаем JWT токен из cookie const token = document.cookie .split('; ') .find(row => row.startsWith('JWT_token=')) ?.split('=')[1]; if (!token) { setMessage({ text: 'Необходимо авторизоваться для покупки билета', type: 'error' }); setTimeout(() => setMessage({text: '', type: null}), 3000); return; } try { // Получаем данные пользователя из JWT const payload = JSON.parse(atob(token.split('.')[1])); console.log("JWT payload:", payload); // ID пользователя может храниться в разных полях JWT const userId = payload.sub || payload.user_id || payload.id || 1; console.log("User ID:", userId); // Создание запроса на оплату const response = await fetch('http://localhost:8000/create-payment/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ amount: data.price, currency: 'RUB', description: `Билет на мероприятие: ${data.title}`, return_url: `${window.location.origin}/payments`, user_id: userId, poster_id: data.id }) }); console.log("Payment response status:", response.status); const responseData = await response.json(); console.log("Payment response data:", responseData); if (response.ok && responseData.confirmation_url) { // Сохраняем payment_id в localStorage перед перенаправлением if (responseData.payment_id) { localStorage.setItem('current_payment_id', responseData.payment_id); } // Добавляем payment_id в URL вручную window.location.href = `${responseData.confirmation_url}&return_payment_id=${responseData.payment_id}`; } else { setMessage({ text: responseData.detail || 'Произошла ошибка при создании платежа', type: 'error' }); setTimeout(() => setMessage({text: '', type: null}), 3000); } } catch (parseError) { console.error('Ошибка при обработке JWT:', parseError); setMessage({ text: 'Ошибка авторизации. Пожалуйста, войдите заново.', type: 'error' }); setTimeout(() => setMessage({text: '', type: null}), 3000); } } catch (error) { console.error('Ошибка при создании платежа:', error); setMessage({ text: 'Не удалось создать платёж. Попробуйте позже.', type: 'error' }); setTimeout(() => setMessage({text: '', type: null}), 3000); } finally { setPaymentLoading(false); } }, [data.id, data.price, data.title, paymentLoading]); if (!isOpen) return null; // Расширенные стили с усиленными эффектами размытия и поддержкой кросс-браузерности const styles = ` @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } @keyframes heartbeat { 0% { transform: scale(1); } 15% { transform: scale(1.25); } 25% { transform: scale(1.15); } 35% { transform: scale(1.25); } 100% { transform: scale(1); } } @keyframes spin { to { transform: rotate(360deg); } } .loading-spinner { animation: spin 1s linear infinite; } /* Улучшенная поддержка эффекта размытия для разных браузеров */ @supports (-webkit-backdrop-filter: none) or (backdrop-filter: none) { .modal-backdrop { -webkit-backdrop-filter: blur(25px) saturate(120%); backdrop-filter: blur(25px) saturate(120%); background-color: rgba(0,0,0,.65); } } @supports not ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) { .modal-backdrop { background-color: rgba(0,0,0,.9); } } `; const modalContainerStyle = { position: 'fixed' as const, top: 0, left: 0, right: 0, bottom: 0, display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 2001, // Повышенный z-index pointerEvents: 'none' as const, }; return ( <>
e.stopPropagation()} style={{ display: 'flex', background: 'rgba(15,15,15,0.35)', border: '1px solid rgba(255,127,39,0.5)', borderRadius: 24, overflow: 'hidden', boxShadow: '0 15px 35px rgba(0,0,0,0.2)', maxWidth: '90vw', maxHeight: '85vh', width: 'auto', height: 'auto', position: 'relative', animation: 'fadeIn 0.3s forwards', pointerEvents: 'auto', }} >
{data.title}

{data.title}

{likesCount} {loading && ( )} {message.text && (
{message.text}
)}
{formatDate(data.date)}
{data.desc}
{data.price.toLocaleString('ru-RU', { style: 'currency', currency: 'RUB' })}
); }