pxsol-booking-search-widget
Version:
Embeddable booking engine search widget with React and Web Component builds.
152 lines (111 loc) • 5.17 kB
Markdown
Embeddable booking engine search widget that ships a React component and a framework-agnostic Web Component. Both builds reuse the same headless state utilities and formatting helpers so you can embed the widget inside modern React apps or legacy pages with a single configuration.
- Airbnb-style search form with date range, guests/rooms, POS, language, currency, and product selector.
- Emits stable lifecycle events (`ready`, `change`, `validate_error`, `search_start`, `search_success`, `search_error`).
- Integrates with the Pxsol Search API via client-side `fetch` calls using a public token.
- Shadow DOM styling for the Web Component with themable CSS variables (`--yw-bg`, `--yw-fg`, `--yw-accent`).
- Dual outputs: React (ESM + CJS, peer React) and Custom Element (ESM + IIFE legacy bundle).
- Headless helpers for formatting, validation, and request payload creation.
## Installation
```bash
npm install @pxsol/booking-search-widget
# or
pnpm add @pxsol/booking-search-widget
```
## Scripts
- `npm run build` – produce all distribution bundles via `tsup`.
- `npm run dev` – watch builds for local iteration.
- `npm run typecheck` – static analysis using `tsc --noEmit`.
- `npm run demo` – build once and launch a static server at http://localhost:4173.
## Local demo
```bash
npm run demo
```
The command runs a one-off build and serves `demo/index.html` plus the compiled bundles from `dist/`. Visit [http://localhost:4173](http://localhost:4173) to interact with the widget, inspect emitted events, and iterate on configuration quickly.
## React usage
```tsx
import React from 'react';
import { BookingSearchWidget } from 'pxsol-booking-search-widget';
// ✅ CSS styles are automatically included - no need to import CSS separately
const config = {
token: 'PUBLIC_SEARCH_TOKEN',
theme: 'light',
locale: 'es',
currency: 'ARS',
pos: 'HotelEscuela',
productId: 2475,
initialStart: '2024-07-01',
initialEnd: '2024-07-05',
onEvent: (event) => {
if (event.type === 'search_success') {
console.log('Search result', event.payload);
}
},
};
export function Demo() {
return <BookingSearchWidget {...config} />;
}
```
```html
<script type="module">
import 'pxsol-booking-search-widget/wc';
</script>
<your-widget
token="PUBLIC_SEARCH_TOKEN"
theme="dark"
locale="es"
currency="ARS"
pos="HotelEscuela"
product-id="2475"
initial-start="2024-07-01"
initial-end="2024-07-05"
></your-widget>
<script>
document.querySelector('your-widget').addEventListener('yw:event', (event) => {
console.log('Widget event', event.detail);
});
</script>
```
```html
<script src="/dist/your-widget.iife.js"></script>
<your-widget token="PUBLIC_SEARCH_TOKEN"></your-widget>
```
The IIFE build self-registers the custom element and exposes a `registerYourWidget()` helper when needed.
See `demo/index.html` for a copy-paste ready template that logs widget events to the screen. The page is served automatically by `npm run demo`.
For detailed integration guides (React via npm and plain HTML via CDN), read [`docs/INTEGRATION.md`](./docs/INTEGRATION.md).
## Events
| Event | Payload | Description |
| --- | --- | --- |
| `ready` | `{ state: WidgetState }` | Widget is mounted and hydrated. |
| `change` | `{ state: WidgetState }` | Any user change to the internal state. |
| `validate_error` | `{ valid: false; errors: Record<string, string> }` | Validation failed prior to calling the API. |
| `search_start` | `SearchRequest` | Outgoing payload before calling the API. |
| `search_success` | `{ request: SearchRequest; response: SearchResponse }` | Successful response from the API. |
| `search_error` | `{ request: SearchRequest; error: Error }` | Network or API error. |
On the Web Component build these events are dispatched through `CustomEvent('yw:event', { detail })` with the widget event in `detail`.
Override the following CSS variables on the host element:
- `--yw-bg` – primary surface background.
- `--yw-fg` – text color.
- `--yw-accent` – primary button color.
Example:
```css
your-widget {
--yw-bg:
--yw-fg:
--yw-accent:
}
```
The widget calls `POST https://gateway-prod.pxsol.com/v2/search` with a public bearer token. Responses include `booking_engine_url` that is automatically opened when `redirect` is enabled. All requests are client-side, so ensure the provided token has the correct CORS permissions. Do **not** embed private keys or secrets in markup.
- Ship the ESM build and apply a CSP like `script-src 'self' https://cdn.yourdomain.com` so the widget loads from a trusted CDN.
- Avoid `unsafe-eval`; the bundles are plain ESM/CJS/IIFE with no dynamic code execution.
- When embedding in PCI-sensitive contexts, consider wrapping the Web Component in an iframe with a hardened `sandbox` attribute and communicate via `postMessage`.
See [`CHANGELOG.md`](./CHANGELOG.md) for release notes.