"use client"; /* eslint-disable @next/next/no-img-element */ import { useEffect, useState, type FormEvent } from "react"; import { QrCode, Radar, Save, ShieldCheck } from "lucide-react"; import { api, type SettingsOut } from "@/lib/api"; import { useAsync } from "@/lib/hooks"; import { useAuth } from "@/lib/auth"; import { useToast } from "@/lib/toast"; import { fmtInt, fmtTs, splitList } from "@/lib/format"; import { Banner, Card, Empty, ErrorState, Field, KV, PageHeader, StatusBadge } from "@/components/ui"; import { JsonView } from "@/components/JsonView"; import { CopyButton } from "@/components/CopyButton"; export default function SettingsPage() { const { me, isOwner, refresh } = useAuth(); const toast = useToast(); const settings = useAsync((s) => api.get("/admin/settings", undefined, s), [], { enabled: isOwner }); // TOTP const [setup, setSetup] = useState<{ otpauth_uri: string; qr_png_data_url: string; secret: string } | null>(null); const [code, setCode] = useState(""); const [busy, setBusy] = useState(false); async function startSetup() { setBusy(true); try { setSetup(await api.post("/auth/totp/setup")); } catch (e) { toast.error(e); } finally { setBusy(false); } } async function verify(e: FormEvent) { e.preventDefault(); setBusy(true); try { await api.post("/auth/totp/verify", { code: code.replace(/\s+/g, "") }); toast.success("Two-factor authentication enabled"); setSetup(null); setCode(""); await refresh(); } catch (err) { toast.error(err); } finally { setBusy(false); } } // Tracked tickers const [tickers, setTickers] = useState(""); const [tickersDirty, setTickersDirty] = useState(false); useEffect(() => { const row = settings.data?.settings.find((s) => s.key === "fundamentals_tracked_tickers"); if (row && !tickersDirty) setTickers(Array.isArray(row.value) ? (row.value as string[]).join("\n") : ""); }, [settings.data, tickersDirty]); async function saveTickers() { setBusy(true); try { const out = await api.put<{ tickers: string[] }>("/admin/settings/tracked-tickers", { tickers: splitList(tickers) }); toast.success("Tracked tickers saved", `${out.tickers.length} tickers`); setTickers(out.tickers.join("\n")); setTickersDirty(false); settings.reload(); } catch (e) { toast.error(e); } finally { setBusy(false); } } // Discovery const [disc, setDisc] = useState(null); async function discover() { setBusy(true); setDisc(null); try { setDisc(await api.post("/admin/discover")); toast.success("Discovery finished"); } catch (e) { toast.error(e); } finally { setBusy(false); } } const env = settings.data?.env; const otherSettings = (settings.data?.settings ?? []).filter((s) => s.key !== "fundamentals_tracked_tickers"); return ( <>
], ["Scopes",
{(me?.scopes ?? []).map((s) => {s})}
], ["Two-factor (TOTP)", me?.totp_enabled ? : ], ]} />
{!setup ? ( ) : (
TOTP enrolment QR code

Scan with your authenticator, or enter the secret manually:

{setup.secret}
setCode(e.target.value)} required />
)}
{!isOwner && Environment flags are visible to the owner only.} {settings.error && } {env && ( ], ["Lake path", {env.lake}], ["Public URL", {env.public_url}], ["Backup target", {env.backup_target}], ["FirstRate Data vendor", ], ["SEC EDGAR", ], ["AI providers", ], ]} /> )}

Secrets are never sent to the browser; only configured yes/no flags are shown.

{isOwner && ( {fmtInt(splitList(tickers).length)} tickers}>