Skip to content

feat(chat): show contact phone number in conversation list and header (EVO-988)#48

Merged
dpaes merged 6 commits into
developfrom
fix/EVO-988
May 12, 2026
Merged

feat(chat): show contact phone number in conversation list and header (EVO-988)#48
dpaes merged 6 commits into
developfrom
fix/EVO-988

Conversation

@nickoliveira23

@nickoliveira23 nickoliveira23 commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Surfaces the contact's phone number in two places so agents can tell at a glance whether they're talking to a domestic or international contact, without opening the details drawer.

  • Conversation list row (ChatSidebar.tsx): small line with the contact phone, rendered between the name row and the last-message preview.
  • Conversation header (ChatHeader.tsx): phone shown inline next to the contact name.
  • Visibility: rendered only when conversation.contact.phone_number is present. Email/website/API conversations don't populate that field, so they remain unchanged.
  • Formatting: prepend + when the stored value doesn't already start with one. No further reformatting (kept minimal on purpose).
  • i18n: new whatsappNumber key under both chatHeader and chatSidebar namespaces in pt-BR and en (used as the tooltip on hover).

No backend changes — phone_number was already in the conversation payload.

Validation

  • pnpm exec tsc -b --noEmit → clean
  • pnpm lint → no new errors in changed files (existing baseline noise unchanged)
  • pnpm exec vitest run src/components/chat → 51/51 passed (6 spec files)

Changed Files

  • src/components/chat/chat-sidebar/ChatSidebar.tsx
  • src/components/chat/chat-header/ChatHeader.tsx
  • src/i18n/locales/pt-BR/chat.json
  • src/i18n/locales/en/chat.json

Linked Issue

Summary by Sourcery

Display WhatsApp contact phone numbers in the chat conversation list and header when available.

New Features:

  • Show the contact phone number inline next to the contact name in the chat header when a phone number is present.
  • Show the contact phone number in each conversation row in the chat sidebar between the contact name and last message preview.

Enhancements:

  • Normalize displayed phone numbers by ensuring they are prefixed with '+' without additional formatting.
  • Add localized tooltip labels for the displayed WhatsApp phone number in both English and Brazilian Portuguese chat translations.

… (EVO-988)

- Render contact phone number as a small line under the contact name in each
  conversation row (ChatSidebar) and inline next to the name in ChatHeader.
- Hidden when contact.phone_number is empty, which naturally excludes
  non-phone channels (email, website, API).
- Localize tooltip via new chatHeader.whatsappNumber and
  chatSidebar.whatsappNumber keys (pt-BR / en).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented May 8, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds contact phone number display to chat conversation header and sidebar list rows, including minimal formatting and localized tooltips, without altering backend payloads.

Flow diagram for phone number display formatting in chat header and sidebar

flowchart TD
  A[Start render<br>conversation/contact] --> B{conversation.contact<br>and conversation.contact.phone_number?}
  B -- No --> C[Set rawPhone to undefined<br>Set phoneDisplay to null<br>Render no phone in UI]
  B -- Yes --> D[Set rawPhone to conversation.contact.phone_number]
  D --> E{rawPhone startsWith +?}
  E -- Yes --> F[Set phoneDisplay to rawPhone]
  E -- No --> G[Set phoneDisplay to + concatenated with rawPhone]
  F --> H[Render phoneDisplay<br>next to contact name in header<br>with title chatHeader.whatsappNumber]
  G --> H
  F --> I[Render phoneDisplay<br>below name in sidebar row<br>with title chatSidebar.whatsappNumber]
  G --> I
Loading

File-Level Changes

Change Details Files
Show the contact phone number next to the contact name in the chat header when available, with minimal formatting and a tooltip.
  • Derives a formatted phoneDisplay value from conversation.contact.phone_number, prefixing a '+' if missing and falling back to null if absent.
  • Renders the phone number inline beside the contact name within a flex container, preserving wrapping behavior.
  • Applies muted small-text styling and a localized tooltip via chatHeader.whatsappNumber when hovering the phone number.
src/components/chat/chat-header/ChatHeader.tsx
Display the contact phone number in each conversation row in the chat sidebar list when available, with truncation and a tooltip.
  • Computes phoneDisplay from conversation.contact.phone_number using the same '+' prefix logic and null fallback as in the header.
  • Inserts a new text line between the name row and last message preview to show the phone, using small muted text and truncate for long numbers.
  • Adds a localized tooltip via chatSidebar.whatsappNumber for the sidebar phone number display.
