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.
Summary
Functions
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.
Maps a connection status string to {daisyUI badge class, label}.
Provider picker — a card grid, one card per provider. Clicking a card
pushes event (default "select_provider") with phx-value-provider.
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.
Simple inline markdown: **bold**, [links](url), `code` and {variables}.
A single provider setup field, rendered from the field's :type.
Collapsible provider setup instructions (from the provider definition).
open defaults closed; callers pass open={@name == nil} to keep it open
during first-time setup.
Functions
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.
Maps a connection status string to {daisyUI badge class, label}.
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 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 tonil.
Simple inline markdown: **bold**, [links](url), `code` and {variables}.
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:
typed_valuepresent — the operator just typed this (a dry-run Test re-render,/newflow) — show it verbatim, unmasked.- No typed value,
saved_valuepresent, and the field is:password— show an empty input with the same "already configured" placeholderAuthorizationuses for its OAuth secrets (S009,oauth_secret_placeholder/2inauthorization.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/2and its mirror inMyIntegrationForm), so this empty render is exactly what that save path expects. - Otherwise — plain fields, or a
:passwordfield with nothing saved yet — showsaved_valueas before.
Attributes
field(:map) (required)typed_value(:string) - Defaults to"".saved_value(:string) - Defaults to"".
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 tonil.open(:boolean) - Defaults tofalse.