import { useState, useEffect } from 'react' import { Code2, CreditCard, Globe, CalendarCheck2, Copy, CheckCheck, Save, ChevronLeft, ChevronRight, Star, Users, Dumbbell, Waves, Eye, Settings2, ArrowRight, Plus, Trash2, Check, X as XIcon, BedDouble, Baby, ToggleLeft, ToggleRight, ChevronDown, ChevronUp, Maximize2, Wifi, Tv2, Wind, Coffee, Bath, Mountain, Shirt, Shield, Scan, Image, Zap, AlertCircle, Loader2, } from 'lucide-react' import { cn } from '../lib/utils' import { useModules } from '../contexts/ModulesContext' import { useAuth } from '../contexts/AuthContext' import { api, type WidgetRoom, type WidgetCategory, type PaymentGateway } from '../lib/api' // ── Widget settings type ─────────────────────────────────────────────────────── export interface FormField { id: string label: string type: 'text' | 'email' | 'phone' | 'textarea' | 'select' required: boolean enabled: boolean isCustom?: boolean } export interface AdditionalService { id: string name: string icon: string price: number unit: string enabled: boolean } export interface WidgetSettings { hotelName: string primaryColor: string language: 'ru' | 'en' showRooms: boolean showRental: boolean roomDisplayMode: 'rooms' | 'categories' minNights: number paymentProvider: 'yukassa' | 'tinkoff' | 'cloudpayments' | 'none' showPromo: boolean allowExtraBeds: boolean allowChildren: boolean formFields: FormField[] additionalServices: AdditionalService[] } const COLORS = [ { label: 'Индиго', value: '#4F46E5' }, { label: 'Изумруд', value: '#059669' }, { label: 'Синий', value: '#2563EB' }, { label: 'Фиолетовый',value: '#7C3AED' }, { label: 'Красный', value: '#DC2626' }, { label: 'Янтарный', value: '#D97706' }, { label: 'Розовый', value: '#DB2777' }, { label: 'Серый', value: '#475569' }, ] const PAYMENT_PROVIDERS = [ { value: 'yukassa', label: 'ЮKassa' }, { value: 'tinkoff', label: 'Тинькофф Pay' }, { value: 'cloudpayments',label: 'CloudPayments' }, { value: 'none', label: 'Без оплаты' }, ] interface MockRoom { id: string name: string beds: number guests: number price: number area: number // m² description: string photos: { bg: string; emoji: string; label: string }[] amenities: string[] has3dTour: boolean } const MOCK_ROOMS: MockRoom[] = [ { id: 'r1', name: 'Стандарт', beds: 1, guests: 2, price: 4500, area: 22, description: 'Уютный номер с двуспальной кроватью, современным ремонтом и всем необходимым для комфортного проживания. Вид во двор.', photos: [ { bg: 'from-slate-200 to-slate-300', emoji: '🛏️', label: 'Спальня' }, { bg: 'from-sky-100 to-sky-200', emoji: '🚿', label: 'Ванная' }, { bg: 'from-amber-100 to-amber-200', emoji: '🪟', label: 'Окно' }, ], amenities: ['Wi-Fi', 'TV', 'Кондиционер', 'Фен', 'Мини-холодильник', 'Сейф'], has3dTour: false, }, { id: 'r2', name: 'Комфорт', beds: 1, guests: 2, price: 6500, area: 30, description: 'Просторный номер с зоной отдыха, большой кроватью и улучшенной ванной комнатой. Вид на парк.', photos: [ { bg: 'from-emerald-100 to-emerald-200', emoji: '🛏️', label: 'Спальня' }, { bg: 'from-teal-100 to-teal-200', emoji: '🛁', label: 'Ванная' }, { bg: 'from-green-100 to-green-200', emoji: '🌳', label: 'Вид из окна' }, { bg: 'from-lime-100 to-lime-200', emoji: '🛋️', label: 'Зона отдыха' }, ], amenities: ['Wi-Fi', 'TV', 'Кондиционер', 'Мини-бар', 'Халат', 'Фен', 'Сейф', 'Кофемашина'], has3dTour: false, }, { id: 'r3', name: 'Делюкс', beds: 2, guests: 3, price: 8900, area: 45, description: 'Роскошный двухкомнатный номер с панорамными окнами. Отдельная гостиная, большая ванная с джакузи. Вид на город.', photos: [ { bg: 'from-violet-100 to-violet-200', emoji: '🛏️', label: 'Спальня' }, { bg: 'from-purple-100 to-purple-200', emoji: '🛁', label: 'Джакузи' }, { bg: 'from-indigo-100 to-indigo-200', emoji: '🌆', label: 'Вид на город' }, { bg: 'from-blue-100 to-blue-200', emoji: '🛋️', label: 'Гостиная' }, { bg: 'from-cyan-100 to-cyan-200', emoji: '🍽️', label: 'Мини-кухня' }, ], amenities: ['Wi-Fi', 'Smart TV', 'Кондиционер', 'Джакузи', 'Мини-бар', 'Халат', 'Тапочки', 'Кофемашина', 'Сейф', 'Балкон'], has3dTour: true, }, { id: 'r4', name: 'Пентхаус', beds: 2, guests: 4, price: 15000, area: 80, description: 'Эксклюзивный пентхаус на верхнем этаже с панорамной террасой и видом 360°. Собственная сауна, бар, гостиная класса люкс.', photos: [ { bg: 'from-amber-200 to-orange-200', emoji: '🌇', label: 'Панорама' }, { bg: 'from-yellow-100 to-amber-200', emoji: '🛏️', label: 'Мастер-спальня' }, { bg: 'from-orange-100 to-red-100', emoji: '🧖', label: 'Сауна' }, { bg: 'from-rose-100 to-pink-100', emoji: '🛋️', label: 'Гостиная' }, { bg: 'from-red-100 to-orange-100', emoji: '🌅', label: 'Терраса' }, { bg: 'from-amber-100 to-yellow-100', emoji: '🍸', label: 'Бар' }, ], amenities: ['Wi-Fi 1Гбит', 'Smart TV 75"', 'Кондиционер', 'Сауна', 'Мини-бар', 'Джакузи', 'Терраса', 'Дворецкий', 'Кофемашина', 'Халат', 'Тапочки', 'Сейф'], has3dTour: true, }, ] interface MockCategory { id: string name: string minPrice: number maxGuests: number amenities: string[] description: string availableCount: number } const MOCK_CATEGORIES: MockCategory[] = [ { id: 'c1', name: 'Стандарт', minPrice: 4500, maxGuests: 2, availableCount: 3, amenities: ['Wi-Fi', 'TV', 'Кондиционер', 'Фен'], description: 'Уютные номера с современным ремонтом и всем необходимым для комфортного проживания.' }, { id: 'c2', name: 'Комфорт', minPrice: 6500, maxGuests: 2, availableCount: 2, amenities: ['Wi-Fi', 'TV', 'Кондиционер', 'Мини-бар', 'Кофемашина'], description: 'Просторные номера с расширенными удобствами и зоной отдыха.' }, { id: 'c3', name: 'Делюкс', minPrice: 8900, maxGuests: 3, availableCount: 1, amenities: ['Wi-Fi', 'Smart TV', 'Кондиционер', 'Джакузи', 'Балкон'], description: 'Роскошные номера с панорамными окнами и собственным джакузи.' }, { id: 'c4', name: 'Пентхаус', minPrice: 15000, maxGuests: 4, availableCount: 1, amenities: ['Wi-Fi 1Гбит', 'Smart TV 75"', 'Сауна', 'Терраса', 'Дворецкий'], description: 'Эксклюзивный пентхаус на верхнем этаже с террасой и видом 360°.' }, ] const AMENITY_ICONS: Record = { 'Wi-Fi': Wifi, 'Wi-Fi 1Гбит': Wifi, 'TV': Tv2, 'Smart TV': Tv2, 'Smart TV 75"': Tv2, 'Кондиционер': Wind, 'Кофемашина': Coffee, 'Халат': Shirt, 'Тапочки': Shirt, 'Джакузи': Bath, 'Ванная': Bath, 'Балкон': Mountain, 'Терраса': Mountain, 'Сейф': Shield, } const EXTRA_BED_PRICE = 1500 export const DEFAULT_FORM_FIELDS: FormField[] = [ { id: 'name', label: 'Имя и фамилия', type: 'text', required: true, enabled: true }, { id: 'email', label: 'Email', type: 'email', required: true, enabled: true }, { id: 'phone', label: 'Телефон', type: 'phone', required: true, enabled: true }, { id: 'comment', label: 'Пожелания', type: 'textarea',required: false, enabled: true }, { id: 'arrival', label: 'Время заезда', type: 'select', required: false, enabled: false }, ] export const DEFAULT_SERVICES: AdditionalService[] = [ { id: 's1', name: 'Теннисный корт', icon: '🎾', price: 1200, unit: 'ч', enabled: true }, { id: 's2', name: 'Сауна', icon: '🧖', price: 2500, unit: 'ч', enabled: true }, { id: 's3', name: 'Банкетный зал', icon: '🎪', price: 15000, unit: 'день',enabled: false }, { id: 's4', name: 'Трансфер', icon: '🚗', price: 3000, unit: 'раз', enabled: true }, ] // ── Widget Preview Component ─────────────────────────────────────────────────── export function WidgetPreview({ settings, slug, realRooms, realCategories, paymentEnabled }: { settings: WidgetSettings slug?: string realRooms?: WidgetRoom[] realCategories?: WidgetCategory[] paymentEnabled?: boolean }) { const [previewTab, setPreviewTab] = useState<'rooms' | 'rental'>('rooms') const [checkIn, setCheckIn] = useState('2026-03-20') const [checkOut, setCheckOut] = useState('2026-03-22') const [guests, setGuests] = useState(2) const [extraBeds, setExtraBeds] = useState(0) const [children, setChildren] = useState(0) const [selected, setSelected] = useState(null) const [expandedRoom, setExpandedRoom] = useState(null) const [photoIndex, setPhotoIndex] = useState>({}) const [step, setStep] = useState<'browse' | 'form' | 'payment' | 'success'>('browse') const [submitting, setSubmitting] = useState(false) const [confirmUrl, setConfirmUrl] = useState(null) // Form state const [formValues, setFormValues] = useState>({}) const [selectedServices, setSelectedServices] = useState([]) // Hourly service time selections: { serviceId: { date, timeFrom, timeTo } } const [serviceSchedule, setServiceSchedule] = useState>({}) // Payment state (kept for mock display) const [cardNumber, setCardNumber] = useState('') const [cardExpiry, setCardExpiry] = useState('') const [cardCvv, setCardCvv] = useState('') const [cardName, setCardName] = useState('') const nights = checkIn && checkOut ? Math.max(0, (new Date(checkOut).getTime() - new Date(checkIn).getTime()) / 86400000) : 0 // Use real rooms if available, otherwise mock const displayRooms = realRooms && realRooms.length > 0 ? realRooms.map(r => ({ id: r.id, name: r.name || `Номер ${r.number}`, beds: 1, guests: r.maxGuests, price: r.baseRate, area: 0, description: r.description, photos: [], amenities: r.amenities, has3dTour: false, })) : MOCK_ROOMS // Categories mode const displayCategories: Array<{ id: string; name: string; minPrice: number; maxGuests: number; amenities: string[]; description: string; availableCount: number }> = realCategories && realCategories.length > 0 ? realCategories.map(c => ({ ...c, availableCount: realRooms ? realRooms.filter(r => r.categoryId === c.id).length : 0, })) : MOCK_CATEGORIES const isCategoryMode = settings.roomDisplayMode === 'categories' const selectedRoom = !isCategoryMode ? displayRooms.find(r => r.id === selected) : undefined const selectedCategory = isCategoryMode ? displayCategories.find(c => c.id === selected) : undefined const selectedName = selectedRoom?.name ?? selectedCategory?.name const roomTotal = (selectedRoom ? selectedRoom.price : (selectedCategory ? selectedCategory.minPrice : 0)) * Math.max(1, nights) const extraTotal = extraBeds * EXTRA_BED_PRICE * Math.max(1, nights) const servicesTotal = selectedServices.reduce((sum, sid) => { const s = settings.additionalServices.find(s => s.id === sid) return sum + (s ? s.price : 0) }, 0) const grandTotal = roomTotal + extraTotal + servicesTotal const activeFields = settings.formFields.filter(f => f.enabled) const needsPayment = paymentEnabled ?? (settings.paymentProvider !== 'none') const handleBook = () => { if (!selected || nights === 0) return setStep('form') } const handleSubmit = async () => { const missing = activeFields.filter(f => f.required && !formValues[f.id]?.trim()) if (missing.length > 0) return if (slug && selected) { // Real API call setSubmitting(true) try { const services = selectedServices .map(sid => settings.additionalServices.find(s => s.id === sid)) .filter(Boolean) .map(s => ({ name: s!.name, price: s!.price })) const result = await api.widget.createBooking(slug, { ...(isCategoryMode ? { categoryId: selected! } : { roomId: selected! }), checkIn, checkOut, guestName: formValues['name'] ?? formValues['full_name'] ?? 'Гость', guestEmail: formValues['email'] ?? undefined, guestPhone: formValues['phone'] ?? undefined, adults: guests, children, totalAmount: grandTotal, notes: formValues['notes'] ?? formValues['comment'] ?? undefined, services, }) if (result.confirmationUrl) { setConfirmUrl(result.confirmationUrl) setStep('payment') } else { setStep('success') } } catch { // fallback — still show success in preview setStep('success') } finally { setSubmitting(false) } } else { // Preview mode without real slug if (needsPayment) { setStep('payment') } else { setStep('success') } } } const handlePay = () => { if (confirmUrl) { window.open(confirmUrl, '_blank') } setStep('success') } const handleBack = () => { if (step === 'payment') { setStep('form'); return } setStep('browse') setFormValues({}) setSelectedServices([]) setServiceSchedule({}) setCardNumber(''); setCardExpiry(''); setCardCvv(''); setCardName('') } const formatCard = (v: string) => v.replace(/\D/g, '').slice(0, 16).replace(/(.{4})/g, '$1 ').trim() const formatExpiry = (v: string) => { const d = v.replace(/\D/g, '').slice(0, 4) return d.length > 2 ? `${d.slice(0, 2)}/${d.slice(2)}` : d } // ── Success screen ── if (step === 'success') { return (

