feat(interests): Email / Call / WhatsApp deep-links on interest header
The interest detail is the rep's workbench — but until now, calling or
emailing the lead meant navigating away to the client page first. Surface
the same Email / Call / WhatsApp affordances that already live on the
client header right where the work is happening.
- getInterestById: extended to also resolve the linked client's primary
phone (display value + canonical E.164 form for wa.me).
`clientPrimaryEmail` is the same column we surfaced earlier for the
EOI prereq checklist; this commit just adds the phone columns
alongside it.
- InterestDetailHeader: new contact-actions row tucked under the meta
line. Each button is asChild over a real <a href> so middle-click,
Cmd-click, and screen-readers behave correctly. Renders only the
buttons whose underlying contact channel is present (Email-only when
no phone is on file, etc.). The whole row is hidden when the client
has no contacts at all.
- WhatsApp number prefers the E.164 form; falls back to digits-stripped
display value when the canonical form is missing.
tsc clean. vitest 835/835 pass. ESLint clean on every file touched.
Closes audit recommendation #1 (top-of-list — biggest sales-workflow
win per click saved).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -282,9 +282,10 @@ export async function getInterestById(id: string, portId: string) {
|
||||
.from(clients)
|
||||
.where(eq(clients.id, interest.clientId));
|
||||
|
||||
// EOI prerequisites: surface enough of the linked client's primary contact
|
||||
// and address so the Documents tab can show the readiness checklist
|
||||
// (Required: name, email, address — Section 2 of the EOI document).
|
||||
// EOI prerequisites + interest-detail header contact actions: surface the
|
||||
// linked client's primary email/phone (and the canonical E.164 form for
|
||||
// wa.me) so the header can render Email / Call / WhatsApp buttons without
|
||||
// a second fetch, and the Documents tab can show the EOI prereq checklist.
|
||||
const [emailContact] = await db
|
||||
.select({ value: clientContacts.value })
|
||||
.from(clientContacts)
|
||||
@@ -292,6 +293,18 @@ export async function getInterestById(id: string, portId: string) {
|
||||
.orderBy(desc(clientContacts.isPrimary), desc(clientContacts.updatedAt))
|
||||
.limit(1);
|
||||
|
||||
const [phoneContact] = await db
|
||||
.select({ value: clientContacts.value, valueE164: clientContacts.valueE164 })
|
||||
.from(clientContacts)
|
||||
.where(
|
||||
and(
|
||||
eq(clientContacts.clientId, interest.clientId),
|
||||
inArray(clientContacts.channel, ['phone', 'whatsapp']),
|
||||
),
|
||||
)
|
||||
.orderBy(desc(clientContacts.isPrimary), desc(clientContacts.updatedAt))
|
||||
.limit(1);
|
||||
|
||||
const [addressRow] = await db
|
||||
.select({ id: clientAddresses.id })
|
||||
.from(clientAddresses)
|
||||
@@ -319,6 +332,8 @@ export async function getInterestById(id: string, portId: string) {
|
||||
...interest,
|
||||
clientName: clientRow?.fullName ?? null,
|
||||
clientPrimaryEmail: emailContact?.value ?? null,
|
||||
clientPrimaryPhone: phoneContact?.value ?? null,
|
||||
clientPrimaryPhoneE164: phoneContact?.valueE164 ?? null,
|
||||
clientHasAddress: !!addressRow,
|
||||
berthMooringNumber,
|
||||
tags: tagRows,
|
||||
|
||||
Reference in New Issue
Block a user