Skip to content

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.

The page reads configuration from URL query parameters with sensible defaults:

ParameterDefaultDescription
tenantmy-clubYour tenant slug
eventsample-eventThe event to display
apiCurrent originYour Seva API base URL

Example: your-page.html?tenant=rotary-west&event=annual-gala&api=https://api.seva.tools

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 &amp; 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>

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.

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.

<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.

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.

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.

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.