"use client"; import * as React from "react"; import { ChevronRight, History, MonitorSmartphone, Play, RotateCcw, Trash2 } from "lucide-react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { Input, Textarea } from "@/components/ui/input"; import { Field, Hint, Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import type { UrlCheck } from "@/lib/playground-url"; import { cn } from "@/lib/utils"; import { CountrySelect } from "./country-select"; import { KvRows } from "./kv-rows"; import { DEVICES, FORMATS, HTTP_METHODS, NETWORKS, NETWORK_LABELS, WAIT_UNTIL, inspectBody, methodHasBody, type BuilderState, type CountryOption, type HttpMethod, type NetworkClass, type OutputFormat, type PlanSummary, type RefererMode, type WaitUntil } from "./types"; const FORMAT_HINTS: Record = { html: "Body as returned by the target", text: "Readable text, tags removed", markdown: "Main content converted to Markdown", json: "Body plus parsed JSON", raw: "Untouched body (base64 for binary)", }; const WAIT_UNTIL_LABELS: Record = { domcontentloaded: "DOM content loaded (default)", load: "Load event", networkidle: "Network idle" }; export interface RequestBuilderProps { state: BuilderState; onChange: (patch: Partial) => void; plan: PlanSummary; availableNetworks: string[]; countries: CountryOption[]; urlCheck: UrlCheck; urlTouched: boolean; onUrlBlur: () => void; onRun: () => void; onReset: () => void; pending: boolean; recent: string[]; onPickRecent: (url: string) => void; onClearRecent: () => void; isMac: boolean; } function Section({ title, count, open, onToggle, children, hint }: { title: string; count?: number; open: boolean; onToggle: () => void; children: React.ReactNode; hint?: string }) { const id = React.useId(); return (
{open ? (
{children}
) : null}
); } export function RequestBuilder(p: RequestBuilderProps) { const { state: s, onChange, plan, availableNetworks, countries, urlCheck, urlTouched, onUrlBlur, onRun, onReset, pending, recent, onPickRecent, onClearRecent, isMac } = p; const [open, setOpen] = React.useState({ headers: s.headers.length > 0, query: s.query.length > 0, body: s.body.length > 0, cookies: s.cookies.length > 0, options: true, browser: s.browser }); const toggle = (k: keyof typeof open) => setOpen((o) => ({ ...o, [k]: !o[k] })); const bodyState = inspectBody(s.body); const showUrlMessage = urlTouched && urlCheck.level !== "ok"; const maxTimeoutS = Math.round(plan.max_timeout_ms / 1000); const filledHeaders = s.headers.filter((r) => r.key.trim()).length; const filledQuery = s.query.filter((r) => r.key.trim()).length; const filledCookies = s.cookies.filter((r) => r.key.trim()).length; return (
{ e.preventDefault(); onRun(); }} > {/* URL + method */}
{recent.length ? ( Recent URLs {recent.map((u) => ( onPickRecent(u)} className="font-mono text-[12px]"> {u} ))} Clear recent ) : null}
onChange({ url: e.target.value })} onBlur={onUrlBlur} aria-invalid={showUrlMessage && urlCheck.level === "error" ? true : undefined} aria-describedby={showUrlMessage ? "pg-url-msg" : undefined} className="h-11 font-mono text-[14px]" />
{showUrlMessage ? (

{urlCheck.message}

) : null}
{/* Sections */}
toggle("headers")}> onChange({ headers })} label="Header" keyPlaceholder="Header-Name" valuePlaceholder="value" addLabel="Add header" /> Sent to the target as-is. A realistic User-Agent is applied automatically when you don't set one.
toggle("query")}> onChange({ query })} label="Query parameter" keyPlaceholder="param" valuePlaceholder="value" addLabel="Add parameter" /> Merged into the URL when the request runs.
{methodHasBody(s.method) ? (
toggle("body")} hint={bodyState === "json" ? "Valid JSON" : bodyState === "text" ? "Plain text" : undefined}>