Developer docs  /  Themes

Cart, embeds & checklist

The "cart" is a stub

Origin ships cart-shaped files, but there is no theme-side cart. sections/cart-dropdown.fluid in full:

{% comment %} Mini-cart dropdown rendered in the site header. {% endcomment %}
<div style="position:relative;display:inline-block">
<a href="/checkout" style="display:flex;align-items:center;gap:4px">View Cart</a>
</div>

and templates/cart.fluid says it outright: "Cart functionality is handled by the checkout system." The real cart state lives inside the Angular checkout (the shell's spaceUId preload adds the space as an order line item; multi-space carts are a checkout concern). Note the stub even links to /checkout (singular) — a dead path in both apps — which tells you how vestigial it is. For a new theme: skip the cart chrome entirely and send visitors straight to /checkouts/cn/booking?spaceUId=.... Don't build a client-side cart in the theme.

Embeds: where booking works, and where it doesn't

Every theme route also exists under /embed/... (resolved in ThemeV2RouteResolver, rendered through layouts/embed.fluid — chrome-less, designed for an iframe on the tenant's external marketing site). Two embed behaviours matter for checkout:

  1. layouts/embed.fluid sets <base target="_top">. Any normal link inside the iframe navigates the parent window, and because the document's URL is on the storefront host, relative hrefs still resolve against the storefront domain. So an embedded space detail page (/embed/spaces/{uid}) reuses templates/space-detail.fluid unchanged — its #bookOnlineBtn works, and clicking it breaks the whole browser tab out of the host site into https://<storefront-host>/checkouts/cn/booking?.... That is the intended behaviour: checkout must never run inside a third-party iframe.
  2. The embedded spaces listing (/embed/spacestemplates/embed-spaces.fluid) deliberately has no booking path from its inline detail view. Its "View" button doesn't navigate; showSpaceDetailInline() in theme.js clones the clicked card into a panel with a Back button so the visitor stays inside the iframe, and the only CTA it reconstructs is the quote button:

javascript /* Reuse the card's own quote button data so the detail view converts straight into a lead without leaving the iframe. */ const quoteBtn = card.querySelector('.js-quote-btn');

If your theme wants embed bookings rather than embed leads, don't try to run checkout in the iframe — render a real link out instead: either to the full space detail page (/t/spaces/{{item.UId}}) or directly to /checkouts/cn/booking?spaceUId={{item.UId}}. With <base target="_top"> in place a plain <a href> already escapes the iframe; add an explicit target="_top" on the anchor if your layout doesn't set the base tag.

The non-listing embed pages already behave this way — snippets/space-card.fluid only renders the inline-detail button when the embed listing opts in via EmbedInlineDetail; everywhere else the card's CTA is the normal /t/spaces/{{item.UId}} link, "so the button is never a dead click".

  1. The parent page sizes the iframe via postMessage. embed.fluid publishes { type: 'alyta:embed:resize', height } from a ResizeObserver on the body — the type string matches the legacy Angular /embed/units broadcaster so existing tenant sites keep working. Not checkout-specific, but if you replace the embed layout, keep this publisher or the tenant's iframe stops auto-sizing.

Checklist for a custom theme

  • [ ] Book Online link = /checkouts/cn/booking?spaceUId={Space.UId}, appending &months=N only when N > 1 and &promotionUId={uid} only when a promotion is selected. Same-host relative URL, GUID UId not int Id.
  • [ ] CTA gated on Settings.ShowBookOnline and Space.AllowOnlineBookings; quote CTA gated on Settings.ShowGetQuote; all pricing hidden under Settings.HidePricing.
  • [ ] Live pricing via GET /api/public/spaces/{id}/price?quantity=N and .../promotions?quantity=N; promotions sorted by priority ascending (nulls first); the selected promotion object replaces the base price object; only rely on the fields listed in §3.
  • [ ] Quote = POST /api/public/spaces/{id}/get-quote with { name, email, phone, contactPreference, recaptchaToken }, reCAPTCHA Enterprise action getquote when Settings.RecaptchaSiteKey is set, integer space id.
  • [ ] No theme-side cart; no theme page under a checkout path (the router excludes it anyway).
  • [ ] Embeds: quote-only inside the iframe, or link out with target="_top" for bookings; keep the alyta:embed:resize height publisher.