diff --git a/apps/rebreak-native/components/blocker/AddDomainSheet.tsx b/apps/rebreak-native/components/blocker/AddDomainSheet.tsx index 1c7bcbd..bfb77c4 100644 --- a/apps/rebreak-native/components/blocker/AddDomainSheet.tsx +++ b/apps/rebreak-native/components/blocker/AddDomainSheet.tsx @@ -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(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 && ( + 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, + }} + > + + + {t('blocker.kind_override_label')} + + + )} + {/* Warning card */} ( } 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(); diff --git a/apps/rebreak-native/locales/de.json b/apps/rebreak-native/locales/de.json index 6274109..e8c0665 100644 --- a/apps/rebreak-native/locales/de.json +++ b/apps/rebreak-native/locales/de.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." }, diff --git a/apps/rebreak-native/locales/en.json b/apps/rebreak-native/locales/en.json index e3ddb2a..07c28e3 100644 --- a/apps/rebreak-native/locales/en.json +++ b/apps/rebreak-native/locales/en.json @@ -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." }, diff --git a/apps/rebreak-native/locales/fr.json b/apps/rebreak-native/locales/fr.json index 4ffea53..441a13a 100644 --- a/apps/rebreak-native/locales/fr.json +++ b/apps/rebreak-native/locales/fr.json @@ -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." },