fix(stressfrei): sync-forwarding sichtbar + Passwort-Push + Toast-Meldungen
Drei Verbesserungen am gestrigen Sync-Feature:
1. Bug-Fix: isProvisioned wurde nie auf true gesetzt
`createEmail` mit `provisionAtProvider: true` hat das Flag
`isProvisioned` nie gesetzt → blieb auf @default(false). Damit
blieb der Refresh-Button in der UI unsichtbar (Bedingung
`emailItem.isProvisioned`). Jetzt:
- createEmail setzt isProvisioned + provisionedAt korrekt
- Self-Healing: syncForwardingForEmail setzt das Flag nachträglich
auf true sobald der Provider-Aufruf erfolgreich war (Backfill
für historisch falsch markierte Einträge)
- UI-Sichtbarkeit: Bedingung entfernt – der Button erscheint jetzt
immer; ein Klick auf eine nicht-provisionierte Adresse liefert
eine sprechende Fehlermeldung statt stiller Verstecken
2. Passwort-Push bei hasMailbox: true
Bisher wurden nur die Forwards aktualisiert. Jetzt entschlüsselt
syncForwardingForEmail bei Mailbox-Adressen zusätzlich das im CRM
gespeicherte Passwort und setzt es am Provider neu – Self-Healing
für IMAP/SMTP-Logins falls jemand im Plesk-UI manuell ein anderes
Passwort gesetzt hat. Response enthält `passwordReset: true` als
Marker.
3. react-hot-toast statt alert()
Erfolgs-Toast listet die neu gesetzten Forward-Targets + Hinweis
ob Passwort-Reset durchgeführt wurde. Fehler-Toast zeigt die
Backend-Fehlermeldung (z.B. „E-Mail-Adresse beim Provider nicht
gefunden – wurde sie dort gelöscht?").
Audit-Log-Label enthält jetzt sowohl Forwards als auch Passwort-Reset-
Marker, damit der Vorgang im AuditLog nachvollziehbar bleibt.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import { useParams, Link, useNavigate, useSearchParams, useLocation } from 'react-router-dom';
|
||||
import { pushHistory, popHistory } from '../../utils/navigation';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import toast from 'react-hot-toast';
|
||||
import { customerApi, addressApi, bankCardApi, documentApi, meterApi, uploadApi, contractApi, stressfreiEmailApi, emailProviderApi, gdprApi, StressfreiEmail, ContractTreeNode } from '../../services/api';
|
||||
import { EmailClientTab } from '../../components/email';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
@@ -2976,21 +2977,26 @@ function StressfreiEmailsTab({
|
||||
});
|
||||
|
||||
// Weiterleitungen am Provider neu setzen (Stamm-Email-Wechsel-Use-Case).
|
||||
// Wenn die Adresse hasMailbox=true ist, wird zusätzlich das im CRM
|
||||
// hinterlegte Passwort am Provider neu gesetzt (Self-Healing nach
|
||||
// manuellen Eingriffen am Provider).
|
||||
const syncForwardingMutation = useMutation({
|
||||
mutationFn: stressfreiEmailApi.syncForwarding,
|
||||
onSuccess: (res) => {
|
||||
const targets = res?.data?.forwardTargets || [];
|
||||
alert(
|
||||
targets.length > 0
|
||||
? `Weiterleitungen aktualisiert:\n${targets.map((t) => `• ${t}`).join('\n')}`
|
||||
: 'Weiterleitungen aktualisiert.',
|
||||
);
|
||||
const passwordReset = res?.data?.passwordReset;
|
||||
const lines = [
|
||||
'Weiterleitungen aktualisiert:',
|
||||
...targets.map((t) => `• ${t}`),
|
||||
];
|
||||
if (passwordReset) lines.push('Mailbox-Passwort am Provider neu gesetzt.');
|
||||
toast.success(lines.join('\n'), { duration: 5000 });
|
||||
queryClient.invalidateQueries({ queryKey: ['customer', customerId] });
|
||||
},
|
||||
onError: (err: any) => {
|
||||
alert(
|
||||
'Fehler beim Aktualisieren der Weiterleitungen:\n' +
|
||||
(err?.response?.data?.error || err?.message || 'Unbekannter Fehler'),
|
||||
toast.error(
|
||||
err?.response?.data?.error || err?.message || 'Fehler beim Aktualisieren der Weiterleitungen',
|
||||
{ duration: 6000 },
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -3054,30 +3060,38 @@ function StressfreiEmailsTab({
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
{emailItem.isProvisioned && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={syncForwardingMutation.isPending}
|
||||
onClick={() => {
|
||||
if (
|
||||
confirm(
|
||||
'Weiterleitungen für ' + emailItem.email + ' jetzt neu setzen?\n\n' +
|
||||
'Alle bestehenden Weiterleitungen werden ersetzt durch:\n' +
|
||||
'• die aktuelle Stamm-E-Mail des Kunden\n' +
|
||||
'• unsere Service-Weiterleitungsadresse aus den Provider-Einstellungen',
|
||||
)
|
||||
) {
|
||||
syncForwardingMutation.mutate(emailItem.id);
|
||||
}
|
||||
}}
|
||||
title="Weiterleitungen synchronisieren – ersetzt die Forwards am Provider durch (Kunden-Stamm-E-Mail + Service-Adresse). Nützlich nach Änderung der Stamm-E-Mail."
|
||||
>
|
||||
<RefreshCw
|
||||
className={`w-4 h-4 ${syncForwardingMutation.isPending ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={syncForwardingMutation.isPending}
|
||||
onClick={() => {
|
||||
const lines = [
|
||||
`Weiterleitungen für ${emailItem.email} jetzt neu setzen?`,
|
||||
'',
|
||||
'Alle bestehenden Weiterleitungen am Provider werden ersetzt durch:',
|
||||
'• die aktuelle Stamm-E-Mail des Kunden',
|
||||
'• unsere Service-Weiterleitungsadresse aus den Provider-Einstellungen',
|
||||
];
|
||||
if (emailItem.hasMailbox) {
|
||||
lines.push(
|
||||
'',
|
||||
'Zusätzlich wird das im CRM hinterlegte Mailbox-Passwort am Provider neu gesetzt.',
|
||||
);
|
||||
}
|
||||
if (confirm(lines.join('\n'))) {
|
||||
syncForwardingMutation.mutate(emailItem.id);
|
||||
}
|
||||
}}
|
||||
title={
|
||||
emailItem.hasMailbox
|
||||
? 'Weiterleitungen + Mailbox-Passwort synchronisieren. Nützlich nach Änderung der Kunden-Stamm-E-Mail oder nach manuellem Eingriff am Provider.'
|
||||
: 'Weiterleitungen synchronisieren – ersetzt die Forwards am Provider durch (Kunden-Stamm-E-Mail + Service-Adresse). Nützlich nach Änderung der Stamm-E-Mail.'
|
||||
}
|
||||
>
|
||||
<RefreshCw
|
||||
className={`w-4 h-4 ${syncForwardingMutation.isPending ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
</Button>
|
||||
{emailItem.isActive ? (
|
||||
<Button
|
||||
variant="ghost"
|
||||
|
||||
@@ -427,11 +427,15 @@ export const stressfreiEmailApi = {
|
||||
const res = await api.post<ApiResponse<{ password: string }>>(`/stressfrei-emails/${id}/reset-password`);
|
||||
return res.data;
|
||||
},
|
||||
// Weiterleitungen neu setzen (z.B. nach Änderung der Kunden-Stamm-E-Mail)
|
||||
// Weiterleitungen neu setzen (z.B. nach Änderung der Kunden-Stamm-E-Mail).
|
||||
// Wenn die Adresse hasMailbox=true ist, wird zusätzlich das im CRM
|
||||
// hinterlegte Passwort am Provider neu gesetzt (Self-Healing).
|
||||
syncForwarding: async (id: number) => {
|
||||
const res = await api.post<ApiResponse<{ forwardTargets: string[]; customerEmail: string }>>(
|
||||
`/stressfrei-emails/${id}/sync-forwarding`,
|
||||
);
|
||||
const res = await api.post<ApiResponse<{
|
||||
forwardTargets: string[];
|
||||
customerEmail: string;
|
||||
passwordReset?: boolean;
|
||||
}>>(`/stressfrei-emails/${id}/sync-forwarding`);
|
||||
return res.data;
|
||||
},
|
||||
// E-Mails synchronisieren
|
||||
|
||||
Reference in New Issue
Block a user