feat(crm): ficha de informações do contato + correções de layout mobile#61
Conversation
Adiciona botão de informações do contato (nome, telefone, e-mail, notas com formatação básica e campos personalizados) tanto no Kanban (popup) quanto em Conversas (painel embutido na própria thread, com X pra voltar). Corrige scroll horizontal indevido no Kanban/Calendário/popovers no mobile, empilha compromissos+calendário no Calendário em telas pequenas, e reorganiza os botões de ação em massa do painel de notificações numa toolbar própria. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds editable CRM lead contact information with notes and custom fields, backed by a sanitized server action and database migration. Integrates the editor into Kanban and threads, while updating CRM mobile layouts, popovers, calendar panes, scrolling, and notification controls. ChangesContact information editing
Responsive CRM interface
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant CRMInterface
participant InformacoesContato
participant editarInformacoesContatoAction
participant crm_leads
User->>CRMInterface: Open contact information
CRMInterface->>InformacoesContato: Render lead editor
User->>InformacoesContato: Edit and save contact data
InformacoesContato->>editarInformacoesContatoAction: Submit FormData
editarInformacoesContatoAction->>crm_leads: Validate and update lead
editarInformacoesContatoAction-->>InformacoesContato: Return result
InformacoesContato-->>User: Refresh and close or show error
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/crm-leads-actions.ts (1)
1-1: 🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy liftStored XSS in contact notes
sanitizarHtmlContatostill lets browser-accepted/separators through (for example<img/onerror=alert(1)>), andcomponents/crm/InformacoesContato.tsxrenders the saved HTML withdangerouslySetInnerHTML. Replace the regex sanitizer with a parser-backed allowlist sanitizer.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/crm-leads-actions.ts` at line 1, Replace the regex-based sanitization in sanitizarHtmlContato with a parser-backed HTML allowlist sanitizer that rejects browser-valid tag separators such as `/` and permits only explicitly approved tags, attributes, and URL schemes. Ensure the sanitized output remains safe for InformacoesContato.tsx before it is passed to dangerouslySetInnerHTML.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/globals.css`:
- Around line 710-715: Update the JSX layout in Calendario so the commitments
pane is rendered before the calendar pane, matching the mobile visual order and
preserving accessibility navigation; then apply explicit desktop flex ordering
so the intended desktop arrangement remains unchanged, using the pane classes
crm-calendario-compromissos-pane and crm-calendario-mes-pane.
In `@components/crm/Kanban.tsx`:
- Around line 799-835: Move the info button currently rendered in the Link card
content outside the Link, making it a sibling overlay within a relative card
wrapper. Preserve the existing onClick, onPointerDown, accessibility attributes,
and styling, and ensure the wrapper provides the positioning context required by
the absolute button.
In `@lib/crm-leads-actions.ts`:
- Around line 35-66: Replace the regex-based implementation in
sanitizarHtmlContato with a real HTML parser/sanitizer that safely handles
malformed tags such as img/onerror payloads, removes disallowed elements and
attributes, and preserves only the existing basic formatting allowlist. Apply
the sanitizer before notas_html is stored or rendered, while retaining the
20,000-character limit.
---
Outside diff comments:
In `@lib/crm-leads-actions.ts`:
- Line 1: Replace the regex-based sanitization in sanitizarHtmlContato with a
parser-backed HTML allowlist sanitizer that rejects browser-valid tag separators
such as `/` and permits only explicitly approved tags, attributes, and URL
schemes. Ensure the sanitized output remains safe for InformacoesContato.tsx
before it is passed to dangerouslySetInnerHTML.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c2eef343-fd12-43fc-821e-e4bc40be2d22
📒 Files selected for processing (11)
app/globals.csscomponents/SinoNotificacoes.tsxcomponents/crm/Calendario.tsxcomponents/crm/Etiquetas.tsxcomponents/crm/InformacoesContato.tsxcomponents/crm/Kanban.tsxcomponents/crm/NovoContato.tsxcomponents/crm/Thread.tsxlib/crm-leads-actions.tslib/crm-leads.tssupabase/migrations/20260716_crm_fase7_info_contato.sql
| .crm-calendario-mes-pane { | ||
| order: 2; | ||
| max-height: 40vh; | ||
| } | ||
| .crm-calendario-compromissos-pane { | ||
| order: 1; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep the DOM order aligned with the mobile visual order.
The commitments pane is visually moved before the calendar with order, but components/crm/Calendario.tsx still renders the calendar first. Screen readers and other DOM-based navigation will therefore encounter the calendar before the commitments panel despite the opposite visual order. Render commitments first in the DOM and apply explicit desktop ordering, or avoid reordering the content with CSS.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/globals.css` around lines 710 - 715, Update the JSX layout in Calendario
so the commitments pane is rendered before the calendar pane, matching the
mobile visual order and preserving accessibility navigation; then apply explicit
desktop flex ordering so the intended desktop arrangement remains unchanged,
using the pane classes crm-calendario-compromissos-pane and
crm-calendario-mes-pane.
| {onAbrirInfo && ( | ||
| <button | ||
| type="button" | ||
| // preventDefault+stopPropagation evita navegar pro Link e (via | ||
| // stopPropagation, que o React aplica na fase de bubbling do | ||
| // synthetic event antes de chegar no <Link>) evita disparar o | ||
| // drag do dnd-kit, cujos listeners ficam no wrapper acima. | ||
| onClick={(e) => { | ||
| e.preventDefault() | ||
| e.stopPropagation() | ||
| onAbrirInfo(lead) | ||
| }} | ||
| onPointerDown={(e) => e.stopPropagation()} | ||
| title="Informações do contato" | ||
| aria-label="Informações do contato" | ||
| style={{ | ||
| position: "absolute", | ||
| top: 6, | ||
| right: 6, | ||
| width: 20, | ||
| height: 20, | ||
| display: "flex", | ||
| alignItems: "center", | ||
| justifyContent: "center", | ||
| borderRadius: 6, | ||
| fontSize: 11, | ||
| lineHeight: 1, | ||
| color: "var(--text-4)", | ||
| background: "rgba(0,0,0,0.25)", | ||
| }} | ||
| > | ||
| ⓘ | ||
| </button> | ||
| )} | ||
| <div className="flex items-center gap-2"> | ||
| <Avatar nome={nomeExibido} cor={cor} fotoUrl={lead.foto_url} size={26} /> | ||
| <div className="min-w-0 flex-1"> | ||
| <div className="min-w-0 flex-1" style={{ paddingRight: onAbrirInfo ? 22 : 0 }}> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the target file structure, then inspect the relevant ranges.
ast-grep outline components/crm/Kanban.tsx --view expanded || true
printf '\n---\n'
sed -n '760,860p' components/crm/Kanban.tsx | cat -nRepository: sejaanomalo-web/metas-anomalo
Length of output: 4217
Move the info button outside the Link.
The nested <button> inside <Link> renders invalid interactive-in-interactive HTML and can lead to inconsistent keyboard/screen-reader behavior. Make the card wrapper relative and render the info button as a sibling overlay instead.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@components/crm/Kanban.tsx` around lines 799 - 835, Move the info button
currently rendered in the Link card content outside the Link, making it a
sibling overlay within a relative card wrapper. Preserve the existing onClick,
onPointerDown, accessibility attributes, and styling, and ensure the wrapper
provides the positioning context required by the absolute button.
| // Tags permitidas nas notas do contato — só o suficiente pro editor de | ||
| // formatação básica (negrito/itálico/sublinhado/lista). Tudo mais (script, | ||
| // style, atributos como onclick/style/href) é removido: o HTML vem do | ||
| // contentEditable do navegador, então precisa ser tratado como input não | ||
| // confiável antes de ir pro banco. | ||
| const TAGS_PERMITIDAS_NOTAS = new Set([ | ||
| "b", "strong", "i", "em", "u", "ul", "ol", "li", "br", "p", "div", "span", | ||
| ]) | ||
|
|
||
| // Casa uma tag inteira (com atributos) tratando valores entre aspas como | ||
| // átomos — assim um atributo tipo title="a>b" não corta o match no '>' de | ||
| // dentro da aspa (armadilha comum de sanitizador ingênuo baseado em regex). | ||
| const TAG_COM_ATRIBUTOS = | ||
| /<\/?([a-zA-Z0-9]+)(?:\s+[a-zA-Z0-9:_-]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]+))?)*\s*\/?>/g | ||
|
|
||
| function sanitizarHtmlContato(bruto: string): string { | ||
| let limpo = bruto.replace(/<!--[\s\S]*?-->/g, "") | ||
| // Blocos perigosos somem inteiros (tag + conteúdo) antes de tudo — o resto | ||
| // vira texto puro pela troca de tags abaixo, então script/style nunca | ||
| // chegam a executar nem a poluir a nota com CSS/JS cru. | ||
| limpo = limpo.replace( | ||
| /<(script|style|iframe|object|embed|link|meta|form|svg)[^>]*>[\s\S]*?<\/\1>/gi, | ||
| "" | ||
| ) | ||
| limpo = limpo.replace(TAG_COM_ATRIBUTOS, (tagCompleta, nomeTag) => { | ||
| const nome = String(nomeTag).toLowerCase() | ||
| if (!TAGS_PERMITIDAS_NOTAS.has(nome)) return "" | ||
| const fechamento = tagCompleta.startsWith("</") ? "/" : "" | ||
| return `<${fechamento}${nome}>` | ||
| }) | ||
| return limpo.slice(0, 20_000) | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift
Regex HTML sanitization is bypassable and allows stored XSS.
TAG_COM_ATRIBUTOS misses malformed tags like <img/onerror=alert(1)>, so the payload survives sanitizarHtmlContato() and is later rendered via dangerouslySetInnerHTML. Replace this regex-based allowlist with a real HTML sanitizer/parser before storing or rendering notas_html.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@lib/crm-leads-actions.ts` around lines 35 - 66, Replace the regex-based
implementation in sanitizarHtmlContato with a real HTML parser/sanitizer that
safely handles malformed tags such as img/onerror payloads, removes disallowed
elements and attributes, and preserves only the existing basic formatting
allowlist. Apply the sanitizer before notas_html is stored or rendered, while
retaining the 20,000-character limit.
Adiciona botão de informações do contato (nome, telefone, e-mail, notas com formatação básica e campos personalizados) tanto no Kanban (popup) quanto em Conversas (painel embutido na própria thread, com X pra voltar). Corrige scroll horizontal indevido no Kanban/Calendário/popovers no mobile, empilha compromissos+calendário no Calendário em telas pequenas, e reorganiza os botões de ação em massa do painel de notificações numa toolbar própria.
Summary by CodeRabbit
New Features
Responsive Design