Full Page Example
This page demonstrates a complete, production-ready integration of all Seva components — the inline registration flow plus the modal member self-service components. It includes dark-mode toggling, live status badges, a member utility bar, an event log panel, and URL-based configuration overrides.
Webflow event pages: prefer the CTA + modal pattern. The example below uses
<seva-event-register>directly so you can see every event in the log — useful for development and debugging. For production Webflow event pages, the recommended pattern is<seva-event-register-cta>(inline anchor) +<seva-event-register-modal>(registration form). See the “Full Example” section in either of those component pages for the recommended HTML.
Configuration
Section titled “Configuration”The page reads configuration from URL query parameters with sensible defaults:
| Parameter | Default | Description |
|---|---|---|
tenant | my-club | Your tenant slug |
event | sample-event | The event to display |
api | Current origin | Your Seva API base URL |
Example: your-page.html?tenant=rotary-west&event=annual-gala&api=https://api.seva.tools
Complete HTML
Section titled “Complete HTML”Copy this file, replace the defaults and YOUR_COMPONENTS_URL, and open it in a browser.
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Event Registration</title> <style> *, *::before, *::after { box-sizing: border-box; } body { margin: 0; padding: 1.5rem; font-family: system-ui, -apple-system, sans-serif; background: #f4f4f5; color: #18181b; } body.dark { background: #18181b; color: #e4e4e7; }
.header { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 1.5rem; max-width: 1100px; margin-left: auto; margin-right: auto; } .header h1 { margin: 0; font-size: 1.25rem; }
.status-bar { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; } .badge { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.2rem 0.6rem; border-radius: 999px; font-size: 0.75rem; font-weight: 600; } .badge.auth-yes { background: #dcfce7; color: #166534; } .badge.auth-no { background: #fef3c7; color: #92400e; } .badge.cart { background: #dbeafe; color: #1e40af; } body.dark .badge.auth-yes { background: #14532d; color: #86efac; } body.dark .badge.auth-no { background: #78350f; color: #fde68a; } body.dark .badge.cart { background: #1e3a5f; color: #93c5fd; }
.controls { display: flex; gap: 0.35rem; } .controls button { padding: 0.45rem 0.75rem; border-radius: 0.375rem; border: 1px solid #d4d4d8; background: #fff; color: #18181b; font-size: 0.75rem; cursor: pointer; } body.dark .controls button { background: #27272a; color: #e4e4e7; border-color: #3f3f46; }
.layout { display: grid; grid-template-columns: minmax(0, 420px) minmax(0, 1fr); gap: 1.5rem; max-width: 1100px; margin: 0 auto; } @media (max-width: 800px) { .layout { grid-template-columns: 1fr; } }
.left-col { display: flex; flex-direction: column; gap: 1.5rem; } .right-col { display: flex; flex-direction: column; gap: 1rem; }
.section-label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: #71717a; margin-bottom: 0.35rem; font-weight: 600; }
#event-log { background: #18181b; color: #a1a1aa; border-radius: 0.5rem; padding: 1rem; font-family: ui-monospace, monospace; font-size: 0.7rem; max-height: 500px; overflow: auto; white-space: pre-wrap; word-break: break-all; flex: 1; min-height: 250px; } </style> </head> <body> <div class="header"> <h1>Event Registration</h1> <div class="status-bar"> <span id="auth-badge" class="badge auth-no">Not Signed In</span> <span id="cart-badge" class="badge cart">Cart: 0 items</span> <div class="controls"> <button onclick="document.dispatchEvent(new CustomEvent('seva:open-my-profile'))" > My Profile </button> <button onclick="document.dispatchEvent(new CustomEvent('seva:open-my-registrations'))" > My Registrations </button> <button onclick="toggleTheme()">Toggle Dark</button> <button onclick="doSignOut()">Sign Out</button> </div> </div> </div>
<div class="layout"> <div class="left-col"> <div> <div class="section-label">1. Authenticate</div> <seva-auth id="auth-el"></seva-auth> </div> <div> <div class="section-label">2. Register for Event</div> <seva-event-register id="register-el"></seva-event-register> </div> <div> <div class="section-label">3. Cart & Checkout</div> <seva-cart id="cart-el"></seva-cart> </div> </div> <div class="right-col"> <div id="event-log">Listening for seva:* events...</div> </div> </div>
<!-- Modal components — placed outside the grid, they portal to body --> <seva-my-profile id="profile-el"></seva-my-profile> <seva-my-registrations id="my-reg-el"></seva-my-registrations> <seva-member-directory id="directory-el"></seva-member-directory>
<!-- IMPORTANT: This non-module script runs synchronously BEFORE the module scripts below (modules are deferred by spec). This ensures connectedCallback sees correct attribute values. --> <script> const params = new URLSearchParams(location.search); const tenantSlug = params.get("tenant") || "my-club"; const eventSlug = params.get("event") || "sample-event"; const apiUrl = params.get("api") || location.origin;
const authEl = document.getElementById("auth-el"); const registerEl = document.getElementById("register-el"); const cartEl = document.getElementById("cart-el"); const profileEl = document.getElementById("profile-el"); const myRegEl = document.getElementById("my-reg-el"); const directoryEl = document.getElementById("directory-el");
authEl.setAttribute("tenant-slug", tenantSlug); authEl.setAttribute("api-url", apiUrl);
registerEl.setAttribute("tenant-slug", tenantSlug); registerEl.setAttribute("event-slug", eventSlug); registerEl.setAttribute("api-url", apiUrl);
cartEl.setAttribute("tenant-slug", tenantSlug); cartEl.setAttribute("api-url", apiUrl);
profileEl.setAttribute("tenant-slug", tenantSlug); profileEl.setAttribute("api-url", apiUrl);
myRegEl.setAttribute("tenant-slug", tenantSlug); myRegEl.setAttribute("api-url", apiUrl);
directoryEl.setAttribute("tenant-slug", tenantSlug); directoryEl.setAttribute("api-url", apiUrl); </script>
<!-- Load component bundles --> <script type="module" src="YOUR_COMPONENTS_URL/seva-auth.js"></script> <script type="module" src="YOUR_COMPONENTS_URL/seva-event-register.js" ></script> <script type="module" src="YOUR_COMPONENTS_URL/seva-cart.js"></script> <script type="module" src="YOUR_COMPONENTS_URL/seva-my-profile.js"></script> <script type="module" src="YOUR_COMPONENTS_URL/seva-my-registrations.js" ></script> <script type="module" src="YOUR_COMPONENTS_URL/seva-member-directory.js" ></script>
<script> const _authEl = document.getElementById("auth-el"); const _registerEl = document.getElementById("register-el"); const _cartEl = document.getElementById("cart-el"); const logEl = document.getElementById("event-log"); const authBadge = document.getElementById("auth-badge"); const cartBadge = document.getElementById("cart-badge");
const _profileEl = document.getElementById("profile-el"); const _myRegEl = document.getElementById("my-reg-el"); const _directoryEl = document.getElementById("directory-el");
// --- Theme --- let dark = false; function toggleTheme() { dark = !dark; document.body.classList.toggle("dark", dark); const theme = dark ? "dark" : "light"; _authEl.theme = theme; _registerEl.theme = theme; _cartEl.theme = theme; _profileEl.theme = theme; _myRegEl.theme = theme; _directoryEl.theme = theme; }
// --- Sign out --- function doSignOut() { _authEl.signOut(); propagateAuth(); }
// --- Auth propagation --- function propagateAuth() { const token = _authEl.getToken(); if (token) { _registerEl.setAttribute("auth-token", token); _cartEl.setAttribute("auth-token", token); } else { _registerEl.removeAttribute("auth-token"); _cartEl.removeAttribute("auth-token"); } updateAuthBadge(); }
function updateAuthBadge() { const user = _authEl.getUser(); if (user) { authBadge.className = "badge auth-yes"; authBadge.textContent = "Signed In: " + user.name; } else { authBadge.className = "badge auth-no"; authBadge.textContent = "Not Signed In"; } }
// --- Event logging --- function logEvent(name, detail) { const ts = new Date().toLocaleTimeString(); logEl.textContent += "\n[" + ts + "] " + name + "\n" + JSON.stringify(detail, null, 2) + "\n"; logEl.scrollTop = logEl.scrollHeight; }
// --- Listeners --- _authEl.addEventListener("seva:authenticated", () => propagateAuth()); _authEl.addEventListener("seva:signed-out", () => propagateAuth());
document.addEventListener("seva:cart-updated", (e) => { const { itemCount, totalCents } = e.detail; const dollars = (totalCents / 100).toFixed(2); cartBadge.textContent = "Cart: " + itemCount + " item" + (itemCount !== 1 ? "s" : "") + " ($" + dollars + ")"; });
// Log all seva events [ "seva:authenticated", "seva:signed-out", "seva:error", "seva:view-changed", "seva:event-loaded", "seva:registration-error", "seva:add-to-cart", "seva:registration-declined", "seva:cart-updated", "seva:checkout-complete", "seva:checkout-error", "seva:profile-updated", "seva:open-my-profile", "seva:open-my-registrations", ].forEach((name) => { document.addEventListener(name, (e) => logEvent(name, e.detail)); }); </script> </body></html>How it works
Section titled “How it works”Attribute-first initialization
Section titled “Attribute-first initialization”The non-module <script> block runs synchronously before the module scripts load. It reads query parameters and sets tenant-slug, event-slug, and api-url on each element. When the custom element classes upgrade those elements, connectedCallback sees the correct values immediately.
Auth propagation
Section titled “Auth propagation”The propagateAuth function reads the token from <seva-auth> and sets or removes auth-token on the registration and cart components. It also updates the status badge in the header.
Automatic cart wiring
Section titled “Automatic cart wiring”<seva-cart> listens on document for seva:add-to-cart. When the registration component dispatches that event, the cart picks it up with no extra glue code.
Theme toggling
Section titled “Theme toggling”All three components accept a theme property. The toggleTheme function sets 'dark' or 'light' on each component and toggles a dark class on <body> for the page styles.
Modal components
Section titled “Modal components”The three modal components (<seva-my-profile>, <seva-my-registrations>, <seva-member-directory>) are placed outside the grid layout at the end of <body>. They don’t need propagateAuth — they self-manage auth from localStorage and listen for seva:authenticated on document. The header buttons dispatch seva:open-my-profile and seva:open-my-registrations to open those modals. The member directory opens via its built-in trigger button.
Event log
Section titled “Event log”The right-hand panel logs every seva:* event with a timestamp and JSON payload. This is useful during development but can be removed in production.