Full Page Example
This page demonstrates a complete, production-ready integration of all three Seva components. It includes dark-mode toggling, live status badges, an event log panel, and URL-based configuration overrides.
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="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>
<!-- 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');
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); </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> 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');
// --- Theme --- let dark = false; function toggleTheme() { dark = !dark; document.body.classList.toggle('dark', dark); _authEl.theme = dark ? 'dark' : 'light'; _registerEl.theme = dark ? 'dark' : 'light'; _cartEl.theme = dark ? 'dark' : 'light'; }
// --- 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' ].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.
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.