Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ body {
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent: 240 4.8% 88.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
Expand Down
5 changes: 2 additions & 3 deletions app/hook/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { AppSidebar } from "@/components/app-sidebar";
import CopyButton from "@/components/copy-button";
import RequestMethodBadge from "@/components/request-method-badge";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import {
Table,
Expand Down Expand Up @@ -74,9 +75,7 @@ export default function Page({ params }: { params: Promise<{ id: string }> }) {
<TableBody>
<TableRow>
<TableCell className="w-1/3">
<span className="bg-primary text-primary-foreground p-1 rounded-md text-xs">
{selected.method}
</span>
<RequestMethodBadge method={selected.method} />
</TableCell>
<TableCell>{selected.path}</TableCell>
</TableRow>
Expand Down
13 changes: 12 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,19 @@ import { useRouter } from "next/navigation";
export default function Home() {
const router = useRouter();

const getOrCreateId = () => {
const storedId = localStorage.getItem("id");
if (storedId) {
return storedId;
}
const newId = Math.random().toString(36).substring(2, 10);
localStorage.setItem("id", newId);
return newId;
};

useEffect(() => {
router.push(`/hook/${Math.random().toString(36).substring(2, 10)}`);
const id = getOrCreateId();
router.push(`/hook/${id}`);
}, [router]);

return null;
Expand Down
250 changes: 139 additions & 111 deletions components/app-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,126 +3,154 @@
import { useEffect, useState } from "react";

import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarHeader,
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarHeader,
} from "@/components/ui/sidebar";
import { formatTimestamp } from "@/lib/datetime";
import { LoaderCircle } from "lucide-react";
import { FaGithub } from "react-icons/fa";
import { FaGithub, FaTrash } from "react-icons/fa";
import Link from "next/link";
import RequestMethodBadge from "./request-method-badge";

export function AppSidebar({
hookId,
onSelected,
selected,
...props
hookId,
onSelected,
selected,
...props
}: React.ComponentProps<typeof Sidebar> & {
hookId: string;
selected?: WebRequest;
onSelected: (request: WebRequest) => void;
hookId: string;
selected?: WebRequest;
onSelected: (request: WebRequest) => void;
}) {
const [requests, setRequests] = useState<WebRequest[]>([]);
const [totalRequests, setTotalRequests] = useState(0);
const [requests, setRequests] = useState<WebRequest[]>([]);
const [totalRequests, setTotalRequests] = useState(0);

useEffect(() => {
fetch(`/api/hook/${hookId}`, {
headers: {
"Content-Type": "text/event-stream",
},
}).then((res) => {
const reader = res.body?.getReader();
if (!reader) return;
reader.read().then(function processText({ done, value }) {
if (done) {
return;
}
const text = new TextDecoder().decode(value);
const lines = text.split("\n").filter((line) => line.trim());
lines.forEach((line) => {
const req: WebRequest = JSON.parse(line);
setTotalRequests(req.serial);
setRequests((prev) => {
const newRequests = [req, ...prev];
return newRequests.slice(0, 100);
});
});
reader.read().then(processText);
});
});
}, [hookId]);
useEffect(() => {
const storedRequests = localStorage.getItem(`requests_${hookId}`);
const parsedRequests = JSON.parse(storedRequests || "[]") as WebRequest[];
if (!!parsedRequests?.length) {
setRequests(parsedRequests);
setTotalRequests(parsedRequests.length);
}

useEffect(() => {
if (!selected && requests.length > 0) {
onSelected(requests[0]);
}
}, [selected, requests]);
fetch(`/api/hook/${hookId}`, {
headers: {
"Content-Type": "text/event-stream",
},
}).then((res) => {
const reader = res.body?.getReader();
if (!reader) return;
reader.read().then(function processText({ done, value }) {
if (done) {
return;
}
const text = new TextDecoder().decode(value);
const lines = text.split("\n").filter((line) => line.trim());
lines.forEach((line) => {
const req: WebRequest = JSON.parse(line);
setTotalRequests((prev) => {
return prev + 1;
});
setRequests((prev) => {
const newRequests = [req, ...prev];
return newRequests.slice(0, 100);
});
});
reader.read().then(processText);
});
});
}, [hookId]);

return (
<Sidebar
collapsible="icon"
className="overflow-hidden [&>[data-sidebar=sidebar]]:flex-row"
{...props}
>
<Sidebar collapsible="none" className="hidden flex-1 md:flex">
<SidebarHeader className="gap-3.5 p-4">
<div className="flex w-full items-center justify-between">
<Link href="/">
<div className="text-base font-semibold text-foreground tracking-normal">
<span className="bg-primary text-primary-foreground px-1 py-0.5 mr-[1px] font-extralight">
W
</span>
irehook
</div>
</Link>
<Link target="_blank" href="https://github.com/runabol/wirehook">
<FaGithub className="w-5 h-5" />
</Link>
</div>
{/* <SidebarInput placeholder="Search..." /> */}
<span className="text-xs font-semibold mt-4 pb-2 border-b">
REQUESTS ({totalRequests})
</span>
</SidebarHeader>
<SidebarContent>
<SidebarGroup className="px-0">
<SidebarGroupContent>
{requests.length === 0 && (
<div className="flex w-full items-center justify-center gap-2 my-4">
<LoaderCircle className="w-4 h-4 animate-spin" /> Waiting for
your first request
</div>
)}
{requests.map((req) => (
<div
key={req.id}
onClick={() => onSelected(req)}
className={`flex hover:cursor-pointer flex-col items-start gap-2 whitespace-nowrap border-b p-4 text-sm leading-tight last:border-b-0 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground ${
selected?.id === req.id
? "bg-sidebar-accent text-sidebar-accent-foreground"
: ""
}`}
>
<div className="flex w-full items-center gap-2">
<span className="bg-primary text-primary-foreground p-1 rounded-md text-xs">
{req.method}
</span>
<span className="border-b p-1 text-sm">
{req.path.substring(0, 30)}
{req.path.length > 30 ? "..." : ""}
</span>
</div>
<span className="text-xs">
{formatTimestamp(req.timestamp)}
</span>
</div>
))}
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</Sidebar>
);
useEffect(() => {
localStorage.setItem(`requests_${hookId}`, JSON.stringify(requests));
}, [hookId, requests]);

useEffect(() => {
if (!selected && requests.length > 0) {
onSelected(requests[0]);
}
}, [selected, requests]);

return (
<Sidebar
collapsible="icon"
className="overflow-hidden [&>[data-sidebar=sidebar]]:flex-row"
{...props}
>
<Sidebar collapsible="none" className="hidden flex-1 md:flex">
<SidebarHeader className="gap-3.5 p-4">
<div className="flex w-full items-center justify-between">
<Link href="/">
<div className="text-base font-semibold text-foreground tracking-normal">
<span className="bg-primary text-primary-foreground px-1 py-0.5 mr-[1px] font-extralight">
W
</span>
irehook
</div>
</Link>
<Link target="_blank" href="https://github.com/runabol/wirehook">
<FaGithub className="w-5 h-5" />
</Link>
</div>
{/* <SidebarInput placeholder="Search..." /> */}
<span className="text-xs font-semibold mt-4 pb-2 border-b">
REQUESTS ({totalRequests})
</span>
</SidebarHeader>
<SidebarContent>
<SidebarGroup className="px-0">
<SidebarGroupContent>
{requests.length === 0 && (
<div className="flex w-full items-center justify-center gap-2 my-4">
<LoaderCircle className="w-4 h-4 animate-spin" /> Waiting for
your first request
</div>
)}
{requests.map((req) => (
<div
key={req.id}
onClick={() => onSelected(req)}
className={`group/request flex hover:cursor-pointer flex-col items-start gap-2 whitespace-nowrap border-b p-4 text-sm leading-tight last:border-b-0 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground ${
selected?.id === req.id
? "bg-sidebar-accent text-sidebar-accent-foreground"
: ""
}`}
>
<div className="flex w-full items-center gap-2">
<RequestMethodBadge method={req.method} />
<span className="border-b p-1 text-sm">
{req.path.substring(0, 30)}
{req.path.length > 30 ? "..." : ""}
</span>
</div>
<div className="flex justify-between w-full items-center">
<span className="text-xs align-right">
{formatTimestamp(req.timestamp)}
</span>
<span>
<button
onClick={(e) => {
e.stopPropagation();
setRequests((prev) =>
prev.filter((r) => r.id !== req.id)
);
setTotalRequests((prev) => prev - 1);
}}
className="text-xs opacity-0 group-hover/request:opacity-100 hover:text-red-500 transition-opacity"
>
<FaTrash className="w-3 h-3" />
</button>
</span>
</div>
</div>
))}
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
</Sidebar>
);
}
31 changes: 31 additions & 0 deletions components/request-method-badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

export default function RequestMethodBadge({ method }: { method: string }) {
return (
<span
className={`inline-flex p-1 rounded-md text-xs ${
method === "GET"
? "bg-green-500 text-white"
: method === "POST"
? "bg-yellow-500 text-white"
: method === "PUT"
? "bg-blue-500 text-white"
: method === "DELETE"
? "bg-red-600 text-white"
: method === "PATCH"
? "bg-purple-500 text-white"
: method === "HEAD"
? "bg-green-700 text-white"
: method === "OPTIONS"
? "bg-pink-500 text-white"
: method === "CONNECT"
? "bg-orange-500 text-white"
: method === "TRACE"
? "bg-indigo-600 text-white"
: "bg-gray-500 text-white"
}`}
>
<span>{method}</span>
</span>
);
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"eslint": "^9",
"eslint-config-next": "15.1.2",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"tailwindcss": "^3.4.17",
"typescript": "^5"
}
}