Bare surfaces

Embed one portal area at a time (endpoints, event catalog, logs, or the dead-letter queue) with no portal header and no tabs, inside your own pages.

Pass surface and the frame renders exactly one portal area inside your own page, with no portal header and no portal tabs. Your navigation takes the place of the portal's tabs: one page per area, each reading as your app's own content. This is for teams that want their own page structure with none of the UI work: their layout, our surfaces, iframe isolation.

import { RepostPortal } from "@repost/portal-react";
 
<RepostPortal url={portalUrl} getPortalToken={getToken} surface="logs" />

The four surfaces

surfaceWhat the customer gets
"endpoints"The portal's endpoint area: list, detail and create, including subscriptions and signing secrets.
"event-catalog"The event catalog: every subscribable event type, with schemas and sample payloads.
"logs"The delivery log: every attempt, newest first, streaming live, with the delivery detail drawer and replay.
"dead-letter-queue"The dead letter queue: deliveries that exhausted their retries, with replay.

Everything the full embed does, a surface does: the token flow over the private port, theming without reloads, and drawers and dialogs that cover your viewport rather than being clipped to the frame. The session comes from the same portal-access call; the props are the same component's props.

One page per area

The intended shape is one surface per page, with your own navigation linking them:

/webhooks/endpoints     <RepostPortal surface="endpoints" ... />
/webhooks/catalog       <RepostPortal surface="event-catalog" ... />
/webhooks/logs          <RepostPortal surface="logs" ... />
/webhooks/dlq           <RepostPortal surface="dead-letter-queue" ... />

The pages differ by nothing but the surface name. Mint one session per customer server-side and share it across the pages: sessions outlive a customer's hop between them, so there is no need to pay a platform round-trip per navigation.

Several surfaces can also share one page. Each is its own frame with its own session, and at most one drawer is open across all of them.

Surfaces stay put

A bare frame never renders another surface. It moves freely inside its own: surface="endpoints" still goes from the list to an endpoint and to the create form. But a navigation that would leave it is suppressed rather than performed. The customer stays where they are, and the frame tells you where it was headed:

<RepostPortal
  url={portalUrl}
  getPortalToken={getToken}
  surface="logs"
  onNavigationIntent={(intent) => router.push(`/webhooks/${intent.surface}`)}
/>

intent.surface is one of the four names, so it maps onto your own routes directly. intent.path is a portal path and stays opaque. Nothing in the portal links out of a surface today, so the callback is optional: the lock is a guarantee about what a frame can render, not a workflow you have to handle.

Swapping surface on a mounted component reloads the frame, because it is a different page. A theme change does not.

Outside React

createPortalEmbed in @repost/portal-js takes the same surface and onNavigationIntent options, so Vue, Svelte and vanilla hosts get the same behavior.

Older portal versions

A portal deployment that predates surfaces renders the requested area with its chrome, functional, just not bare. Portal deploys precede SDK releases, so in practice a released SDK never meets such a portal.

Continue