Виджет реализован и работает с реальными данными Relaxi API. Сейчас доступ предоставляется бесплатно активным организациям; контракт проверки доступа уже позволяет позже заменить бесплатную политику подпиской.
relaxi-widget — встраиваемый модальный клиент бронирования. Владелец бизнеса подключает один JavaScript-файл и размещает на своем сайте обычные кнопки. По клику виджет открывается поверх страницы и проводит посетителя до создания брони.
На одну Organization приходится один виджет. Сейчас внешний widgetId совпадает с UUID организации. Это публичный идентификатор, не секрет.
| Компонент | Ответственность |
|---|---|
relaxi-widget |
Интерфейс выбора филиала, ресурса, услуги, даты, времени, дополнений и контактов. |
| Business Workspace | Конструктор кода подключения и темы. Отдельная конфигурация виджета в БД не создается. |
subscription-checker-service |
Проверка доступа и выдача актуального JavaScript из S3. |
relaxi-api |
Контекст, Availability, расчет стоимости и создание брони. |
| Redis | Кэш решения о доступе. |
| S3 | Версионные сборки JavaScript и заранее сжатые варианты. |
Доменная логика расписания, доступности, цены и бронирования остается в relaxi-api.
В production Business Workspace формирует готовый код:
<script src="https://checker-host/widget/organization-uuid"></script>
Если checker и API находятся на разных origin:
<script
src="https://checker-host/widget/organization-uuid"
data-relaxi-api-url="https://api-host/api/v1/public"
></script>
Загрузчик создает один custom element relaxi-booking-widget в body. Клики обрабатываются через делегирование событий, поэтому кнопки могут появляться и после загрузки скрипта.
Полный сценарий:
<button data-relaxi-open>Забронировать</button>
Конкретный филиал:
<button data-relaxi-open data-relaxi-venue-id="venue-uuid">
Забронировать в филиале
</button>
Конкретный ресурс:
<button data-relaxi-open data-relaxi-resource-id="resource-uuid">
Забронировать зал
</button>
Конкретная услуга:
<button data-relaxi-open data-relaxi-service-id="service-uuid">
Записаться на услугу
</button>
Услугу можно сочетать с data-relaxi-venue-id или data-relaxi-resource-id. Если услуга допускает бронирование без зала, после филиала открывается расписание. Если услуге нужен ресурс, виджет показывает только подходящие ресурсы.
Несовместимые UUID не заменяются молча: пользователь получает ошибку.
Контакты временно сохраняются в relaxi-widget:{widgetId}:contact-draft и удаляются после успешного создания брони.
Календарь не строится по упрощенной недельной маске. Виджет получает итоговые интервалы:
POST /api/v1/public/venues/:venueId/effective-schedule
Расчет выполняет существующий Availability Engine. Он учитывает:
Venue;Venue;Venue;Resource;Resource;Закрытая дата скрывается среди ближайших дат и отключается в календаре. Разово открытая дата становится доступной. Для услуги без ресурса используется итоговое расписание филиала; при выбранном ресурсе — его итоговое расписание.
Расписание отвечает только на вопрос, когда объект работает. Конкретный слот дополнительно проверяется с учетом продолжительности, гостей, занятости и требований услуги. Availability не гарантирует создание брони: API проверяет все повторно.
| Маршрут | Назначение |
|---|---|
GET /api/v1/public/widgets/:widgetId/context |
Организация, валюта и филиалы. |
GET /api/v1/public/venues/:venueId |
Публичные данные филиала, ресурсы и услуги. |
GET /api/v1/public/venues/:venueId/products |
Товары для дополнений. |
POST /api/v1/public/venues/:venueId/effective-schedule |
Итоговое расписание с overrides. |
POST /api/v1/public/venues/:venueId/availability |
Доступные слоты ресурсов. |
POST /api/v1/public/bookings/options |
Проверка времени и расчет стоимости. |
POST /api/v1/public/widgets/:widgetId/bookings |
Создание гостевой брони из виджета. |
Во все запросы добавляется заголовок:
X-Relaxi-Widget-Id: {widgetId}
Он включает CORS для публичных маршрутов с внешнего сайта владельца, но не открывает business или admin API.
Для создания брони не нужен аккаунт посетителя. API связывает widgetId с организацией, повторно проверяет доступность и цену, применяет идемпотентность, сохраняет бронь транзакционно и создает событие в outbox.
Виджет использует Svelte 5, Tailwind CSS 4, Vite library mode, custom element и Shadow DOM.
Реализованы:
+РЕЛАКСИ на выбранный филиал;Конструктор Business Workspace формирует атрибуты темы:
<script
src="https://checker-host/widget/organization-uuid"
data-relaxi-primary="#5f663f"
data-relaxi-primary-text="#fafaf8"
data-relaxi-background="#ffffff"
data-relaxi-surface="#eceae4"
data-relaxi-text="#0a0a0a"
data-relaxi-muted="#6f6b60"
data-relaxi-border="#deddd7"
data-relaxi-radius="16px"
data-relaxi-font="Inter, ui-sans-serif, system-ui"
></script>
Тема применяется через CSS-переменные внутри Shadow DOM. Открывающая кнопка остается обычным элементом страницы и сохраняет стили сайта владельца.
Публичная ссылка ведет на:
GET /widget/:orgId
Checker:
widget:subscription:{orgId}.Accept-Encoding возвращает Brotli, gzip или обычный JavaScript.Структура S3:
widget/dev/0.1.0/widget.js
widget/dev/0.1.0/widget.js.gz
widget/dev/0.1.0/widget.js.br
widget/prod/0.1.0/widget.js
widget/prod/0.1.0/widget.js.gz
widget/prod/0.1.0/widget.js.br
Checker учитывает только версии с полным набором трех файлов, выбирает максимальную SemVer-версию и кэширует найденный релиз на 30 секунд. После публикации новой полной версии менять Consul и перезапускать checker не нужно.
После появления платных тарифов бесплатная политика будет заменена проверкой подписки без изменения URL. Основной API уже проверяет доступ перед выдачей widget context и созданием widget booking.
cd relaxi-widget
npm install
npm run dev
Development-переменные:
VITE_RELAXI_API_URL=https://localhost:8080/api/v1/public
VITE_RELAXI_PUBLIC_SITE_URL=https://localhost:4200
Проверка и сборка:
npm run check
npm run build:dev
npm run build:prod
Версия берется из package.json. Результат:
dist/{version}/widget.js
dist/{version}/widget.js.gz
dist/{version}/widget.js.br
Во время разработки интерфейса checker не нужен. Полная интеграционная проверка выполняется с собранным файлом через subscription-checker-service.
Organization.widgetId сейчас равен UUID организации.static-site-widget.md — общий контракт виджета, checker и будущих статических сайтов.relaxi-widget/README.md — разработка, сборка и frontend-контракт.relaxi-api/docs/subscription-checker-service.md — checker, Redis, S3 и запуск.relaxi-api/docs/availability-engine.md — Availability Engine.