fix: widget payment screen always shown, phone mask, today's date default

- Always show payment step when paymentEnabled=true even if YooKassa
  confirmationUrl is null (payment creation failed silently)
- Add third payment screen variant: "manager will contact you" when no
  YooKassa URL and no bank details configured
- Add Russian phone mask +7 (___) ___-__-__ on phone type fields
- Default check-in/check-out dates to today/tomorrow instead of hardcoded past dates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-08 02:10:02 +03:00
parent 2b20fc18b3
commit bd27be72c1

View File

@@ -196,8 +196,10 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
paymentEnabled?: boolean paymentEnabled?: boolean
}) { }) {
const [previewTab, setPreviewTab] = useState<'rooms' | 'rental'>('rooms') const [previewTab, setPreviewTab] = useState<'rooms' | 'rental'>('rooms')
const [checkIn, setCheckIn] = useState('2026-03-20') const todayStr = new Date().toISOString().slice(0, 10)
const [checkOut, setCheckOut] = useState('2026-03-22') const tomorrowStr = new Date(Date.now() + 86400000).toISOString().slice(0, 10)
const [checkIn, setCheckIn] = useState(todayStr)
const [checkOut, setCheckOut] = useState(tomorrowStr)
const [guests, setGuests] = useState(2) const [guests, setGuests] = useState(2)
const [extraBeds, setExtraBeds] = useState(0) const [extraBeds, setExtraBeds] = useState(0)
const [children, setChildren] = useState(0) const [children, setChildren] = useState(0)
@@ -289,7 +291,8 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
if (result.confirmationUrl) { if (result.confirmationUrl) {
setConfirmUrl(result.confirmationUrl) setConfirmUrl(result.confirmationUrl)
setStep('payment') setStep('payment')
} else if (settings.bankDetails) { } else if (settings.bankDetails || needsPayment) {
// YooKassa failed or bank transfer configured — always show payment screen
setStep('payment') setStep('payment')
} else { } else {
setStep('success') setStep('success')
@@ -379,6 +382,7 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
// ── Payment screen ── // ── Payment screen ──
if (step === 'payment') { if (step === 'payment') {
const isYookassa = !!confirmUrl const isYookassa = !!confirmUrl
const hasBankDetails = !isYookassa && !!settings.bankDetails
return ( return (
<div className="bg-white rounded-2xl shadow-2xl overflow-hidden border border-slate-200" style={{ fontFamily: 'Inter, sans-serif' }}> <div className="bg-white rounded-2xl shadow-2xl overflow-hidden border border-slate-200" style={{ fontFamily: 'Inter, sans-serif' }}>
@@ -410,14 +414,14 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
</p> </p>
<p className="text-xs text-slate-400">🔒 Защищённое соединение · ЮKassa</p> <p className="text-xs text-slate-400">🔒 Защищённое соединение · ЮKassa</p>
</div> </div>
) : ( ) : hasBankDetails ? (
/* Bank transfer details */ /* Bank transfer details */
<div className="space-y-3"> <div className="space-y-3">
<p className="text-sm font-medium text-slate-700"> <p className="text-sm font-medium text-slate-700">
{settings.language === 'ru' ? 'Реквизиты для оплаты' : 'Payment details'} {settings.language === 'ru' ? 'Реквизиты для оплаты' : 'Payment details'}
</p> </p>
<div className="bg-slate-50 rounded-xl p-4 text-sm text-slate-700 whitespace-pre-wrap leading-relaxed"> <div className="bg-slate-50 rounded-xl p-4 text-sm text-slate-700 whitespace-pre-wrap leading-relaxed">
{settings.bankDetails || 'Реквизиты не указаны'} {settings.bankDetails}
</div> </div>
<p className="text-xs text-slate-400"> <p className="text-xs text-slate-400">
{settings.language === 'ru' {settings.language === 'ru'
@@ -425,6 +429,16 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
: 'After transfer, the hotel will confirm your booking. Include your name and dates in the payment reference.'} : 'After transfer, the hotel will confirm your booking. Include your name and dates in the payment reference.'}
</p> </p>
</div> </div>
) : (
/* No payment configured — pending confirmation */
<div className="text-center space-y-3 py-2">
<div className="w-14 h-14 rounded-full flex items-center justify-center mx-auto bg-amber-50">
<AlertCircle size={28} className="text-amber-500" />
</div>
<p className="text-sm text-slate-700 font-medium">
{settings.language === 'ru' ? 'Бронирование принято. Менеджер отеля свяжется с вами для подтверждения оплаты.' : 'Booking received. Hotel manager will contact you to confirm payment.'}
</p>
</div>
)} )}
</div> </div>
@@ -437,7 +451,7 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
> >
{settings.language === 'ru' ? `Перейти к оплате · ${grandTotal.toLocaleString('ru-RU')}` : `Proceed to pay · ${grandTotal.toLocaleString('ru-RU')}`} {settings.language === 'ru' ? `Перейти к оплате · ${grandTotal.toLocaleString('ru-RU')}` : `Proceed to pay · ${grandTotal.toLocaleString('ru-RU')}`}
</button> </button>
) : ( ) : hasBankDetails ? (
<button <button
onClick={() => setStep('success')} onClick={() => setStep('success')}
className="w-full py-3 rounded-xl text-white font-semibold text-sm transition-opacity hover:opacity-90" className="w-full py-3 rounded-xl text-white font-semibold text-sm transition-opacity hover:opacity-90"
@@ -445,6 +459,14 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
> >
{settings.language === 'ru' ? 'Я перевёл оплату' : 'I have paid'} {settings.language === 'ru' ? 'Я перевёл оплату' : 'I have paid'}
</button> </button>
) : (
<button
onClick={() => setStep('success')}
className="w-full py-3 rounded-xl text-white font-semibold text-sm transition-opacity hover:opacity-90"
style={{ background: settings.primaryColor }}
>
{settings.language === 'ru' ? 'Понятно' : 'OK'}
</button>
)} )}
</div> </div>
</div> </div>
@@ -603,13 +625,29 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
</select> </select>
) : ( ) : (
<input <input
type={field.type} type={field.type === 'phone' ? 'tel' : field.type}
inputMode={field.type === 'phone' ? 'tel' : undefined}
placeholder={field.type === 'phone' ? '+7 (___) ___-__-__' : undefined}
className="w-full text-sm border border-slate-200 rounded-lg px-3 py-2 focus:outline-none" className="w-full text-sm border border-slate-200 rounded-lg px-3 py-2 focus:outline-none"
value={formValues[field.id] ?? ''} value={formValues[field.id] ?? ''}
onChange={e => { onChange={e => {
setFormValues(prev => ({ ...prev, [field.id]: e.target.value })) let val = e.target.value
if (field.id === 'email') doGuestLookup(e.target.value, formValues['phone']) if (field.type === 'phone') {
if (field.id === 'phone') doGuestLookup(formValues['email'], e.target.value) const digits = val.replace(/\D/g, '').slice(0, 11)
if (digits.length === 0) { val = '' }
else {
const d = digits.startsWith('8') ? '7' + digits.slice(1) : digits
const n = d.startsWith('7') ? d : '7' + d
val = '+7'
if (n.length > 1) val += ' (' + n.slice(1, 4)
if (n.length > 4) val += ') ' + n.slice(4, 7)
if (n.length > 7) val += '-' + n.slice(7, 9)
if (n.length > 9) val += '-' + n.slice(9, 11)
}
}
setFormValues(prev => ({ ...prev, [field.id]: val }))
if (field.id === 'email') doGuestLookup(val, formValues['phone'])
if (field.id === 'phone') doGuestLookup(formValues['email'], val)
}} }}
/> />
)} )}