{settings.hotelName || 'Название отеля'}

Бронирование принято!

{needsPayment ? 'Оплата прошла успешно. Подтверждение придёт на email.' : 'Оплата на месте при заезде. Подтверждение придёт на email.'}

{isCategoryMode ? 'Категория' : 'Номер'}: {selectedName}

Заезд: {checkIn}

Выезд: {checkOut}

Итого: {grandTotal.toLocaleString('ru-RU')} ₽

) } // ── Payment screen ── if (step === 'payment') { const payLabel = settings.paymentProvider === 'yukassa' ? 'ЮKassa' : settings.paymentProvider === 'tinkoff' ? 'Тинькофф Pay' : 'CloudPayments' return (

{settings.language === 'ru' ? 'Оплата' : 'Payment'}

{payLabel} · {grandTotal.toLocaleString('ru-RU')} ₽

{/* Amount summary */}
{selectedName} · {nights} ноч. {grandTotal.toLocaleString('ru-RU')} ₽
{/* Card form */}
setCardNumber(formatCard(e.target.value))} maxLength={19} />
setCardExpiry(formatExpiry(e.target.value))} maxLength={5} />
setCardCvv(e.target.value.replace(/\D/g, '').slice(0, 3))} maxLength={3} />
setCardName(e.target.value.toUpperCase())} />

