fix: Portal-E-Mail-Feld konnte nur per Paste befüllt werden
Folge-Symptom des Pentest-29.4-Email-Validators: das Portal-Email-
Input feuerte bei jedem Keystroke einen PUT
/customers/:id/portal mit dem Zwischenstand ("p", "po", "por@") –
der Backend-Validator lehnte das mit 400 ab, der Server-State blieb
unverändert, das Input re-renderte mit dem alten Wert. Effekt: man
konnte nichts tippen, nur per Paste in einem Event eine
vollständige Adresse setzen.
Fix: lokaler emailDraft-State. Während getippt wird, bleibt der
Wert nur im Client. Commit erfolgt erst onBlur oder bei Enter –
oder wird mit Escape verworfen. Bei Mutations-Error gibt's jetzt
auch einen toast statt stiller Revert.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1902,6 +1902,14 @@ function PortalTab({
|
|||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [searchResults, setSearchResults] = useState<CustomerSummary[]>([]);
|
const [searchResults, setSearchResults] = useState<CustomerSummary[]>([]);
|
||||||
const [isSearching, setIsSearching] = useState(false);
|
const [isSearching, setIsSearching] = useState(false);
|
||||||
|
// Lokaler Eingabezustand für die Portal-E-Mail. Wenn wir den Input
|
||||||
|
// direkt vom Server-State (`portal?.portalEmail`) speisen und auf
|
||||||
|
// jedes Keystroke einen PUT feuern, lehnt der Backend-Validator
|
||||||
|
// (Pentest 29.4) die Zwischenstände ("p", "po", "por") als
|
||||||
|
// ungültige E-Mail mit 400 ab → Server-State bleibt unverändert,
|
||||||
|
// Input rendert mit altem Wert → man konnte nur per Paste
|
||||||
|
// tippen. Lokaler State + Commit on Blur löst das.
|
||||||
|
const [emailDraft, setEmailDraft] = useState<string | null>(null);
|
||||||
|
|
||||||
// Lade Portal-Einstellungen
|
// Lade Portal-Einstellungen
|
||||||
const { data: portalData, isLoading: portalLoading } = useQuery({
|
const { data: portalData, isLoading: portalLoading } = useQuery({
|
||||||
@@ -1921,6 +1929,11 @@ function PortalTab({
|
|||||||
customerApi.updatePortalSettings(customerId, data),
|
customerApi.updatePortalSettings(customerId, data),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['customer-portal', customerId] });
|
queryClient.invalidateQueries({ queryKey: ['customer-portal', customerId] });
|
||||||
|
// Lokalen Draft verwerfen – kommt jetzt frisch vom Server.
|
||||||
|
setEmailDraft(null);
|
||||||
|
},
|
||||||
|
onError: (err: Error) => {
|
||||||
|
toast.error(err.message || 'Speichern fehlgeschlagen');
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2036,8 +2049,30 @@ function PortalTab({
|
|||||||
<label className="block text-sm font-medium text-gray-700 mb-1">Portal E-Mail</label>
|
<label className="block text-sm font-medium text-gray-700 mb-1">Portal E-Mail</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Input
|
<Input
|
||||||
value={portal?.portalEmail || ''}
|
value={emailDraft ?? portal?.portalEmail ?? ''}
|
||||||
onChange={(e) => updatePortalMutation.mutate({ portalEmail: e.target.value || null })}
|
onChange={(e) => setEmailDraft(e.target.value)}
|
||||||
|
onBlur={() => {
|
||||||
|
if (emailDraft === null) return;
|
||||||
|
const trimmed = emailDraft.trim();
|
||||||
|
// Nur committen wenn sich was geändert hat – sonst
|
||||||
|
// hauen wir bei jedem Tab-Wechsel einen sinnlosen
|
||||||
|
// PUT raus.
|
||||||
|
if (trimmed === (portal?.portalEmail || '')) {
|
||||||
|
setEmailDraft(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updatePortalMutation.mutate({ portalEmail: trimmed || null });
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
(e.target as HTMLInputElement).blur();
|
||||||
|
}
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
setEmailDraft(null);
|
||||||
|
(e.target as HTMLInputElement).blur();
|
||||||
|
}
|
||||||
|
}}
|
||||||
placeholder="portal@example.com"
|
placeholder="portal@example.com"
|
||||||
disabled={!canEdit || !portal?.portalEnabled}
|
disabled={!canEdit || !portal?.portalEnabled}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
@@ -2045,6 +2080,7 @@ function PortalTab({
|
|||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mt-1">
|
<p className="text-xs text-gray-500 mt-1">
|
||||||
Diese E-Mail wird für den Login ins Kundenportal verwendet.
|
Diese E-Mail wird für den Login ins Kundenportal verwendet.
|
||||||
|
Speichern erfolgt automatisch beim Verlassen des Felds (oder Enter).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user