import { useEffect, useState } from 'react'; import { ActivityIndicator, ScrollView, Text, TextInput, TouchableOpacity, View, } from 'react-native'; import { Image } from 'expo-image'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { isValidDomain, normalizeDomain, type AddDomainResult, type Tier, } from '../../hooks/useCustomDomains'; import { useColors, type ColorScheme } from '../../lib/theme'; import { FormSheet } from '../FormSheet'; type Props = { visible: boolean; tier: Tier; onClose: () => void; onAdd: ( pattern: string, kind?: 'web' | 'mail', opts?: { addToVip?: boolean }, ) => Promise; }; function detectKind(input: string): 'web' | 'mail' | null { const raw = input.trim(); if (!raw) return null; if (raw.includes('@')) return 'mail'; if (raw.includes('.')) return 'web'; return null; } function mailDomain(input: string): string { const raw = input.trim(); const atIdx = raw.lastIndexOf('@'); if (atIdx === -1) return raw.toLowerCase(); return raw.slice(atIdx + 1).trim().toLowerCase(); } export function AddDomainSheet({ visible, tier, onClose, onAdd }: Props) { const { t } = useTranslation(); const colors = useColors(); const [input, setInput] = useState(''); 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); // Fall 3: Domain ist in Layer 1, aber nicht in der kuratierten VIP. Hält die // Domain fest, für die der User entscheiden soll, ob er sie zur VIP nimmt. const [vipPrompt, setVipPrompt] = useState(null); const detected = detectKind(input); const kind: 'web' | 'mail' | null = kindOverride ?? detected; const normalizedWeb = kind === 'web' ? normalizeDomain(input) : ''; const normalizedMail = kind === 'mail' ? mailDomain(input) : ''; const inVipMode = vipPrompt !== null; // Reset override sobald User komplett neuen Input tippt useEffect(() => { if (!input) setKindOverride(null); }, [input]); function close() { setInput(''); setKindOverride(null); setConfirmPermanent(false); setError(null); setVipPrompt(null); onClose(); } function isInputValid(): boolean { if (kind === 'web') return isValidDomain(input); if (kind === 'mail') return normalizedMail.length > 0; return false; } async function handleAdd() { if (!isInputValid() || !confirmPermanent || adding) return; setAdding(true); setError(null); const pattern = kind === 'web' ? normalizeDomain(input) : normalizedMail; // Pass kind explicitly — we've already stripped the local-part for mail, // so the backend's auto-detect (which keys on the "@" character) can no // longer infer the type from the pattern alone. const result = await onAdd(pattern, kind === 'mail' ? 'mail' : 'web'); setAdding(false); if (result.ok) { close(); return; } // Fall 3: in Layer 1, nicht in kuratierter VIP → User entscheidet if (result.inGlobalNotVip) { setVipPrompt(pattern); return; } // Fall 2: schon voll geschützt (Layer 1 + kuratierte VIP) if (result.alreadyProtected) { setError(t('blocker.add_sheet_already_protected', { domain: pattern })); return; } if (result.alreadyGlobal) { setError(t('blocker.add_sheet_already_global', { domain: pattern })); return; } const raw = (result.error ?? '').toLowerCase(); if (raw.includes('public_domain')) { setError(t('blocker.error_public_domain')); } else if (raw.includes('limit_reached')) { setError(t('blocker.error_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 { setError(t('blocker.add_sheet_add_failed')); } } // Fall 3 bestätigt: Domain als VIP-Zweitschutz aufnehmen (Backend speichert // sie als 'approved' — kein Slot, erscheint nur in der VIP-Liste). async function handleConfirmVip() { if (!vipPrompt || adding) return; setAdding(true); setError(null); const result = await onAdd(vipPrompt, 'web', { addToVip: true }); setAdding(false); if (result.ok) { close(); return; } setVipPrompt(null); setError(t('blocker.add_sheet_add_failed')); } const warningText = tier.plan === 'free' ? t('blocker.add_sheet_warning_free') : t('blocker.add_sheet_warning_pro'); const canSubmitNormal = isInputValid() && confirmPermanent && !adding; const ctaEnabled = inVipMode ? !adding : canSubmitNormal; const ctaColor = inVipMode ? colors.brandOrange : canSubmitNormal ? '#dc2626' : '#d4d4d4'; return ( {/* Input field */} {t('blocker.add_sheet_label')} { setInput(v); setError(null); setVipPrompt(null); }} editable={!inVipMode} placeholder={t('blocker.add_sheet_placeholder')} placeholderTextColor={colors.textMuted} keyboardType="email-address" autoCapitalize="none" autoCorrect={false} style={{ backgroundColor: colors.surfaceElevated, borderRadius: 10, padding: 12, fontSize: 14, fontFamily: 'Nunito_400Regular', color: colors.text, borderWidth: 1, borderColor: error ? '#dc2626' : colors.border, opacity: inVipMode ? 0.6 : 1, }} /> {error && ( {error} )} {inVipMode ? ( /* Fall 3: Erklärungs-Karte — Domain ist in Layer 1, kann zusätzlich in den VIP-Zweitschutz aufgenommen werden. */ {t('blocker.add_sheet_in_global_not_vip', { domain: vipPrompt ?? '' })} ) : ( <> {/* Help text */} {t('blocker.add_sheet_help')} {/* Preview card — only when user has typed something */} {input.trim().length > 0 && ( )} {/* 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 */} {warningText} {/* Confirm checkbox */} setConfirmPermanent((v) => !v)} activeOpacity={0.7} style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 10, paddingVertical: 4, }} > {confirmPermanent && } {t('blocker.add_sheet_confirm_permanent')} )} {/* Buttons */} {t('common.cancel')} {adding ? ( ) : ( {inVipMode ? t('blocker.add_sheet_add_to_vip_cta') : t('blocker.add_sheet_cta')} )} ); } // ─── PreviewCard ────────────────────────────────────────────────────────────── function PreviewCard({ kind, normalizedWeb, normalizedMail, placeholder, colors, t, }: { kind: 'web' | 'mail' | null; normalizedWeb: string; normalizedMail: string; placeholder: string; colors: ColorScheme; t: (key: string, opts?: Record) => string; }) { if (kind === 'web') { return ( {t('blocker.preview_web', { value: normalizedWeb || '…' })} ); } if (kind === 'mail') { return ( {t('blocker.preview_mail', { value: normalizedMail || '…' })} ); } return ( {t('blocker.preview_invalid')} ); }