diff --git a/public/sessions-scripts.js b/public/sessions-scripts.js index b6a548a..0a0021e 100644 --- a/public/sessions-scripts.js +++ b/public/sessions-scripts.js @@ -5,30 +5,74 @@ function showNotice(msg, isError) { if (!banner) return; banner.textContent = msg; banner.style.display = "block"; + banner.style.position = "fixed"; + banner.style.top = "1.25rem"; + banner.style.right = "1.25rem"; + banner.style.zIndex = "99999"; + banner.style.minWidth = "280px"; + banner.style.maxWidth = "480px"; + banner.style.padding = "0.85rem 1.25rem"; + banner.style.borderRadius = "var(--radius-md)"; + banner.style.boxShadow = "0 10px 25px -5px rgba(0, 0, 0, 0.35)"; + banner.style.fontSize = "0.9rem"; + banner.style.fontWeight = "600"; banner.style.background = isError ? "var(--danger-bg)" : "var(--success-bg)"; banner.style.color = isError ? "var(--danger-text)" : "var(--success-text)"; banner.style.border = isError ? "1px solid var(--danger-border)" : "1px solid var(--success-border)"; - setTimeout(() => { + if (banner._timeout) clearTimeout(banner._timeout); + banner._timeout = setTimeout(() => { banner.style.display = "none"; - }, 5000); + }, 4000); } globalThis.showNotice = showNotice; +function setTabLock(locked) { + const btnDirect = document.getElementById("tabBtnDirectPass"); + const btnWorkshop = document.getElementById("tabBtnWorkshopPass"); + if (btnDirect) { + btnDirect.disabled = locked; + btnDirect.style.pointerEvents = locked ? "none" : "auto"; + btnDirect.style.opacity = locked ? "0.5" : "1"; + } + if (btnWorkshop) { + btnWorkshop.disabled = locked; + btnWorkshop.style.pointerEvents = locked ? "none" : "auto"; + btnWorkshop.style.opacity = locked ? "0.5" : "1"; + } +} + +function resetDirectPassForm() { + const form = document.getElementById("delegateForm"); + if (form) form.reset(); + const createEl = document.getElementById("singleSessionCreateState"); + if (createEl) createEl.style.display = "block"; + const handoffEl = document.getElementById("handoffModal"); + if (handoffEl) handoffEl.style.display = "none"; + setTabLock(false); +} +globalThis.resetDirectPassForm = resetDirectPassForm; + +function resetWorkshopForm() { + const form = document.getElementById("eventForm"); + if (form) form.reset(); + const createEl = document.getElementById("eventCreateState"); + if (createEl) createEl.style.display = "block"; + const handoffEl = document.getElementById("eventHandoffState"); + if (handoffEl) handoffEl.style.display = "none"; + setTabLock(false); +} +globalThis.resetWorkshopForm = resetWorkshopForm; + function openDelegateDrawer() { const drawer = document.getElementById("delegateDrawer"); if (!drawer) return; drawer.style.display = "block"; drawer.scrollIntoView({ behavior: "smooth" }); - const eventCreateState = document.getElementById("eventCreateState"); - if (eventCreateState) eventCreateState.style.display = "block"; - const eventHandoffState = document.getElementById("eventHandoffState"); - if (eventHandoffState) eventHandoffState.style.display = "none"; - const eventForm = document.getElementById("eventForm"); - if (eventForm) eventForm.reset(); - + resetDirectPassForm(); + resetWorkshopForm(); switchDelegateTab("tabDirectPass"); } globalThis.openDelegateDrawer = openDelegateDrawer; @@ -36,6 +80,8 @@ globalThis.openDelegateDrawer = openDelegateDrawer; function closeDelegateDrawer() { const drawer = document.getElementById("delegateDrawer"); if (drawer) drawer.style.display = "none"; + resetDirectPassForm(); + resetWorkshopForm(); } globalThis.closeDelegateDrawer = closeDelegateDrawer; @@ -132,7 +178,7 @@ async function handleDelegateSession(e) { const hoursInput = document.getElementById("delegateHours"); const lifespanHours = parseInt(hoursInput ? hoursInput.value : "1", 10) || 1; const modeInput = document.getElementById("delegateMode"); - let mode = modeInput ? modeInput.value : "read_only"; + let mode = modeInput ? modeInput.value : "read"; const targetAppInput = document.getElementById("delegateTargetApp"); const targetApp = targetAppInput ? targetAppInput.value : ""; @@ -179,8 +225,13 @@ async function handleDelegateSession(e) { if (curlElem) { curlElem.textContent = '-H "Authorization: Bearer ' + data.token + '"'; } + + const createEl = document.getElementById("singleSessionCreateState"); + if (createEl) createEl.style.display = "none"; const handoffModal = document.getElementById("handoffModal"); if (handoffModal) handoffModal.style.display = "block"; + + setTabLock(true); showNotice("Delegated session created for: " + data.label, false); } else { showNotice(data.error || "Failed to delegate session", true); @@ -255,6 +306,7 @@ async function handleCreateEvent(e) { const handoffState = document.getElementById("eventHandoffState"); if (handoffState) handoffState.style.display = "block"; + setTabLock(true); showNotice("Workshop pass created: " + ev.name, false); } else { showNotice(data.error || "Failed to create event pass", true); @@ -319,8 +371,9 @@ function copyHandoff(type) { globalThis.copyHandoff = copyHandoff; function closeHandoffModal() { - const modal = document.getElementById("handoffModal"); - if (modal) modal.style.display = "none"; + resetDirectPassForm(); + const drawer = document.getElementById("delegateDrawer"); + if (drawer) drawer.style.display = "none"; if (globalThis.location) globalThis.location.reload(); } globalThis.closeHandoffModal = closeHandoffModal; @@ -466,12 +519,12 @@ async function rotatePin(eventId) { } } - showNotice("Event credentials rotated successfully", false); + showNotice("PIN rotated successfully", false); } else { - showNotice(data.error || "Failed to rotate credentials", true); + showNotice(data.error || "Failed to rotate PIN", true); } } catch (_err) { - showNotice("Network error rotating credentials", true); + showNotice("Network error rotating PIN", true); } } globalThis.rotatePin = rotatePin; @@ -885,6 +938,31 @@ function bindEventCockpitActions() { const data = await res.json().catch(() => ({})); if (res.ok && data.success) { showNotice("Event extended by 1 hour!", false); + + const drawer = document.getElementById("attendeesDrawer"); + if (drawer && drawer.classList.contains("open")) { + const countdownElem = document.getElementById( + "guestDrawerCountdown", + ); + if (countdownElem && data.event && data.event.expires_at) { + countdownElem.setAttribute( + "data-expires-at", + data.event.expires_at, + ); + const expDate = new Date(data.event.expires_at); + const expiresAtElem = document.getElementById( + "guestDrawerExpiresAt", + ); + if (expiresAtElem) { + expiresAtElem.textContent = expDate.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }); + } + updateAllCountdowns(); + } + } + setTimeout(() => { if (globalThis.location) globalThis.location.reload(); }, 600); @@ -927,12 +1005,12 @@ function bindEventCockpitActions() { const data = await res.json().catch(() => ({})); showNotice(data.error || "Failed to end event", true); e.currentTarget.disabled = false; - e.currentTarget.textContent = "๐Ÿ”ด End & Revoke All"; + e.currentTarget.textContent = "End Event"; } } catch (_err) { showNotice("Network error ending event", true); e.currentTarget.disabled = false; - e.currentTarget.textContent = "๐Ÿ”ด End & Revoke All"; + e.currentTarget.textContent = "End Event"; } }); }); diff --git a/src/features/events/cockpit_fragments.tsx b/src/features/events/cockpit_fragments.tsx index 52ee711..7c3f2b0 100644 --- a/src/features/events/cockpit_fragments.tsx +++ b/src/features/events/cockpit_fragments.tsx @@ -5,6 +5,14 @@ export const EventCockpitDeckFragment = ( ) => { 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 (
@@ -36,7 +44,7 @@ export const EventCockpitDeckFragment = (
- {eventPasses.map((event) => { + {activePasses.map((event) => { const expDate = new Date(event.expires_at); const timeString = expDate.toLocaleTimeString([], { hour: "2-digit", @@ -192,7 +200,6 @@ export const EventCockpitDeckFragment = ( class="btn-outline" aria-label={`Manage attendees for ${event.name}`} style="justify-content: center; min-height: 38px; font-size: 0.85rem;" - data-on-click={`@get("/api/events/${event.id}/attendees")`} onclick={`openAttendeesDrawer('${event.id}', '${ event.name.replace(/'/g, "\\'") }')`} @@ -204,7 +211,6 @@ export const EventCockpitDeckFragment = ( class="btn-outline" aria-label={`Add 5 seats to ${event.name}`} style="justify-content: center; min-height: 38px; font-size: 0.85rem;" - data-on-click={`@post("/api/events/${event.id}/expand")`} onclick={`expandSeats('${event.id}', 5)`} > +5 Seats @@ -214,17 +220,15 @@ export const EventCockpitDeckFragment = (
+ + {/* Collapsible Expired Events Section */} + {expiredPasses.length > 0 && ( +
+ + ๐Ÿ“ Expired Events ({expiredPasses.length}) + +
+ {expiredPasses.map((event) => ( +
+
+
+ + {event.name} + + Expired +
+
+ Expired {new Date(event.expires_at).toLocaleDateString()} ยท + {" "} + {event.seats_claimed} claimed seats +
+
+
+ + +
+
+ ))} +
+
+ )} +
); diff --git a/src/features/events/drawer_fragments.tsx b/src/features/events/drawer_fragments.tsx index 7320cd5..bdfb741 100644 --- a/src/features/events/drawer_fragments.tsx +++ b/src/features/events/drawer_fragments.tsx @@ -1,4 +1,8 @@ -export const WorkshopPassDrawerFragment = ({ apps }: { apps: any[] }) => { +export const WorkshopPassDrawerFragment = ({ + apps = [], +}: { + apps?: any[]; +}) => { return (