import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { type BackupBundle, exportDeviceBackupWithRecovery } from '../lib/deviceBackup'; import { AlertIcon, CopyIcon, LockIcon, ShieldIcon, SpinnerIcon, XIcon } from './icons'; interface Props { open: boolean; userId: string; deviceId: string; privateKey: Uint8Array; onClose: () => void; } // Exports the device's private key + identity into a passphrase-protected // portable string. The user can store this string anywhere (password manager, // printed paper, encrypted file on a USB stick). Without it, losing local // storage on this install means losing all past conversation keys. export function BackupExportDialog({ open, userId, deviceId, privateKey, onClose }: Props) { const { t } = useTranslation(['app']); const [passphrase, setPassphrase] = useState(''); const [confirm, setConfirm] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [bundle, setBundle] = useState(null); const [copied, setCopied] = useState(false); const [copiedRecovery, setCopiedRecovery] = useState(false); const canGenerate = useMemo(() => { return passphrase.length >= 8 && passphrase === confirm && !busy; }, [passphrase, confirm, busy]); const reset = useCallback(() => { setPassphrase(''); setConfirm(''); setBundle(null); setError(null); setCopied(false); setCopiedRecovery(false); }, []); const handleClose = useCallback(() => { reset(); onClose(); }, [reset, onClose]); const handleGenerate = useCallback(async () => { if (!canGenerate) return; setBusy(true); setError(null); try { const b = await exportDeviceBackupWithRecovery({ userId, deviceId, privateKey, passphrase, }); setBundle(b); } catch (err: unknown) { setError(err instanceof Error ? err.message : String(err)); } finally { setBusy(false); } }, [canGenerate, userId, deviceId, privateKey, passphrase]); const copyText = async (text: string, marker: 'main' | 'recovery'): Promise => { try { await navigator.clipboard.writeText(text); if (marker === 'main') { setCopied(true); window.setTimeout(() => setCopied(false), 1500); } else { setCopiedRecovery(true); window.setTimeout(() => setCopiedRecovery(false), 1500); } } catch { /* fall back — user can select manually */ } }; const handleDownload = useCallback(() => { if (!bundle) return; const text = '=== Passphrase backup ===\n' + bundle.passphraseBackup + '\n\n=== Recovery code ===\n' + bundle.recoveryCode + '\n\n=== Recovery backup (use with the recovery code) ===\n' + bundle.recoveryBackup + '\n'; const blob = new Blob([text], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `chatapp-device-backup-${deviceId.slice(0, 8)}.txt`; a.click(); URL.revokeObjectURL(url); }, [bundle, deviceId]); if (!open) return null; return (
e.stopPropagation()} className="flex max-h-[90vh] w-full max-w-lg flex-col overflow-hidden rounded-2xl border border-line bg-surface-3 shadow-xl" >

{t('app:backup.export_title', { defaultValue: 'Gerätesicherung erstellen' })}

{!bundle ? ( <>

{t('app:backup.export_explainer', { defaultValue: 'Verschlüssele den Geräteschlüssel mit einer Passphrase. Ohne Passphrase UND Backup-String ist keine Wiederherstellung möglich.', })}

setPassphrase(e.target.value)} placeholder="min. 8 Zeichen" className="w-full rounded-lg border border-line bg-surface-2 px-3 py-2.5 text-sm text-fg placeholder-fg-muted transition focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/30" />
setConfirm(e.target.value)} className="w-full rounded-lg border border-line bg-surface-2 px-3 py-2.5 text-sm text-fg placeholder-fg-muted transition focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/30" />
{confirm.length > 0 && confirm !== passphrase && (

{t('app:backup.passphrase_mismatch', { defaultValue: 'Passphrasen stimmen nicht überein.' })}

)}
{t('app:backup.export_warning', { defaultValue: 'Anthropic: Backup + Passphrase sicher aufbewahren. Passphrase kann nicht wiederhergestellt werden.', })}
{error && (

{error}

)} ) : ( <>
{t('app:backup.export_success', { defaultValue: 'Backup erstellt. Speichere diesen String + Passphrase in einem Passwortmanager oder drucke ihn aus.', })}