"use client"; import { Bookmark as BookmarkIcon, Plus, Trash2 } from "lucide-react"; import Link from "next/link"; import { useCallback, useEffect, useState } from "react"; import { EventRow } from "@/components/event-row"; import { Chip, Empty, Panel, SkeletonRows } from "@/components/ui"; import type { Bookmark } from "@/lib/api"; import { relTime, SAVED_VIEWS, utcDateTime } from "@/lib/format"; import { ownerFetch } from "@/lib/owner"; interface SavedView { id: string; name: string; query: string; created_at?: string; } function normalizeQuery(q: string): string { const s = q.trim().replace(/^\/?live/, "").replace(/^\?/, ""); return s; } export function Bookmarks() { const [items, setItems] = useState(null); const [views, setViews] = useState(null); const [err, setErr] = useState(null); const [viewErr, setViewErr] = useState(null); const [form, setForm] = useState({ name: "", query: "" }); const load = useCallback( () => ownerFetch<{ items: Bookmark[] }>("/api/v1/bookmarks?limit=200") .then((r) => setItems(r.items)) .catch((e: Error) => { setErr(e.message); setItems([]); }), [], ); const loadViews = useCallback( () => ownerFetch<{ items: SavedView[] }>("/api/v1/views") .then((r) => setViews(r.items)) .catch((e: Error) => { setViewErr(e.message); setViews([]); }), [], ); useEffect(() => { void load(); void loadViews(); }, [load, loadViews]); const remove = async (id: string): Promise => { setItems((prev) => prev?.filter((b) => b.id !== id) ?? prev); try { await ownerFetch(`/api/v1/bookmarks/${encodeURIComponent(id)}`, { method: "DELETE" }); } catch (e) { setErr((e as Error).message); await load(); } }; const createView = async (): Promise => { const query = normalizeQuery(form.query); if (!query) { setViewErr("Paste a live-feed query string, e.g. group=security&signal_min=70"); return; } setViewErr(null); try { await ownerFetch("/api/v1/views", { method: "POST", body: JSON.stringify({ name: form.name.trim() || "My view", query }) }); setForm({ name: "", query: "" }); await loadViews(); } catch (e) { setViewErr((e as Error).message); } }; const removeView = async (id: string): Promise => { setViews((prev) => prev?.filter((v) => v.id !== id) ?? prev); try { await ownerFetch(`/api/v1/views/${encodeURIComponent(id)}`, { method: "DELETE" }); } catch (e) { setViewErr((e as Error).message); await loadViews(); } }; return (
Saved events {items?.length ?? "…"}} dense> {items === null ? ( ) : items.length ? (
    {items.map((b) => (
  • {relTime(b.bookmarked_at)}
  • ))}
) : ( }> No saved events yet — use the bookmark icon in the intelligence panel.
Open the live feed →
)} {err &&

{err}

}
); }