Single-binary self-hosted market watcher for stocks, ETFs, indexes, and futures: live charts, key stats, fundamentals, SEC filings, and SSE streaming.
axumdockerfinancerustself-hostedsqlitestocksvite
1// Dashboard watchlist editing (Phase C).
2//
3// Add and remove post to /api/watchlist; on success the page reloads so the
4// server re-renders the cards, re-registers the live stream tickers, and the
5// hero graph re-fetches with the new set. A brand-new symbol is validated and
6// backfilled server-side before it returns, so the reloaded page is complete.
7
8async function post(url, ticker) {
9 try {
10 const res = await fetch(url, {
11 method: "POST",
12 headers: { "Content-Type": "application/json" },
13 body: JSON.stringify({ ticker }),
14 });
15 return await res.json();
16 } catch {
17 return { ok: false, error: "Network error — try again." };
18 }
19}
20
21export function initWatchlist() {
22 const form = document.querySelector('[data-role="watch-add"]');
23 const msg = document.querySelector('[data-role="watch-msg"]');
24
25 function showMsg(text, isError) {
26 if (!msg) return;
27 msg.textContent = text;
28 msg.hidden = false;
29 msg.classList.toggle("is-error", !!isError);
30 }
31
32 if (form) {
33 form.addEventListener("submit", async (e) => {
34 e.preventDefault();
35 const input = form.querySelector('[name="ticker"]');
36 const ticker = (input.value || "").trim();
37 if (!ticker) return;
38 const btn = form.querySelector("button");
39 if (btn) btn.disabled = true;
40 showMsg("Adding " + ticker.toUpperCase() + "…", false);
41 const res = await post("/api/watchlist", ticker);
42 if (res.ok) {
43 location.reload();
44 } else {
45 if (btn) btn.disabled = false;
46 showMsg(res.error || "Could not add that symbol.", true);
47 }
48 });
49 }
50
51 document.querySelectorAll("[data-remove]").forEach((btn) => {
52 btn.addEventListener("click", async () => {
53 const ticker = btn.dataset.ticker;
54 if (!ticker) return;
55 btn.disabled = true;
56 const res = await post("/api/watchlist/remove", ticker);
57 if (res.ok) location.reload();
58 else btn.disabled = false;
59 });
60 });
61}