🔒 Платёж защищён 3-D Secure · {payLabel}

) } // ── Guest form screen ── if (step === 'form') { return (

{settings.language === 'ru' ? 'Данные гостя' : 'Guest details'}

{selectedName} · {nights} ноч.

{/* Step indicator */}
{[1, 2, needsPayment ? 3 : null].filter(Boolean).map((s, i) => (
))}
{needsPayment &&
}
{/* Summary */}
{selectedName} × {nights} ноч. {roomTotal.toLocaleString('ru-RU')} ₽
{extraBeds > 0 && (
Доп. места × {extraBeds} {extraTotal.toLocaleString('ru-RU')} ₽
)} {selectedServices.map(sid => { const s = settings.additionalServices.find(s => s.id === sid) return s ? (
{s.icon} {s.name} {s.price.toLocaleString('ru-RU')} ₽
) : null })}
Итого {grandTotal.toLocaleString('ru-RU')} ₽
{/* Additional services */} {settings.additionalServices.filter(s => s.enabled).length > 0 && (

{settings.language === 'ru' ? 'Дополнительные услуги' : 'Additional services'}

{settings.additionalServices.filter(s => s.enabled).map(s => { const isHourly = s.unit === 'ч' const schedule = serviceSchedule[s.id] const isSelected = selectedServices.includes(s.id) return (
{/* Hourly time picker */} {isSelected && isHourly && (
setServiceSchedule(prev => ({ ...prev, [s.id]: { ...prev[s.id], date: e.target.value, timeFrom: prev[s.id]?.timeFrom ?? '10:00', timeTo: prev[s.id]?.timeTo ?? '12:00' } }))} />
)}
) })}
)} {/* Form fields */} {activeFields.map(field => (
{field.type === 'textarea' ? (