Elle yazılmış tek-dosya CSS (sabit koyu tema, ham table/button, window.confirm ile silme onayı) yerine tutarlı bir bileşen sistemi: Card/Table/Badge/Button/ Input/Switch/AlertDialog primitifleri, Hanken Grotesk + JetBrains Mono tipografi çifti, "broadcast/monitör" temalı özel bir renk paleti (camgöbeği accent, ok/warn/err/live durum rengi sözlüğü). Tüm sayfalar (dashboard, kanal detay, segmentler, ayarlar, istatistik, kullanıcılar, login) aynı oturumda taşındı — silme onayları artık gerçek AlertDialog, native confirm() değil. Veri akışı/server action'lar değişmedi, tamamen görsel katman. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
43 lines
1.2 KiB
TypeScript
43 lines
1.2 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
export function LiveLogViewer({ sessionId }: { sessionId: string }) {
|
|
const [lines, setLines] = useState<string[]>([]);
|
|
const boxRef = useRef<HTMLPreElement>(null);
|
|
|
|
useEffect(() => {
|
|
let cancelled = false;
|
|
|
|
async function poll() {
|
|
try {
|
|
const res = await fetch(`/api/debug/capture/${sessionId}`, { cache: "no-store" });
|
|
const data = (await res.json()) as { lines: string[] };
|
|
if (!cancelled) setLines(data.lines);
|
|
} catch {
|
|
// transient fetch failures are fine to just skip — next tick retries
|
|
}
|
|
}
|
|
|
|
poll();
|
|
const timer = setInterval(poll, 4000);
|
|
return () => {
|
|
cancelled = true;
|
|
clearInterval(timer);
|
|
};
|
|
}, [sessionId]);
|
|
|
|
useEffect(() => {
|
|
if (boxRef.current) boxRef.current.scrollTop = boxRef.current.scrollHeight;
|
|
}, [lines]);
|
|
|
|
return (
|
|
<pre
|
|
ref={boxRef}
|
|
className="font-mono-num mt-3 max-h-56 overflow-y-auto rounded-md border border-border bg-background/60 p-3 text-[0.7rem] leading-relaxed whitespace-pre-wrap text-muted-foreground"
|
|
>
|
|
{lines.length > 0 ? lines.join("\n") : "Log bekleniyor…"}
|
|
</pre>
|
|
);
|
|
}
|