From c221e32855ebfc38c0e918bf605fed91bb891f77 Mon Sep 17 00:00:00 2001
From: Tyler Gillispie
Date: Thu, 27 Aug 2026 23:05:18 -0700
Subject: [PATCH] fix(join): wire handleJoin and handleSplashJoin in public
scripts and join fragments to fix 400 error on /join
---
public/sessions-scripts.js | 113 +++++++++++++++++++++++
src/features/events/events.test.ts | 2 +-
src/features/events/join_fragments.tsx | 4 +-
src/features/events/splash_fragments.tsx | 27 +++---
4 files changed, 133 insertions(+), 13 deletions(-)
diff --git a/public/sessions-scripts.js b/public/sessions-scripts.js
index 13f94f0..f13f7ee 100644
--- a/public/sessions-scripts.js
+++ b/public/sessions-scripts.js
@@ -1076,3 +1076,116 @@ document.addEventListener("keydown", (e) => {
closeAttendeesDrawer();
}
});
+
+async function handleJoin(e) {
+ if (e && e.preventDefault) e.preventDefault();
+ const input = document.getElementById("eventCode");
+ const code = input ? input.value.trim() : "";
+ if (!code) return;
+
+ const btn = document.getElementById("joinBtn");
+ const notice = document.getElementById("joinNotice");
+ if (btn) {
+ btn.disabled = true;
+ btn.textContent = "Claiming Seat...";
+ }
+ if (notice) notice.style.display = "none";
+
+ try {
+ const res = await fetch("/api/join", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ },
+ body: JSON.stringify({ code }),
+ });
+ const data = await res.json();
+ if (res.ok && data.success) {
+ showNotice("Seat claimed! Redirecting...", false);
+ globalThis.location.href = data.redirectUrl || "/dashboard";
+ } else {
+ if (notice) {
+ notice.textContent = data.error ||
+ "Invalid event code or workshop capacity reached";
+ notice.style.display = "block";
+ notice.style.background = "var(--danger-bg)";
+ notice.style.color = "var(--danger-text)";
+ notice.style.border = "1px solid var(--danger-border)";
+ }
+ showNotice(data.error || "Invalid event code", true);
+ if (btn) {
+ btn.disabled = false;
+ btn.textContent = "⚡ Enter Workshop";
+ }
+ }
+ } catch (_err) {
+ if (notice) {
+ notice.textContent = "Network error connecting to event";
+ notice.style.display = "block";
+ notice.style.background = "var(--danger-bg)";
+ notice.style.color = "var(--danger-text)";
+ notice.style.border = "1px solid var(--danger-border)";
+ }
+ showNotice("Network error connecting to event", true);
+ if (btn) {
+ btn.disabled = false;
+ btn.textContent = "⚡ Enter Workshop";
+ }
+ }
+}
+globalThis.handleJoin = handleJoin;
+
+async function handleSplashJoin(slug) {
+ const btn = document.getElementById("joinSplashBtn");
+ const notice = document.getElementById("splashNotice");
+ if (btn) {
+ btn.disabled = true;
+ btn.textContent = "Claiming Seat...";
+ }
+ if (notice) notice.style.display = "none";
+
+ try {
+ const res = await fetch("/api/join", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Accept": "application/json",
+ },
+ body: JSON.stringify({ code: slug }),
+ });
+ const data = await res.json();
+ if (res.ok && data.success) {
+ showNotice("Seat claimed! Redirecting...", false);
+ globalThis.location.href = data.redirectUrl || "/dashboard";
+ } else {
+ if (notice) {
+ notice.textContent = data.error ||
+ "Invalid event code or workshop is full";
+ notice.style.display = "block";
+ notice.style.background = "var(--danger-bg)";
+ notice.style.color = "var(--danger-text)";
+ notice.style.border = "1px solid var(--danger-border)";
+ }
+ showNotice(data.error || "Failed to enter workshop", true);
+ if (btn) {
+ btn.disabled = false;
+ btn.textContent = "⚡ Enter Workshop & Claim Seat";
+ }
+ }
+ } catch (_err) {
+ if (notice) {
+ notice.textContent = "Network error connecting to event";
+ notice.style.display = "block";
+ notice.style.background = "var(--danger-bg)";
+ notice.style.color = "var(--danger-text)";
+ notice.style.border = "1px solid var(--danger-border)";
+ }
+ showNotice("Network error connecting to event", true);
+ if (btn) {
+ btn.disabled = false;
+ btn.textContent = "⚡ Enter Workshop & Claim Seat";
+ }
+ }
+}
+globalThis.handleSplashJoin = handleSplashJoin;
diff --git a/src/features/events/events.test.ts b/src/features/events/events.test.ts
index 8515459..a02019c 100644
--- a/src/features/events/events.test.ts
+++ b/src/features/events/events.test.ts
@@ -10,7 +10,7 @@ Deno.test("[Events] GET /join returns EventJoinPage HTML", async () => {
assertEquals(res.status, 200);
const html = await res.text();
assertStringIncludes(html, "Join Event or Workshop");
- assertStringIncludes(html, 'data-on-submit="@post('/api/join')"');
+ assertStringIncludes(html, 'onsubmit="handleJoin(event)"');
});
Deno.test("[Events] POST /api/join without code returns HTML error fragment", async () => {
diff --git a/src/features/events/join_fragments.tsx b/src/features/events/join_fragments.tsx
index 933c39e..b0deb63 100644
--- a/src/features/events/join_fragments.tsx
+++ b/src/features/events/join_fragments.tsx
@@ -32,7 +32,7 @@ export const EventJoinPageFragment = () => {
-
+
)
: (