The portal timeline is now available
Webhook Dashboards, Ready to Paste
The Repost portal's surfaces as blocks: delivery history, endpoint management, the dead-letter queue and the event catalog. Wired to your data, owned by you. Open Source.
Files
"use client";
import { RepostPortalProvider, type PortalClient, useEndpoints, useEventTypes, useSendSampleEvent } from "@repost/portal-react";
import { useMemo, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { EventCatalogBrowser, type CatalogEventType } from "@/components/event-catalog-browser";
export type EventCatalogProps = {
/** Return a portal access token minted by your backend. */
getToken?: () => Promise<string>;
/**
* A pre-built client, instead of `getToken` — used by previews and tests to
* run the block against fixtures.
*/
client?: PortalClient;
};
/**
* The event catalog, wired: browse the environment's event types with
* schema-derived samples and send signed test events to the customer's
* first endpoint. Hooks at the top, props down.
*/
export function EventCatalog({ getToken, client }: EventCatalogProps) {
return (
<RepostPortalProvider {...(client ? { client } : { getToken: getToken! })}>
<EventCatalogContent />
</RepostPortalProvider>
);
}
function EventCatalogContent() {
const [sendingEventTypeId, setSendingEventTypeId] = useState<string | null>(null);
const [lastOutcome, setLastOutcome] = useState<string | null>(null);
const eventTypes = useEventTypes();
const endpoints = useEndpoints();
const sendSample = useSendSampleEvent();
const targetEndpoint = useMemo(() => (endpoints.data ?? []).find((endpoint) => endpoint.status === "ENABLED"), [endpoints.data]);
const handleSendSample = (eventType: CatalogEventType, samplePayload: unknown) => {
if (!targetEndpoint) {
setLastOutcome("No enabled endpoint to send to — create one first.");
return;
}
setSendingEventTypeId(eventType.id);
sendSample
.mutateAsync({
eventTypeId: eventType.id,
destination: { kind: "endpoint", endpointId: targetEndpoint.id },
data: samplePayload,
})
.then((result) =>
setLastOutcome(result.delivered ? `Delivered to ${targetEndpoint.url} (${result.status})` : `Failed: ${result.error ?? result.status}`),
)
.catch((error: Error) => setLastOutcome(`Failed: ${error.message}`))
.finally(() => setSendingEventTypeId(null));
};
return (
<div className="flex flex-col gap-3">
<header className="flex items-center gap-2">
<h2 className="text-lg font-semibold">Event catalog</h2>
{lastOutcome && (
<Badge variant={lastOutcome.startsWith("Delivered") ? "success" : "warning"} size="sm">
{lastOutcome}
</Badge>
)}
</header>
<EventCatalogBrowser
eventTypes={eventTypes.data ?? []}
isLoading={eventTypes.isLoading}
onSendSample={handleSendSample}
sendingEventTypeId={sendingEventTypeId}
/>
</div>
);
}
Event types with sample payloads and signed test sends.
event-catalog

