Click-to-call button
A call button for customer records. Click it: the agent's phone rings first, the customer is bridged in on answer, and the button narrates every step.
Click it — the demo simulates the full lifecycle: dialing → ringing → connected (with a live timer) → ended.
Installation
npx shadcn@latest add @handset/click-to-call-buttonUsage
import { ClickToCallButton } from "@/components/handset/click-to-call-button";
<ClickToCallButton
from={tenant.phoneNumberId} // caller ID both parties see
to={customer.phone} // who you're calling
connectTo={agent.directLine} // the agent's phone rings first
transcribe // optional: live speech-to-text
/>This wraps useClickToCall: one POST to /calls, then status polling until the call settles. Outcomes (no answer, failed) show briefly, then the button returns to idle. Add onStatusChange to open a transcript panel the moment the call connects.
Security note: the proxy's POST /calls allowlist entry ships with a reminder to validate connect_to server-side against the signed-in agent's own number, so users can't ring arbitrary phones.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| from | string | — | Tenant number id (num_…) or E.164 — the caller ID. |
| to | string | — | The customer's number, E.164. |
| connectTo | string | — | The agent's number, E.164 — rings first. |
| transcribe | boolean | false | Stream live speech-to-text (billed per transcribed minute). |
| onStatusChange | (call: Call) => void | — | Fires on every lifecycle change. |
| children | ReactNode | "Call" | Button label. |
| className | string | — | Merged onto the wrapper. |