Skip to content
Tier

Environment connect

Pro

Connect an environment: sign in with its provider (OAuth), or enter the credentials it needs, then test the connection and see what the test found. The header names the environment in its own colour, as environment-band draws it, warns when it's production, and says whether it's connected now. Open it from environment-band's onReconnect. Secret fields never show a saved value back; left blank on an existing connection, they keep it. With requireTest, Connect stays locked, saying why, until a test with the current values has passed.

When the other side has to act first, hold says so in place of the ways of connecting: an admin there must approve the app (who, and where they do it, with Check again), or their policy blocks it (what is blocked, and who can unblock it). Each has a way back (onBack). When a hold comes or goes while the person is working here, focus moves to the hold's heading, or back to the environment's; when focus is elsewhere, on the page or nowhere, it stays and the hold is announced.

Markdown
Version 5.2.0· other versions

This page is for 5.2.0, the current release. An earlier release’s page is the Markdown that release published, so the docs match the version you have installed (candor-lock.json records it).

Not in the docs of 1.3.0, 1.4.0, 1.5.0, 1.5.1, 1.6.0, 1.7.0, 1.8.0, 1.8.1, 1.9.0, 1.9.1, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 2.4.0, 2.5.0, 2.5.1, 2.6.0, 2.6.1 and 2.6.2: the page came later, or had another name then.

Docs pages weren’t kept for 1.0.0, 1.0.1, 1.1.0, 1.1.1, 1.2.0 and 1.2.1: they are kept from 1.3.0 on, and before that only llms.txt was published.

Preview

Loading the preview

Install

With the Candor CLI

First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/environment-connect

Brings in: bidi, button, environment-band, heading-level, heading-scope, input, locale, locked, relative-time, team, tokens, use-announcer, use-focus-within, utils · npm: lucide-react@^1.67.0

Usage

TSX · The example above
"use client"

import * as React from "react"
import { EnvironmentConnect, type ConnectionHold } from "@/components/candor/environment-connect"

export default function Example() {
  // Signing in with the provider here stands for an install that an admin on the other side has to approve: the
  // panel says who and where, until Check again finds it approved or Back returns to the ways of connecting.
  const [hold, setHold] = React.useState<ConnectionHold | undefined>(undefined)
  return (
    <EnvironmentConnect
      className="max-w-md"
      environment={{ id: "e1", name: "Shop", detail: "staging", production: false, hue: 2, connection: "expired" }}
      oauth={{
        provider: "the provider",
        onStart: () => setHold({ kind: "waiting", who: "An admin of the Shop account", where: "Settings › Connected apps", askedAt: new Date().toISOString() }),
      }}
      hold={hold}
      onCheckAgain={async () => {
        await new Promise((r) => setTimeout(r, 500))
        setHold((h) => (h?.kind === "waiting" ? { ...h, checkedAt: new Date().toISOString() } : h))
      }}
      onBack={() => setHold(undefined)}
      fields={[
        { id: "url", label: "Instance URL", type: "url", placeholder: "https://shop.example.com" },
        { id: "clientId", label: "Client ID" },
        { id: "secret", label: "Client secret", type: "secret" },
      ]}
      values={{ url: "https://staging.shop.example.com", clientId: "agents-staging" }}
      onTest={async (v) => {
        await new Promise((r) => setTimeout(r, 500))
        return v.url?.startsWith("https://")
          ? { ok: true, checkedAt: new Date().toISOString(), detail: "Signed in as agents-staging; 3 of 3 permissions granted." }
          : { ok: false, checkedAt: new Date().toISOString(), reason: "The instance didn’t answer at that address." }
      }}
      onConnect={async () => {
        await new Promise((r) => setTimeout(r, 400))
      }}
      requireTest
    />
  )
}

<EnvironmentConnect>

PropTypeDefaultDescription
environmentrequiredEnvironment—The environment being connected: its name, colour, production flag and current connection.
fieldsConnectionField[] | undefined[]The credentials to ask for. Leave it empty for OAuth only.
valuesRecord<string, string> | undefined—Saved values for the non-secret fields, by field id. Secret values are never passed back to the browser.
oauth{ provider: string; onStart: () => void } | undefined—Shows a button to connect through the provider's own sign-in.
onTest((values: Record<string, string>) => Promise<ConnectionTestResult>) | undefined—Shows Test connection. Called with the values entered; resolve with what the server found. If it throws or rejects, the form says the test couldn't run, with the error's message as the reason.
onConnect((values: Record<string, string>) => void | Promise<void>) | undefined—Called with the values entered when the person connects. The button waits on a returned promise.
requireTestboolean | undefinedfalseConnect stays locked until a test with the current values passed. Without `onTest` no test can run, so Connect stays locked and says so.
busyboolean | undefinedfalseThe app is connecting: the button says so, and stays locked, saying why, until it is done.
errorstring | undefined—Why connecting failed, in the app's words.
holdConnectionHold | undefined—The other side has to act first: an admin there must approve the app, or their policy blocks it. While it is set, a panel saying who has to act, and where or what, stands in place of the ways of connecting.
onCheckAgain(() => void | Promise<void>) | undefined—Shows Check again while `hold` is waiting; the button waits on a returned promise. Update `hold` with what the check found.
onBack(() => void) | undefined—Shows the way back from a hold, to the ways of connecting: clear `hold` here. Pass it whenever you pass `hold`, or the person has no way back.
classNamestring | undefined—Classes, merged with Candor's.
headingLevelHeadingLevel | undefined—The level of the panel's heading, so it fits under the page's own headings. Default: one below the nearest `HeadingScope`, else 2 (`<h2>`). Its sub-headings sit one level below it.

