import { useState } from 'react'; import { ActivityIndicator, Linking, ScrollView, Switch, Text, TouchableOpacity, View, } from 'react-native'; import { Ionicons } from '@expo/vector-icons'; import { useTranslation } from 'react-i18next'; import { useMailConnect, detectProvider, type MailProvider } from '../../hooks/useMailConnect'; import { humanizeMailError } from '../../lib/mailErrors'; import { apiFetch } from '../../lib/api'; import { useColors } from '../../lib/theme'; import { FormSheet } from '../FormSheet'; import { SheetFieldStack } from '../SheetFieldStack'; import { useMailConnectDraft } from '../../stores/mailConnectDraft'; const CONSENT_VERSION = 'art9-mail-v1-2026-05-13'; const PRIVACY_URL = 'https://rebreak.org/datenschutz'; type Props = { visible: boolean; onClose: () => void; onSuccess: () => void; }; type ProviderConfig = { id: MailProvider; labelKey: string; icon: React.ComponentProps['name']; color: string; guideKey: string; guideUrl: string; disabled?: boolean; disabledLabelKey?: string; }; const PROVIDERS: ProviderConfig[] = [ { id: 'gmail', labelKey: 'mail.provider_gmail', icon: 'mail', color: '#EA4335', guideKey: 'mail.app_password_guide_gmail', guideUrl: 'https://myaccount.google.com/apppasswords', }, { id: 'icloud', labelKey: 'mail.provider_icloud', icon: 'cloud', color: '#007AFF', guideKey: 'mail.app_password_guide_icloud', guideUrl: 'https://appleid.apple.com/account/manage', }, { id: 'outlook', labelKey: 'mail.provider_outlook', icon: 'mail-open', color: '#0078D4', guideKey: 'mail.app_password_guide_outlook', guideUrl: 'https://account.microsoft.com/security', disabled: true, disabledLabelKey: 'mail.provider_outlook_disabled_badge', }, { id: 'yahoo', labelKey: 'mail.provider_yahoo', icon: 'at', color: '#7C3AED', guideKey: 'mail.app_password_guide_yahoo', guideUrl: 'https://login.yahoo.com/account/security', }, { id: 'gmx', labelKey: 'mail.provider_gmx', icon: 'mail-unread', color: '#E87A22', guideKey: 'mail.app_password_guide_gmx', guideUrl: 'https://www.gmx.net/mail/security', }, { id: 'other', labelKey: 'mail.provider_other', icon: 'server', color: '#737373', guideKey: 'mail.app_password_guide_other', guideUrl: '', }, ]; /** * Bottom-Sheet zum Verbinden eines Postfachs. * * Drei Ansichten im selben Sheet (kein Navigations-Header): * 1. Consent-Screen (Art. 9 DSGVO) — MUSS zuerst bestätigt werden * 2. Provider-Grid (6 Tiles) — nach Consent-Bestätigung freigeschaltet * 3. Formular: Email + App-Passwort als SheetFieldStack */ export function ConnectMailSheet({ visible, onClose, onSuccess }: Props) { const { t } = useTranslation(); const colors = useColors(); const { connect, connecting, error: connectError } = useMailConnect(); const { view, consentGiven, selectedProvider, email, title, setView, setConsentGiven, setSelectedProvider, setEmail, setTitle, reset: resetDraft, } = useMailConnectDraft(); const [password, setPassword] = useState(''); const [passwordVisible, setPasswordVisible] = useState(false); const [formError, setFormError] = useState(null); const [fieldsComplete, setFieldsComplete] = useState(false); function handleClose() { resetDraft(); setPassword(''); setPasswordVisible(false); setFormError(null); setFieldsComplete(false); onClose(); } function defaultTitleForProvider(provider: ProviderConfig | null): string { if (!provider) return ''; const labelMap: Record = { gmail: 'Mein Gmail', icloud: 'Mein iCloud', outlook: 'Mein Outlook', yahoo: 'Mein Yahoo', gmx: 'Mein GMX', other: '', }; return labelMap[provider.id] ?? ''; } function handleConsentNext() { setView('grid'); } function handleProviderSelect(provider: ProviderConfig) { setSelectedProvider(provider); setEmail(''); setPassword(''); setTitle(defaultTitleForProvider(provider)); setFormError(null); setFieldsComplete(false); setView('form'); } async function handleConnect() { setFormError(null); try { await apiFetch('/api/mail-connections/consent', { method: 'POST', body: { consentVersion: CONSENT_VERSION }, }); } catch { // Backend macht Consent atomar beim Connect-Endpoint — Fehler hier ignorieren. } const result = await connect({ email: email.trim(), password }); if (result.ok) { if (title.trim()) { try { const connections = await apiFetch<{ id: string; email: string }[]>('/api/mail-connections'); const match = connections.find((c) => c.email === email.trim()); if (match) { await apiFetch(`/api/mail-connections/${match.id}`, { method: 'PATCH', body: { title: title.trim() }, }); } } catch { // Title-PATCH ist best-effort — Connection selbst ist OK } } handleClose(); onSuccess(); } else if (result.error?.includes('412') || result.error?.includes('consent_required')) { setView('consent'); setConsentGiven(false); } else { setFormError(t(humanizeMailError(result.error))); } } const sheetTitle = view === 'form' && selectedProvider ? t(selectedProvider.labelKey) : t('mail.connect_sheet_title'); return ( {view === 'consent' ? ( ) : view === 'grid' ? ( ) : ( { setEmail(v); setFormError(null); }, keyboardType: 'email-address', autoCapitalize: 'none', autoCorrect: false, validate: (v) => v.trim().length === 0 ? t('mail.form_fields_required') : undefined, }, { key: 'title', label: t('mail.title_label'), placeholder: t('mail.title_placeholder'), value: title, onChangeText: setTitle, autoCapitalize: 'sentences', autoCorrect: false, }, { key: 'password', label: t('mail.form_password_label'), placeholder: t('mail.form_password_placeholder'), value: password, onChangeText: (v) => { setPassword(v); setFormError(null); }, secureTextEntry: !passwordVisible, autoCapitalize: 'none', autoCorrect: false, validate: (v) => v.trim().length === 0 ? t('mail.form_fields_required') : undefined, suffix: ( setPasswordVisible((p) => !p)} hitSlop={8} > ), }, ]} intro={ {/* App-Password-Guide — provider-spezifisch, nicht für 'other' */} {selectedProvider && selectedProvider.id !== 'other' && ( {t('mail.app_password_required_title')} {t(selectedProvider.guideKey)} {selectedProvider.guideUrl.length > 0 && ( Linking.openURL(selectedProvider.guideUrl)} > {t('mail.app_password_open_link')} → )} )} {/* Datenschutz-Zusicherung — immer sichtbar */} {t('mail.form_privacy_note')} } onComplete={() => setFieldsComplete(true)} > {/* Fehler */} {(formError ?? (connectError ? t(humanizeMailError(connectError)) : null)) && ( {formError ?? t(humanizeMailError(connectError))} )} {/* Connect-Button */} {connecting ? ( ) : ( {t('mail.form_connect_btn')} )} )} ); } // --------------------------------------------------------------------------- // Sub-View: Consent (Art. 9 DSGVO) — muss als erster Schritt bestätigt werden // --------------------------------------------------------------------------- function ConsentStep({ consentGiven, onToggleConsent, onNext, t, colors, }: { consentGiven: boolean; onToggleConsent: (v: boolean) => void; onNext: () => void; t: (key: string) => string; colors: ReturnType; }) { return ( {t('mail.consent.intro')} {t('mail.consent.legal_text')} onToggleConsent(!consentGiven)} style={{ flex: 1 }} > {t('mail.consent.checkbox_label')} Linking.openURL(PRIVACY_URL)} > {t('mail.consent.more_link')} → {t('mail.consent.cta_next')} ); } // --------------------------------------------------------------------------- // Sub-View: Provider-Grid // --------------------------------------------------------------------------- function ProviderGrid({ providers, onSelect, t, }: { providers: ProviderConfig[]; onSelect: (p: ProviderConfig) => void; t: (key: string) => string; }) { const colors = useColors(); return ( {t('mail.connect_sheet_subtitle')} {providers.map((p) => ( onSelect(p)} activeOpacity={p.disabled ? 1 : 0.7} disabled={p.disabled} style={{ width: '47%', opacity: p.disabled ? 0.45 : 1 }} > {t(p.labelKey)} {p.disabled && p.disabledLabelKey && ( {t(p.disabledLabelKey)} )} {!p.disabled && ( )} ))} ); }