Nasaq
Components

ContactMerge

Merge duplicate contacts by choosing a survivor, then choosing which value wins for every field that differs, with a live result summary and a confirmation step.

PreviewOpen ↗

Code

<ContactMerge  records={[    { id: "a", name: "Sara Ali", values: { email: "[email protected]", phone: "0501" } },    { id: "b", name: "Sara A.", values: { email: "[email protected]", phone: "0501" } },  ]}  fields={[{ id: "email", label: "Email", ltr: true }, { id: "phone", label: "Phone", ltr: true }]}  onMerge={async (outcome) => merge(outcome)}/>

CRM · beta

Live examples and controls: ContactMerge in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/contact-merge.json

Two or more records for the same person. Pick the one that survives, then settle each field where they disagree. Fields that hold a list (tags, emails) can be combined. A sticky summary shows what the merged contact will be and how much moves over, and nothing happens until the person confirms.

When to use

  • A "merge these contacts" flow opened from a duplicate finder or a selection in a list.

When not to use

  • Deleting a duplicate outright: use a confirm dialog.
  • Editing one contact: use a form.

Import

import { ContactMerge } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

<ContactMerge
  records={[
    { id: "a", name: "Sara Ali", values: { email: "[email protected]", phone: "0501" } },
    { id: "b", name: "Sara A.", values: { email: "[email protected]", phone: "0501" } },
  ]}
  fields={[{ id: "email", label: "Email", ltr: true }, { id: "phone", label: "Phone", ltr: true }]}
  onMerge={async (outcome) => merge(outcome)}
/>

Anatomy

  1. Survivor choice, one card per record.
  2. One radio group per field that differs (list fields add "Combine all").
  3. A collapsible list of fields that are the same everywhere.
  4. Result panel: final values, identities and activity that move over, consent (the most restrictive wins).
  5. Confirmation dialog.

API

PropTypeDescription
recordsContactMergeRecord[]{ id, name, avatar?, values, createdAt?, stats?, identities?, consent? }. At least two.
fieldsContactMergeField[]{ id, label, multi?, ltr? }. Default: name, email, phone, company, job title, owner, tags.
defaultSurvivorIdstringDefault: the first record.
onMerge(outcome) => Promise<void or { error? }>outcome holds the survivor id, absorbed ids and resolved values.
onCancel() => voidShows a cancel button.
labelsContactMergeLabelOverridesOverride any string.

Pure helpers: resolveContactMerge, defaultContactMergeChoices, resolveContactMergeValue, contactMergeConflict, combineMergeLists, rebaseContactMergeChoices, isEmptyMergeValue.

Examples

Changing the survivor keeps every choice you already made and only re-defaults fields that no longer apply.

Accessibility

Radio groups are named by field. The confirmation is an alert dialog. Errors from onMerge show in an alert.

RTL & i18n

English and Arabic ship. Values marked ltr (emails, phones) stay left-to-right.

Styling & tokens

Semantic tokens only. Extend with className.

Do / Don't

  • Do show what will be lost before the person confirms.
  • Don't auto-merge on similarity alone.

Lab

https://docs.nasaqui.com/?path=/docs/components-crm-contact-merge--docs

On this page