src/components/chat/chat-sidebar/ChatSidebar.tsx
Add i18n strings for the WhatsApp number tooltip in English and Brazilian Portuguese for both header and sidebar contexts.
  • Defines chatHeader.whatsappNumber key in en and pt-BR chat.json locale files.
  • Defines chatSidebar.whatsappNumber key in en and pt-BR chat.json locale files.
src/i18n/locales/en/chat.json
src/i18n/locales/pt-BR/chat.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The rawPhonephoneDisplay formatting logic is duplicated in both ChatHeader and ChatSidebar; consider extracting this into a small shared utility or hook to keep behavior consistent and easier to adjust later.
  • When deriving phoneDisplay, consider defensively handling non-string or whitespace-only phone_number values (e.g., via type check and trim()) so you don't end up rendering a bare + or an unexpected value.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `rawPhone``phoneDisplay` formatting logic is duplicated in both `ChatHeader` and `ChatSidebar`; consider extracting this into a small shared utility or hook to keep behavior consistent and easier to adjust later.
- When deriving `phoneDisplay`, consider defensively handling non-string or whitespace-only `phone_number` values (e.g., via type check and `trim()`) so you don't end up rendering a bare `+` or an unexpected value.

## Individual Comments

### Comment 1
<location path="src/components/chat/chat-header/ChatHeader.tsx" line_range="91-96" />
<code_context>
   const isArchived = Boolean(conversation.custom_attributes?.archived);

   const inboxName = conversation.inbox?.name || '';
+  const rawPhone = conversation.contact?.phone_number;
+  const phoneDisplay = rawPhone
+    ? rawPhone.startsWith('+')
+      ? rawPhone
+      : `+${rawPhone}`
+    : null;

   const renderConversationStatusDropdown = () => {
</code_context>
<issue_to_address>
**suggestion:** Consider extracting phone display formatting into a shared helper to avoid duplication.

The same `rawPhone` / `phoneDisplay` logic exists in `ChatSidebar`. Centralizing this into a shared utility (e.g., `formatContactPhoneForDisplay`) or hook would keep header/sidebar formatting consistent and reduce maintenance if the requirements change.

Suggested implementation:

```typescript
  const inboxName = conversation.inbox?.name || '';
  const phoneDisplay = formatContactPhoneForDisplay(
    conversation.contact?.phone_number
  );

```

To fully implement the shared helper and avoid duplication, you should also:

1. **Create the shared helper** (if it does not already exist), for example in `src/utils/phone.ts` (or wherever phone-related utilities are typically stored in your project):

```ts
export const formatContactPhoneForDisplay = (rawPhone?: string | null) => {
  if (!rawPhone) {
    return null;
  }

  return rawPhone.startsWith('+') ? rawPhone : `+${rawPhone}`;
};
```

2. **Import the helper in `ChatHeader.tsx`**, adding an import like:

```ts
import { formatContactPhoneForDisplay } from 'src/utils/phone';
```

(Adjust the import path to match your project's directory structure and existing conventions.)

3. **Update `ChatSidebar` (and any other components duplicating this logic)** to also use the same helper:

   - Replace the inline `rawPhone` / `phoneDisplay` logic with:
     ```ts
     const phoneDisplay = formatContactPhoneForDisplay(contact?.phone_number);
     ```
   - Add the same `formatContactPhoneForDisplay` import to those files.

These additional steps will centralize phone display formatting, keep header/sidebar consistent, and make future maintenance easier.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/components/chat/chat-header/ChatHeader.tsx Outdated
nickoliveira23 and others added 5 commits May 12, 2026 13:28
…ormat, a11y)

Applies the merge blockers and pre-merge ideals from the PR #48 code review:

- H1 i18n completeness: rename the `whatsappNumber` key to the more accurate
  `phoneNumber` in en + pt-BR (it covers SMS, Telegram and Twilio too, not
  just WhatsApp) and add the new key under `chatHeader` and `chatSidebar`
  in the four locales that were missing it (es, fr, it, pt). Tooltip stops
  rendering the raw i18n key for Spanish/French/Italian/Portuguese users.
- M1 channel-aware visibility: introduce `isPhoneBearingChannel` in
  channelUtils so ChatHeader and ChatSidebar only show the phone for
  whatsapp / sms / telegram / twilio_sms channels. Email, Website, API and
  social channels stop rendering an unrelated stored number for unified
  contacts.
- M2 + L1 + M3 phone display: extract `formatContactPhone` to
  `src/utils/contact/` and use it from both components. Brazilian E.164
  mobile and landline numbers are grouped (e.g. `+55 11 99999-9999`).
  Anything else falls back to a plain `+digits` string so unexpected shapes
  never blank the UI silently. Eliminates the inline `rawPhone/phoneDisplay`
  duplication between the two components.
- M5 accessibility: replace mouse-only `title` with `aria-label` that
  includes the contextual prefix so screen readers and mobile users get
  the labelled value.
- M4 test coverage: new `formatContactPhone.spec.ts` (8 cases covering
  empty input, BR mobile, BR landline, mixed punctuation, non-BR fallback,
  leading `+`) and new `channelUtils.spec.ts` (5 cases covering canonical
  Channel:: types, lowercase aliases, non-phone channels, empty input,
  unknown channel types).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ws (EVO-988)

Out of strict EVO-988 scope but flagged during review polish: phone numbers
were rendered raw in every contact-scoped surface (contact-sidebar in the
chat, contacts table, contacts cards, contact details page, merge modals).
Reuse `formatContactPhone` so the same Brazilian E.164 grouping introduced
for the conversation list and header is consistent everywhere a contact
phone shows up.

No channel filter is applied here: these are views of the contact's stored
data, not of a specific conversation, so the phone is part of the contact
identity and should always render if present.

Touched:
- chat right panel: contact-sidebar/ContactHeader, contact-sidebar/ContactDetails
- /contacts: ContactsTable, ContactCard, ContactDetails (main field +
  linked companies + linked persons)
- merge UX: ContactMergeModal, ContactMergeSelectorModal

ContactForm inputs are intentionally untouched — formatting a value being
typed would fight with the user. ContactExportModal only references the
field label, not a rendered value.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…channels (EVO-988)

Addresses H1-novo from the PR #48 round 2 review. `PHONE_BEARING_CHANNEL_TYPES`
only listed `Channel::Whatsapp`, hiding the phone field for any tenant using
WhatsApp Cloud (Meta Business API) or 360Dialog — likely the majority of new
tenants. The codebase already enumerates these variants in multiple places
(scheduledActionChannelUtils, messageTemplatesService, WhatsappCloudPayload
type) so this is a clear omission, not a design choice.

Set now covers Channel::Whatsapp, Channel::WhatsappCloud,
Channel::Whatsapp360Dialog plus their lowercase aliases (whatsapp_cloud,
whatsappcloud, whatsapp_360dialog, whatsapp360dialog). channelUtils.spec.ts
gains a focused block asserting all three variants are recognised.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…988)

