Merge pull request #33 from mrteye/phase-1-ui-sessions-decomposition-8623875160406573030

refactor(ui): decompose SessionsPage monolith (Phase 1)
This commit is contained in:
Tyler Gillispie 2026-08-25 16:20:12 -07:00 committed by GitHub
commit 9a90f4493c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 831 additions and 803 deletions

View File

@ -1,4 +1,8 @@
import { AuthenticatedLayout } from "./AuthenticatedLayout.tsx"; import { AuthenticatedLayout } from "./AuthenticatedLayout.tsx";
import { EventCockpitDeck } from "./sessions/EventCockpitDeck.tsx";
import { DirectPassDrawer } from "./sessions/DirectPassDrawer.tsx";
import { WorkshopDrawer } from "./sessions/WorkshopDrawer.tsx";
import { ScopeModal } from "./sessions/ScopeModal.tsx";
export const SessionsPage = ({ export const SessionsPage = ({
sessions, sessions,
@ -24,114 +28,7 @@ export const SessionsPage = ({
style="display: none; margin-bottom: 1rem; padding: 0.75rem 1rem; border-radius: var(--radius-md); font-size: 0.9rem;" style="display: none; margin-bottom: 1rem; padding: 0.75rem 1rem; border-radius: var(--radius-md); font-size: 0.9rem;"
/> />
{eventPasses && eventPasses.length > 0 && ( <EventCockpitDeck eventPasses={eventPasses} />
<div style="margin-bottom: 2rem;">
<h2 style="font-size: 1.25rem; font-weight: 700; margin: 0 0 1rem 0; color: var(--text-primary); border-bottom: 1px solid var(--border-subtle); padding-bottom: 0.5rem;">
Event Cockpit Deck (Live Workshops)
</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1rem;">
{eventPasses.map((event) => (
<div
key={event.id}
class="card"
style="border-left: 4px solid var(--primary); margin: 0;"
>
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.75rem;">
<div>
<h3 style="margin: 0 0 0.25rem 0; font-size: 1.1rem; color: var(--text-primary);">
{event.name}
</h3>
<div style="font-size: 0.8rem; color: var(--text-secondary);">
Expires: {new Date(event.expires_at).toLocaleString()}
</div>
</div>
<span class="badge badge-success">Active</span>
</div>
{/* Progress Bar for Seats */}
<div style="margin-bottom: 1rem;">
<div style="display: flex; justify-content: space-between; font-size: 0.8rem; margin-bottom: 0.25rem; color: var(--text-secondary);">
<span>Seats Claimed</span>
<strong style="color: var(--text-primary);">
{event.seats_claimed} /{" "}
{event.max_seats === 0 ? "∞" : event.max_seats}
</strong>
</div>
<div style="width: 100%; height: 8px; background: var(--surface-muted); border-radius: 4px; overflow: hidden;">
<div
style={`height: 100%; background: var(--primary); width: ${
event.max_seats > 0
? Math.min(
(event.seats_claimed / event.max_seats) * 100,
100,
)
: 100
}%;`}
>
</div>
</div>
</div>
{/* Quick Copy Snippets */}
<div style="display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 0.8rem; font-weight: 600; width: 60px; color: var(--text-muted);">
PIN:
</span>
<code style="flex: 1; padding: 0.35rem 0.5rem; background: var(--surface-muted); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.9rem; text-align: center; letter-spacing: 2px;">
{event.pin_code}
</code>
<button
type="button"
class="btn-outline"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 28px;"
onclick={`copyText('${event.pin_code}')`}
>
Copy
</button>
</div>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 0.8rem; font-weight: 600; width: 60px; color: var(--text-muted);">
Link:
</span>
<code style="flex: 1; padding: 0.35rem 0.5rem; background: var(--surface-muted); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
/e/{event.slug}
</code>
<button
type="button"
class="btn-outline"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 28px;"
onclick={`copyText(window.location.origin + '/e/${event.slug}')`}
>
Copy
</button>
</div>
</div>
{/* Cockpit Actions */}
<div style="display: flex; gap: 0.5rem;">
<button
type="button"
class="btn-outline extend-event-btn"
data-event-id={event.id}
style="flex: 1; justify-content: center; min-height: 38px; font-size: 0.85rem;"
>
+1h Extend
</button>
<button
type="button"
class="btn-danger end-event-btn"
data-event-id={event.id}
style="flex: 1; justify-content: center; min-height: 38px; font-size: 0.85rem;"
>
🔴 End & Revoke All
</button>
</div>
</div>
))}
</div>
</div>
)}
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem;"> <div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem;">
<div> <div>
@ -199,703 +96,11 @@ export const SessionsPage = ({
</div> </div>
</div> </div>
<div id="tabDirectPass"> <DirectPassDrawer apps={apps} isAdmin={isAdmin} />
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0 0 1rem 0;"> <WorkshopDrawer apps={apps} />
Mint an isolated, scoped child session for 1-click magic links, CLI
tools, team members, or AI assistants without exposing your primary
passkeys.
</p>
<form
id="delegateForm"
onsubmit="handleDelegateSession(event)"
style="margin-top: 1rem;"
>
{/* Label Input */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
Session Label / Purpose *
</label>
<input
type="text"
id="delegateLabel"
placeholder="e.g. Friend Demo Pass, Antigravity Assistant, CI/CD Runner, Terminal CLI"
required
style="width: 100%;"
/>
</div> </div>
{/* Direct 1-Click Destination (App Selection) */} <ScopeModal apps={apps} />
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
1-Click Destination App (Optional)
</label>
<select
id="delegateTargetApp"
onchange="handleTargetAppChange(this.value)"
style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
>
<option value="">Auth-Yes Central Dashboard (Default)</option>
{apps.map((app) => (
<option value={app.name}>
{app.name} ({app.domain || "Internal App"})
</option>
))}
</select>
<p style="font-size: 0.75rem; color: var(--text-muted); margin: 0.35rem 0 0 0;">
Selecting an app scopes the pass and automatically redirects the
recipient to that app upon 1-click redemption.
</p>
</div>
{/* Lifespan Presets */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
Lifespan & Auto-Expiration
</label>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button
type="button"
class="pill-btn lifespan-pill active"
data-hours="1"
onclick="selectLifespan(this, 1)"
>
1 Hour (Quick Task)
</button>
<button
type="button"
class="pill-btn lifespan-pill"
data-hours="12"
onclick="selectLifespan(this, 12)"
>
🛠 12 Hours (Work Session)
</button>
<button
type="button"
class="pill-btn lifespan-pill"
data-hours="24"
onclick="selectLifespan(this, 24)"
>
📅 24 Hours
</button>
<button
type="button"
class="pill-btn lifespan-pill"
data-hours="168"
onclick="selectLifespan(this, 168)"
>
🗓 7 Days
</button>
</div>
<input type="hidden" id="delegateHours" value="1" />
</div>
{/* Access Mode Presets */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
Access Mode & Permissions
</label>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button
type="button"
class="pill-btn mode-pill active"
data-mode="read_only"
onclick="selectMode(this, 'read_only')"
>
👁 Read-Only (Audit & Inspection)
</button>
<button
type="button"
class="pill-btn mode-pill"
data-mode="operator"
onclick="selectMode(this, 'operator')"
>
Operator (App Actions)
</button>
{isAdmin && (
<button
type="button"
class="pill-btn mode-pill"
data-mode="admin"
onclick="selectMode(this, 'admin')"
>
👑 Full Admin
</button>
)}
<button
type="button"
class="pill-btn mode-pill"
data-mode="custom"
onclick="selectMode(this, 'custom')"
>
🛠 Custom Scopes
</button>
</div>
<input type="hidden" id="delegateMode" value="read_only" />
</div>
{/* Progressive Disclosure: Specific App & Capability Scopes */}
<details
id="customScopesSection"
style="margin-bottom: 1.25rem; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 0.75rem 1rem; background: var(--surface-muted);"
>
<summary style="cursor: pointer; font-weight: 600; font-size: 0.875rem; color: var(--text-primary);">
Customize Specific App Grants & Capabilities (Optional)
</summary>
<div style="margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem;">
<p style="margin: 0 0 0.5rem 0; font-size: 0.8rem; color: var(--text-secondary);">
Select which subsidiary microservices and IAM capabilities
this delegated token is permitted to access:
</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem;">
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
class="scope-checkbox"
value="read:audit"
checked
/>
Read Audit Ledger
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
class="scope-checkbox"
value="read:users"
checked
/>
Read Users List
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
class="scope-checkbox"
value="read:apps"
checked
/>
Read Apps Registry
</label>
{apps.map((app) => (
<label
key={app.id}
style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;"
>
<input
type="checkbox"
class="scope-checkbox"
value={`app:${app.name}`}
/>
{app.name} ({app.domain || "Internal"})
</label>
))}
</div>
</div>
</details>
<div style="display: flex; gap: 0.75rem;">
<button
type="submit"
id="submitDelegateBtn"
class="btn-primary"
style="min-height: 42px;"
>
Mint & Delegate Session
</button>
<button
type="button"
class="btn-outline"
onclick="closeDelegateDrawer()"
style="min-height: 42px;"
>
Cancel
</button>
</div>
</form>
{/* Hand-Off Card (Revealed upon generation) */}
<div
id="handoffModal"
style="display: none; margin-top: 1.5rem; padding: 1.25rem; background: var(--surface-card); border: 1px solid var(--primary); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 1.2rem;">🔑</span>
<strong style="color: var(--text-primary); font-size: 1rem;">
Session Delegated Successfully!
</strong>
</div>
<span class="badge badge-success">Active Now</span>
</div>
<p style="font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 1rem 0;">
Copy this token, 1-click magic link, or CLI export string. You can
revoke or extend this session anytime from the active session
cards below.
</p>
<div style="display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1rem;">
{/* 1-Click Magic Link */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
1-Click Magic Link (Web / Friend / Interview)
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="handoffMagicLinkText"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--success);"
>
</code>
<button
type="button"
class="btn-primary"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyHandoff('magic')"
>
Copy Link
</button>
</div>
</div>
{/* 1-Tap Copy CLI */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
Terminal Environment Export (CLI)
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="handoffCliText"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
>
</code>
<button
type="button"
class="btn-outline"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyHandoff('cli')"
>
Copy CLI
</button>
</div>
</div>
{/* 1-Tap Copy cURL Header */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
HTTP Authorization Header
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="handoffCurlText"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
>
</code>
<button
type="button"
class="btn-outline"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyHandoff('curl')"
>
Copy Header
</button>
</div>
</div>
</div>
<button
type="button"
class="btn-outline"
style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
onclick="closeHandoffModal()"
>
Done (Session is Active)
</button>
</div>
</div>
<div id="tabWorkshopPass" style="display: none;">
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0 0 1rem 0;">
Generate a shared event pass with a universal 6-digit PIN, vanity
link (<code style="font-size: 0.8rem;">/e/slug</code>), and CLI
1-liner for workshops, hackathons, and multi-user kiosks.
</p>
<form
id="eventForm"
onsubmit="handleCreateEvent(event)"
style="margin-top: 1rem;"
>
{/* Event Name */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
Workshop / Event Name *
</label>
<input
type="text"
id="eventName"
placeholder="e.g. Deno & SPIRE Zero-Trust Workshop, Elite Fleet Live Demo"
required
style="width: 100%;"
/>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 1.25rem;">
{/* Target App */}
<div>
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
Target App Destination
</label>
<select
id="eventAppId"
style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
>
<option value="">Universal Sandbox (Default)</option>
{apps.map((app) => (
<option value={app.id}>
{app.name} ({app.domain || "Internal"})
</option>
))}
</select>
</div>
{/* Guest Role */}
<div>
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
Attendee Access Role
</label>
<select
id="eventRole"
style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
>
<option value="viewer">Viewer (Read-Only Demo)</option>
<option value="operator">Operator (Workshop Actions)</option>
<option value="user">Standard User</option>
</select>
</div>
</div>
{/* Seat Capacity Presets */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
Seat Capacity Limit
</label>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
<button
type="button"
class="pill-btn seat-pill"
onclick="selectSeats(this, 25)"
>
25 Seats
</button>
<button
type="button"
class="pill-btn seat-pill active"
onclick="selectSeats(this, 50)"
>
50 Seats
</button>
<button
type="button"
class="pill-btn seat-pill"
onclick="selectSeats(this, 100)"
>
100 Seats
</button>
<button
type="button"
class="pill-btn seat-pill"
onclick="selectSeats(this, 0)"
>
Unlimited
</button>
<input
type="number"
id="eventMaxSeats"
value="50"
min="0"
max="10000"
style="width: 100px; padding: 0.35rem 0.5rem; text-align: center;"
title="Max seats (0 = unlimited)"
/>
</div>
</div>
{/* Duration Presets */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
Event Duration & Expiration
</label>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button
type="button"
class="pill-btn event-lifespan-pill"
onclick="selectEventLifespan(this, 1)"
>
1 Hour
</button>
<button
type="button"
class="pill-btn event-lifespan-pill active"
onclick="selectEventLifespan(this, 3)"
>
🛠 3 Hours (Standard Workshop)
</button>
<button
type="button"
class="pill-btn event-lifespan-pill"
onclick="selectEventLifespan(this, 12)"
>
📅 12 Hours (All-Day Hackathon)
</button>
<button
type="button"
class="pill-btn event-lifespan-pill"
onclick="selectEventLifespan(this, 24)"
>
🗓 24 Hours
</button>
</div>
<input type="hidden" id="eventLifespanHours" value="3" />
</div>
{/* Custom Codes Accordion (Optional) */}
<details style="margin-bottom: 1.5rem; background: var(--surface-muted); padding: 0.75rem 1rem; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle);">
<summary style="font-size: 0.875rem; font-weight: 600; color: var(--text-primary); cursor: pointer;">
Custom Vanity Slug & Custom PIN Code (Optional)
</summary>
<div style="margin-top: 0.75rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
<div>
<label style="display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.25rem;">
Vanity Slug (e.g. /e/deno-lab)
</label>
<input
type="text"
id="eventSlug"
placeholder="auto-generated if blank"
style="width: 100%; font-size: 0.85rem;"
/>
</div>
<div>
<label style="display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.25rem;">
Custom 6-Digit PIN (e.g. 749-123)
</label>
<input
type="text"
id="eventPinCode"
placeholder="auto-generated if blank"
style="width: 100%; font-size: 0.85rem;"
/>
</div>
</div>
</details>
<div style="display: flex; gap: 0.75rem;">
<button
type="submit"
id="submitEventBtn"
class="btn-primary"
style="min-height: 42px; background: var(--success); border-color: var(--success);"
>
🎟 Launch Workshop Pass
</button>
<button
type="button"
class="btn-outline"
onclick="closeDelegateDrawer()"
style="min-height: 42px;"
>
Cancel
</button>
</div>
</form>
{/* Event Launch Success Modal / Card */}
<div
id="eventHandoffModal"
style="display: none; margin-top: 1.5rem; padding: 1.25rem; background: var(--surface-card); border: 2px solid var(--success); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 1.3rem;">🎟</span>
<strong
id="createdEventTitle"
style="color: var(--text-primary); font-size: 1.1rem;"
>
Workshop Pass Live!
</strong>
</div>
<span class="badge badge-success">Active Now</span>
</div>
<p style="font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 1rem 0;">
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.
</p>
<div style="display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.25rem;">
{/* PIN Code */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
6-Digit Universal PIN (for /join or Kiosk Entry)
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="eventHandoffPin"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 1.1rem; text-align: center; letter-spacing: 3px; font-weight: 700; color: var(--primary);"
>
</code>
<button
type="button"
class="btn-primary"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyEventHandoff('pin')"
>
Copy PIN
</button>
</div>
</div>
{/* 1-Click Vanity Link */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
Direct 1-Click Workshop Entrance URL
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="eventHandoffLink"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--success);"
>
</code>
<button
type="button"
class="btn-outline"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyEventHandoff('link')"
>
Copy URL
</button>
</div>
</div>
{/* CLI 1-Liner */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
Terminal 1-Liner (Environment Injector)
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="eventHandoffCli"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.8rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
>
</code>
<button
type="button"
class="btn-outline"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyEventHandoff('cli')"
>
Copy 1-Liner
</button>
</div>
</div>
</div>
<button
type="button"
class="btn-outline"
style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
onclick="closeEventHandoffModal()"
>
Done & Refresh Cockpit
</button>
</div>
</div>
</div>
{/* Edit Scopes Modal */}
<div
id="editScopesModal"
style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.6); z-index: 9999; justify-content: center; align-items: center;"
>
<div style="background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); width: 90%; max-width: 500px; padding: 1.5rem; box-shadow: var(--shadow-lg);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="margin: 0; font-size: 1.1rem; color: var(--text-primary);">
Update Scopes for:{" "}
<span id="editScopesLabel" style="color: var(--primary);"></span>
</h3>
<button
type="button"
onclick="closeEditScopesModal()"
style="background: none; border: none; font-size: 1.2rem; cursor: pointer; color: var(--text-muted);"
>
&times;
</button>
</div>
<input type="hidden" id="editScopesSessionId" value="" />
<div style="display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1.25rem;">
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
id="scope_read_audit"
class="edit-scope-chk"
value="read:audit"
/>
Read Audit Ledger
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
id="scope_read_users"
class="edit-scope-chk"
value="read:users"
/>
Read Users List
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
id="scope_read_apps"
class="edit-scope-chk"
value="read:apps"
/>
Read Apps Registry
</label>
{apps.map((app) => (
<label
key={app.id}
style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;"
>
<input
type="checkbox"
class="edit-scope-chk"
value={`app:${app.name}`}
/>
Access {app.name}
</label>
))}
</div>
<div style="display: flex; justify-content: flex-end; gap: 0.5rem;">
<button
type="button"
class="btn-outline"
onclick="closeEditScopesModal()"
>
Cancel
</button>
<button
type="button"
class="btn-primary"
onclick="saveUpdatedScopes()"
>
Save Scopes
</button>
</div>
</div>
</div>
{/* Desktop Table View (≥ 768px) */} {/* Desktop Table View (≥ 768px) */}
<div class="card desktop-only" style="display: none;"> <div class="card desktop-only" style="display: none;">

View File

@ -0,0 +1,323 @@
export const DirectPassDrawer = ({
apps = [],
isAdmin = false,
}: {
apps?: any[];
isAdmin?: boolean;
}) => {
return (
<div id="tabDirectPass">
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0 0 1rem 0;">
Mint an isolated, scoped child session for 1-click magic links, CLI
tools, team members, or AI assistants without exposing your primary
passkeys.
</p>
<form
id="delegateForm"
onsubmit="handleDelegateSession(event)"
style="margin-top: 1rem;"
>
{/* Label Input */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
Session Label / Purpose *
</label>
<input
type="text"
id="delegateLabel"
placeholder="e.g. Friend Demo Pass, Antigravity Assistant, CI/CD Runner, Terminal CLI"
required
style="width: 100%;"
/>
</div>
{/* Direct 1-Click Destination (App Selection) */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
1-Click Destination App (Optional)
</label>
<select
id="delegateTargetApp"
onchange="handleTargetAppChange(this.value)"
style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
>
<option value="">Auth-Yes Central Dashboard (Default)</option>
{apps.map((app) => (
<option value={app.name} key={app.id}>
{app.name} ({app.domain || "Internal App"})
</option>
))}
</select>
<p style="font-size: 0.75rem; color: var(--text-muted); margin: 0.35rem 0 0 0;">
Selecting an app scopes the pass and automatically redirects the
recipient to that app upon 1-click redemption.
</p>
</div>
{/* Lifespan Presets */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
Lifespan & Auto-Expiration
</label>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button
type="button"
class="pill-btn lifespan-pill active"
data-hours="1"
onclick="selectLifespan(this, 1)"
>
1 Hour (Quick Task)
</button>
<button
type="button"
class="pill-btn lifespan-pill"
data-hours="12"
onclick="selectLifespan(this, 12)"
>
🛠 12 Hours (Work Session)
</button>
<button
type="button"
class="pill-btn lifespan-pill"
data-hours="24"
onclick="selectLifespan(this, 24)"
>
📅 24 Hours
</button>
<button
type="button"
class="pill-btn lifespan-pill"
data-hours="168"
onclick="selectLifespan(this, 168)"
>
🗓 7 Days
</button>
</div>
<input type="hidden" id="delegateHours" value="1" />
</div>
{/* Access Mode Presets */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
Access Mode & Permissions
</label>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button
type="button"
class="pill-btn mode-pill active"
data-mode="read_only"
onclick="selectMode(this, 'read_only')"
>
👁 Read-Only (Audit & Inspection)
</button>
<button
type="button"
class="pill-btn mode-pill"
data-mode="operator"
onclick="selectMode(this, 'operator')"
>
Operator (App Actions)
</button>
{isAdmin && (
<button
type="button"
class="pill-btn mode-pill"
data-mode="admin"
onclick="selectMode(this, 'admin')"
>
👑 Full Admin
</button>
)}
<button
type="button"
class="pill-btn mode-pill"
data-mode="custom"
onclick="selectMode(this, 'custom')"
>
🛠 Custom Scopes
</button>
</div>
<input type="hidden" id="delegateMode" value="read_only" />
</div>
{/* Progressive Disclosure: Specific App & Capability Scopes */}
<details
id="customScopesSection"
style="margin-bottom: 1.25rem; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 0.75rem 1rem; background: var(--surface-muted);"
>
<summary style="cursor: pointer; font-weight: 600; font-size: 0.875rem; color: var(--text-primary);">
Customize Specific App Grants & Capabilities (Optional)
</summary>
<div style="margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.5rem;">
<p style="margin: 0 0 0.5rem 0; font-size: 0.8rem; color: var(--text-secondary);">
Select which subsidiary microservices and IAM capabilities
this delegated token is permitted to access:
</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0.5rem;">
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
class="scope-checkbox"
value="read:audit"
checked
/>
Read Audit Ledger
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
class="scope-checkbox"
value="read:users"
checked
/>
Read Users List
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
class="scope-checkbox"
value="read:apps"
checked
/>
Read Apps Registry
</label>
{apps.map((app) => (
<label
key={app.id}
style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;"
>
<input
type="checkbox"
class="scope-checkbox"
value={`app:${app.name}`}
/>
{app.name} ({app.domain || "Internal"})
</label>
))}
</div>
</div>
</details>
<div style="display: flex; gap: 0.75rem;">
<button
type="submit"
id="submitDelegateBtn"
class="btn-primary"
style="min-height: 42px;"
>
Mint & Delegate Session
</button>
<button
type="button"
class="btn-outline"
onclick="closeDelegateDrawer()"
style="min-height: 42px;"
>
Cancel
</button>
</div>
</form>
{/* Hand-Off Card (Revealed upon generation) */}
<div
id="handoffModal"
style="display: none; margin-top: 1.5rem; padding: 1.25rem; background: var(--surface-card); border: 1px solid var(--primary); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 1.2rem;">🔑</span>
<strong style="color: var(--text-primary); font-size: 1rem;">
Session Delegated Successfully!
</strong>
</div>
<span class="badge badge-success">Active Now</span>
</div>
<p style="font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 1rem 0;">
Copy this token, 1-click magic link, or CLI export string. You can
revoke or extend this session anytime from the active session
cards below.
</p>
<div style="display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1rem;">
{/* 1-Click Magic Link */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
1-Click Magic Link (Web / Friend / Interview)
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="handoffMagicLinkText"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--success);"
>
</code>
<button
type="button"
class="btn-primary"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyHandoff('magic')"
>
Copy Link
</button>
</div>
</div>
{/* 1-Tap Copy CLI */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
Terminal Environment Export (CLI)
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="handoffCliText"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
>
</code>
<button
type="button"
class="btn-outline"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyHandoff('cli')"
>
Copy CLI
</button>
</div>
</div>
{/* 1-Tap Copy cURL Header */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
HTTP Authorization Header
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="handoffCurlText"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
>
</code>
<button
type="button"
class="btn-outline"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyHandoff('curl')"
>
Copy Header
</button>
</div>
</div>
</div>
<button
type="button"
class="btn-outline"
style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
onclick="closeHandoffModal()"
>
Done (Session is Active)
</button>
</div>
</div>
);
};

View File

@ -0,0 +1,112 @@
export const EventCockpitDeck = ({ eventPasses }: { eventPasses: any[] }) => {
if (!eventPasses || eventPasses.length === 0) return null;
return (
<div style="margin-bottom: 2rem;">
<h2 style="font-size: 1.25rem; font-weight: 700; margin: 0 0 1rem 0; color: var(--text-primary); border-bottom: 1px solid var(--border-subtle); padding-bottom: 0.5rem;">
Event Cockpit Deck (Live Workshops)
</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1rem;">
{eventPasses.map((event) => (
<div
key={event.id}
class="card"
style="border-left: 4px solid var(--primary); margin: 0;"
>
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.75rem;">
<div>
<h3 style="margin: 0 0 0.25rem 0; font-size: 1.1rem; color: var(--text-primary);">
{event.name}
</h3>
<div style="font-size: 0.8rem; color: var(--text-secondary);">
Expires: {new Date(event.expires_at).toLocaleString()}
</div>
</div>
<span class="badge badge-success">Active</span>
</div>
{/* Progress Bar for Seats */}
<div style="margin-bottom: 1rem;">
<div style="display: flex; justify-content: space-between; font-size: 0.8rem; margin-bottom: 0.25rem; color: var(--text-secondary);">
<span>Seats Claimed</span>
<strong style="color: var(--text-primary);">
{event.seats_claimed} /{" "}
{event.max_seats === 0 ? "∞" : event.max_seats}
</strong>
</div>
<div style="width: 100%; height: 8px; background: var(--surface-muted); border-radius: 4px; overflow: hidden;">
<div
style={`height: 100%; background: var(--primary); width: ${
event.max_seats > 0
? Math.min(
(event.seats_claimed / event.max_seats) * 100,
100,
)
: 100
}%;`}
>
</div>
</div>
</div>
{/* Quick Copy Snippets */}
<div style="display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 0.8rem; font-weight: 600; width: 60px; color: var(--text-muted);">
PIN:
</span>
<code style="flex: 1; padding: 0.35rem 0.5rem; background: var(--surface-muted); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.9rem; text-align: center; letter-spacing: 2px;">
{event.pin_code}
</code>
<button
type="button"
class="btn-outline"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 28px;"
onclick={`copyText('${event.pin_code}')`}
>
Copy
</button>
</div>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 0.8rem; font-weight: 600; width: 60px; color: var(--text-muted);">
Link:
</span>
<code style="flex: 1; padding: 0.35rem 0.5rem; background: var(--surface-muted); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.75rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
/e/{event.slug}
</code>
<button
type="button"
class="btn-outline"
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 28px;"
onclick={`copyText(window.location.origin + '/e/${event.slug}')`}
>
Copy
</button>
</div>
</div>
{/* Cockpit Actions */}
<div style="display: flex; gap: 0.5rem;">
<button
type="button"
class="btn-outline extend-event-btn"
data-event-id={event.id}
style="flex: 1; justify-content: center; min-height: 38px; font-size: 0.85rem;"
>
+1h Extend
</button>
<button
type="button"
class="btn-danger end-event-btn"
data-event-id={event.id}
style="flex: 1; justify-content: center; min-height: 38px; font-size: 0.85rem;"
>
🔴 End & Revoke All
</button>
</div>
</div>
))}
</div>
</div>
);
};

View File

@ -0,0 +1,86 @@
export const ScopeModal = ({ apps = [] }: { apps?: any[] }) => {
return (
<div
id="editScopesModal"
style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.6); z-index: 9999; justify-content: center; align-items: center;"
>
<div style="background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-md); width: 90%; max-width: 500px; padding: 1.5rem; box-shadow: var(--shadow-lg);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 style="margin: 0; font-size: 1.1rem; color: var(--text-primary);">
Update Scopes for:{" "}
<span id="editScopesLabel" style="color: var(--primary);"></span>
</h3>
<button
type="button"
onclick="closeEditScopesModal()"
style="background: none; border: none; font-size: 1.2rem; cursor: pointer; color: var(--text-muted);"
>
&times;
</button>
</div>
<input type="hidden" id="editScopesSessionId" value="" />
<div style="display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1.25rem;">
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
id="scope_read_audit"
class="edit-scope-chk"
value="read:audit"
/>
Read Audit Ledger
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
id="scope_read_users"
class="edit-scope-chk"
value="read:users"
/>
Read Users List
</label>
<label style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;">
<input
type="checkbox"
id="scope_read_apps"
class="edit-scope-chk"
value="read:apps"
/>
Read Apps Registry
</label>
{apps.map((app) => (
<label
key={app.id}
style="display: flex; align-items: center; gap: 0.5rem; font-size: 0.85rem; color: var(--text-primary); cursor: pointer;"
>
<input
type="checkbox"
class="edit-scope-chk"
value={`app:${app.name}`}
/>
Access {app.name}
</label>
))}
</div>
<div style="display: flex; justify-content: flex-end; gap: 0.5rem;">
<button
type="button"
class="btn-outline"
onclick="closeEditScopesModal()"
>
Cancel
</button>
<button
type="button"
class="btn-primary"
onclick="saveUpdatedScopes()"
>
Save Scopes
</button>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,302 @@
export const WorkshopDrawer = ({ apps = [] }: { apps?: any[] }) => {
return (
<div id="tabWorkshopPass" style="display: none;">
<p style="color: var(--text-secondary); font-size: 0.9rem; margin: 0 0 1rem 0;">
Generate a shared event pass with a universal 6-digit PIN, vanity
link (<code style="font-size: 0.8rem;">/e/slug</code>), and CLI
1-liner for workshops, hackathons, and multi-user kiosks.
</p>
<form
id="eventForm"
onsubmit="handleCreateEvent(event)"
style="margin-top: 1rem;"
>
{/* Event Name */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
Workshop / Event Name *
</label>
<input
type="text"
id="eventName"
placeholder="e.g. Deno & SPIRE Zero-Trust Workshop, Elite Fleet Live Demo"
required
style="width: 100%;"
/>
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 1.25rem;">
{/* Target App */}
<div>
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
Target App Destination
</label>
<select
id="eventAppId"
style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
>
<option value="">Universal Sandbox (Default)</option>
{apps.map((app) => (
<option value={app.id} key={app.id}>
{app.name} ({app.domain || "Internal"})
</option>
))}
</select>
</div>
{/* Guest Role */}
<div>
<label style="display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.875rem; color: var(--text-secondary);">
Attendee Access Role
</label>
<select
id="eventRole"
style="width: 100%; padding: 0.5rem 0.75rem; background: var(--surface-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); color: var(--text-primary); font-size: 0.9rem;"
>
<option value="viewer">Viewer (Read-Only Demo)</option>
<option value="operator">Operator (Workshop Actions)</option>
<option value="user">Standard User</option>
</select>
</div>
</div>
{/* Seat Capacity Presets */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
Seat Capacity Limit
</label>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
<button
type="button"
class="pill-btn seat-pill"
onclick="selectSeats(this, 25)"
>
25 Seats
</button>
<button
type="button"
class="pill-btn seat-pill active"
onclick="selectSeats(this, 50)"
>
50 Seats
</button>
<button
type="button"
class="pill-btn seat-pill"
onclick="selectSeats(this, 100)"
>
100 Seats
</button>
<button
type="button"
class="pill-btn seat-pill"
onclick="selectSeats(this, 0)"
>
Unlimited
</button>
<input
type="number"
id="eventMaxSeats"
value="50"
min="0"
max="10000"
style="width: 100px; padding: 0.35rem 0.5rem; text-align: center;"
title="Max seats (0 = unlimited)"
/>
</div>
</div>
{/* Duration Presets */}
<div style="margin-bottom: 1.25rem;">
<label style="display: block; font-weight: 600; margin-bottom: 0.45rem; font-size: 0.875rem; color: var(--text-secondary);">
Event Duration & Expiration
</label>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button
type="button"
class="pill-btn event-lifespan-pill"
onclick="selectEventLifespan(this, 1)"
>
1 Hour
</button>
<button
type="button"
class="pill-btn event-lifespan-pill active"
onclick="selectEventLifespan(this, 3)"
>
🛠 3 Hours (Standard Workshop)
</button>
<button
type="button"
class="pill-btn event-lifespan-pill"
onclick="selectEventLifespan(this, 12)"
>
📅 12 Hours (All-Day Hackathon)
</button>
<button
type="button"
class="pill-btn event-lifespan-pill"
onclick="selectEventLifespan(this, 24)"
>
🗓 24 Hours
</button>
</div>
<input type="hidden" id="eventLifespanHours" value="3" />
</div>
{/* Custom Codes Accordion (Optional) */}
<details style="margin-bottom: 1.5rem; background: var(--surface-muted); padding: 0.75rem 1rem; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle);">
<summary style="font-size: 0.875rem; font-weight: 600; color: var(--text-primary); cursor: pointer;">
Custom Vanity Slug & Custom PIN Code (Optional)
</summary>
<div style="margin-top: 0.75rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem;">
<div>
<label style="display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.25rem;">
Vanity Slug (e.g. /e/deno-lab)
</label>
<input
type="text"
id="eventSlug"
placeholder="auto-generated if blank"
style="width: 100%; font-size: 0.85rem;"
/>
</div>
<div>
<label style="display: block; font-size: 0.8rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.25rem;">
Custom 6-Digit PIN (e.g. 749-123)
</label>
<input
type="text"
id="eventPinCode"
placeholder="auto-generated if blank"
style="width: 100%; font-size: 0.85rem;"
/>
</div>
</div>
</details>
<div style="display: flex; gap: 0.75rem;">
<button
type="submit"
id="submitEventBtn"
class="btn-primary"
style="min-height: 42px; background: var(--success); border-color: var(--success);"
>
🎟 Launch Workshop Pass
</button>
<button
type="button"
class="btn-outline"
onclick="closeDelegateDrawer()"
style="min-height: 42px;"
>
Cancel
</button>
</div>
</form>
{/* Event Launch Success Modal / Card */}
<div
id="eventHandoffModal"
style="display: none; margin-top: 1.5rem; padding: 1.25rem; background: var(--surface-card); border: 2px solid var(--success); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<span style="font-size: 1.3rem;">🎟</span>
<strong
id="createdEventTitle"
style="color: var(--text-primary); font-size: 1.1rem;"
>
Workshop Pass Live!
</strong>
</div>
<span class="badge badge-success">Active Now</span>
</div>
<p style="font-size: 0.85rem; color: var(--text-secondary); margin: 0 0 1rem 0;">
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.
</p>
<div style="display: flex; flex-direction: column; gap: 0.75rem; margin-bottom: 1.25rem;">
{/* PIN Code */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
6-Digit Universal PIN (for /join or Kiosk Entry)
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="eventHandoffPin"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 1.1rem; text-align: center; letter-spacing: 3px; font-weight: 700; color: var(--primary);"
>
</code>
<button
type="button"
class="btn-primary"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyEventHandoff('pin')"
>
Copy PIN
</button>
</div>
</div>
{/* 1-Click Vanity Link */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
Direct 1-Click Workshop Entrance URL
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="eventHandoffLink"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.85rem; overflow-x: auto; white-space: nowrap; color: var(--success);"
>
</code>
<button
type="button"
class="btn-outline"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyEventHandoff('link')"
>
Copy URL
</button>
</div>
</div>
{/* CLI 1-Liner */}
<div>
<label style="display: block; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.25rem;">
Terminal 1-Liner (Environment Injector)
</label>
<div style="display: flex; gap: 0.5rem;">
<code
id="eventHandoffCli"
style="flex: 1; padding: 0.5rem 0.75rem; background: var(--surface-muted); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: monospace; font-size: 0.8rem; overflow-x: auto; white-space: nowrap; color: var(--text-primary);"
>
</code>
<button
type="button"
class="btn-outline"
style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
onclick="copyEventHandoff('cli')"
>
Copy 1-Liner
</button>
</div>
</div>
</div>
<button
type="button"
class="btn-outline"
style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
onclick="closeEventHandoffModal()"
>
Done & Refresh Cockpit
</button>
</div>
</div>
);
};