Drawer WhatsApp: banner persistente em erros de envio

friendlySendError (string única) virou classifySendError, que devolve
{ code, status, message, hint, action, secondaryAction }. UI passa a
renderizar banner persistente no chat (não só toast efêmero) com título
+ dica explicativa + CTA contextual.

Casos cobertos:
- 502/503/504 -> "Servidor de WhatsApp fora do ar" + CTA Configurar +
  CTA Comprar créditos (caso ainda não tenha contratado)
- insufficient_credits -> CTA Comprar créditos
- canal nao configurado / inativo -> CTA Configurar agora
- credenciais evolution incompletas -> CTA Configuracoes WhatsApp
- twilio credenciais incompletas -> sem CTA (fala pra contatar suporte)
- evolution retornou ... -> CTA Ver status
- twilio_send_failed... -> CTA Configuracoes WhatsApp
- auth -> "sessao expirou", sem CTA
- forbidden -> sem CTA

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-04-28 17:11:44 -03:00
parent a57cf27a6a
commit dac3198873
2 changed files with 186 additions and 21 deletions
@@ -1103,6 +1103,45 @@ function insertEmoji(emoji) {
</div>
</div>
<!-- Banner de erro de envio (persistente até user enviar com sucesso ou dispensar) -->
<div
v-if="store.lastSendError"
class="flex items-start gap-2.5 mt-2 px-3 py-2.5 rounded-lg border border-amber-300/60 bg-amber-50 text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-100"
>
<i class="pi pi-exclamation-triangle text-amber-600 dark:text-amber-300 mt-0.5 shrink-0" />
<div class="flex-1 min-w-0">
<div class="text-sm font-medium leading-snug">{{ store.lastSendError.message }}</div>
<div v-if="store.lastSendError.hint" class="text-xs opacity-85 mt-0.5 leading-snug">
{{ store.lastSendError.hint }}
</div>
<div v-if="store.lastSendError.action || store.lastSendError.secondaryAction" class="flex flex-wrap gap-3 mt-1.5">
<RouterLink
v-if="store.lastSendError.action"
:to="store.lastSendError.action.url"
class="inline-flex items-center gap-1 text-xs font-semibold underline hover:no-underline"
@click="store.dismissSendError()"
>
{{ store.lastSendError.action.label }} <i class="pi pi-arrow-right text-[0.6rem]" />
</RouterLink>
<RouterLink
v-if="store.lastSendError.secondaryAction"
:to="store.lastSendError.secondaryAction.url"
class="inline-flex items-center gap-1 text-xs font-medium underline hover:no-underline opacity-90"
@click="store.dismissSendError()"
>
{{ store.lastSendError.secondaryAction.label }} <i class="pi pi-arrow-right text-[0.6rem]" />
</RouterLink>
</div>
</div>
<button
class="shrink-0 w-6 h-6 grid place-items-center rounded hover:bg-amber-100 dark:hover:bg-amber-500/20 transition-colors"
title="Dispensar"
@click="store.dismissSendError()"
>
<i class="pi pi-times text-[0.7rem]" />
</button>
</div>
<!-- Compose -->
<div v-if="store.thread.channel === 'whatsapp' && store.thread.contact_number" class="border-t border-[var(--surface-border)] pt-2 flex flex-col gap-2">
<div class="flex items-end gap-2">