import { useRouter } from 'expo-router'; import { useState } from 'react'; import { ActivityIndicator, Pressable, StyleSheet, Text, View, } from 'react-native'; import { PinInput } from '../../components/PinInput'; import { useAuth } from '../../lib/authContext'; import { loadOrUnlockUserKey, resetIdentity } from '../../lib/userIdentity'; import { colors } from '../../theme/colors'; type Mode = 'pin' | 'recovery'; export default function UnlockScreen() { const router = useRouter(); const { userId, userKeyState, refreshUserKeyState } = useAuth(); const [mode, setMode] = useState('pin'); const [pin, setPin] = useState(''); const [recovery, setRecovery] = useState(''); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const locked = userKeyState.status === 'needs-unlock' && userKeyState.lockedUntil !== null; const hasRecovery = userKeyState.status === 'needs-unlock' ? userKeyState.hasRecovery : false; async function attempt(value: string, isRecoveryCode: boolean) { if (!userId) return; setSubmitting(true); setError(null); try { const out = await loadOrUnlockUserKey({ userId, pin: value, isRecoveryCode }); if (out.kind === 'unlocked') { await refreshUserKeyState(); router.replace('/(app)/chats'); return; } if (out.kind === 'locked') { setError('Konto bis ' + new Date(out.lockedUntil).toLocaleString('de-DE') + ' gesperrt.'); await refreshUserKeyState(); return; } setError('Kein Identitäts-Datensatz auf dem Server.'); } catch (err) { setError(err instanceof Error ? err.message : 'Entsperren fehlgeschlagen'); if (isRecoveryCode) setRecovery(''); else setPin(''); await refreshUserKeyState(); } finally { setSubmitting(false); } } async function handleReset() { if (!userId) return; setSubmitting(true); setError(null); try { await resetIdentity({ userId, pin: '000000' }); await refreshUserKeyState(); router.replace('/(app)/setup'); } catch (err) { setError(err instanceof Error ? err.message : 'Reset fehlgeschlagen'); } finally { setSubmitting(false); } } return ( Netralax entsperren setMode('pin')} > PIN setMode('recovery')} > Recovery-Code {mode === 'pin' ? ( <> attempt(pin, false)} > {submitting ? ( ) : ( Entsperren )} ) : ( <> Recovery-Code eingeben (mit oder ohne Bindestriche): {recovery} attempt(recovery, true)} > {submitting ? ( ) : ( Mit Recovery entsperren )} )} {error && {error}} {locked && ( Identität zurücksetzen (alte Chats verloren) )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.bg, paddingTop: 64, paddingHorizontal: 24, gap: 16, }, title: { color: colors.text, fontSize: 22, fontWeight: '700' }, subtitle: { color: colors.textMuted, fontSize: 13 }, tabs: { flexDirection: 'row', gap: 8 }, tab: { flex: 1, paddingVertical: 10, alignItems: 'center', borderBottomWidth: 2, borderBottomColor: colors.border, }, tabActive: { borderBottomColor: colors.accent }, tabText: { color: colors.textMuted, fontWeight: '600' }, tabTextActive: { color: colors.text }, recoveryDisplay: { color: colors.text, fontFamily: 'Courier', fontSize: 18, letterSpacing: 1.5, backgroundColor: colors.surface, padding: 16, borderRadius: 10, }, primary: { backgroundColor: colors.accent, paddingVertical: 14, borderRadius: 12, alignItems: 'center', }, primaryText: { color: colors.text, fontWeight: '700' }, disabled: { opacity: 0.5 }, danger: { paddingVertical: 12, alignItems: 'center', borderColor: colors.danger, borderWidth: 1, borderRadius: 12, }, dangerText: { color: colors.danger, fontWeight: '700' }, error: { color: colors.danger, fontSize: 13 }, });