"use client"; import { useEffect, useRef, useState } from "react"; import { LuX, LuTerminal, LuSend } from "react-icons/lu"; export interface LogEntry { id: number; method: string; path: string; url: string; status: number | null; duration: number | null; timestamp: string; response: string | null; } interface DevConsoleProps { open: boolean; width: number; isMobile: boolean; onClose: () => void; onWidthChange: (w: number) => void; logs: LogEntry[]; } function tryPretty(text: string): string { try { return JSON.stringify(JSON.parse(text), null, 2); } catch { return text; } } function methodBg(method: string): string { switch (method) { case "GET": return "#2563eb"; case "POST": return "#16a34a"; case "PUT": return "#d97706"; case "PATCH": return "#7c3aed"; case "DELETE": return "#dc2626"; default: return "#6b7280"; } } export default function DevConsole({ open, width, isMobile, onClose, onWidthChange, logs, }: DevConsoleProps) { const [activeTab, setActiveTab] = useState<"logs" | "request">("logs"); const [expandedId, setExpandedId] = useState(null); const [reqMethod, setReqMethod] = useState("GET"); const [reqUrl, setReqUrl] = useState("/api/power"); const [reqBody, setReqBody] = useState(""); const [reqResponse, setReqResponse] = useState<{ status: number; body: string } | null>(null); const [reqLoading, setReqLoading] = useState(false); const dragRef = useRef<{ startX: number; startWidth: number } | null>(null); useEffect(() => { const onMove = (e: MouseEvent) => { if (!dragRef.current) return; const delta = dragRef.current.startX - e.clientX; const next = Math.max(300, Math.min(900, dragRef.current.startWidth + delta)); onWidthChange(next); }; const onUp = () => { if (!dragRef.current) return; dragRef.current = null; document.body.style.userSelect = ""; document.body.style.cursor = ""; }; document.addEventListener("mousemove", onMove); document.addEventListener("mouseup", onUp); return () => { document.removeEventListener("mousemove", onMove); document.removeEventListener("mouseup", onUp); }; }, [onWidthChange]); async function sendRequest() { setReqLoading(true); setReqResponse(null); try { const isAbsolute = reqUrl.startsWith("http://") || reqUrl.startsWith("https://"); let fetchUrl: string; let fetchInit: RequestInit; if (isAbsolute) { fetchUrl = "/api/dev/proxy"; fetchInit = { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ method: reqMethod, url: reqUrl, body: reqBody || undefined }), }; } else { fetchUrl = reqUrl; fetchInit = { method: reqMethod, headers: reqBody ? { "Content-Type": "application/json" } : {}, body: ["POST", "PUT", "PATCH"].includes(reqMethod) && reqBody ? reqBody : undefined, }; } const res = await fetch(fetchUrl, fetchInit); const text = await res.text(); setReqResponse({ status: res.status, body: tryPretty(text) }); } catch (e) { setReqResponse({ status: 0, body: String(e) }); } finally { setReqLoading(false); } } const panelWidth = isMobile ? "100%" : `${width}px`; const panelLeft = isMobile ? "0" : "auto"; return (
{/* Drag handle — desktop only */} {!isMobile && (
{ e.preventDefault(); dragRef.current = { startX: e.clientX, startWidth: width }; document.body.style.userSelect = "none"; document.body.style.cursor = "col-resize"; }} /> )} {/* Header */}
Dev Console
{(["logs", "request"] as const).map((tab) => ( ))}
{/* Logs tab */} {activeTab === "logs" && (
{logs.length === 0 ? (
No requests captured yet
) : ( [...logs].reverse().map((entry) => ( setExpandedId(expandedId === entry.id ? null : entry.id)} /> )) )}
)} {/* Request tab */} {activeTab === "request" && (
setReqUrl(e.target.value)} onKeyDown={(e) => e.key === "Enter" && sendRequest()} placeholder="/api/power" style={{ flex: 1, background: "#f9fafb", border: "1px solid #e5e7eb", borderRadius: "8px", color: "#111827", fontSize: "0.68rem", padding: "5px 8px", fontFamily: "ui-monospace, monospace", outline: "none", minWidth: 0, }} />
{["POST", "PUT", "PATCH"].includes(reqMethod) && (