Files
hotelsync/src/pages/GuestReviewPage.tsx
HotelSync 26da3eafa8 Fix TypeScript build errors
- GuestReviewPage: fix hotel config lookup type (slug && obj returns '' | obj)
- PosPage, ReviewsPage: change ComponentType<{size?:number}> to ElementType for Lucide icon compatibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 19:43:20 +03:00

205 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { useParams } from 'react-router-dom'
import { Star, ExternalLink, CheckCircle2, Heart } from 'lucide-react'
import { cn } from '../lib/utils'
// ── Mock hotel config (in real app — fetched by slug from API) ────────────────
interface HotelConfig {
name: string
color: string
logoLetter: string
redirectThreshold: number // min rating (15) to show redirect buttons
platforms: { name: string; url: string }[]
welcomeText: string
showTextField: boolean
}
const MOCK_HOTELS: Record<string, HotelConfig> = {
'grand-palace': {
name: 'Гранд Палас',
color: '#2563eb',
logoLetter: 'Г',
redirectThreshold: 4,
welcomeText: 'Как вам у нас?',
showTextField: true,
platforms: [
{ name: 'Booking.com', url: 'https://www.booking.com/hotel/ru/' },
{ name: 'Яндекс Путешествия', url: 'https://travel.yandex.ru/hotels/' },
{ name: 'Google', url: 'https://g.page/r/' },
],
},
}
const DEFAULT_CONFIG: HotelConfig = {
name: 'Отель',
color: '#2563eb',
logoLetter: 'О',
redirectThreshold: 4,
welcomeText: 'Как вам у нас?',
showTextField: true,
platforms: [
{ name: 'Booking.com', url: 'https://www.booking.com' },
{ name: 'Google', url: 'https://google.com' },
],
}
// ── Component ─────────────────────────────────────────────────────────────────
export function GuestReviewPage() {
const { slug } = useParams<{ slug: string }>()
const hotel: HotelConfig = (slug ? MOCK_HOTELS[slug] : null) ?? DEFAULT_CONFIG
const [rating, setRating] = useState(0)
const [hover, setHover] = useState(0)
const [text, setText] = useState('')
const [submitted, setSubmitted] = useState(false)
const isPositive = rating >= hotel.redirectThreshold
const handleSubmit = () => {
if (rating === 0) return
setSubmitted(true)
// In real app: POST /api/reviews { slug, rating, text }
}
return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center p-4">
<div className="w-full max-w-sm">
{/* Card */}
<div className="bg-white rounded-3xl shadow-2xl overflow-hidden">
{/* Top color bar */}
<div className="h-2" style={{ backgroundColor: hotel.color }} />
<div className="px-8 py-8 text-center space-y-6">
{/* Logo */}
<div
className="w-20 h-20 rounded-2xl mx-auto flex items-center justify-center text-3xl font-bold text-white shadow-lg"
style={{ backgroundColor: hotel.color }}
>
{hotel.logoLetter}
</div>
{/* Hotel name */}
<div>
<h1 className="text-xl font-bold text-slate-900">{hotel.name}</h1>
{!submitted && (
<p className="text-slate-500 mt-1 text-base">{hotel.welcomeText}</p>
)}
</div>
{/* ── NOT YET SUBMITTED ── */}
{!submitted && (
<>
{/* Stars */}
<div className="flex justify-center gap-2">
{Array.from({ length: 5 }, (_, i) => (
<button
key={i}
onMouseEnter={() => setHover(i + 1)}
onMouseLeave={() => setHover(0)}
onClick={() => setRating(i + 1)}
className="transition-transform hover:scale-110 active:scale-95"
>
<Star
size={40}
className={cn(
'transition-colors drop-shadow-sm',
(hover || rating) > i
? 'text-amber-400 fill-amber-400'
: 'text-slate-200 fill-slate-200',
)}
/>
</button>
))}
</div>
{/* Star label */}
{(hover || rating) > 0 && (
<p className="text-sm font-medium text-slate-600 -mt-2">
{['', 'Очень плохо', 'Плохо', 'Нормально', 'Хорошо', 'Отлично!'][hover || rating]}
</p>
)}
{/* Text field */}
{rating > 0 && hotel.showTextField && (
<textarea
className="w-full border border-slate-200 rounded-2xl p-3.5 text-sm text-slate-700 bg-slate-50 resize-none focus:outline-none focus:border-blue-400 transition-colors placeholder:text-slate-400"
rows={3}
placeholder="Расскажите подробнее... (необязательно)"
value={text}
onChange={e => setText(e.target.value)}
/>
)}
{/* Submit */}
<button
onClick={handleSubmit}
disabled={rating === 0}
className="w-full py-3.5 rounded-2xl text-white font-semibold text-base transition-all hover:opacity-90 active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed shadow-lg"
style={{ backgroundColor: hotel.color }}
>
Отправить отзыв
</button>
</>
)}
{/* ── SUBMITTED: NEGATIVE ── */}
{submitted && !isPositive && (
<div className="space-y-4 py-2">
<div className="w-16 h-16 rounded-full bg-slate-100 flex items-center justify-center mx-auto">
<Heart size={28} className="text-slate-400" />
</div>
<div>
<p className="font-bold text-slate-900 text-lg">Спасибо за честность</p>
<p className="text-slate-500 text-sm mt-1.5 leading-relaxed">
Мы сожалеем, что что-то пошло не так. Ваш отзыв поможет нам стать лучше.
</p>
</div>
</div>
)}
{/* ── SUBMITTED: POSITIVE ── */}
{submitted && isPositive && (
<div className="space-y-4 py-2">
<div className="w-16 h-16 rounded-full bg-emerald-50 flex items-center justify-center mx-auto">
<CheckCircle2 size={32} className="text-emerald-500" />
</div>
<div>
<p className="font-bold text-slate-900 text-lg">Спасибо за отзыв!</p>
<p className="text-slate-500 text-sm mt-1.5 leading-relaxed">
Если вам не сложно поделитесь впечатлениями на одной из площадок:
</p>
</div>
<div className="space-y-2.5">
{hotel.platforms.map(p => (
<a
key={p.name}
href={p.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between w-full px-4 py-3 rounded-2xl border-2 border-slate-100 hover:border-blue-200 bg-slate-50 hover:bg-blue-50 text-slate-700 font-medium text-sm transition-all group"
>
<span>{p.name}</span>
<ExternalLink size={14} className="text-slate-400 group-hover:text-blue-500 transition-colors" />
</a>
))}
</div>
</div>
)}
</div>
{/* Footer */}
<div className="px-8 pb-6 text-center">
<p className="text-xs text-slate-300">HotelSync · система сбора отзывов</p>
</div>
</div>
</div>
</div>
)
}