Strings

The fixed words, in English by default. Change any of them under environmentConnect in <LocaleProvider strings={{ … }}>; the table is exported as environmentConnectStrings.

KeyEnglish
headingConnect {name}
formLabel{Environment}
productionProduction: what agents do here reaches real users.
connectedConnected
expiredThe connection expired. Connect again to keep agents working here.
notConnectedNot connected yet
oauthConnect with {provider}
oauthHintYou sign in at {provider} and come back here. Your password stays with {provider}.
oror enter credentials
optional{label} (optional)
secretKeptLeave blank to keep the saved value.
testTest connection
testingTesting…
testPassedConnection works
testFailedConnection failed
testNotRunCouldn’t run the test
testNotRunWhyThe test gave no result, so the connection is unchecked.
checkedAtchecked {when}
staleThe values changed since this test.
connectConnect
saveSave connection
savingSaving…
connectFailedSaving the connection failed.
needsFieldFill in {label}.
needsTestTest the connection first.
needsPassingTestThe last test failed. Fix the values and test again.
cantTestThis connection has to pass a test first, and there’s no way to run one here. Ask whoever set it up.
waitTestWait for the test to finish.
waitSaveWait for the connection to be saved.
waitingTitleWaiting for approval on their side
waitingNoteNothing connects until they approve the app.
blockedTitleBlocked by their policy
blockedNoteNothing connects while their policy blocks it.
approveWhoWho has to approve
approveWhereWhere
blockedWhatWhat’s blocked
unblockerWho can unblock it
askedAtAsked {when}
holdCheckedAtLast checked {when}
checkAgainCheck again
checkingChecking…
waitCheckWait for the check to finish.
checkFailedChecking failed. Try again in a moment.
backBack to connecting

Types

ConnectionHold
The other system has to act before the connection can finish, as the app's server reports it: an admin there must approve the app (`waiting`), or their policy blocks it (`blocked`). Every string is the app's words, or the other system's, and is drawn as text.
{ kind: "waiting"; /** Who on the other side has to approve the app, e.g. "An admin of the Shop account". */ who: string; /** Where they approve it, e.g. "Settings › Connected apps". */ where: string; /** When the app asked, as an ISO time. Left out, no time is shown. */ askedAt?: string | undefined; /** When the app last checked whether they approved, as an ISO time. Left out, no time is shown. */ checkedAt?: string | undefined; } | { kind: "blocked"; /** What their policy blocks, e.g. "Apps from outside the company signing in with OAuth". */ what: string; /** Who can unblock it, e.g. "Their security team". */ who: string; }
ConnectionField
A credential the environment needs, as the app defines it.
{ id: string; /** What it is, e.g. "Instance URL" or "Client secret". */ label: string; /** `secret` masks it and never shows a saved value back. Defaults to text. */ type?: "text" | "url" | "secret" | undefined; /** A line under the field: where to find it. */ hint?: string | undefined; placeholder?: string | undefined; /** Connect works without it. */ optional?: boolean | undefined; }
ConnectionTestResult
What a connection test found, as the app's server reports it.
| { ok: true; checkedAt: string; detail?: string | undefined } | { ok: false; checkedAt: string; reason: string }

Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

Environment from @/lib/candor/team
Where agents act: a CRM instance, a database, a repository, a project. The app names them; LocaleProvider's `nouns` sets what they're called ("project", "workspace").
{ id: string; /** The name shown everywhere, e.g. "Shop". */ name: string; /** What tells same-named ones apart, e.g. "dev-2" or "staging". */ detail?: string | undefined; /** Changes here reach real users, so components warn and ask for more care. */ production: boolean; /** A chart slot (1–5) so each environment keeps one colour everywhere it appears. */ hue?: 1 | 2 | 3 | 4 | 5 | undefined; connection: "connected" | "expired" | "not-connected"; }
HeadingLevel from @/lib/candor/heading-level
A heading's level in the page's outline: `1` is `<h1>`, `6` is `<h6>`. Every Candor item that draws a heading takes its `headingLevel` as this type; the level is where the heading sits in the outline, never how large it looks.
1 | 2 | 3 | 4 | 5 | 6