189 lines
8.7 KiB
TypeScript
189 lines
8.7 KiB
TypeScript
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 Passes
|
|
</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: 45px; 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;"
|
|
id={`pin-${event.id}`}
|
|
>
|
|
{event.pin_code}
|
|
</code>
|
|
<button
|
|
type="button"
|
|
class="btn-outline"
|
|
aria-label={`Copy PIN code for ${event.name}`}
|
|
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 28px;"
|
|
onclick={`copyText(document.getElementById('pin-${event.id}').textContent.trim())`}
|
|
>
|
|
Copy
|
|
</button>
|
|
</div>
|
|
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
|
<span style="font-size: 0.8rem; font-weight: 600; width: 45px; 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"
|
|
aria-label={`Copy direct link for ${event.name}`}
|
|
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 style="display: flex; align-items: center; gap: 0.5rem;">
|
|
<span style="font-size: 0.8rem; font-weight: 600; width: 45px; color: var(--text-muted);">
|
|
CLI:
|
|
</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;">
|
|
curl -sSL {Deno.env.get("RP_ID")
|
|
? `https://${Deno.env.get("RP_ID")}`
|
|
: ""}/join/{event.slug}?format=env | source /dev/stdin
|
|
</code>
|
|
<button
|
|
type="button"
|
|
class="btn-outline"
|
|
aria-label={`Copy CLI command for ${event.name}`}
|
|
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 28px;"
|
|
onclick={`copyText("curl -sSL " + window.location.origin + "/join/${event.slug}?format=env | source /dev/stdin")`}
|
|
>
|
|
Copy
|
|
</button>
|
|
</div>
|
|
|
|
<details style="font-size: 0.75rem; margin-top: 0.1rem;">
|
|
<summary style="cursor: pointer; color: var(--primary); font-weight: 600; user-select: none;">
|
|
▸ Expand full CLI command
|
|
</summary>
|
|
<textarea
|
|
readonly
|
|
rows={2}
|
|
style="width: 100%; margin-top: 0.4rem; padding: 0.4rem 0.5rem; font-family: monospace; font-size: 0.75rem; background: var(--surface-muted); color: var(--text-primary); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); resize: vertical; box-sizing: border-box;"
|
|
onclick="this.select()"
|
|
>
|
|
{`curl -sSL ${
|
|
Deno.env.get("RP_ID")
|
|
? `https://${Deno.env.get("RP_ID")}`
|
|
: ""
|
|
}/join/${event.slug}?format=env | source /dev/stdin`}
|
|
</textarea>
|
|
</details>
|
|
</div>
|
|
|
|
{/* Cockpit Actions */}
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 0.5rem;">
|
|
<button
|
|
type="button"
|
|
class="btn-outline"
|
|
aria-label={`Manage attendees for ${event.name}`}
|
|
style="justify-content: center; min-height: 38px; font-size: 0.85rem;"
|
|
onclick={`openAttendeesDrawer('${event.id}', '${
|
|
event.name.replace(/'/g, "\\'")
|
|
}')`}
|
|
>
|
|
👥 Manage Attendees ({event.seats_claimed})
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn-outline"
|
|
aria-label={`Add 5 seats to ${event.name}`}
|
|
style="justify-content: center; min-height: 38px; font-size: 0.85rem;"
|
|
onclick={`expandSeats('${event.id}', 5)`}
|
|
>
|
|
+5 Seats
|
|
</button>
|
|
</div>
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.5rem;">
|
|
<button
|
|
type="button"
|
|
class="btn-outline"
|
|
aria-label={`Rotate PIN code for ${event.name}`}
|
|
style="justify-content: center; min-height: 38px; font-size: 0.82rem; padding: 0 0.25rem;"
|
|
onclick={`rotatePin('${event.id}')`}
|
|
>
|
|
🔄 Rotate PIN
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn-outline extend-event-btn"
|
|
data-event-id={event.id}
|
|
aria-label={`Extend lifespan by 1 hour for ${event.name}`}
|
|
style="justify-content: center; min-height: 38px; font-size: 0.82rem; padding: 0 0.25rem;"
|
|
>
|
|
+1h Extend
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn-danger end-event-btn"
|
|
data-event-id={event.id}
|
|
aria-label={`End event ${event.name} and revoke all attendees`}
|
|
style="justify-content: center; min-height: 38px; font-size: 0.82rem; padding: 0 0.25rem;"
|
|
>
|
|
End Event
|
|
</button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|