TypeScript 55.4%
Python 43.2%
SQL 1.2%
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