# `PhoenixKitWeb.Components.Core.IntegrationsUI`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.13.9/lib/phoenix_kit_web/components/core/integrations_ui.ex#L1)

Shared UI for the integration setup screens — the provider picker, the
provider/status header card, a single setup field, and the collapsible
setup instructions, plus the small display helpers they need
(status badge, relative timestamps, inline markdown).

Extracted so the website-wide integrations pages
(`Live.Settings.Integrations` / `IntegrationForm`) and the personal
pages (`Live.Integrations.MyIntegrations` / `MyIntegrationForm`) render
the *same* markup instead of drifting apart. The personal screens use it
today; the website screens carry their own equivalents pending a
convergence pass.

# `format_relative`

```elixir
@spec format_relative(String.t() | nil) :: String.t() | nil
```

Humanizes a UTC ISO8601 timestamp into a relative phrase like
"2 hours ago" / "3 days ago" / "Apr 28 2026". `nil`/`""` → `nil`; falls
back to the raw string on parse failure.

# `integration_status_badge`

```elixir
@spec integration_status_badge(String.t() | nil) :: {String.t(), String.t()}
```

Maps a connection status string to `{daisyUI badge class, label}`.

# `provider_picker`

Provider picker — a card grid, one card per provider. Clicking a card
pushes `event` (default `"select_provider"`) with `phx-value-provider`.

## Attributes

* `providers` (`:list`) (required)
* `event` (`:string`) - Defaults to `"select_provider"`.

# `provider_status_card`

Provider info / status header card. No badge is shown until the
connection has actually been saved (`name` set) — a fresh `/new` flow has
nothing to report yet.

## Attributes

* `provider` (`:map`) (required)
* `data` (`:map`) (required)
* `name` (`:string`) - Defaults to `nil`.

# `render_markdown_inline`

```elixir
@spec render_markdown_inline(String.t(), map() | keyword()) :: String.t()
```

Simple inline markdown: `**bold**`, `[links](url)`, `` `code` `` and `{variables}`.

# `setup_field`

A single provider setup field, rendered from the field's `:type`.

Credential-shaped fields stay `type="text"` on purpose — they're API keys /
tokens, not site logins, and `type="password"` would trigger browsers'
password-save heuristics. `:select`, `:textarea` and `:number` render as
themselves; anything else falls back to a text input, so a provider that
declares a type this component has never heard of still gets a usable field
rather than a blank spot in the form.

A `:select` with no stored value falls to the first option, so a provider
should list its default first (that is how the SMTP `security` / `auth` /
`verify_cert` fields keep their historical behavior on connections created
before they existed).

`:password` fields never echo a saved secret back into the markup (D011 —
it used to round-trip in plain text on every `Edit` render). Resolution is
three-way, in priority order:

  1. `typed_value` present — the operator just typed this (a dry-run Test
     re-render, `/new` flow) — show it verbatim, unmasked.
  2. No typed value, `saved_value` present, and the field is `:password` —
     show an empty input with the same "already configured" placeholder
     `Authorization` uses for its OAuth secrets (S009, `oauth_secret_placeholder/2`
     in `authorization.ex`) — same class of leak, same wording, so an
     operator doesn't see two phrasings of "a secret is already saved" in
     the same admin. An empty submit for an untouched password field is
     already read as "keep the existing credential" on the save side
     (`extract_setup_attrs/2` and its mirror in `MyIntegrationForm`), so
     this empty render is exactly what that save path expects.
  3. Otherwise — plain fields, or a `:password` field with nothing saved
     yet — show `saved_value` as before.

## Attributes

* `field` (`:map`) (required)
* `typed_value` (`:string`) - Defaults to `""`.
* `saved_value` (`:string`) - Defaults to `""`.

# `setup_instructions`

Collapsible provider setup instructions (from the provider definition).
`open` defaults closed; callers pass `open={@name == nil}` to keep it open
during first-time setup.

## Attributes

* `provider` (`:map`) (required)
* `redirect_uri` (`:string`) - Defaults to `nil`.
* `open` (`:boolean`) - Defaults to `false`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
