feat: widget rental tab — real objects, time slots, actual rental_bookings
- Add rental_objects to /api/widget/:slug/config response - Add POST /api/widget/:slug/rental-bookings endpoint with availability check - Widget rental tab now shows real rental objects (not additionalServices) - Date picker, hourly or full-day toggle, start/end hour selectors - Booking submit creates actual rental_booking record in DB - Success screen shows rental object, date and time slot - WidgetRentalObject + WidgetRentalBookingPayload types added to api.ts - BookingWidgetStandalonePage passes rentalObjects to WidgetPreview Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,16 @@ const publicWidget: FastifyPluginAsync = async (fastify) => {
|
||||
[hotel.id],
|
||||
)
|
||||
|
||||
// Load rental objects (only if widget_show_rental = true)
|
||||
const { rows: rentalObjects } = await db.query(
|
||||
`SELECT id, name, icon, price_per_hour, price_per_day,
|
||||
open_hour, close_hour, max_hours_per_slot, buffer_minutes, sort_order
|
||||
FROM rental_objects
|
||||
WHERE hotel_id = $1
|
||||
ORDER BY sort_order, name`,
|
||||
[hotel.id],
|
||||
)
|
||||
|
||||
// Check if YooKassa gateway is configured for booking-widget
|
||||
const gateway = await getGatewayForModule(hotel.id, 'booking-widget')
|
||||
|
||||
@@ -92,9 +102,79 @@ const publicWidget: FastifyPluginAsync = async (fastify) => {
|
||||
minPrice: Number(c.min_price),
|
||||
maxGuests: Number(c.max_guests),
|
||||
})),
|
||||
rentalObjects: rentalObjects.map((o: any) => ({
|
||||
id: o.id,
|
||||
name: o.name,
|
||||
icon: o.icon ?? '🏨',
|
||||
pricePerHour: Number(o.price_per_hour ?? 0),
|
||||
pricePerDay: Number(o.price_per_day ?? 0),
|
||||
openHour: Number(o.open_hour ?? 8),
|
||||
closeHour: Number(o.close_hour ?? 22),
|
||||
maxHoursPerSlot: o.max_hours_per_slot ? Number(o.max_hours_per_slot) : null,
|
||||
bufferMinutes: Number(o.buffer_minutes ?? 0),
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
// ── POST /api/widget/:slug/rental-bookings ────────────────────────────────
|
||||
fastify.post<SlugParam & {
|
||||
Body: {
|
||||
objectId: string
|
||||
date: string
|
||||
isFullDay?: boolean
|
||||
startHour?: number
|
||||
endHour?: number
|
||||
guestName: string
|
||||
guestEmail?: string
|
||||
guestPhone?: string
|
||||
totalAmount: number
|
||||
notes?: string
|
||||
}
|
||||
}>('/api/widget/:slug/rental-bookings', async (req, reply) => {
|
||||
const hotel = await getHotelId(req.params.slug)
|
||||
if (!hotel) return reply.code(404).send({ error: 'Hotel not found' })
|
||||
|
||||
const { objectId, date, isFullDay, startHour, endHour, guestName, guestEmail, guestPhone, totalAmount, notes } = req.body
|
||||
|
||||
if (!objectId || !date || !guestName) {
|
||||
return reply.code(400).send({ error: 'Missing required fields' })
|
||||
}
|
||||
|
||||
// Check object exists
|
||||
const { rows: objRows } = await db.query(
|
||||
`SELECT id FROM rental_objects WHERE id = $1 AND hotel_id = $2`,
|
||||
[objectId, hotel.id],
|
||||
)
|
||||
if (!objRows[0]) return reply.code(404).send({ error: 'Rental object not found' })
|
||||
|
||||
// Availability check (hourly bookings)
|
||||
if (!isFullDay && startHour !== undefined && endHour !== undefined) {
|
||||
const { rows: conflicts } = await db.query(
|
||||
`SELECT id FROM rental_bookings
|
||||
WHERE object_id = $1 AND date = $2 AND status != 'cancelled'
|
||||
AND NOT (end_hour <= $3 OR start_hour >= $4)`,
|
||||
[objectId, date, startHour, endHour],
|
||||
)
|
||||
if (conflicts.length > 0) {
|
||||
return reply.code(409).send({ error: 'This time slot is already booked' })
|
||||
}
|
||||
}
|
||||
|
||||
// Create rental booking
|
||||
const { rows } = await db.query(
|
||||
`INSERT INTO rental_bookings
|
||||
(hotel_id, object_id, date, is_full_day, start_hour, end_hour,
|
||||
guest_name, guest_phone, total_amount, notes, status)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,'confirmed') RETURNING id`,
|
||||
[hotel.id, objectId, date, isFullDay ?? false,
|
||||
startHour ?? 0, endHour ?? 0,
|
||||
guestName, guestPhone ?? '', Math.round(totalAmount * 100),
|
||||
notes ?? null],
|
||||
)
|
||||
|
||||
return { bookingId: rows[0].id, status: 'confirmed' }
|
||||
})
|
||||
|
||||
// ── GET /api/widget/:slug/guests/lookup ───────────────────────────────────
|
||||
// Lookup existing guest by email or phone (for auto-fill, no auth)
|
||||
fastify.get<SlugParam & { Querystring: { email?: string; phone?: string } }>(
|
||||
|
||||
@@ -885,6 +885,16 @@ export const api = {
|
||||
return r.json() as Promise<{ first_name: string; last_name: string; middle_name?: string; email?: string; phone?: string; is_blacklisted: boolean }>
|
||||
})
|
||||
},
|
||||
createRentalBooking: (slug: string, data: WidgetRentalBookingPayload) =>
|
||||
fetch(`${BASE}/api/widget/${slug}/rental-bookings`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data),
|
||||
}).then(async r => {
|
||||
const json = await r.json()
|
||||
if (!r.ok) throw { status: r.status, ...json }
|
||||
return json as { bookingId: string; status: string }
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1662,6 +1672,31 @@ export interface WidgetCategory {
|
||||
maxGuests: number
|
||||
}
|
||||
|
||||
export interface WidgetRentalObject {
|
||||
id: string
|
||||
name: string
|
||||
icon: string
|
||||
pricePerHour: number
|
||||
pricePerDay: number
|
||||
openHour: number
|
||||
closeHour: number
|
||||
maxHoursPerSlot: number | null
|
||||
bufferMinutes: number
|
||||
}
|
||||
|
||||
export interface WidgetRentalBookingPayload {
|
||||
objectId: string
|
||||
date: string
|
||||
isFullDay?: boolean
|
||||
startHour?: number
|
||||
endHour?: number
|
||||
guestName: string
|
||||
guestEmail?: string
|
||||
guestPhone?: string
|
||||
totalAmount: number
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export interface WidgetConfig {
|
||||
hotelId: string
|
||||
hotelName: string
|
||||
@@ -1670,6 +1705,7 @@ export interface WidgetConfig {
|
||||
currency: string
|
||||
rooms: WidgetRoom[]
|
||||
categories: WidgetCategory[]
|
||||
rentalObjects?: WidgetRentalObject[]
|
||||
widgetSettings?: {
|
||||
primaryColor: string
|
||||
language: string
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
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'
|
||||
import { api, type WidgetRoom, type WidgetCategory, type WidgetRentalObject, type PaymentGateway } from '../lib/api'
|
||||
|
||||
// ── Widget settings type ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -188,11 +188,12 @@ export const DEFAULT_SERVICES: AdditionalService[] = [
|
||||
|
||||
// ── Widget Preview Component ───────────────────────────────────────────────────
|
||||
|
||||
export function WidgetPreview({ settings, slug, realRooms, realCategories, paymentEnabled }: {
|
||||
export function WidgetPreview({ settings, slug, realRooms, realCategories, realRentalObjects, paymentEnabled }: {
|
||||
settings: WidgetSettings
|
||||
slug?: string
|
||||
realRooms?: WidgetRoom[]
|
||||
realCategories?: WidgetCategory[]
|
||||
realRentalObjects?: WidgetRentalObject[]
|
||||
paymentEnabled?: boolean
|
||||
}) {
|
||||
const [previewTab, setPreviewTab] = useState<'rooms' | 'rental'>('rooms')
|
||||
@@ -222,6 +223,12 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
|
||||
// Guest lookup
|
||||
const [guestLookupTimer, setGuestLookupTimer] = useState<ReturnType<typeof setTimeout> | null>(null)
|
||||
const [guestSuggestion, setGuestSuggestion] = useState<{ first_name: string; last_name: string; middle_name?: string; phone?: string; email?: string } | null>(null)
|
||||
// Rental booking state
|
||||
const [rentalDate, setRentalDate] = useState(() => localDate(0))
|
||||
const [rentalIsFullDay, setRentalIsFullDay] = useState(false)
|
||||
const [rentalStartHour, setRentalStartHour] = useState(10)
|
||||
const [rentalEndHour, setRentalEndHour] = useState(12)
|
||||
const rentalObjects: WidgetRentalObject[] = realRentalObjects ?? []
|
||||
|
||||
const nights = checkIn && checkOut
|
||||
? Math.max(0, (new Date(checkOut).getTime() - new Date(checkIn).getTime()) / 86400000)
|
||||
@@ -262,7 +269,8 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
|
||||
const needsPayment = paymentEnabled ?? (settings.paymentProvider !== 'none')
|
||||
|
||||
const handleBook = () => {
|
||||
if (!selected || nights === 0) return
|
||||
if (!selected) return
|
||||
if (previewTab === 'rooms' && nights === 0) return
|
||||
setStep('form')
|
||||
}
|
||||
|
||||
@@ -272,34 +280,57 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
|
||||
setBookingError(null)
|
||||
|
||||
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 {
|
||||
if (previewTab === 'rental') {
|
||||
// Rental booking
|
||||
const obj = rentalObjects.find(o => o.id === selected)
|
||||
const rentalAmount = rentalIsFullDay
|
||||
? obj?.pricePerDay ?? 0
|
||||
: (rentalEndHour - rentalStartHour) * (obj?.pricePerHour ?? 0)
|
||||
await api.widget.createRentalBooking(slug, {
|
||||
objectId: selected,
|
||||
date: rentalDate,
|
||||
isFullDay: rentalIsFullDay,
|
||||
startHour: rentalIsFullDay ? undefined : rentalStartHour,
|
||||
endHour: rentalIsFullDay ? undefined : rentalEndHour,
|
||||
guestName: formValues['name'] ?? formValues['full_name'] ?? 'Гость',
|
||||
guestEmail: formValues['email'] ?? undefined,
|
||||
guestPhone: formValues['phone'] ?? undefined,
|
||||
totalAmount: rentalAmount,
|
||||
notes: formValues['notes'] ?? formValues['comment'] ?? undefined,
|
||||
})
|
||||
setStep('success')
|
||||
} else {
|
||||
// Room booking
|
||||
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 (err: any) {
|
||||
if (err?.status === 403) {
|
||||
setBookingError(err?.error ?? 'Невозможно завершить бронирование. Попробуйте позже или свяжитесь с отелем.')
|
||||
} else if (err?.status === 409) {
|
||||
setBookingError(err?.error ?? 'Выбранное время уже занято. Пожалуйста, выберите другое.')
|
||||
} else {
|
||||
setBookingError('Ошибка при создании бронирования. Попробуйте ещё раз или свяжитесь с отелем.')
|
||||
}
|
||||
@@ -308,7 +339,7 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
|
||||
}
|
||||
} else {
|
||||
// Preview mode without real slug
|
||||
if (needsPayment) {
|
||||
if (needsPayment && previewTab === 'rooms') {
|
||||
setStep('payment')
|
||||
} else {
|
||||
setStep('success')
|
||||
@@ -367,10 +398,20 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
|
||||
: 'Оплата на месте при заезде. Подтверждение придёт на email.'}
|
||||
</p>
|
||||
<div className="bg-slate-50 rounded-xl p-4 text-left space-y-1">
|
||||
<p className="text-xs text-slate-500">{isCategoryMode ? 'Категория' : 'Номер'}: <span className="font-medium text-slate-700">{selectedName}</span></p>
|
||||
<p className="text-xs text-slate-500">Заезд: <span className="font-medium text-slate-700">{checkIn}</span></p>
|
||||
<p className="text-xs text-slate-500">Выезд: <span className="font-medium text-slate-700">{checkOut}</span></p>
|
||||
<p className="text-xs text-slate-500">Итого: <span className="font-bold text-slate-900">{grandTotal.toLocaleString('ru-RU')} ₽</span></p>
|
||||
{previewTab === 'rental' ? (
|
||||
<>
|
||||
<p className="text-xs text-slate-500">Объект: <span className="font-medium text-slate-700">{rentalObjects.find(o => o.id === selected)?.name ?? selected}</span></p>
|
||||
<p className="text-xs text-slate-500">Дата: <span className="font-medium text-slate-700">{rentalDate}</span></p>
|
||||
{!rentalIsFullDay && <p className="text-xs text-slate-500">Время: <span className="font-medium text-slate-700">{rentalStartHour}:00 – {rentalEndHour}:00</span></p>}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-xs text-slate-500">{isCategoryMode ? 'Категория' : 'Номер'}: <span className="font-medium text-slate-700">{selectedName}</span></p>
|
||||
<p className="text-xs text-slate-500">Заезд: <span className="font-medium text-slate-700">{checkIn}</span></p>
|
||||
<p className="text-xs text-slate-500">Выезд: <span className="font-medium text-slate-700">{checkOut}</span></p>
|
||||
<p className="text-xs text-slate-500">Итого: <span className="font-bold text-slate-900">{grandTotal.toLocaleString('ru-RU')} ₽</span></p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => { setStep('browse'); setSelected(null); setFormValues({}) }}
|
||||
@@ -1049,29 +1090,78 @@ export function WidgetPreview({ settings, slug, realRooms, realCategories, payme
|
||||
{/* Rental list */}
|
||||
{previewTab === 'rental' && (
|
||||
<div className="p-4 space-y-3">
|
||||
<p className="text-xs text-slate-500">Выберите объект и удобное время</p>
|
||||
{settings.additionalServices.filter(s => s.enabled).map(obj => (
|
||||
<div
|
||||
key={obj.id}
|
||||
onClick={() => setSelected(obj.id === selected ? null : obj.id)}
|
||||
className={cn(
|
||||
'flex items-center gap-4 p-3 rounded-xl border-2 cursor-pointer transition-all',
|
||||
selected === obj.id ? '' : 'border-slate-200 hover:border-slate-300',
|
||||
)}
|
||||
style={selected === obj.id ? { borderColor: settings.primaryColor, background: settings.primaryColor + '08' } : {}}
|
||||
>
|
||||
<div className="w-12 h-12 rounded-xl bg-slate-100 flex items-center justify-center text-2xl shrink-0">
|
||||
{obj.icon}
|
||||
{rentalObjects.length === 0 ? (
|
||||
<p className="text-xs text-slate-400 text-center py-4">Объекты аренды не настроены</p>
|
||||
) : (
|
||||
<>
|
||||
{/* Date picker for rental */}
|
||||
<div>
|
||||
<label className="block text-xs font-medium text-slate-500 mb-1">
|
||||
{settings.language === 'ru' ? 'Дата' : 'Date'}
|
||||
</label>
|
||||
<input
|
||||
type="date"
|
||||
value={rentalDate}
|
||||
min={localDate(0)}
|
||||
onChange={e => setRentalDate(e.target.value)}
|
||||
className="w-full text-sm border border-slate-200 rounded-lg px-3 py-2 focus:outline-none focus:ring-2"
|
||||
style={{ '--tw-ring-color': settings.primaryColor } as React.CSSProperties}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-slate-900">{obj.name}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-bold text-slate-900">{obj.price.toLocaleString('ru-RU')} ₽</p>
|
||||
<p className="text-xs text-slate-400">/{obj.unit}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{/* Objects */}
|
||||
{rentalObjects.map(obj => {
|
||||
const isSelected = selected === obj.id
|
||||
const hourlyAvailable = obj.pricePerHour > 0
|
||||
const dayAvailable = obj.pricePerDay > 0
|
||||
const hours = rentalIsFullDay ? 0 : Math.max(0, rentalEndHour - rentalStartHour)
|
||||
const price = isSelected
|
||||
? (rentalIsFullDay ? obj.pricePerDay : hours * obj.pricePerHour)
|
||||
: (hourlyAvailable ? obj.pricePerHour : obj.pricePerDay)
|
||||
const priceLabel = isSelected
|
||||
? (rentalIsFullDay ? `${obj.pricePerDay.toLocaleString('ru-RU')} ₽/день` : `${price.toLocaleString('ru-RU')} ₽ (${hours} ч)`)
|
||||
: (hourlyAvailable ? `${obj.pricePerHour.toLocaleString('ru-RU')} ₽/ч` : `${obj.pricePerDay.toLocaleString('ru-RU')} ₽/день`)
|
||||
return (
|
||||
<div key={obj.id} className="rounded-xl border-2 overflow-hidden" style={isSelected ? { borderColor: settings.primaryColor } : { borderColor: '#e2e8f0' }}>
|
||||
<div
|
||||
onClick={() => setSelected(isSelected ? null : obj.id)}
|
||||
className="flex items-center gap-3 p-3 cursor-pointer"
|
||||
style={isSelected ? { background: settings.primaryColor + '0d' } : {}}
|
||||
>
|
||||
<div className="w-10 h-10 rounded-lg bg-slate-100 flex items-center justify-center text-xl shrink-0">{obj.icon}</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-slate-900 text-sm">{obj.name}</p>
|
||||
<p className="text-xs text-slate-400">{obj.openHour}:00 – {obj.closeHour}:00</p>
|
||||
</div>
|
||||
<p className="font-bold text-sm text-slate-900">{priceLabel}</p>
|
||||
</div>
|
||||
{/* Time controls when selected */}
|
||||
{isSelected && (
|
||||
<div className="px-3 pb-3 pt-1 space-y-2 border-t border-slate-100">
|
||||
{hourlyAvailable && dayAvailable && (
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setRentalIsFullDay(false)} className={cn('flex-1 text-xs py-1.5 rounded-lg border font-medium transition-colors', !rentalIsFullDay ? 'text-white border-transparent' : 'bg-white text-slate-500 border-slate-200')} style={!rentalIsFullDay ? { background: settings.primaryColor } : {}}>По часам</button>
|
||||
<button onClick={() => setRentalIsFullDay(true)} className={cn('flex-1 text-xs py-1.5 rounded-lg border font-medium transition-colors', rentalIsFullDay ? 'text-white border-transparent' : 'bg-white text-slate-500 border-slate-200')} style={rentalIsFullDay ? { background: settings.primaryColor } : {}}>Весь день</button>
|
||||
</div>
|
||||
)}
|
||||
{!rentalIsFullDay && hourlyAvailable && (
|
||||
<div className="flex items-center gap-2 text-xs">
|
||||
<span className="text-slate-500">С</span>
|
||||
<select value={rentalStartHour} onChange={e => { const v = Number(e.target.value); setRentalStartHour(v); if (rentalEndHour <= v) setRentalEndHour(v + 1) }} className="flex-1 border border-slate-200 rounded-lg px-2 py-1.5 text-xs">
|
||||
{Array.from({ length: obj.closeHour - obj.openHour }, (_, i) => obj.openHour + i).map(h => <option key={h} value={h}>{h}:00</option>)}
|
||||
</select>
|
||||
<span className="text-slate-500">до</span>
|
||||
<select value={rentalEndHour} onChange={e => setRentalEndHour(Number(e.target.value))} className="flex-1 border border-slate-200 rounded-lg px-2 py-1.5 text-xs">
|
||||
{Array.from({ length: obj.closeHour - obj.openHour }, (_, i) => obj.openHour + i + 1).filter(h => h > rentalStartHour).map(h => <option key={h} value={h}>{h}:00</option>)}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useParams, useSearchParams } from 'react-router-dom'
|
||||
import { api, type WidgetRoom, type WidgetCategory } from '../lib/api'
|
||||
import { api, type WidgetRoom, type WidgetCategory, type WidgetRentalObject } from '../lib/api'
|
||||
import { WidgetPreview, DEFAULT_FORM_FIELDS, DEFAULT_SERVICES } from './BookingWidgetPage'
|
||||
import type { WidgetSettings } from './BookingWidgetPage'
|
||||
|
||||
@@ -8,8 +8,9 @@ export function BookingWidgetStandalonePage() {
|
||||
const { slug } = useParams<{ slug: string }>()
|
||||
const [searchParams] = useSearchParams()
|
||||
|
||||
const [realRooms, setRealRooms] = useState<WidgetRoom[]>([])
|
||||
const [realCategories, setRealCategories] = useState<WidgetCategory[]>([])
|
||||
const [realRooms, setRealRooms] = useState<WidgetRoom[]>([])
|
||||
const [realCategories, setRealCategories] = useState<WidgetCategory[]>([])
|
||||
const [realRentalObjects, setRealRentalObjects] = useState<WidgetRentalObject[]>([])
|
||||
const [paymentEnabled, setPaymentEnabled] = useState<boolean | undefined>(undefined)
|
||||
const [settings, setSettings] = useState<WidgetSettings>({
|
||||
hotelName: '',
|
||||
@@ -34,6 +35,7 @@ export function BookingWidgetStandalonePage() {
|
||||
.then(config => {
|
||||
setRealRooms(config.rooms ?? [])
|
||||
setRealCategories(config.categories ?? [])
|
||||
setRealRentalObjects(config.rentalObjects ?? [])
|
||||
setPaymentEnabled(config.paymentEnabled)
|
||||
if (config.widgetSettings) {
|
||||
const ws = config.widgetSettings
|
||||
@@ -64,6 +66,7 @@ export function BookingWidgetStandalonePage() {
|
||||
slug={slug}
|
||||
realRooms={realRooms.length > 0 ? realRooms : undefined}
|
||||
realCategories={realCategories.length > 0 ? realCategories : undefined}
|
||||
realRentalObjects={realRentalObjects.length > 0 ? realRentalObjects : undefined}
|
||||
paymentEnabled={paymentEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user