auth-yes/src/features/events/cockpit_fragments.tsx

360 lines
16 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { EVENT_COCKPIT_CSS } from "./cockpit_styles.ts";
export const EventCockpitDeckFragment = (
{ eventPasses }: { eventPasses: any[] },
) => {
if (!eventPasses || eventPasses.length === 0) return null;
const now = Date.now();
const activePasses = eventPasses.filter((e) =>
!e.expires_at || new Date(e.expires_at).getTime() > now
);
const expiredPasses = eventPasses.filter((e) =>
e.expires_at && new Date(e.expires_at).getTime() <= now
);
return (
<div style="margin-bottom: 2rem;">
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-subtle); padding-bottom: 0.5rem; margin-bottom: 1rem;">
<h2 style="font-size: 1.25rem; font-weight: 700; margin: 0; color: var(--text-primary);">
Events
</h2>
<div style="display: flex; background: var(--surface-muted); padding: 2px; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); gap: 2px;">
<button
type="button"
id="viewModeGrid"
class="view-mode-btn active"
onclick="setEventViewMode('grid')"
aria-label="Grid View"
aria-pressed="true"
>
🗂 Grid
</button>
<button
type="button"
id="viewModeCompact"
class="view-mode-btn"
onclick="setEventViewMode('compact')"
aria-label="Compact View"
aria-pressed="false"
>
📋 Compact
</button>
</div>
</div>
<div id="eventDeckContainer" class="grid-view">
{activePasses.map((event) => {
const expDate = new Date(event.expires_at);
const timeString = expDate.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
});
return (
<div
key={event.id}
class="card event-card"
style="border-left: 4px solid var(--primary); margin: 0;"
>
<div class="event-card-header">
<div>
<h3 style="margin: 0 0 0.25rem 0; font-size: 1.1rem; color: var(--text-primary);">
{event.name}
</h3>
<div
class="countdown-pill"
data-expires-at={event.expires_at}
>
0h 0m left · (Expires {timeString})
</div>
</div>
<span class="badge badge-success status-badge">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
class="event-card-snippets"
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;"
data-ignore
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: 45px; color: var(--text-muted);">
Link:
</span>
<code
id={`link-code-${event.id}`}
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"
id={`link-btn-${event.id}`}
class="btn-outline"
aria-label={`Copy direct link for ${event.name}`}
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 28px;"
data-ignore
onclick={`copyText(window.location.origin + '/e/${event.slug}')`}
>
Copy
</button>
</div>
<details
class="cli-details"
style="margin-top: 0.1rem; width: 100%;"
>
<summary style="display: flex; align-items: center; gap: 0.5rem; cursor: pointer; user-select: none;">
<span style="font-size: 0.8rem; font-weight: 600; width: 45px; color: var(--text-muted);">
CLI:
</span>
<code
id={`cli-code-${event.id}`}
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; cursor: pointer;"
>
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"
id={`cli-btn-${event.id}`}
class="btn-outline"
aria-label={`Copy CLI command for ${event.name}`}
style="padding: 0.25rem 0.5rem; font-size: 0.75rem; min-height: 28px;"
data-ignore
onclick={`event.stopPropagation(); copyText("curl -sSL " + window.location.origin + "/join/${event.slug}?format=env | source /dev/stdin")`}
>
Copy
</button>
<span style="font-size: 0.75rem; padding-right: 0.25rem;">
[ ]
</span>
</summary>
<textarea
id={`cli-textarea-${event.id}`}
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 class="event-card-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 Guests ({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 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>
{/* Compact Actions */}
<div class="event-card-compact-row-2">
<div class="compact-pills">
<span
class="compact-pill"
data-ignore
onclick={`copyText('${event.pin_code}')`}
>
PIN:{" "}
<span id={`compact-pin-${event.id}`}>{event.pin_code}</span>
{" "}
(Copy)
</span>
<span
id={`compact-link-${event.id}`}
class="compact-pill"
data-ignore
onclick={`copyText(window.location.origin + '/e/${event.slug}')`}
>
Link (Copy)
</span>
<span
id={`compact-cli-${event.id}`}
class="compact-pill"
data-ignore
onclick={`copyText("curl -sSL " + window.location.origin + "/join/${event.slug}?format=env | source /dev/stdin")`}
>
CLI (Copy)
</span>
</div>
<div class="compact-actions-right">
<button
type="button"
class="btn-outline"
style="padding: 0.25rem 0.5rem; font-size: 0.8rem; min-height: 28px;"
onclick={`openAttendeesDrawer('${event.id}', '${
event.name.replace(/'/g, "\\'")
}')`}
>
👥 Guests ({event.seats_claimed})
</button>
<button
type="button"
class="btn-outline extend-event-btn"
data-event-id={event.id}
style="padding: 0.25rem 0.5rem; font-size: 0.8rem; min-height: 28px;"
>
🔄 +1h
</button>
</div>
</div>
</div>
);
})}
</div>
{/* Collapsible Expired Events Section */}
{expiredPasses.length > 0 && (
<details style="margin-top: 1.5rem; border: 1px solid var(--border-subtle); border-radius: var(--radius-md); padding: 0.75rem 1rem; background: var(--surface-muted);">
<summary style="font-weight: 600; font-size: 0.95rem; color: var(--text-secondary); cursor: pointer;">
📁 Expired Events ({expiredPasses.length})
</summary>
<div style="margin-top: 1rem; display: flex; flex-direction: column; gap: 0.75rem;">
{expiredPasses.map((event) => (
<div
key={event.id}
class="card"
style="padding: 0.75rem 1rem; margin: 0; display: flex; justify-content: space-between; align-items: center; opacity: 0.85; border-left: 3px solid var(--text-muted); flex-wrap: wrap; gap: 0.75rem;"
>
<div>
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem;">
<strong style="color: var(--text-primary); font-size: 0.95rem;">
{event.name}
</strong>
<span class="badge badge-secondary">Expired</span>
</div>
<div style="font-size: 0.8rem; color: var(--text-secondary);">
Expired {new Date(event.expires_at).toLocaleDateString()} ·
{" "}
{event.seats_claimed} claimed seats
</div>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<button
type="button"
class="btn-outline extend-event-btn"
data-event-id={event.id}
style="padding: 0.3rem 0.65rem; font-size: 0.8rem; min-height: 32px;"
>
🔄 +1h Reopen
</button>
<button
type="button"
class="btn-danger end-event-btn"
data-event-id={event.id}
style="padding: 0.3rem 0.65rem; font-size: 0.8rem; min-height: 32px;"
>
End Event
</button>
</div>
</div>
))}
</div>
</details>
)}
<style>{EVENT_COCKPIT_CSS}</style>
</div>
);
};