feat: enhance ErrorModal and ChatWindow error handling
- Added HelpCircle icon to ErrorModal for improved visual feedback. - Implemented logic to differentiate between instructions and technical details in ErrorModal. - Updated ChatWindow to utilize traduzirErro for user-friendly error messages during call initiation. - Enhanced error handling to provide clearer instructions and details based on error types.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { AlertCircle, X } from 'lucide-svelte';
|
import { AlertCircle, X, HelpCircle } from 'lucide-svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -10,6 +10,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
let { open = $bindable(false), title = 'Erro', message, details, onClose }: Props = $props();
|
let { open = $bindable(false), title = 'Erro', message, details, onClose }: Props = $props();
|
||||||
|
|
||||||
|
// Verificar se details contém instruções ou apenas detalhes técnicos
|
||||||
|
const temInstrucoes = $derived.by(() => {
|
||||||
|
if (!details) return false;
|
||||||
|
// Se contém palavras-chave de instruções, é uma instrução
|
||||||
|
return details.includes('Por favor') ||
|
||||||
|
details.includes('aguarde') ||
|
||||||
|
details.includes('recarregue') ||
|
||||||
|
details.includes('Verifique') ||
|
||||||
|
details.includes('tente novamente') ||
|
||||||
|
details.match(/^\d+\./); // Começa com número (lista numerada)
|
||||||
|
});
|
||||||
|
|
||||||
let modalRef: HTMLDialogElement;
|
let modalRef: HTMLDialogElement;
|
||||||
|
|
||||||
@@ -37,12 +49,12 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="border-base-300 flex items-center justify-between border-b px-6 py-4">
|
<div class="border-base-300 flex items-center justify-between border-b px-6 py-4">
|
||||||
<h2 id="modal-title" class="text-error flex items-center gap-2 text-xl font-bold">
|
<h2 id="modal-title" class="text-error flex items-center gap-2 text-xl font-bold">
|
||||||
<AlertCircle class="h-5 w-5" strokeWidth={2} />
|
<AlertCircle class="h-6 w-6" strokeWidth={2.5} />
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-circle"
|
class="btn btn-sm btn-circle btn-ghost"
|
||||||
onclick={handleClose}
|
onclick={handleClose}
|
||||||
aria-label="Fechar"
|
aria-label="Fechar"
|
||||||
>
|
>
|
||||||
@@ -52,17 +64,41 @@
|
|||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="px-6 py-6">
|
<div class="px-6 py-6">
|
||||||
<p class="text-base-content mb-4">{message}</p>
|
<!-- Mensagem principal -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<p class="text-base-content text-base leading-relaxed font-medium">{message}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Instruções ou detalhes (se houver) -->
|
||||||
{#if details}
|
{#if details}
|
||||||
<div class="bg-base-200 mb-4 rounded-lg p-4">
|
<div class="bg-info/10 border-info/30 mb-4 rounded-lg border-l-4 p-4">
|
||||||
<p class="text-base-content/70 text-sm whitespace-pre-line">{details}</p>
|
<div class="flex items-start gap-3">
|
||||||
|
<HelpCircle class="text-info h-5 w-5 shrink-0 mt-0.5" strokeWidth={2} />
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-base-content/90 text-sm font-semibold mb-2">
|
||||||
|
{temInstrucoes ? 'Como resolver:' : 'Informação adicional:'}
|
||||||
|
</p>
|
||||||
|
<div class="text-base-content/80 text-sm space-y-2">
|
||||||
|
{#each details.split('\n').filter(line => line.trim().length > 0) as linha (linha)}
|
||||||
|
{#if linha.trim().match(/^\d+\./)}
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<span class="text-info font-semibold shrink-0">{linha.trim().split('.')[0]}.</span>
|
||||||
|
<span class="flex-1 leading-relaxed">{linha.trim().substring(linha.trim().indexOf('.') + 1).trim()}</span>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<p class="leading-relaxed">{linha.trim()}</p>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<div class="modal-action px-6 pb-6">
|
<div class="modal-action border-base-300 border-t px-6 pb-6 pt-4">
|
||||||
<button class="btn btn-primary" onclick={handleClose}> Fechar </button>
|
<button class="btn btn-primary btn-md" onclick={handleClose}> Entendi, obrigado </button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
import ErrorModal from '../ErrorModal.svelte';
|
import ErrorModal from '../ErrorModal.svelte';
|
||||||
import { getAvatarUrl } from '$lib/utils/avatarGenerator';
|
import { getAvatarUrl } from '$lib/utils/avatarGenerator';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
import { traduzirErro } from '$lib/utils/erroHelpers';
|
||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
X,
|
X,
|
||||||
@@ -47,6 +48,7 @@
|
|||||||
let showErrorModal = $state(false);
|
let showErrorModal = $state(false);
|
||||||
let errorTitle = $state('Erro');
|
let errorTitle = $state('Erro');
|
||||||
let errorMessage = $state('');
|
let errorMessage = $state('');
|
||||||
|
let errorInstructions = $state<string | undefined>(undefined);
|
||||||
let errorDetails = $state<string | undefined>(undefined);
|
let errorDetails = $state<string | undefined>(undefined);
|
||||||
|
|
||||||
const chamadaAtivaQuery = useQuery(api.chamadas.obterChamadaAtiva, {
|
const chamadaAtivaQuery = useQuery(api.chamadas.obterChamadaAtiva, {
|
||||||
@@ -146,8 +148,9 @@
|
|||||||
// Funções para chamadas
|
// Funções para chamadas
|
||||||
async function iniciarChamada(tipo: 'audio' | 'video'): Promise<void> {
|
async function iniciarChamada(tipo: 'audio' | 'video'): Promise<void> {
|
||||||
if (chamadaAtual) {
|
if (chamadaAtual) {
|
||||||
errorTitle = 'Chamada Ativa';
|
errorTitle = 'Chamada já em andamento';
|
||||||
errorMessage = 'Já existe uma chamada ativa nesta conversa';
|
errorMessage = 'Já existe uma chamada ativa nesta conversa. Você precisa finalizar a chamada atual antes de iniciar uma nova.';
|
||||||
|
errorInstructions = 'Finalize a chamada atual e tente novamente.';
|
||||||
errorDetails = undefined;
|
errorDetails = undefined;
|
||||||
showErrorModal = true;
|
showErrorModal = true;
|
||||||
return;
|
return;
|
||||||
@@ -166,23 +169,17 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erro ao iniciar chamada:', error);
|
console.error('Erro ao iniciar chamada:', error);
|
||||||
|
|
||||||
// Tratar diferentes tipos de erro
|
// Traduzir erro técnico para mensagem amigável
|
||||||
if (error instanceof Error) {
|
const erroTraduzido = traduzirErro(error);
|
||||||
errorTitle = 'Erro ao Iniciar Chamada';
|
|
||||||
|
errorTitle = erroTraduzido.titulo;
|
||||||
// Verificar se é erro do Convex sobre função não encontrada
|
errorMessage = erroTraduzido.mensagem;
|
||||||
if (error.message.includes('Could not find public function')) {
|
errorInstructions = erroTraduzido.instrucoes;
|
||||||
errorMessage = 'O servidor de chamadas não está sincronizado. Por favor, aguarde alguns segundos e tente novamente.';
|
|
||||||
errorDetails = `Erro técnico: ${error.message}\n\nSugestão: Verifique se o backend Convex está rodando corretamente.`;
|
// Apenas mostrar detalhes técnicos se solicitado e disponível
|
||||||
} else {
|
errorDetails = erroTraduzido.mostrarDetalhesTecnicos && erroTraduzido.detalhesTecnicos
|
||||||
errorMessage = error.message || 'Erro ao iniciar chamada';
|
? erroTraduzido.detalhesTecnicos
|
||||||
errorDetails = error.stack;
|
: undefined;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorTitle = 'Erro ao Iniciar Chamada';
|
|
||||||
errorMessage = 'Ocorreu um erro desconhecido ao iniciar a chamada. Por favor, tente novamente.';
|
|
||||||
errorDetails = String(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
showErrorModal = true;
|
showErrorModal = true;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -193,6 +190,7 @@
|
|||||||
function fecharErrorModal(): void {
|
function fecharErrorModal(): void {
|
||||||
showErrorModal = false;
|
showErrorModal = false;
|
||||||
errorMessage = '';
|
errorMessage = '';
|
||||||
|
errorInstructions = undefined;
|
||||||
errorDetails = undefined;
|
errorDetails = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,6 +630,6 @@
|
|||||||
open={showErrorModal}
|
open={showErrorModal}
|
||||||
title={errorTitle}
|
title={errorTitle}
|
||||||
message={errorMessage}
|
message={errorMessage}
|
||||||
details={errorDetails}
|
details={errorInstructions || errorDetails}
|
||||||
onClose={fecharErrorModal}
|
onClose={fecharErrorModal}
|
||||||
/>
|
/>
|
||||||
|
|||||||
129
apps/web/src/lib/utils/erroHelpers.ts
Normal file
129
apps/web/src/lib/utils/erroHelpers.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* Utilitários para tratar e traduzir erros técnicos em mensagens amigáveis ao usuário
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface MensagemErro {
|
||||||
|
titulo: string;
|
||||||
|
mensagem: string;
|
||||||
|
instrucoes?: string;
|
||||||
|
mostrarDetalhesTecnicos?: boolean;
|
||||||
|
detalhesTecnicos?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traduzir erros técnicos para mensagens amigáveis ao usuário
|
||||||
|
*/
|
||||||
|
export function traduzirErro(error: unknown): MensagemErro {
|
||||||
|
if (!(error instanceof Error)) {
|
||||||
|
return {
|
||||||
|
titulo: 'Erro inesperado',
|
||||||
|
mensagem: 'Ocorreu um erro inesperado. Por favor, tente novamente em alguns instantes.',
|
||||||
|
instrucoes: 'Se o problema persistir, entre em contato com o suporte técnico.',
|
||||||
|
detalhesTecnicos: String(error)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mensagemErro = error.message.toLowerCase();
|
||||||
|
const erroCompleto = error.message;
|
||||||
|
|
||||||
|
// Erro: Função do Convex não encontrada (servidor não sincronizado)
|
||||||
|
if (mensagemErro.includes('could not find public function')) {
|
||||||
|
return {
|
||||||
|
titulo: 'Servidor em atualização',
|
||||||
|
mensagem: 'O sistema está sendo atualizado no momento. Isso geralmente leva apenas alguns segundos.',
|
||||||
|
instrucoes: 'Por favor, aguarde de 10 a 30 segundos e tente iniciar a chamada novamente. Se o problema persistir, recarregue a página (F5).',
|
||||||
|
mostrarDetalhesTecnicos: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erro: Não autenticado
|
||||||
|
if (mensagemErro.includes('não autenticado') || mensagemErro.includes('não autenticado')) {
|
||||||
|
return {
|
||||||
|
titulo: 'Sessão expirada',
|
||||||
|
mensagem: 'Sua sessão expirou. É necessário fazer login novamente para continuar.',
|
||||||
|
instrucoes: 'Você será redirecionado para a tela de login em alguns instantes.',
|
||||||
|
mostrarDetalhesTecnicos: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erro: Permissão negada
|
||||||
|
if (
|
||||||
|
mensagemErro.includes('permissão') ||
|
||||||
|
mensagemErro.includes('não tem permissão') ||
|
||||||
|
mensagemErro.includes('não participa')
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
titulo: 'Acesso negado',
|
||||||
|
mensagem: 'Você não tem permissão para realizar esta ação.',
|
||||||
|
instrucoes: 'Verifique se você faz parte desta conversa ou se possui as permissões necessárias.',
|
||||||
|
mostrarDetalhesTecnicos: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erro: Recurso já existe
|
||||||
|
if (
|
||||||
|
mensagemErro.includes('já existe') ||
|
||||||
|
mensagemErro.includes('já está') ||
|
||||||
|
mensagemErro.includes('ativo')
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
titulo: 'Ação não disponível',
|
||||||
|
mensagem: erroCompleto,
|
||||||
|
instrucoes: 'Verifique o status atual e tente novamente.',
|
||||||
|
mostrarDetalhesTecnicos: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erro: Conexão
|
||||||
|
if (
|
||||||
|
mensagemErro.includes('connection') ||
|
||||||
|
mensagemErro.includes('network') ||
|
||||||
|
mensagemErro.includes('conexão') ||
|
||||||
|
mensagemErro.includes('timeout')
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
titulo: 'Problema de conexão',
|
||||||
|
mensagem: 'Não foi possível conectar com o servidor. Verifique sua conexão com a internet.',
|
||||||
|
instrucoes: 'Verifique se você está conectado à internet e tente novamente. Se o problema persistir, recarregue a página (F5).',
|
||||||
|
mostrarDetalhesTecnicos: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erro: Não encontrado
|
||||||
|
if (mensagemErro.includes('não encontrado') || mensagemErro.includes('not found')) {
|
||||||
|
return {
|
||||||
|
titulo: 'Recurso não encontrado',
|
||||||
|
mensagem: 'O item que você está tentando acessar não foi encontrado.',
|
||||||
|
instrucoes: 'Verifique se o item ainda existe ou se foi removido. Recarregue a página (F5) para atualizar a lista.',
|
||||||
|
mostrarDetalhesTecnicos: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erro genérico com mensagem do sistema
|
||||||
|
if (erroCompleto && erroCompleto.length < 200) {
|
||||||
|
// Se a mensagem for curta e compreensível, usá-la diretamente
|
||||||
|
const mensagemLimpa = erroCompleto
|
||||||
|
.replace(/\[.*?\]/g, '') // Remove tags como [CONVEX M(...)]
|
||||||
|
.replace(/Request ID:.*/i, '') // Remove Request IDs
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (mensagemLimpa.length > 0) {
|
||||||
|
return {
|
||||||
|
titulo: 'Erro ao processar ação',
|
||||||
|
mensagem: mensagemLimpa,
|
||||||
|
instrucoes: 'Por favor, tente novamente. Se o problema persistir, recarregue a página (F5) ou entre em contato com o suporte.',
|
||||||
|
mostrarDetalhesTecnicos: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erro genérico desconhecido
|
||||||
|
return {
|
||||||
|
titulo: 'Erro ao processar ação',
|
||||||
|
mensagem: 'Ocorreu um erro ao processar sua solicitação. Por favor, tente novamente.',
|
||||||
|
instrucoes: 'Se o problema persistir:\n1. Recarregue a página (pressione F5)\n2. Aguarde alguns instantes e tente novamente\n3. Entre em contato com o suporte técnico se o erro continuar',
|
||||||
|
mostrarDetalhesTecnicos: true,
|
||||||
|
detalhesTecnicos: erroCompleto
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user