1290 lines
58 KiB
Diff
1290 lines
58 KiB
Diff
diff --git a/ui/components/SessionsPage.tsx b/ui/components/SessionsPage.tsx
|
|
index de74c47..a4aaddb 100644
|
|
--- a/ui/components/SessionsPage.tsx
|
|
+++ b/ui/components/SessionsPage.tsx
|
|
@@ -145,15 +145,6 @@ export const SessionsPage = ({
|
|
</div>
|
|
|
|
<div style="display: flex; gap: 0.75rem; flex-wrap: wrap;">
|
|
- <button
|
|
- type="button"
|
|
- class="btn-outline"
|
|
- style="min-height: 42px; display: inline-flex; align-items: center; gap: 0.4rem;"
|
|
- onclick="openEventDrawer()"
|
|
- >
|
|
- <span style="font-size: 1.1rem;">🎟️</span>
|
|
- <span>+ Create Workshop Pass</span>
|
|
- </button>
|
|
<button
|
|
type="button"
|
|
class="btn-primary"
|
|
@@ -161,659 +152,664 @@ export const SessionsPage = ({
|
|
onclick="openDelegateDrawer()"
|
|
>
|
|
<span style="font-size: 1.1rem;">🔑</span>
|
|
- <span>+ Delegate 1:1 Pass / Session</span>
|
|
+ <span>+ Delegate Session</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
- {/* Create Multi-Claim Event Pass Drawer */}
|
|
+ {/* Delegate Session Drawer */}
|
|
<div
|
|
- id="eventDrawer"
|
|
+ id="delegateDrawer"
|
|
class="card"
|
|
- style="display: none; border-left: 4px solid var(--success); margin-bottom: 1.5rem;"
|
|
+ style="display: none; border-left: 4px solid var(--primary); margin-bottom: 1.5rem;"
|
|
>
|
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
|
|
- <div>
|
|
- <h3 style="margin: 0 0 0.25rem 0; color: var(--text-primary);">
|
|
- Create Multi-Claim Workshop / Event Pass
|
|
- </h3>
|
|
- <p style="color: var(--text-secondary); font-size: 0.9rem; margin: 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>
|
|
- </div>
|
|
- <button
|
|
- type="button"
|
|
- onclick="closeEventDrawer()"
|
|
- style="background: none; border: none; font-size: 1.3rem; color: var(--text-muted); cursor: pointer;"
|
|
- >
|
|
- ×
|
|
- </button>
|
|
- </div>
|
|
-
|
|
- <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>
|
|
+ <div style="width: 100%;">
|
|
+ <div style="display: flex; justify-content: space-between; align-items: center;">
|
|
+ <h3 style="margin: 0 0 0.25rem 0; color: var(--text-primary);">
|
|
+ Create New Pass / Session
|
|
+ </h3>
|
|
<button
|
|
type="button"
|
|
- class="pill-btn seat-pill"
|
|
- onclick="selectSeats(this, 100)"
|
|
+ onclick="closeDelegateDrawer()"
|
|
+ style="background: none; border: none; font-size: 1.3rem; color: var(--text-muted); cursor: pointer;"
|
|
>
|
|
- 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>
|
|
+ <div style="display: flex; background: var(--surface-muted); padding: 4px; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); gap: 4px; margin-top: 1rem; margin-bottom: 1rem;">
|
|
<button
|
|
type="button"
|
|
- class="pill-btn event-lifespan-pill active"
|
|
- onclick="selectEventLifespan(this, 3)"
|
|
+ id="tabBtnDirectPass"
|
|
+ class="delegate-tab-btn active"
|
|
+ onclick="switchDelegateTab('tabDirectPass')"
|
|
>
|
|
- 🛠️ 3 Hours (Standard Workshop)
|
|
+ 🔑 1:1 Direct Pass
|
|
</button>
|
|
<button
|
|
type="button"
|
|
- class="pill-btn event-lifespan-pill"
|
|
- onclick="selectEventLifespan(this, 12)"
|
|
+ id="tabBtnWorkshopPass"
|
|
+ class="delegate-tab-btn"
|
|
+ onclick="switchDelegateTab('tabWorkshopPass')"
|
|
>
|
|
- 📅 12 Hours (All-Day Hackathon)
|
|
- </button>
|
|
- <button
|
|
- type="button"
|
|
- class="pill-btn event-lifespan-pill"
|
|
- onclick="selectEventLifespan(this, 24)"
|
|
- >
|
|
- 🗓️ 24 Hours
|
|
+ 🎟️ Multi-Claim Workshop Pass
|
|
</button>
|
|
</div>
|
|
- <input type="hidden" id="eventLifespanHours" value="3" />
|
|
</div>
|
|
+ </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 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>
|
|
|
|
- <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="closeEventDrawer()"
|
|
- style="min-height: 42px;"
|
|
- >
|
|
- Cancel
|
|
- </button>
|
|
- </div>
|
|
- </form>
|
|
+ <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>
|
|
|
|
- {/* 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;"
|
|
+ {/* 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;"
|
|
>
|
|
- Workshop Pass Live!
|
|
- </strong>
|
|
+ <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>
|
|
- <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)
|
|
+ {/* 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;">
|
|
- <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);"
|
|
+ <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)"
|
|
>
|
|
- </code>
|
|
+ ⚡ 1 Hour (Quick Task)
|
|
+ </button>
|
|
<button
|
|
type="button"
|
|
- class="btn-primary"
|
|
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
|
|
- onclick="copyEventHandoff('pin')"
|
|
+ class="pill-btn lifespan-pill"
|
|
+ data-hours="12"
|
|
+ onclick="selectLifespan(this, 12)"
|
|
>
|
|
- Copy PIN
|
|
+ 🛠️ 12 Hours (Work Session)
|
|
</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);"
|
|
+ <button
|
|
+ type="button"
|
|
+ class="pill-btn lifespan-pill"
|
|
+ data-hours="24"
|
|
+ onclick="selectLifespan(this, 24)"
|
|
>
|
|
- </code>
|
|
+ 📅 24 Hours
|
|
+ </button>
|
|
<button
|
|
type="button"
|
|
- class="btn-outline"
|
|
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
|
|
- onclick="copyEventHandoff('link')"
|
|
+ class="pill-btn lifespan-pill"
|
|
+ data-hours="168"
|
|
+ onclick="selectLifespan(this, 168)"
|
|
>
|
|
- Copy URL
|
|
+ 🗓️ 7 Days
|
|
</button>
|
|
</div>
|
|
+ <input type="hidden" id="delegateHours" value="1" />
|
|
</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)
|
|
+ {/* 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;">
|
|
- <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);"
|
|
+ <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')"
|
|
>
|
|
- </code>
|
|
+ ⚡ 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="btn-outline"
|
|
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
|
|
- onclick="copyEventHandoff('cli')"
|
|
+ class="pill-btn mode-pill"
|
|
+ data-mode="custom"
|
|
+ onclick="selectMode(this, 'custom')"
|
|
>
|
|
- Copy 1-Liner
|
|
+ 🛠️ Custom Scopes
|
|
</button>
|
|
</div>
|
|
+ <input type="hidden" id="delegateMode" value="read_only" />
|
|
</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>
|
|
-
|
|
- {/* Delegate Session Drawer */}
|
|
- <div
|
|
- id="delegateDrawer"
|
|
- class="card"
|
|
- style="display: none; border-left: 4px solid var(--primary); margin-bottom: 1.5rem;"
|
|
- >
|
|
- <div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
|
|
- <div>
|
|
- <h3 style="margin: 0 0 0.25rem 0; color: var(--text-primary);">
|
|
- Delegate New 1:1 Pass / Session
|
|
- </h3>
|
|
- <p style="color: var(--text-secondary); font-size: 0.9rem; margin: 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>
|
|
- </div>
|
|
- <button
|
|
- type="button"
|
|
- onclick="closeDelegateDrawer()"
|
|
- style="background: none; border: none; font-size: 1.3rem; color: var(--text-muted); cursor: pointer;"
|
|
- >
|
|
- ×
|
|
- </button>
|
|
- </div>
|
|
-
|
|
- <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;"
|
|
+ {/* 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);"
|
|
>
|
|
- <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>
|
|
+ <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>
|
|
|
|
- {/* 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>
|
|
+ <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>
|
|
|
|
- {/* 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>
|
|
+ <div style="display: flex; gap: 0.75rem;">
|
|
<button
|
|
- type="button"
|
|
- class="pill-btn mode-pill"
|
|
- data-mode="operator"
|
|
- onclick="selectMode(this, 'operator')"
|
|
+ type="submit"
|
|
+ id="submitDelegateBtn"
|
|
+ class="btn-primary"
|
|
+ style="min-height: 42px;"
|
|
>
|
|
- ⚡ Operator (App Actions)
|
|
+ Mint & Delegate Session
|
|
</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')"
|
|
+ class="btn-outline"
|
|
+ onclick="closeDelegateDrawer()"
|
|
+ style="min-height: 42px;"
|
|
>
|
|
- 🛠️ Custom Scopes
|
|
+ Cancel
|
|
</button>
|
|
</div>
|
|
- <input type="hidden" id="delegateMode" value="read_only" />
|
|
- </div>
|
|
+ </form>
|
|
|
|
- {/* 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);"
|
|
+ {/* 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);"
|
|
>
|
|
- <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: 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>
|
|
|
|
- <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
|
|
+ <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>
|
|
- <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
|
|
+ <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>
|
|
- <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
|
|
+ <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>
|
|
- {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;"
|
|
+ <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);"
|
|
>
|
|
- <input
|
|
- type="checkbox"
|
|
- class="scope-checkbox"
|
|
- value={`app:${app.name}`}
|
|
- />
|
|
- {app.name} ({app.domain || "Internal"})
|
|
- </label>
|
|
- ))}
|
|
+ </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>
|
|
- </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;"
|
|
+ style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
|
|
+ onclick="closeHandoffModal()"
|
|
>
|
|
- Cancel
|
|
+ Done (Session is Active)
|
|
</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>
|
|
+ </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.
|
|
+ <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>
|
|
|
|
- <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)
|
|
+ <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>
|
|
- <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);"
|
|
+ <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;"
|
|
>
|
|
- </code>
|
|
- <button
|
|
- type="button"
|
|
- class="btn-primary"
|
|
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
|
|
- onclick="copyHandoff('magic')"
|
|
+ <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;"
|
|
>
|
|
- Copy Link
|
|
- </button>
|
|
+ <option value="viewer">Viewer (Read-Only Demo)</option>
|
|
+ <option value="operator">Operator (Workshop Actions)</option>
|
|
+ <option value="user">Standard User</option>
|
|
+ </select>
|
|
</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)
|
|
+ {/* 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;">
|
|
- <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);"
|
|
+ <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)"
|
|
>
|
|
- </code>
|
|
+ 100 Seats
|
|
+ </button>
|
|
<button
|
|
type="button"
|
|
- class="btn-outline"
|
|
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
|
|
- onclick="copyHandoff('cli')"
|
|
+ class="pill-btn seat-pill"
|
|
+ onclick="selectSeats(this, 0)"
|
|
>
|
|
- Copy CLI
|
|
+ ∞ 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>
|
|
|
|
- {/* 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
|
|
+ {/* 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;">
|
|
- <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);"
|
|
+ <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)"
|
|
>
|
|
- </code>
|
|
+ 🛠️ 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="btn-outline"
|
|
- style="min-height: 36px; padding: 0 0.85rem; font-size: 0.8rem;"
|
|
- onclick="copyHandoff('curl')"
|
|
+ class="pill-btn event-lifespan-pill"
|
|
+ onclick="selectEventLifespan(this, 24)"
|
|
>
|
|
- Copy Header
|
|
+ 🗓️ 24 Hours
|
|
</button>
|
|
</div>
|
|
+ <input type="hidden" id="eventLifespanHours" value="3" />
|
|
</div>
|
|
- </div>
|
|
|
|
- <button
|
|
- type="button"
|
|
- class="btn-outline"
|
|
- style="width: 100%; min-height: 38px; justify-content: center; font-size: 0.85rem;"
|
|
- onclick="closeHandoffModal()"
|
|
+ {/* 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);"
|
|
>
|
|
- Done (Session is Active)
|
|
- </button>
|
|
+ <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>
|
|
|
|
@@ -1178,6 +1174,22 @@ export const SessionsPage = ({
|
|
border-color: var(--primary);
|
|
color: var(--primary);
|
|
}
|
|
+ .delegate-tab-btn {
|
|
+ flex: 1;
|
|
+ padding: 0.5rem 0.75rem;
|
|
+ border-radius: var(--radius-sm);
|
|
+ border: none;
|
|
+ font-weight: 600;
|
|
+ cursor: pointer;
|
|
+ transition: all 0.15s;
|
|
+ background: transparent;
|
|
+ color: var(--text-secondary);
|
|
+ }
|
|
+ .delegate-tab-btn.active {
|
|
+ background: var(--surface-card);
|
|
+ color: var(--primary);
|
|
+ box-shadow: var(--shadow-xs);
|
|
+ }
|
|
`}
|
|
</style>
|
|
|
|
@@ -1197,8 +1209,6 @@ 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' });
|
|
}
|
|
@@ -1207,15 +1217,18 @@ 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 switchDelegateTab(tabId) {
|
|
+ document.getElementById('tabDirectPass').style.display = 'none';
|
|
+ document.getElementById('tabWorkshopPass').style.display = 'none';
|
|
+ document.getElementById('tabBtnDirectPass').classList.remove('active');
|
|
+ document.getElementById('tabBtnWorkshopPass').classList.remove('active');
|
|
|
|
- function closeEventDrawer() {
|
|
- document.getElementById('eventDrawer').style.display = 'none';
|
|
+ document.getElementById(tabId).style.display = 'block';
|
|
+ if (tabId === 'tabDirectPass') {
|
|
+ document.getElementById('tabBtnDirectPass').classList.add('active');
|
|
+ } else {
|
|
+ document.getElementById('tabBtnWorkshopPass').classList.add('active');
|
|
+ }
|
|
}
|
|
|
|
function selectSeats(btn, count) {
|