@odeva/booking-widgets
v0.1.8
Published
Web components for Odeva-powered booking widgets
Maintainers
Readme
Odeva Booking Widgets
Web components for adding Odeva-powered booking flows to any site.
Install
npm install @odeva/booking-widgetsUse from the CDN
For sites that are not using npm or a bundler, load the hosted widget script directly:
<script src="https://booking.odeva.app/widgets/booking.js" defer></script>
<odeva-booking-widget org="my-org"></odeva-booking-widget>Use
Import the package once, then render the widget.
import "@odeva/booking-widgets";<odeva-booking-widget
org="my-org"
api-url="https://booking.odeva.app/graphql"
api-key="pk_live_…"
></odeva-booking-widget>Attributes
org— Odeva organisation slug.api-url— GraphQL API URL. Usehttps://booking.odeva.app/graphqlfor embeds on third-party sites.api-key— publishable API key (pk_live_…). Required for cross-origin embeds. Create one in the admin under Settings → API keys with type Publishable.checkout-url— optional override for the hosted checkout base URL.accommodation-id— optional accommodation ID to deep-link straight into the detail view.
The package also exports the lower-level components: <odeva-search>, <odeva-search-and-book>, <odeva-accommodations>, <odeva-detail>, <odeva-booking>. They each accept api-url and api-key with the same semantics.
<odeva-search-and-book> is a full "search & book" results page: sidebar with dates, guests, and facility (metafield) filters grouped by category, plus a results list. It emits odeva-select-accommodation on card click — wire it into <odeva-booking-widget> or handle navigation yourself.
Static website components
The package also includes reusable content components for marketing pages that want to match Odeva-hosted websites without using the full website editor:
<odeva-content-grid>— feature, facilities, policies, benefits, and location highlight grids.<odeva-category-grid>— image cards for stay types or accommodation categories.<odeva-faq>— frequently asked questions.<odeva-gallery-mosaic>— responsive image gallery.<odeva-review-summary>— rating plus guest quotes.<odeva-cta-panel>— seasonal notices, map prompts, and direct booking CTAs.
All static components use the same --odeva-* theme variables as the booking widgets. Collection-style attributes accept JSON:
<odeva-content-grid
heading="Why book direct?"
intro="Book directly for clear prices and personal help."
columns="3"
items='[
{"title":"Best direct price","body":"No hidden platform fees."},
{"title":"Personal contact","body":"Questions go straight to the host."},
{"title":"Secure booking","body":"Reserve through the Odeva booking flow."}
]'
></odeva-content-grid>
<odeva-faq
heading="Good to know"
items='[
{"question":"Can I book directly?","answer":"Yes, use the search form on this site."},
{"question":"Can I ask a question first?","answer":"Yes, contact the host before booking."}
]'
></odeva-faq>Theming
Widgets are Shadow DOM web components, so host sites theme them through CSS custom properties on the element or any ancestor:
odeva-booking-widget {
--odeva-color-primary: #b86116;
--odeva-color-secondary: #6f6253;
--odeva-color-background: #ffffff;
--odeva-color-surface: #f7f5f1;
--odeva-color-surface-raised: #ffffff;
--odeva-color-border: #ded8cf;
--odeva-color-text: #4c443c;
--odeva-color-text-muted: #81766a;
--odeva-border-radius: 0.375rem;
--odeva-font-family: "Your Site Font", system-ui, sans-serif;
}Layout-specific hooks:
--odeva-detail-max-width— maximum width of<odeva-detail>.--odeva-booking-max-width— maximum width of<odeva-booking>.--odeva-sticky-top— top offset for sticky detail/summary panels.--odeva-search-background— search panel background override.
Embedding on a third-party site
Publishable keys are intended to be visible in HTML. They unlock only the public scope — search, list accommodations, availability, pricing, reservation creation, and checkout. They cannot read other organisations' data or perform admin actions.
When creating the key in the admin, set Allowed origins to the domains you'll embed on. The server rejects requests whose Origin header doesn't match.
Note on the origin check: This is anti-griefing, not security. Browsers send
Originreliably, so it stops casual copy-paste abuse. A determined attacker can spoof the header from a non-browser client. The real security comes from the publishable scope — apk_live_…only unlocks public operations.
Examples of allowed origins:
https://example.com
https://*.example.com
http://localhost:5173Leave empty to allow any origin (recommended only during development).
Proxy pattern (for secret keys)
If you need to call privileged operations (server-side reservation tools, audit logs, etc.), use a secret key (sk_live_…) and proxy through your own backend. Never expose secret keys in the browser.
// app server (e.g. Next.js route handler)
export async function POST(req: Request) {
const upstream = await fetch("https://booking.odeva.app/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Api-Key": process.env.ODEVA_SECRET_KEY,
},
body: await req.text(),
});
return new Response(upstream.body, { status: upstream.status });
}Point the widget at the proxy:
<odeva-booking-widget org="my-org" api-url="/api/odeva"></odeva-booking-widget>