Spaces:
Runtime error
Runtime error
File size: 5,260 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | "use client";
import { useState } from "react";
import { EventChecklist } from "../shared/EventChecklist";
import { PayloadPreview } from "../shared/PayloadPreview";
interface Step3Props {
webhookId?: string;
events: string[];
enabled: boolean;
description: string;
onChangeEvents: (events: string[]) => void;
onChangeEnabled: (enabled: boolean) => void;
onChangeDescription: (desc: string) => void;
t: (key: string, opts?: Record<string, unknown>) => string;
}
interface TestResult {
delivered: boolean;
status: number;
latencyMs: number;
payloadSent: Record<string, unknown> | null;
responseBody: string;
error?: string | null;
}
export function Step3EventsAndTest({
webhookId,
events,
enabled,
description,
onChangeEvents,
onChangeEnabled,
onChangeDescription,
t,
}: Step3Props) {
const [testState, setTestState] = useState<"idle" | "sending" | "ok" | "fail">("idle");
const [testResult, setTestResult] = useState<TestResult | null>(null);
const sendTest = async () => {
if (!webhookId) return;
setTestState("sending");
setTestResult(null);
try {
const res = await fetch(`/api/webhooks/${webhookId}/test`, { method: "POST" });
const data: TestResult & { error?: string } = await res.json().catch(() => ({}));
if (!res.ok || data.delivered === false) {
throw new Error(data.error || t("testFailed"));
}
setTestResult(data);
setTestState("ok");
} catch (err) {
setTestState("fail");
setTestResult((prev) => ({
delivered: false,
status: 0,
latencyMs: 0,
payloadSent: prev?.payloadSent ?? null,
responseBody: prev?.responseBody ?? "",
error: err instanceof Error ? err.message : t("testFailed"),
}));
}
};
const parseResponseBody = (raw: string): Record<string, unknown> | null => {
try {
return JSON.parse(raw) as Record<string, unknown>;
} catch {
return raw ? { raw } : null;
}
};
return (
<div className="space-y-5">
<div>
<label className="text-xs font-medium uppercase tracking-wider text-text-muted">
{t("name")}
</label>
<input
value={description}
onChange={(e) => onChangeDescription(e.target.value)}
placeholder={t("namePlaceholder")}
className="mt-1 w-full rounded-lg border border-border bg-surface px-3 py-2 text-sm text-text-main focus:outline-none focus:ring-2 focus:ring-primary/40"
/>
</div>
<div>
<label className="mb-2 block text-xs font-medium uppercase tracking-wider text-text-muted">
{t("events")}
</label>
<EventChecklist
selected={events}
onChange={onChangeEvents}
allEventsLabel={t("allEvents")}
/>
</div>
<label className="flex items-center gap-3 rounded-lg border border-border p-3">
<input
type="checkbox"
checked={enabled}
onChange={(e) => onChangeEnabled(e.target.checked)}
className="size-4 accent-primary"
/>
<span>
<span className="block text-sm font-medium text-text-main">{t("enabled")}</span>
<span className="block text-xs text-text-muted">{t("enabledDesc")}</span>
</span>
</label>
{webhookId && (
<div className="rounded-lg border border-border p-4 space-y-3">
<p className="text-sm font-medium text-text-main">{t("testWebhook")}</p>
<button
type="button"
onClick={() => void sendTest()}
disabled={testState === "sending"}
className="inline-flex items-center gap-2 rounded-lg border border-border px-4 py-2 text-sm font-medium text-text-main transition-colors hover:bg-sidebar disabled:opacity-40"
>
<span
className={`material-symbols-outlined text-[18px] ${
testState === "sending" ? "animate-spin" : ""
}`}
>
{testState === "sending" ? "sync" : "send"}
</span>
{t("testWebhook")}
</button>
{testState === "ok" && testResult && (
<div className="space-y-3">
<p className="text-xs font-medium text-emerald-500">
✅ {testResult.status} · {testResult.latencyMs}ms · {t("testSuccess")}
</p>
{testResult.payloadSent && (
<PayloadPreview payload={testResult.payloadSent} label={t("testPayloadSent")} />
)}
{testResult.responseBody && (
<PayloadPreview
payload={parseResponseBody(testResult.responseBody)}
label={t("testResponse")}
/>
)}
</div>
)}
{testState === "fail" && testResult && (
<div className="space-y-3">
<p className="text-xs text-red-500">{testResult.error ?? t("testFailed")}</p>
{testResult.payloadSent && (
<PayloadPreview payload={testResult.payloadSent} label={t("testPayloadSent")} />
)}
</div>
)}
</div>
)}
</div>
);
}
|