Addresses M1-novo from the PR #48 round 2 review. The Phone InfoField in
the chat right-side ContactDetails was passing the formatted display value
(`+55 11 99999-9999`) into `handleCopy`, breaking integrations that expect
raw E.164 digits (e.g. `wa.me/<digits>`, automations, DB queries).

InfoField now accepts an optional `copyValue` prop. When present, the copy
button writes that value to the clipboard instead of the rendered display
value. The phone field passes the raw `contact.phone_number` as `copyValue`
while keeping the formatted version on screen.

Drops the previous `?? contact.phone_number` fallback at the call site —
the formatter never returns null for truthy input anymore (it returns the
raw string when it cannot recognise the shape), so the defensive fallback
is no longer load-bearing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…forcing + prefix (EVO-988)

Addresses M2-novo + L1-novo from the PR #48 round 2 review.

The previous fallback in `formatContactPhone` returned `+${digits}` for any
input that did not match the Brazilian E.164 patterns. For legacy contacts
saved without DDI (e.g. `(11) 99999-9999`), this turned the display into
`+11999999999` — which dialers interpret as US (+1). Misleading at best,
actively harmful at worst.

Fallback now returns the original raw string unchanged. The formatter still
groups recognised Brazilian mobile (13 digits) and landline (12 digits)
numbers in E.164. Everything else — short numbers, numbers without DDI,
non-BR international, already-formatted strings — is passed through as-is.

Trade-off: chat-sidebar / chat-header displays for non-BR numbers no longer
get an automatic `+` prefix. Acceptable: prefixing `+` on a 5-digit string
never helped distinguish domestic vs. international anyway, and the
previous behaviour was actively wrong for the common legacy case.

Updated `formatContactPhone.spec.ts` to assert the new contract: punctuated
local numbers, short strings, and non-BR digits are returned untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@dpaes dpaes left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aprovado. Rodada 3 endereçou todos os achados das rodadas 1 e 2. Verificação detalhada no comentário no Linear EVO-988.

@dpaes dpaes merged commit c42fec0 into develop May 12, 2026
1 check passed
@dpaes dpaes deleted the fix/EVO-988 branch May 12, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants