fix(native): human error messages + kind override checkbox in AddDomainSheet
Two related fixes after the user saw a raw 400 JSON dump in the sheet
("API 400: { error: true, message: 'Eintrag bereits vorhanden' … }").
1. apiFetch now extracts the prettiest available message from the
response body (data.message → message → statusMessage → raw text →
bare status code) and throws an Error whose .message is that string
only. Stashes the structured pieces on the Error too (.code, .data,
.status) so callers that switch on error codes still have them, but
the default `e?.message` path delivers a clean human sentence.
2. AddDomainSheet maps the known error codes to localized strings —
WEB_LIMIT_REACHED / MAIL_LIMIT_REACHED / INVALID_MAIL_DOMAIN /
DISPLAY_NAME_NOT_SUPPORTED / INVALID_DOMAIN / "Eintrag bereits
vorhanden" (duplicate) — and falls back to a generic copy if the
code is unknown. The raw API JSON never appears in the UI again.
Plus the kind-override checkbox: the auto-detect (input contains "@" →
mail, contains "." → web) is fine for the typical case but a user can
type a clean domain and still want it filtered against mail senders
(e.g. they know "casino.de" is also their casino's sender domain).
The new pill below the preview toggles between mail and web, defaults
to whatever auto-detect said, and resets when the input is cleared. The
local-part strip still runs for mail-mode so the stored value stays a
domain.
i18n: error_invalid_mail / error_invalid_input / error_duplicate /
kind_override_label across DE/EN/FR.
This commit is contained in:
parent
80d89303f5
commit
0a35b58cd9
@ -1,4 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
@ -47,13 +47,22 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
|
||||
const [confirmPermanent, setConfirmPermanent] = useState(false);
|
||||
const [adding, setAdding] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
// User-Override über den Auto-Detect. null = follow auto-detect, sonst forced.
|
||||
const [kindOverride, setKindOverride] = useState<'web' | 'mail' | null>(null);
|
||||
|
||||
const kind = detectKind(input);
|
||||
const detected = detectKind(input);
|
||||
const kind: 'web' | 'mail' | null = kindOverride ?? detected;
|
||||
const normalizedWeb = kind === 'web' ? normalizeDomain(input) : '';
|
||||
const normalizedMail = kind === 'mail' ? mailDomain(input) : '';
|
||||
|
||||
// Reset override sobald User komplett neuen Input tippt
|
||||
useEffect(() => {
|
||||
if (!input) setKindOverride(null);
|
||||
}, [input]);
|
||||
|
||||
function close() {
|
||||
setInput('');
|
||||
setKindOverride(null);
|
||||
setConfirmPermanent(false);
|
||||
setError(null);
|
||||
onClose();
|
||||
@ -83,12 +92,23 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
|
||||
}
|
||||
if (result.alreadyGlobal) {
|
||||
setError(t('blocker.add_sheet_already_global', { domain: pattern }));
|
||||
} else if (result.error?.includes('WEB_LIMIT_REACHED')) {
|
||||
setError(t('blocker.error_web_limit_reached'));
|
||||
} else if (result.error?.includes('MAIL_LIMIT_REACHED')) {
|
||||
setError(t('blocker.error_mail_limit_reached'));
|
||||
} else {
|
||||
setError(result.error ?? t('blocker.add_sheet_add_failed'));
|
||||
const raw = (result.error ?? '').toLowerCase();
|
||||
if (raw.includes('web_limit_reached')) {
|
||||
setError(t('blocker.error_web_limit_reached'));
|
||||
} else if (raw.includes('mail_limit_reached')) {
|
||||
setError(t('blocker.error_mail_limit_reached'));
|
||||
} else if (raw.includes('invalid_mail_domain') || raw.includes('display_name_not_supported')) {
|
||||
setError(t('blocker.error_invalid_mail'));
|
||||
} else if (raw.includes('invalid_domain') || raw.includes('invalid_pattern')) {
|
||||
setError(t('blocker.error_invalid_input'));
|
||||
} else if (raw.includes('eintrag bereits vorhanden') || raw.includes('duplicate')) {
|
||||
setError(t('blocker.error_duplicate'));
|
||||
} else {
|
||||
// Letzter Fallback: niemals raw JSON anzeigen. Wenn message-Feld da war, kommt's
|
||||
// sauber als String an — sonst generic Fehler.
|
||||
setError(t('blocker.add_sheet_add_failed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,6 +202,41 @@ export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) {
|
||||
t={t}
|
||||
/>
|
||||
|
||||
{/* Override toggle — User kann Auto-Detect korrigieren falls falsch erkannt */}
|
||||
{detected !== null && (
|
||||
<TouchableOpacity
|
||||
onPress={() => setKindOverride(kind === 'mail' ? 'web' : 'mail')}
|
||||
activeOpacity={0.7}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 12,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.border,
|
||||
backgroundColor: colors.surface,
|
||||
}}
|
||||
>
|
||||
<Ionicons
|
||||
name={kind === 'mail' ? 'checkbox' : 'square-outline'}
|
||||
size={20}
|
||||
color={kind === 'mail' ? colors.brandOrange : colors.textMuted}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
fontSize: 13,
|
||||
fontFamily: 'Nunito_600SemiBold',
|
||||
color: colors.text,
|
||||
}}
|
||||
>
|
||||
{t('blocker.kind_override_label')}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
|
||||
{/* Warning card */}
|
||||
<View
|
||||
style={{
|
||||
|
||||
@ -77,7 +77,31 @@ export async function apiFetch<T = any>(
|
||||
} catch {}
|
||||
}
|
||||
|
||||
throw new Error(`API ${res.status}: ${text}`);
|
||||
// Throw a human-readable Error.message. Backend `createError({ data: { error, message } })`
|
||||
// serialises into { error: true, statusCode, statusMessage, message?, data: {…} }.
|
||||
// Caller code only ever displays `e.message`, so collapse the prettiest field into
|
||||
// it; stash the rest on the Error so callers that want to switch on error_code
|
||||
// (e.g. WEB_LIMIT_REACHED, ALREADY_GLOBAL) can still inspect `(e as any).code`.
|
||||
let humanMessage = `API ${res.status}`;
|
||||
let errorCode: string | undefined;
|
||||
let errorData: any;
|
||||
try {
|
||||
const parsed = JSON.parse(text);
|
||||
errorData = parsed?.data ?? parsed;
|
||||
errorCode = errorData?.error ?? parsed?.statusMessage;
|
||||
humanMessage =
|
||||
errorData?.message ??
|
||||
parsed?.message ??
|
||||
parsed?.statusMessage ??
|
||||
humanMessage;
|
||||
} catch {
|
||||
if (text) humanMessage = text;
|
||||
}
|
||||
const err = new Error(humanMessage);
|
||||
(err as any).status = res.status;
|
||||
(err as any).code = errorCode;
|
||||
(err as any).data = errorData;
|
||||
throw err;
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
@ -338,6 +338,10 @@
|
||||
"count_label": "%{count}/%{max}",
|
||||
"error_web_limit_reached": "Du hast alle Domain-Slots aufgebraucht. Entferne eine Domain oder upgrade auf Pro/Legend.",
|
||||
"error_mail_limit_reached": "Du hast alle Mail-Slots aufgebraucht. Entferne ein Mail-Pattern oder upgrade auf Pro/Legend.",
|
||||
"error_invalid_mail": "Bitte eine vollständige Mail-Adresse oder Mail-Domain eingeben (z.B. info@only4-subscribers.com).",
|
||||
"error_invalid_input": "Bitte eine gültige Domain oder Mail-Adresse eingeben.",
|
||||
"error_duplicate": "Diesen Eintrag hast du schon — er ist bereits in deiner Filter-Liste.",
|
||||
"kind_override_label": "Das ist eine E-Mail-Adresse / Mail-Absender",
|
||||
"empty_web": "Noch keine eigenen Domains.\nTippe + um eine hinzuzufügen.",
|
||||
"empty_mail": "Noch keine Mail-Domains. Tippe + um eine E-Mail-Adresse oder Domain zu blockieren."
|
||||
},
|
||||
|
||||
@ -338,6 +338,10 @@
|
||||
"count_label": "%{count}/%{max}",
|
||||
"error_web_limit_reached": "You've used all your domain slots. Remove a domain or upgrade to Pro/Legend.",
|
||||
"error_mail_limit_reached": "You've used all your email slots. Remove an email pattern or upgrade to Pro/Legend.",
|
||||
"error_invalid_mail": "Please enter a full email address or mail domain (e.g. info@only4-subscribers.com).",
|
||||
"error_invalid_input": "Please enter a valid domain or email address.",
|
||||
"error_duplicate": "You've already added this entry — it's in your filter list.",
|
||||
"kind_override_label": "This is an email address / mail sender",
|
||||
"empty_web": "No custom domains yet.\nTap + to add one.",
|
||||
"empty_mail": "No mail domains yet. Tap + to block an email address or domain."
|
||||
},
|
||||
|
||||
@ -338,6 +338,10 @@
|
||||
"count_label": "%{count}/%{max}",
|
||||
"error_web_limit_reached": "Vous avez utilisé tous vos emplacements de domaines. Supprimez un domaine ou passez à Pro/Legend.",
|
||||
"error_mail_limit_reached": "Vous avez utilisé tous vos emplacements e-mail. Supprimez un modèle ou passez à Pro/Legend.",
|
||||
"error_invalid_mail": "Veuillez saisir une adresse e-mail complète ou un domaine mail (ex. info@only4-subscribers.com).",
|
||||
"error_invalid_input": "Veuillez saisir un domaine ou une adresse e-mail valide.",
|
||||
"error_duplicate": "Vous avez déjà ajouté cette entrée — elle est dans votre liste de filtres.",
|
||||
"kind_override_label": "C'est une adresse e-mail / expéditeur mail",
|
||||
"empty_web": "Aucun domaine personnalisé.\nAppuyez sur + pour en ajouter un.",
|
||||
"empty_mail": "Aucun domaine mail. Appuyez sur + pour bloquer une adresse ou un domaine."
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user