Spaces:
No application file
No application file
| export {}; | |
| interface SubmitDownloadResponse { | |
| job?: { id: string }; | |
| error?: string; | |
| } | |
| const form = document.getElementById("submitForm") as HTMLFormElement | null; | |
| const titleInput = document.getElementById("titleInput") as HTMLInputElement | null; | |
| const urlInput = document.getElementById("urlInput") as HTMLInputElement | null; | |
| const errorEl = document.getElementById("formError") as HTMLElement | null; | |
| function showError(message: string): void { | |
| if (!errorEl) return; | |
| errorEl.textContent = message; | |
| errorEl.hidden = false; | |
| } | |
| function clearError(): void { | |
| if (!errorEl) return; | |
| errorEl.hidden = true; | |
| errorEl.textContent = ""; | |
| } | |
| form?.addEventListener("submit", async (e: Event) => { | |
| e.preventDefault(); | |
| clearError(); | |
| const title = titleInput?.value.trim() || ""; | |
| const url = urlInput?.value.trim() || ""; | |
| if (!title || !url) { | |
| showError("Both a title and a source link are needed."); | |
| return; | |
| } | |
| const submitBtn = form.querySelector<HTMLButtonElement>("button[type=submit]"); | |
| if (submitBtn) submitBtn.disabled = true; | |
| try { | |
| const res = await fetch("/api/downloads", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ title, url }), | |
| }); | |
| const data = (await res.json()) as SubmitDownloadResponse; | |
| if (!res.ok) { | |
| showError(data.error || "The booth couldn't take that link."); | |
| return; | |
| } | |
| form.reset(); | |
| // job-feed.ts listens on /api/events and will render the new job as soon | |
| // as the server emits it — no need to duplicate that rendering here. | |
| } catch { | |
| showError("Couldn't reach the booth. Check your connection and try again."); | |
| } finally { | |
| if (submitBtn) submitBtn.disabled = false; | |
| } | |
| }); | |