feat: blacklist, guest lookup, bank transfer payment page
- Add is_blacklisted column to guests (migration 068) - POST /widget/bookings: block blacklisted guests (403, generic message) - GET /widget/:slug/guests/lookup: find existing guest by email/phone for auto-fill - Widget form: debounce guest lookup on email/phone input, show "Заполнить" suggestion - Widget payment screen: replace fake card form with proper YooKassa redirect screen or bank transfer details page - Add bank transfer details field to widget settings (saved as widget_bank_details) - GuestsPage: blacklist toggle button in edit modal + 🚫 ЧС badge in table Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react'
|
||||
import {
|
||||
Search, Star, Phone, Mail, User, TrendingUp, Award, Users,
|
||||
Calendar, CreditCard, X, ChevronRight, MessageSquare, Tag,
|
||||
Repeat2, ShieldCheck, Plus, ChevronsUpDown, AlertCircle, Loader2,
|
||||
Repeat2, Shield, ShieldCheck, Plus, ChevronsUpDown, AlertCircle, Loader2,
|
||||
} from 'lucide-react'
|
||||
import { cn } from '../lib/utils'
|
||||
import { api, type GuestApiType } from '../lib/api'
|
||||
@@ -100,6 +100,7 @@ function GuestModal({ guestId, slug, onClose, onUpdated }: GuestModalProps) {
|
||||
const [rating, setRating] = useState(3)
|
||||
const [notes, setNotes] = useState('')
|
||||
const [tags, setTags] = useState<string[]>([])
|
||||
const [isBlacklisted, setIsBlacklisted] = useState(false)
|
||||
// Passport
|
||||
const [passportSeries, setPassportSeries] = useState('')
|
||||
const [passportNumber, setPassportNumber] = useState('')
|
||||
@@ -120,6 +121,7 @@ function GuestModal({ guestId, slug, onClose, onUpdated }: GuestModalProps) {
|
||||
setRating(g.rating)
|
||||
setNotes(g.notes ?? '')
|
||||
setTags(g.tags ?? [])
|
||||
setIsBlacklisted(g.isBlacklisted ?? false)
|
||||
setPassportSeries(g.passportSeries ?? '')
|
||||
setPassportNumber(g.passportNumber ?? '')
|
||||
setBirthDate(g.birthDate?.slice(0, 10) ?? '')
|
||||
@@ -143,6 +145,7 @@ function GuestModal({ guestId, slug, onClose, onUpdated }: GuestModalProps) {
|
||||
rating,
|
||||
notes,
|
||||
tags,
|
||||
is_blacklisted: isBlacklisted,
|
||||
passport_series: passportSeries || undefined,
|
||||
passport_number: passportNumber || undefined,
|
||||
birth_date: birthDate || undefined,
|
||||
@@ -317,6 +320,30 @@ function GuestModal({ guestId, slug, onClose, onUpdated }: GuestModalProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Blacklist */}
|
||||
<div>
|
||||
<button
|
||||
onClick={() => setIsBlacklisted(v => !v)}
|
||||
className={cn(
|
||||
'w-full flex items-center justify-between px-4 py-3 rounded-xl border transition-colors',
|
||||
isBlacklisted
|
||||
? 'bg-red-50 border-red-300 text-red-700 dark:bg-red-900/20 dark:border-red-700 dark:text-red-400'
|
||||
: 'bg-white dark:bg-slate-700 border-slate-200 dark:border-slate-600 text-slate-600 dark:text-slate-400 hover:border-red-300 hover:text-red-600',
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<Shield size={14} className={isBlacklisted ? 'text-red-500' : 'text-slate-400'} />
|
||||
{isBlacklisted ? '🚫 Гость в чёрном списке' : 'Добавить в чёрный список'}
|
||||
</div>
|
||||
<span className="text-xs opacity-60">{isBlacklisted ? 'Нажмите чтобы убрать' : 'Нажмите чтобы добавить'}</span>
|
||||
</button>
|
||||
{isBlacklisted && (
|
||||
<p className="text-xs text-red-600 dark:text-red-400 mt-1 px-1">
|
||||
Этот гость не сможет забронировать номер через виджет онлайн-бронирования.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Notes */}
|
||||
<div>
|
||||
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">
|
||||
@@ -915,6 +942,11 @@ export function GuestsPage() {
|
||||
</td>
|
||||
<td className="px-4 py-3 hidden sm:table-cell">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{(guest as any).isBlacklisted && (
|
||||
<span className="flex items-center gap-0.5 px-2 py-0.5 rounded-md text-xs font-medium border bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800">
|
||||
🚫 ЧС
|
||||
</span>
|
||||
)}
|
||||
{guest.tags.map(tag => (
|
||||
<span
|
||||
key={tag}
|
||||
|
||||
Reference in New Issue
Block a user