Docyrus

Contact Channels Panel

A Docyrus-backed panel for managing a record's contact channels, the append-only consent ledger and the validation engine.

Client Only

ContactChannelsPanel wires the full Contact Channels & Consent API to a single component, laid out as a Contact Points & Consent detail view: a preferred email / phone summary, grouped Emails / Phones (and messaging / social / web when present) cards showing each channel's validation status, validating provider and source, per-brand consent breakdowns, and a merged consent + validation history trail.

Give it an authenticated client and the owning (appSlug, dataSourceSlug, recordId) and it lists the record's channels, lets you add / edit / archive / restore / delete them, promote a primary per kind, append to the consent ledger (scoped by brand → medium → purpose), record a validation result and run the validation engine — all through useDocyrusContactChannels, which you can also use standalone to build a custom UI.

This is a Docyrus-connection component. It requires an authenticated RestApiClient, so the preview above is informational only — wire it against your tenant (or the playground page on base.contact) to see it live. For a backend-free editor, use the Contact Channels Field.

Installation

pnpm dlx @docyrus/cli add @docyrus/ui-contact-channels-panel
Required Packages(2 packages)
pnpm add @docyrus/api-client @tanstack/react-query

Usage

import { useDocyrusAuth } from '@docyrus/signin';
import { ContactChannelsPanel } from '@docyrus/ui/components/contact-channels-panel';

function ContactChannels({ recordId }: { recordId: string }) {
  const { client } = useDocyrusAuth();

  if (!client) return null;

  return (
    <ContactChannelsPanel
      client={client}
      appSlug="base"
      dataSourceSlug="contact"
      recordId={recordId}
    />
  );
}

As a side drawer

Pass open / onOpenChange to render the panel inside an AwesomeDialog — a right-side sheet by default. The typical pairing is the compact ContactChannelsField summary whose Details link opens this panel, with the contact / organization name as the subtitle:

const [open, setOpen] = useState(false);

<ContactChannelsField value={channels} brands={brands} readOnly onDetailsClick={() => setOpen(true)} />
<ContactChannelsPanel
  open={open}
  onOpenChange={setOpen}
  client={client}
  appSlug="base"
  dataSourceSlug="contact"
  recordId={recordId}
  title="Contact Points & Consent"
  subtitle="Emre Terzi · Emre Company"
  container="sheet"   // 'sheet' (default) · 'drawer' · 'modal'
  side="right"
  size="xl"
/>

Endpoints

The panel (via useDocyrusContactChannels) targets, under the record base path /v1/apps/{appSlug}/data-sources/{dataSourceSlug}/items/{recordId}:

GroupOperations
ChannelsGET /channels · POST /channels · PATCH /channels/{id} · POST /channels/{id}/make-primary · POST /channels/{id}/archive · POST /channels/{id}/unarchive · DELETE /channels/{id}
ConsentGET /consent (record-wide) · GET /channels/{id}/consent · POST /channels/{id}/consent · POST /channels/{id}/consent/bulk · POST /channels/consent/bulk
ValidationGET /validations (record-wide) · GET /channels/{id}/validations · POST /channels/{id}/validations · POST /channels/{id}/validate
BrandsGET /v1/tenant/brands (for brand-aware consent labels)

Read endpoints require the DS.Read.All scope; write endpoints require DS.ReadWrite.All. The record-wide consent / validation queries avoid a per-channel N+1 in the panel. The consent and validation ledgers are append-only; the channel's consent / validation_status caches are maintained server-side by DB triggers and treated as read-only.

API Reference

PropTypeDefaultDescription
clientContactChannelsClient—Authenticated REST client (@docyrus/api-client's RestApiClient works).
appSlugstring—App slug of the owning record.
dataSourceSlugstring—Data source slug of the owning record.
recordIdstring—Owning record id (uuid).
titlestring'Contact Points & Consent'Dialog heading.
subtitlestring—Sub-heading under the title (e.g. the contact and organization names).
includeArchivedbooleanfalseInitial state of the "Show archived" toggle.
enabledbooleantrueDefer queries until ready (e.g. auth resolved).
loadBrandsbooleantrueFetch tenant brands for brand-aware consent.
readOnlybooleanfalseHide every write affordance.
maxHeightstring'40rem'Max height when rendered inline (the dialog body scrolls).
onChange(channels: ContactChannel[]) => void—Fired with the refreshed channel list after mutations.
classNamestring—Additional class on the root.
openboolean—Dialog open state. Providing open/onOpenChange renders the panel in an AwesomeDialog.
onOpenChange(open: boolean) => void—Dialog open-change handler.
container'sheet' | 'drawer' | 'modal''sheet'Dialog container when shown as a dialog.
side'left' | 'right' | 'top' | 'bottom''right'Side for the sheet / drawer container.
size'sm' | 'default' | 'lg' | 'xl' | 'full''xl'Dialog size preset.

Hook

useDocyrusContactChannels({ client, appSlug, dataSourceSlug, recordId }) returns the data and a typed mutation for every endpoint:

const channels = useDocyrusContactChannels({ client, appSlug, dataSourceSlug, recordId });

// data
channels.channels;          // ContactChannel[]
channels.brands;            // ContactBrand[]
channels.consentHistory;    // ConsentRecord[]  (record-wide)
channels.validationHistory; // ValidationRecord[] (record-wide)
channels.isLoading;
channels.error;

// mutations (all return promises)
await channels.createChannel({ channelKind: 'email', channelType: 'email', value: 'a@b.com' });
await channels.updateChannel(id, { label: 'work' });
await channels.makePrimary(id);
await channels.archiveChannel(id, 'old work email');
await channels.unarchiveChannel(id);
await channels.deleteChannel(id);
await channels.recordConsent(id, { purpose: 'marketing', action: 'opt_in', consentStatus: 'opted_in', consentChannel: 'sms' });
await channels.bulkRecordConsent(id, items);
await channels.bulkRecordRecordConsent(items);  // spans many channels
await channels.recordValidation(id, { method: 'manual', status: 'valid' });
await channels.runValidation(id);               // POST /channels/{id}/validate

Components

ComponentDescription
ContactChannelsPanelFull Docyrus-backed panel (inline or as an AwesomeDialog drawer).
PanelSummaryPreferred email / phone summary cards.
ChannelCardPer-channel read card with status grid (validation, provider, source) and the action menu.
BrandConsentCardOne brand's consent breakdown (EMAIL / PHONE mediums + capture metadata).
ConsentHistoryMerged, collapsible consent + validation audit trail.
ChannelFormDialogAdd / edit dialog (wraps the backend-agnostic ChannelEditor).
ConsentDialogRecords a single consent action (channel · purpose · action · status · medium · brand).
ValidationDialogRecords an externally-supplied validation result (POST /channels/{id}/validations).
useDocyrusContactChannelsThe data + mutation hook.

Type Exports

TypeDescription
ContactChannelsPanelPropsProps for ContactChannelsPanel.
ContactChannelsClientMinimal { get, post, patch, delete } client contract.
UseDocyrusContactChannelsOptions / UseDocyrusContactChannelsResultHook options + return shape.
ConsentEntryWithChannelA consent entry plus its target channelId (record-wide bulk).
ContactChannel / ConsentRecord / ValidationRecord / ContactBrandRe-exported from the field package.

On this page