/** * Import existing key — dark minimalist. * Два пути: * 1. Paste JSON напрямую в textarea. * 2. Pick файл .json через DocumentPicker. */ import React, { useState } from 'react'; import { View, Text, ScrollView, TextInput, Alert, Pressable, ActivityIndicator, } from 'react-native'; import { router } from 'expo-router'; import { Ionicons } from '@expo/vector-icons'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import * as DocumentPicker from 'expo-document-picker'; import * as Clipboard from 'expo-clipboard'; import { saveKeyFile } from '@/lib/storage'; import { useStore } from '@/lib/store'; import { safeBack } from '@/lib/utils'; import type { KeyFile } from '@/lib/types'; import { Header } from '@/components/Header'; import { IconButton } from '@/components/IconButton'; type Tab = 'paste' | 'file'; const REQUIRED_FIELDS: (keyof KeyFile)[] = ['pub_key', 'priv_key', 'x25519_pub', 'x25519_priv']; function validateKeyFile(raw: string): KeyFile { let parsed: any; try { parsed = JSON.parse(raw.trim()); } catch { throw new Error('Invalid JSON — check that you copied the full key file.'); } for (const field of REQUIRED_FIELDS) { if (!parsed[field] || typeof parsed[field] !== 'string') { throw new Error(`Missing or invalid field: "${field}"`); } if (!/^[0-9a-f]+$/i.test(parsed[field])) { throw new Error(`Field "${field}" must be a hex string.`); } } return parsed as KeyFile; } export default function ImportKeyScreen() { const insets = useSafeAreaInsets(); const setKeyFile = useStore(s => s.setKeyFile); const [tab, setTab] = useState('paste'); const [jsonText, setJsonText] = useState(''); const [fileName, setFileName] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); async function applyKey(kf: KeyFile) { setLoading(true); setError(null); try { await saveKeyFile(kf); setKeyFile(kf); router.replace('/(app)/chats' as never); } catch (e: any) { setError(e?.message ?? 'Import failed'); } finally { setLoading(false); } } async function handlePasteImport() { setError(null); const text = jsonText.trim(); if (!text) { const clip = await Clipboard.getStringAsync(); if (clip) setJsonText(clip); return; } try { await applyKey(validateKeyFile(text)); } catch (e: any) { setError(e?.message ?? 'Import failed'); } } async function pickFile() { setError(null); try { const result = await DocumentPicker.getDocumentAsync({ type: ['application/json', 'text/plain', '*/*'], copyToCacheDirectory: true, }); if (result.canceled) return; const asset = result.assets[0]; setFileName(asset.name); const response = await fetch(asset.uri); const raw = await response.text(); await applyKey(validateKeyFile(raw)); } catch (e: any) { setError(e?.message ?? 'Import failed'); } } return (
safeBack('/')} />} /> Restore your account from a previously exported{' '} dchain_key.json. {/* Tabs */} {(['paste', 'file'] as Tab[]).map(t => ( setTab(t)} style={{ flex: 1, alignItems: 'center', paddingVertical: 8, borderRadius: 999, backgroundColor: tab === t ? '#1d9bf0' : 'transparent', }} > {t === 'paste' ? 'Paste JSON' : 'Pick file'} ))} {tab === 'paste' ? ( <> ({ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 12, borderRadius: 999, marginTop: 12, backgroundColor: loading ? '#1a1a1a' : pressed ? '#1a8cd8' : '#1d9bf0', })} > {loading ? ( ) : ( {jsonText.trim() ? 'Import key' : 'Paste from clipboard'} )} ) : ( <> ({ alignItems: 'center', justifyContent: 'center', paddingVertical: 40, borderRadius: 14, backgroundColor: pressed ? '#111111' : '#0a0a0a', borderWidth: 1, borderStyle: 'dashed', borderColor: '#1f1f1f', })} > {fileName ?? 'Tap to pick key.json'} Will auto-import on selection {loading && ( )} )} {error && ( {error} )} ); }