Contact timeline
Every message, call, and voicemail with one customer, merged into a single newest-first feed. This is the component CRMs actually want.
Maria's full history — texts and a call (expand it for the transcript). The composer replies in place.
Installation
npx shadcn@latest add @handset/contact-timelineInstalls voicemail-player, call-transcript, and composer as dependencies.
Usage
import { ContactTimeline } from "@/components/handset/contact-timeline";
// In your CRM's contact panel:
<ContactTimeline
externalNumber={contact.phone} // E.164
withComposer // reply without leaving the panel
/>The timeline keys on the customer's phone number, so it works from any record that has one — a candidate, a tenant, a patient, a homeowner. Calls that ended in voicemail appear once, as the voicemail (with player and transcript), not as a duplicate call row. Completed calls expand into their transcript in place.
Under the hood this is useContactTimeline: conversations, calls, and voicemails fetched in parallel, filtered to the contact, merged and sorted. Use the hook directly if you want a custom feed rendering.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| externalNumber | string | null | — | The customer's number, E.164. |
| withComposer | boolean | false | Show a composer wired to the contact's conversation. |
| tenantId | string | — | Scope to one tenant (usually handled server-side). |
| pollMs | number | 10000 | Refresh interval. 0 disables polling. |
| limit | number | 50 | Max items fetched per source. |
| className | string | — | Merged onto the wrapper. |