diff --git a/ui/components/SessionsPage.tsx b/ui/components/SessionsPage.tsx
index cb2ddee..de74c47 100644
--- a/ui/components/SessionsPage.tsx
+++ b/ui/components/SessionsPage.tsx
@@ -136,32 +136,352 @@ export const SessionsPage = ({
- Active Sessions & Delegations
+ Active Sessions & Passes
- Manage browser logins and mint isolated, scoped sessions for 1-click
- passes, team members, CLI tools, or AI agents.
+ Manage logins, mint 1:1 ephemeral links & CLI tokens, or launch
+ multi-claim workshop event passes.
-
-
+
-
-
- Delegate Session
-
+
🎟️
+
+ Create Workshop Pass
+
+
+ 🔑
+ + Delegate 1:1 Pass / Session
+
+
+
+
+ {/* Create Multi-Claim Event Pass Drawer */}
+
+
+
+
+ Create Multi-Claim Workshop / Event Pass
+
+
+ Generate a shared event pass with a universal 6-digit PIN, vanity
+ link (/e/slug), and CLI
+ 1-liner for workshops, hackathons, and multi-user kiosks.
+
+
+
+ ×
+
+
+
+
+
+ {/* Event Launch Success Modal / Card */}
+
+
+
+ 🎟️
+
+ Workshop Pass Live!
+
+
+
Active Now
+
+
+
+ Share the 6-digit PIN, 1-click link, or CLI 1-liner with attendees.
+ You can monitor live claimed seats and trigger the master kill
+ switch from the Event Cockpit below.
+
+
+
+ {/* PIN Code */}
+
+
+ 6-Digit Universal PIN (for /join or Kiosk Entry)
+
+
+
+
+
+ Copy PIN
+
+
+
+
+ {/* 1-Click Vanity Link */}
+
+
+ Direct 1-Click Workshop Entrance URL
+
+
+
+
+
+ Copy URL
+
+
+
+
+ {/* CLI 1-Liner */}
+
+
+ Terminal 1-Liner (Environment Injector)
+
+
+
+
+
+ Copy 1-Liner
+
+
+
+
+
+
+ Done & Refresh Cockpit
+
+
{/* Delegate Session Drawer */}
@@ -173,7 +493,7 @@ export const SessionsPage = ({
+ {/* Direct 1-Click Destination (App Selection) */}
+
+
+ 1-Click Destination App (Optional)
+
+
+ Auth-Yes Central Dashboard (Default)
+ {apps.map((app) => (
+
+ {app.name} ({app.domain || "Internal App"})
+
+ ))}
+
+
+ Selecting an app scopes the pass and automatically redirects the
+ recipient to that app upon 1-click redemption.
+
+
+
{/* Lifespan Presets */}
@@ -854,6 +1197,8 @@ export const SessionsPage = ({
}
function openDelegateDrawer() {
+ const eventDrawer = document.getElementById('eventDrawer');
+ if (eventDrawer) eventDrawer.style.display = 'none';
document.getElementById('delegateDrawer').style.display = 'block';
document.getElementById('delegateDrawer').scrollIntoView({ behavior: 'smooth' });
}
@@ -862,6 +1207,38 @@ export const SessionsPage = ({
document.getElementById('delegateDrawer').style.display = 'none';
}
+ function openEventDrawer() {
+ const delegateDrawer = document.getElementById('delegateDrawer');
+ if (delegateDrawer) delegateDrawer.style.display = 'none';
+ document.getElementById('eventDrawer').style.display = 'block';
+ document.getElementById('eventDrawer').scrollIntoView({ behavior: 'smooth' });
+ }
+
+ function closeEventDrawer() {
+ document.getElementById('eventDrawer').style.display = 'none';
+ }
+
+ function selectSeats(btn, count) {
+ document.querySelectorAll('.seat-pill').forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+ document.getElementById('eventMaxSeats').value = count;
+ }
+
+ function selectEventLifespan(btn, hours) {
+ document.querySelectorAll('.event-lifespan-pill').forEach(b => b.classList.remove('active'));
+ btn.classList.add('active');
+ document.getElementById('eventLifespanHours').value = hours;
+ }
+
+ function handleTargetAppChange(appName) {
+ if (appName) {
+ const scopeVal = 'app:' + appName;
+ document.querySelectorAll('.scope-checkbox').forEach(cb => {
+ if (cb.value === scopeVal) cb.checked = true;
+ });
+ }
+ }
+
function selectLifespan(btn, hours) {
document.querySelectorAll('.lifespan-pill').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
@@ -883,13 +1260,22 @@ export const SessionsPage = ({
e.preventDefault();
const label = document.getElementById('delegateLabel').value.trim();
const lifespanHours = parseInt(document.getElementById('delegateHours').value) || 1;
- const mode = document.getElementById('delegateMode').value;
+ let mode = document.getElementById('delegateMode').value;
+ const targetApp = document.getElementById('delegateTargetApp') ? document.getElementById('delegateTargetApp').value : '';
const customScopes = [];
- if (mode === 'custom') {
+ if (targetApp) {
+ customScopes.push('app:' + targetApp);
+ }
+ if (mode === 'custom' || targetApp) {
document.querySelectorAll('.scope-checkbox:checked').forEach(cb => {
- customScopes.push(cb.value);
+ if (!customScopes.includes(cb.value)) {
+ customScopes.push(cb.value);
+ }
});
+ if (targetApp && mode !== 'custom' && mode !== 'admin') {
+ mode = 'custom';
+ }
}
const btn = document.getElementById('submitDelegateBtn');
@@ -921,6 +1307,60 @@ export const SessionsPage = ({
}
}
+ async function handleCreateEvent(e) {
+ e.preventDefault();
+ const name = document.getElementById('eventName').value.trim();
+ const appId = document.getElementById('eventAppId').value || null;
+ const role = document.getElementById('eventRole').value || 'viewer';
+ const maxSeats = parseInt(document.getElementById('eventMaxSeats').value) || 0;
+ const lifespanHours = parseInt(document.getElementById('eventLifespanHours').value) || 3;
+ const slug = document.getElementById('eventSlug').value.trim() || undefined;
+ const pinCode = document.getElementById('eventPinCode').value.trim() || undefined;
+
+ const btn = document.getElementById('submitEventBtn');
+ btn.disabled = true;
+ btn.textContent = 'Launching...';
+
+ try {
+ const res = await fetch('/api/events', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ name, appId, role, maxSeats, lifespanHours, slug, pinCode }),
+ });
+ const data = await res.json();
+ if (res.ok && data.success) {
+ const ev = data.event;
+ document.getElementById('createdEventTitle').textContent = '🎟️ ' + ev.name + ' Live!';
+ document.getElementById('eventHandoffPin').textContent = ev.pin_code;
+ document.getElementById('eventHandoffLink').textContent = window.location.origin + '/e/' + ev.slug;
+ document.getElementById('eventHandoffCli').textContent = 'curl -sSL ' + window.location.origin + '/join/' + ev.slug + '?format=env | source /dev/stdin';
+ document.getElementById('eventHandoffModal').style.display = 'block';
+ showNotice('Workshop pass created: ' + ev.name, false);
+ } else {
+ showNotice(data.error || 'Failed to create event pass', true);
+ }
+ } catch (err) {
+ showNotice('Network error creating event pass', true);
+ } finally {
+ btn.disabled = false;
+ btn.textContent = '🎟️ Launch Workshop Pass';
+ }
+ }
+
+ function copyEventHandoff(type) {
+ let text = '';
+ if (type === 'pin') text = document.getElementById('eventHandoffPin').textContent;
+ if (type === 'link') text = document.getElementById('eventHandoffLink').textContent;
+ if (type === 'cli') text = document.getElementById('eventHandoffCli').textContent;
+ navigator.clipboard.writeText(text);
+ showNotice('Copied to clipboard: ' + text, false);
+ }
+
+ function closeEventHandoffModal() {
+ document.getElementById('eventHandoffModal').style.display = 'none';
+ window.location.reload();
+ }
+
function copyHandoff(type) {
let text = '';
if (type === 'link') text = document.getElementById('handoffLinkText').textContent;