import { useRef, useEffect, useState, useMemo } from 'react'; import { View, Text, TouchableOpacity, Animated, ActivityIndicator } from 'react-native'; import { Image } from 'expo-image'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { useColors, type ColorScheme } from '../../lib/theme'; import { RemoveDomainSheet } from './RemoveDomainSheet'; import type { CustomDomain } from '../../hooks/useCustomDomains'; type Props = { domains: CustomDomain[]; webCount: number; webLimit: number; globalBlocklistCount: number; onAddPress: () => void; onRemoveDomain: (id: string) => Promise; colors: ColorScheme; }; /** * VIP-Sektion: "Meine geblockten Seiten". * * Zeigt eigene Web-Custom-Domains des Users (kind='web') + Zähler (X / Limit). * Domain entfernen → 3-Click Friction via RemoveDomainSheet (selbes Pattern wie * Schutz-Deaktivieren). Domain hinzufügen → frei via onAddPress. * Darunter: ruhiger Hinweis auf die automatische globale Blocklist. */ export function VipDomainList({ domains, webCount, webLimit, globalBlocklistCount, onAddPress, onRemoveDomain, colors, }: Props) { const { t } = useTranslation(); const webDomains = useMemo( () => domains.filter((d) => (d.type === 'web' || !d.type) && d.status !== 'approved'), [domains], ); const atLimit = webCount >= webLimit; const fillAnim = useRef(new Animated.Value(0)).current; const ratio = webLimit > 0 ? Math.min(webCount / webLimit, 1) : 0; useEffect(() => { Animated.timing(fillAnim, { toValue: ratio, duration: 380, useNativeDriver: false }).start(); }, [ratio]); const pct = ratio * 100; const barColor = pct >= 90 ? colors.error : pct >= 60 ? colors.warning : colors.brandOrange; const badgeBg = atLimit ? '#fee2e2' : colors.surfaceElevated; const badgeFg = atLimit ? colors.error : colors.textMuted; return ( {/* Header */} {t('blocker.vip_section_title')} {t('blocker.count_label', { count: webCount, max: webLimit })} {/* Progress bar */} {/* Domain list or empty state */} {webDomains.length === 0 ? ( ) : ( )} {/* Global list hint — ruhig, nicht als Casino-Trigger */} {t('blocker.vip_global_hint', { count: globalBlocklistCount })} ); } // ─── Empty State ────────────────────────────────────────────────────────────── function VipEmptyState({ onAddPress, colors }: { onAddPress: () => void; colors: ColorScheme }) { const { t } = useTranslation(); return ( {t('blocker.vip_empty')} ); } // ─── Domain Tiles ───────────────────────────────────────────────────────────── function VipDomainTiles({ domains, onRemoveDomain, }: { domains: CustomDomain[]; onRemoveDomain: (id: string) => Promise; }) { return ( {domains.map((d) => ( ))} ); } function VipDomainTile({ domain, onRemove, }: { domain: CustomDomain; onRemove: (id: string) => Promise; }) { const { t } = useTranslation(); const colors = useColors(); const [imgError, setImgError] = useState(false); const [removeSheetOpen, setRemoveSheetOpen] = useState(false); const [removing, setRemoving] = useState(false); const stripped = domain.domain.replace(/^www\./, ''); const statusColor: string = (() => { switch (domain.status) { case 'submitted': return colors.warning; case 'rejected': return colors.error; default: return colors.brandOrange; } })(); const badgeLabel: string = (() => { switch (domain.status) { case 'submitted': return t('blocker.domain_badge_pruefung'); case 'rejected': return t('blocker.domain_badge_rejected'); default: return t('blocker.domain_badge_active'); } })(); async function handleConfirmRemove() { setRemoving(true); try { await onRemove(domain.id); } finally { setRemoving(false); } } return ( <> {/* Status badge row */} {badgeLabel} {/* Favicon + domain name */} {!imgError ? ( setImgError(true)} /> ) : ( {stripped.slice(0, 2).toUpperCase()} )} {stripped} {/* Remove button */} setRemoveSheetOpen(true)} disabled={removing} activeOpacity={0.65} style={{ height: 26, borderRadius: 6, borderWidth: 1, borderColor: colors.border, alignItems: 'center', justifyContent: 'center', }} > {removing ? ( ) : ( )} setRemoveSheetOpen(false)} onConfirmRemove={handleConfirmRemove} /> ); }