From 013f167d4766026fb0af8986a662c842cd0550f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Ign=C3=A1cio=20Fontoura=20Solla?= Date: Sun, 26 Jul 2026 20:16:01 -0300 Subject: [PATCH] Fix /campanha/liderancas 500: RSC-safe phone display CampaignCopyableCell is a Client Component; passing formatDisplay as a function from the leaderships RSC page broke serialization in production. Use a preformatted displayValue string at the call sites instead. --- docs/plans/email-celular-lista-liderancas.md | 2 ++ .../(campaign)/campanha/(app)/liderancas/page.tsx | 2 +- src/components/campaign/advisor/AdvisorsTable.tsx | 4 +++- .../campaign/shared/CampaignCopyableCell.tsx | 14 ++++++++------ 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/plans/email-celular-lista-liderancas.md b/docs/plans/email-celular-lista-liderancas.md index 51210ee0..f4657a48 100644 --- a/docs/plans/email-celular-lista-liderancas.md +++ b/docs/plans/email-celular-lista-liderancas.md @@ -9,6 +9,8 @@ Responsável: — **Nota de revisão (2026-07-26):** entregue como especificado — `CampaignCopyableCell` nasceu compartilhado (`shared/`) já no 2º call site (`AdvisorsTable.tsx` migrado no mesmo PR), `whatsAppHrefForPhone` promovido a `lib/phone.ts`, `LeadershipRowViewModel.email` populado sem query nova, e `loadLeadershipDetail` deduplicado (reusa `row.email` de `toLeadershipRows` em vez de reparsear `doc.contact`). Ordem de colunas final: Nome → E-mail → Celular → Status → Municípios → Organizações → Acesso ao app → Ações (WhatsApp), exatamente como travado abaixo. Gate completo (tsc/lint/format/knip/cycles/test/build) verde; `pnpm exec knip` mantém o erro pré-existente ao carregar `payload.config.ts` (P3, não relacionado); `pnpm test:e2e` reproduziu apenas o flake pré-existente já documentado em `campaignMunicipalities.e2e.spec.ts` (checkbox de consentimento do `LeaderContactsPanel` antes da hidratação) — nenhuma falha nas rotas tocadas por este item. +**Hotfix produção (2026-07-26 noite):** `/campanha/liderancas` 500 em prod — RSC não pode passar `formatDisplay` (função) para o Client Component `CampaignCopyableCell`. API trocada para `displayValue?: string` (formatação no caller); Vercel runtime error: "Functions cannot be passed directly to Client Components". Assessores (`AdvisorsTable`, já `'use client'`) não quebrava, mas foi alinhado. + ## Design (Impeccable) Âncoras: `PRODUCT.md` (princípios 2 clareza sob pressão, 8 Feel the action; anti spreadsheet) / `DESIGN.md` (register `product`; Field Desk) · tema `data-theme='campaign'` · precedente de interação [`AdvisorsTable.tsx`](../../src/components/campaign/advisor/AdvisorsTable.tsx) (B19 ✓) · shells `CampaignTable` / `CampaignPageShell`. diff --git a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx index b4036dd0..55e0083f 100644 --- a/src/app/(campaign)/campanha/(app)/liderancas/page.tsx +++ b/src/app/(campaign)/campanha/(app)/liderancas/page.tsx @@ -71,7 +71,7 @@ const leadershipColumns = ( ), diff --git a/src/components/campaign/advisor/AdvisorsTable.tsx b/src/components/campaign/advisor/AdvisorsTable.tsx index f532cfbb..154a185c 100644 --- a/src/components/campaign/advisor/AdvisorsTable.tsx +++ b/src/components/campaign/advisor/AdvisorsTable.tsx @@ -357,7 +357,9 @@ export const AdvisorsTable = ({ )} diff --git a/src/components/campaign/shared/CampaignCopyableCell.tsx b/src/components/campaign/shared/CampaignCopyableCell.tsx index 9553f787..70d7b3df 100644 --- a/src/components/campaign/shared/CampaignCopyableCell.tsx +++ b/src/components/campaign/shared/CampaignCopyableCell.tsx @@ -22,8 +22,12 @@ type CampaignCopyableCellProps = { value: string | null /** Used in the toast ("`label` copiado.") and the button's `aria-label`. */ label: string - /** Transforms `value` for display only — the clipboard still gets the raw value (e.g. phone masking). */ - formatDisplay?: (value: string) => string + /** + * Preformatted label (e.g. masked phone). Clipboard still gets raw `value`. + * String, not a formatter — Client Component; RSC cannot pass functions + * (broke `/campanha/liderancas` after B28). + */ + displayValue?: string emptyDisplay?: string className?: string } @@ -37,7 +41,7 @@ type CampaignCopyableCellProps = { export const CampaignCopyableCell = ({ value, label, - formatDisplay, + displayValue, emptyDisplay = '—', className, }: CampaignCopyableCellProps) => { @@ -49,8 +53,6 @@ export const CampaignCopyableCell = ({ ) } - const displayValue = formatDisplay ? formatDisplay(value) : value - return ( ) }