From cf4370c9056cfe8d2b67b593adf0a0a5f91b45d7 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Thu, 23 Apr 2026 15:16:04 +0200 Subject: [PATCH] Toast-Benachrichtigungen bei IMAP-Sync- und SMTP-Send-Fehlern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bisher blieb ein fehlgeschlagener IMAP-Sync oder E-Mail-Versand still – der User sah nur im Browser-Devtools, dass etwas schief lief. Jetzt erscheint eine rote Toast-Benachrichtigung (8 Sekunden) mit der konkreten Fehlermeldung des Servers, z.B. 'Sync fehlgeschlagen: IMAP-Authentifizierung fehlgeschlagen: NO [AUTHENTICATIONFAILED]'. EmailClientTab (Synchronisieren-Button): - toast.success bei erfolgreichem Sync - toast.error bei Fehler + bei Backend-Response mit success=false ComposeEmailModal (Senden): - toast.success bei erfolgreichem Versand - toast.error bei SMTP-Fehler mit Server-Response (zusätzlich zum Inline-Fehler) Außerdem im imapService.testImapConnection: - Roh-Error wird jetzt geloggt (code, response, responseStatus, authenticationFailed) - ImapFlow-spezifische Felder werden in die Fehlermeldung übernommen, sodass z.B. '2 NO [AUTHENTICATIONFAILED] Authentication failed.' direkt sichtbar wird Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/src/services/imapService.ts | 27 +++++++++++++++++++ .../components/email/ComposeEmailModal.tsx | 19 ++++++++++--- .../src/components/email/EmailClientTab.tsx | 14 +++++++++- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/backend/src/services/imapService.ts b/backend/src/services/imapService.ts index 02dcbfee..88ba25ac 100644 --- a/backend/src/services/imapService.ts +++ b/backend/src/services/imapService.ts @@ -297,9 +297,36 @@ export async function testImapConnection(credentials: ImapCredentials): Promise< // Ignorieren } + // Rohes Error-Objekt loggen, damit wir ImapFlow-spezifische Felder sehen + console.error('[testImapConnection] Raw error:', error); + if (error && typeof error === 'object') { + const e = error as any; + console.error('[testImapConnection] Details:', { + code: e.code, + response: e.response, + responseStatus: e.responseStatus, + responseText: e.responseText, + authenticationFailed: e.authenticationFailed, + serverResponseCode: e.serverResponseCode, + }); + } + if (error instanceof Error) { const msg = error.message.toLowerCase(); const errorCode = (error as NodeJS.ErrnoException).code?.toLowerCase() || ''; + const e = error as any; + + // ImapFlow-spezifische Details durchreichen wenn vorhanden + if (e.authenticationFailed) { + throw new Error( + `IMAP-Authentifizierung fehlgeschlagen${e.response ? `: ${e.response}` : ''}`, + ); + } + if (e.response || e.responseText) { + throw new Error( + `IMAP ${e.responseStatus || 'Fehler'}: ${e.response || e.responseText}`, + ); + } if (msg.includes('authentication') || msg.includes('login')) { throw new Error('IMAP-Authentifizierung fehlgeschlagen'); diff --git a/frontend/src/components/email/ComposeEmailModal.tsx b/frontend/src/components/email/ComposeEmailModal.tsx index ee8d9844..30f14b23 100644 --- a/frontend/src/components/email/ComposeEmailModal.tsx +++ b/frontend/src/components/email/ComposeEmailModal.tsx @@ -1,5 +1,6 @@ import { useState, useRef, useEffect } from 'react'; import { Send, Paperclip, X, FileText } from 'lucide-react'; +import toast from 'react-hot-toast'; import Modal from '../ui/Modal'; import Button from '../ui/Button'; import { stressfreiEmailApi, CachedEmail, MailboxAccount, EmailAttachment } from '../../services/api'; @@ -150,12 +151,24 @@ export default function ComposeEmailModal({ attachments: attachments.length > 0 ? attachments : undefined, contractId, }), - onSuccess: () => { + onSuccess: (result) => { + // Backend kann success=false zurückgeben auch bei HTTP 200 + if (result && (result as any).success === false) { + const msg = (result as any).error || 'E-Mail-Versand fehlgeschlagen'; + setError(msg); + toast.error(`SMTP-Fehler: ${msg}`, { duration: 8000 }); + return; + } + toast.success('E-Mail versendet'); onSuccess?.(); handleClose(); }, - onError: (err) => { - setError(err instanceof Error ? err.message : 'Fehler beim Senden'); + onError: (err: any) => { + const msg = + err?.response?.data?.error || + (err instanceof Error ? err.message : 'Fehler beim Senden'); + setError(msg); + toast.error(`SMTP-Fehler: ${msg}`, { duration: 8000 }); }, }); diff --git a/frontend/src/components/email/EmailClientTab.tsx b/frontend/src/components/email/EmailClientTab.tsx index d404c050..0a048d93 100644 --- a/frontend/src/components/email/EmailClientTab.tsx +++ b/frontend/src/components/email/EmailClientTab.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react'; import { RefreshCw, Plus, Mail, Inbox, Send, Trash2 } from 'lucide-react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import toast from 'react-hot-toast'; import { cachedEmailApi, stressfreiEmailApi, CachedEmail } from '../../services/api'; import { useAuth } from '../../context/AuthContext'; import Button from '../ui/Button'; @@ -95,7 +96,14 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) { // Synchronisation const syncMutation = useMutation({ mutationFn: (accountId: number) => stressfreiEmailApi.syncEmails(accountId), - onSuccess: () => { + onSuccess: (result) => { + // Backend liefert success=false bei IMAP-Fehler, aber ohne HTTP-Error + if (result && (result as any).success === false) { + const err = (result as any).error || 'IMAP-Synchronisation fehlgeschlagen'; + toast.error(`Sync fehlgeschlagen: ${err}`, { duration: 8000 }); + return; + } + toast.success('E-Mails synchronisiert'); // E-Mail-Listen neu laden queryClient.invalidateQueries({ queryKey: ['emails'] }); // Ordner-Anzahlen aktualisieren @@ -103,6 +111,10 @@ export default function EmailClientTab({ customerId }: EmailClientTabProps) { // Mailbox-Accounts aktualisieren queryClient.invalidateQueries({ queryKey: ['mailbox-accounts', customerId] }); }, + onError: (error: any) => { + const msg = error?.response?.data?.error || error?.message || 'Unbekannter Fehler'; + toast.error(`Sync fehlgeschlagen: ${msg}`, { duration: 8000 }); + }, }); const handleSync = () => {