import { MagnifyingGlassIcon } from "@heroicons/react/20/solid"; import { useEffect, useMemo, useState } from "react"; import { useTypedFetcher } from "remix-typedjson"; import { Input } from "~/components/primitives/Input"; import { Spinner } from "~/components/primitives/Spinner"; import { cn } from "~/utils/cn"; import { type WebhookProviderMeta, type WebhookSampleMeta, type loader as samplesLoader, } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.webhooks.samples"; export function SampleSourcePicker({ samplesPath, endpointSource, onLoad, }: { samplesPath: string; /** The endpoint's provider (e.g. "github"); pre-selected in the producer list. */ endpointSource?: string; onLoad: (body: string, headers: Record) => void; }) { const listFetcher = useTypedFetcher(); const bodyFetcher = useTypedFetcher(); const [selectedProvider, setSelectedProvider] = useState(null); const [producerQuery, setProducerQuery] = useState(""); const [topicQuery, setTopicQuery] = useState(""); useEffect(() => { if (listFetcher.state === "idle" && listFetcher.data === undefined) { listFetcher.load(samplesPath); } }, [listFetcher, samplesPath]); useEffect(() => { const data = bodyFetcher.data; if (data?.kind === "body") { onLoad(data.body, data.extraHeaders ?? {}); } }, [bodyFetcher.data, onLoad]); const manifest = listFetcher.data?.kind === "manifest" ? listFetcher.data : undefined; const providers = manifest?.providers; const samples = manifest?.samples; const listLoading = listFetcher.data === undefined; useEffect(() => { if (!providers || providers.length === 0) return; // oxlint-disable-next-line react/set-state-in-effect -- This effect intentionally synchronizes local state after an external or lifecycle change. setSelectedProvider((current) => { if (current && providers.some((p) => p.id === current)) return current; if (endpointSource && providers.some((p) => p.id === endpointSource)) return endpointSource; return null; }); }, [providers, endpointSource]); const filteredProviders = useMemo(() => { if (!providers) return []; const query = producerQuery.trim().toLowerCase(); if (!query) return providers; return providers.filter( (p) => p.label.toLowerCase().includes(query) || (p.category ?? "").includes(query) ); }, [providers, producerQuery]); const groupedProviders = useMemo(() => { const groups = new Map(); for (const provider of filteredProviders) { const key = provider.category ?? "other"; const group = groups.get(key) ?? []; group.push(provider); groups.set(key, group); } return [...groups.entries()]; }, [filteredProviders]); const events = (samples ?? []) .filter((item) => item.provider === selectedProvider) .filter((item) => { const query = topicQuery.trim().toLowerCase(); return !query || item.eventType.toLowerCase().includes(query); }); const [loadingEventType, setLoadingEventType] = useState(undefined); function selectEvent(item: WebhookSampleMeta) { setLoadingEventType(item.eventType); const query = new URLSearchParams({ provider: item.provider, eventType: item.eventType }); bodyFetcher.load(`${samplesPath}?${query.toString()}`); } return (
{listLoading ? (
) : filteredProviders.length === 0 ? (

No providers

) : ( groupedProviders.map(([category, group]) => (

{group[0]?.categoryLabel ?? category}

{group.map((provider) => ( ))}
)) )}
{!selectedProvider ? (

Select a producer to see its events.

) : events.length === 0 ? (

No matching events

) : ( events.map((item) => ( )) )}
Curated event payloads. Signed with this endpoint's config at send time.
); } function SearchInput({ placeholder, value, onChange, }: { placeholder: string; value: string; onChange: (value: string) => void; }) { return (
onChange(event.target.value)} />
); }