feat(chat): show contact phone number in conversation list and header (EVO-988)#48
Merged
Conversation
… (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>
Reviewer's GuideAdds 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 sidebarflowchart 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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The
rawPhone→phoneDisplayformatting logic is duplicated in bothChatHeaderandChatSidebar; 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-onlyphone_numbervalues (e.g., via type check andtrim()) 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
…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
approved these changes
May 12, 2026
dpaes
left a comment
Contributor
There was a problem hiding this comment.
Aprovado. Rodada 3 endereçou todos os achados das rodadas 1 e 2. Verificação detalhada no comentário no Linear EVO-988.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
ChatSidebar.tsx): small line with the contact phone, rendered between the name row and the last-message preview.ChatHeader.tsx): phone shown inline next to the contact name.conversation.contact.phone_numberis present. Email/website/API conversations don't populate that field, so they remain unchanged.+when the stored value doesn't already start with one. No further reformatting (kept minimal on purpose).whatsappNumberkey under bothchatHeaderandchatSidebarnamespaces inpt-BRanden(used as the tooltip on hover).No backend changes —
phone_numberwas already in the conversation payload.Validation
pnpm exec tsc -b --noEmit→ cleanpnpm 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.tsxsrc/components/chat/chat-header/ChatHeader.tsxsrc/i18n/locales/pt-BR/chat.jsonsrc/i18n/locales/en/chat.jsonLinked Issue
Summary by Sourcery
Display WhatsApp contact phone numbers in the chat conversation list and header when available.
New Features:
Enhancements: