SPB Git forge

spb/websensor

Public
33commits 1branches 0releases
3.4 MBsize
maindefault branch
10 days agolast push
TypeScript 55.4% Python 43.2% SQL 1.2%
2.4 KB · 55 lines tsx
Raw Blame History
1"use client";23import { Check, Eye, Link2 } from "lucide-react";4import { useState } from "react";5import type { Watchlist } from "@/lib/api";6import { ownerFetch } from "@/lib/owner";78/** Adds an entity/source to the user's default watchlist (created on demand). */9export function WatchButton({ kind, value, label }: { kind: "entity" | "source" | "keyword" | "category"; value: string; label?: string }) {10  const [state, setState] = useState<"idle" | "busy" | "done" | "error">("idle");11  const add = async (): Promise<void> => {12    setState("busy");13    try {14      const { items } = await ownerFetch<{ items: Watchlist[] }>("/api/v1/watchlists");15      let wl = items[0];16      if (!wl) wl = await ownerFetch<Watchlist>("/api/v1/watchlists", { method: "POST", body: JSON.stringify({ name: "My watchlist", items: [] }) });17      const existing = (wl.items ?? []).map((i) => ({ kind: i.kind, value: i.value }));18      if (!existing.some((i) => i.kind === kind && i.value === value)) existing.push({ kind, value });19      await ownerFetch(`/api/v1/watchlists/${wl.id}`, { method: "PUT", body: JSON.stringify({ items: existing }) });20      setState("done");21    } catch {22      setState("error");23    }24  };25  return (26    <button type="button" onClick={add} disabled={state === "busy" || state === "done"} className="inline-flex items-center gap-1.5 rounded-md border border-line bg-panel px-2.5 py-1 text-[12px] hover:border-line-strong disabled:opacity-70">27      {state === "done" ? <Check className="size-3.5 text-signal" /> : <Eye className="size-3.5" />}28      {state === "done" ? "Watching" : state === "error" ? "Failed — retry" : label ?? "Watch"}29    </button>30  );31}3233export function ShareButton({ path }: { path: string }) {34  const [done, setDone] = useState(false);35  return (36    <button37      type="button"38      onClick={async () => {39        const url = `${window.location.origin}${path}`;40        try {41          await navigator.clipboard.writeText(url);42          setDone(true);43          setTimeout(() => setDone(false), 1500);44        } catch {45          window.prompt("Copy link", url);46        }47      }}48      className="inline-flex items-center gap-1.5 rounded-md border border-line bg-panel px-2.5 py-1 text-[12px] hover:border-line-strong"49    >50      {done ? <Check className="size-3.5 text-signal" /> : <Link2 className="size-3.5" />}51      {done ? "Copied" : "Share"}52    </button>53  );54}55