import React, { useState } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, SafeAreaView, ScrollView, TextInput, ActivityIndicator, Alert, Platform, } from 'react-native'; import { useNavigation, NavigationProp, ParamListBase } from '@react-navigation/native'; import { LinearGradient } from 'expo-linear-gradient'; import { KurdistanColors } from '../theme/colors'; import { usePezkuwi } from '../contexts/PezkuwiContext'; import { mnemonicGenerate, mnemonicValidate } from '@pezkuwi/util-crypto'; // Alert button type for cross-platform compatibility interface AlertButton { text: string; onPress?: () => void; style?: 'default' | 'cancel' | 'destructive'; } // Cross-platform alert helper const showAlert = (title: string, message: string, buttons?: AlertButton[]) => { if (Platform.OS === 'web') { window.alert(`${title}\n\n${message}`); if (buttons?.[0]?.onPress) buttons[0].onPress(); } else { Alert.alert(title, message, buttons); } }; type SetupStep = 'choice' | 'create-show' | 'create-verify' | 'import' | 'wallet-name' | 'success'; const WalletSetupScreen: React.FC = () => { const navigation = useNavigation>(); const { createWallet, importWallet, connectWallet, isReady } = usePezkuwi(); const [step, setStep] = useState('choice'); const [mnemonic, setMnemonic] = useState([]); const [walletName, setWalletName] = useState(''); const [importMnemonic, setImportMnemonic] = useState(''); const [verificationIndices, setVerificationIndices] = useState([]); const [selectedWords, setSelectedWords] = useState<{[key: number]: string}>({}); const [isLoading, setIsLoading] = useState(false); const [createdAddress, setCreatedAddress] = useState(''); const [isCreateFlow, setIsCreateFlow] = useState(true); // Generate mnemonic when entering create flow const handleCreateNew = () => { const generatedMnemonic = mnemonicGenerate(12); setMnemonic(generatedMnemonic.split(' ')); setIsCreateFlow(true); setStep('create-show'); }; // Go to import flow const handleImport = () => { setIsCreateFlow(false); setStep('import'); }; // After showing mnemonic, go to verification const handleMnemonicConfirmed = () => { // Select 3 random indices for verification const indices: number[] = []; while (indices.length < 3) { const randomIndex = Math.floor(Math.random() * 12); if (!indices.includes(randomIndex)) { indices.push(randomIndex); } } indices.sort((a, b) => a - b); setVerificationIndices(indices); setSelectedWords({}); setStep('create-verify'); }; // Verify selected words const handleVerifyWord = (index: number, word: string) => { setSelectedWords(prev => ({ ...prev, [index]: word })); }; // Check if verification is complete and correct const isVerificationComplete = () => { return verificationIndices.every(idx => selectedWords[idx] === mnemonic[idx]); }; // After verification, go to wallet name const handleVerificationComplete = () => { if (!isVerificationComplete()) { showAlert('Incorrect', 'The words you selected do not match. Please try again.'); setSelectedWords({}); return; } setStep('wallet-name'); }; // Create wallet with name const handleCreateWallet = async () => { if (!walletName.trim()) { showAlert('Error', 'Please enter a wallet name'); return; } if (!isReady) { showAlert('Error', 'Crypto libraries are still loading. Please wait a moment and try again.'); return; } setIsLoading(true); try { const { address } = await createWallet(walletName.trim(), mnemonic.join(' ')); setCreatedAddress(address); await connectWallet(); setStep('success'); } catch (error: unknown) { if (__DEV__) console.error('[WalletSetup] Create wallet error:', error); showAlert('Error', error instanceof Error ? error.message : 'Failed to create wallet'); } finally { setIsLoading(false); } }; // Import wallet with mnemonic or dev URI (like //Alice) const handleImportWallet = async () => { const trimmedInput = importMnemonic.trim(); // Check if it's a dev URI (starts with //) if (trimmedInput.startsWith('//')) { // Dev URI like //Alice, //Bob, etc. setMnemonic([trimmedInput]); // Store as single-element array to indicate URI setStep('wallet-name'); return; } // Otherwise treat as mnemonic const words = trimmedInput.toLowerCase().split(/\s+/); if (words.length !== 12 && words.length !== 24) { showAlert('Invalid Input', 'Please enter a valid 12 or 24 word recovery phrase, or a dev URI like //Alice'); return; } if (!mnemonicValidate(trimmedInput.toLowerCase())) { showAlert('Invalid Mnemonic', 'The recovery phrase is invalid. Please check and try again.'); return; } setMnemonic(words); setStep('wallet-name'); }; // After naming imported wallet const handleImportComplete = async () => { if (!walletName.trim()) { showAlert('Error', 'Please enter a wallet name'); return; } if (!isReady) { showAlert('Error', 'Crypto libraries are still loading. Please wait a moment and try again.'); return; } setIsLoading(true); try { const { address } = await importWallet(walletName.trim(), mnemonic.join(' ')); setCreatedAddress(address); await connectWallet(); setStep('success'); } catch (error: unknown) { if (__DEV__) console.error('[WalletSetup] Import wallet error:', error); showAlert('Error', error instanceof Error ? error.message : 'Failed to import wallet'); } finally { setIsLoading(false); } }; const handleSuccess = () => { // Navigate to main wallet screen // Using replace to prevent going back to setup // eslint-disable-next-line @typescript-eslint/no-explicit-any (navigation as any).replace('Wallet'); }; // Go back to previous step (TODO: add back button UI) const _handleBack = () => { switch (step) { case 'create-show': case 'import': setStep('choice'); break; case 'create-verify': setStep('create-show'); break; case 'wallet-name': if (isCreateFlow) { setStep('create-verify'); } else { setStep('import'); } break; default: navigation.goBack(); } }; // Generate shuffled words for verification options const getShuffledOptions = (correctWord: string): string[] => { const allWords = [...mnemonic]; const options = [correctWord]; while (options.length < 4) { const randomWord = allWords[Math.floor(Math.random() * allWords.length)]; if (!options.includes(randomWord)) { options.push(randomWord); } } // Shuffle options return options.sort(() => Math.random() - 0.5); }; // ============================================================ // RENDER FUNCTIONS // ============================================================ const renderChoiceStep = () => ( 👛 Set Up Your Wallet Create a new wallet or import an existing one using your recovery phrase Create New Wallet Generate a new recovery phrase 📥 Import Existing Wallet Use your 12 or 24 word phrase ); const renderCreateShowStep = () => ( Your Recovery Phrase Write down these 12 words in order and keep them safe. This is the only way to recover your wallet. ⚠️ Never share your recovery phrase with anyone. Anyone with these words can access your funds. {mnemonic.map((word, index) => ( {index + 1} {word} ))} I've Written It Down ); const renderCreateVerifyStep = () => ( Verify Your Phrase Select the correct words to verify you've saved your recovery phrase {verificationIndices.map((wordIndex) => ( Word #{wordIndex + 1} {getShuffledOptions(mnemonic[wordIndex]).map((option, optIdx) => ( handleVerifyWord(wordIndex, option)} testID={`verify-option-${wordIndex}-${optIdx}`} > {option} ))} ))} Verify & Continue ); const renderImportStep = () => ( Import Wallet Enter your 12 or 24 word recovery phrase, or a dev URI like //Alice Mnemonic: separate words with space | Dev URI: //Alice, //Bob, etc. Continue ); const renderWalletNameStep = () => ( Name Your Wallet Give your wallet a name to easily identify it {isLoading ? ( ) : ( {isCreateFlow ? 'Create Wallet' : 'Import Wallet'} )} ); const renderSuccessStep = () => ( Wallet Created! Your wallet is ready to use. You can now send and receive tokens. Your Wallet Address {createdAddress} Go to Wallet ); const renderStep = () => { switch (step) { case 'choice': return renderChoiceStep(); case 'create-show': return renderCreateShowStep(); case 'create-verify': return renderCreateVerifyStep(); case 'import': return renderImportStep(); case 'wallet-name': return renderWalletNameStep(); case 'success': return renderSuccessStep(); default: return renderChoiceStep(); } }; return ( {/* Header */} {step !== 'choice' && step !== 'success' && ( {['create-show', 'create-verify', 'wallet-name'].includes(step) && isCreateFlow && ( <> )} {['import', 'wallet-name'].includes(step) && !isCreateFlow && ( <> )} )} {/* Close button on choice screen */} {step === 'choice' && ( navigation.goBack()} style={styles.closeButton} testID="wallet-setup-close"> )} {renderStep()} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#FFFFFF', }, header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 16, paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#F0F0F0', }, backButton: { paddingVertical: 4, paddingRight: 16, }, backButtonText: { fontSize: 16, color: KurdistanColors.kesk, fontWeight: '500', }, closeButton: { width: 32, height: 32, borderRadius: 16, backgroundColor: '#F5F5F5', justifyContent: 'center', alignItems: 'center', }, closeButtonText: { fontSize: 18, color: '#666', }, progressContainer: { flexDirection: 'row', gap: 8, }, progressDot: { width: 8, height: 8, borderRadius: 4, backgroundColor: '#E0E0E0', }, progressDotActive: { backgroundColor: KurdistanColors.kesk, }, headerSpacer: { width: 60, }, content: { flex: 1, }, contentContainer: { padding: 24, paddingBottom: 40, }, stepContainer: { flex: 1, }, iconContainer: { alignItems: 'center', marginBottom: 24, marginTop: 20, }, mainIcon: { fontSize: 80, }, title: { fontSize: 28, fontWeight: 'bold', color: KurdistanColors.reş, textAlign: 'center', marginBottom: 12, }, subtitle: { fontSize: 16, color: '#666', textAlign: 'center', lineHeight: 24, marginBottom: 32, }, // Choice buttons choiceButtons: { gap: 16, }, choiceButton: { borderRadius: 16, overflow: 'hidden', }, choiceButtonGradient: { padding: 24, alignItems: 'center', }, choiceButtonOutline: { padding: 24, alignItems: 'center', borderWidth: 2, borderColor: '#E0E0E0', borderRadius: 16, }, choiceButtonIcon: { fontSize: 40, marginBottom: 12, }, choiceButtonTitle: { fontSize: 18, fontWeight: '600', color: '#FFFFFF', marginBottom: 4, }, choiceButtonSubtitle: { fontSize: 14, color: 'rgba(255,255,255,0.8)', }, // Warning box warningBox: { flexDirection: 'row', backgroundColor: '#FFF3CD', borderRadius: 12, padding: 16, marginBottom: 24, alignItems: 'flex-start', }, warningIcon: { fontSize: 20, marginRight: 12, }, warningText: { flex: 1, fontSize: 14, color: '#856404', lineHeight: 20, }, // Mnemonic grid mnemonicGrid: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between', marginBottom: 32, }, wordCard: { width: '31%', backgroundColor: '#F8F9FA', borderRadius: 12, padding: 12, marginBottom: 12, flexDirection: 'row', alignItems: 'center', }, wordNumber: { fontSize: 12, color: '#999', marginRight: 8, minWidth: 20, }, wordText: { fontSize: 14, fontWeight: '600', color: KurdistanColors.reş, }, // Verification verificationContainer: { marginBottom: 32, }, verificationItem: { marginBottom: 24, }, verificationLabel: { fontSize: 16, fontWeight: '600', color: KurdistanColors.reş, marginBottom: 12, }, verificationOptions: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, }, verificationOption: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 20, backgroundColor: '#F5F5F5', borderWidth: 2, borderColor: 'transparent', }, verificationOptionSelected: { borderColor: KurdistanColors.kesk, backgroundColor: 'rgba(0, 143, 67, 0.1)', }, verificationOptionCorrect: { borderColor: KurdistanColors.kesk, backgroundColor: 'rgba(0, 143, 67, 0.15)', }, verificationOptionText: { fontSize: 14, color: '#333', }, verificationOptionTextSelected: { color: KurdistanColors.kesk, fontWeight: '600', }, // Import importInputContainer: { marginBottom: 32, }, importInput: { backgroundColor: '#F8F9FA', borderRadius: 16, padding: 16, fontSize: 16, color: KurdistanColors.reş, minHeight: 120, textAlignVertical: 'top', borderWidth: 1, borderColor: '#E0E0E0', }, importHint: { fontSize: 12, color: '#999', marginTop: 8, marginLeft: 4, }, // Wallet name nameInputContainer: { marginBottom: 32, }, nameInput: { backgroundColor: '#F8F9FA', borderRadius: 16, padding: 16, fontSize: 18, color: KurdistanColors.reş, borderWidth: 1, borderColor: '#E0E0E0', textAlign: 'center', }, // Primary button primaryButton: { backgroundColor: KurdistanColors.kesk, borderRadius: 16, padding: 18, alignItems: 'center', }, primaryButtonDisabled: { opacity: 0.5, }, primaryButtonText: { fontSize: 18, fontWeight: '600', color: '#FFFFFF', }, // Success successIconContainer: { alignItems: 'center', marginBottom: 24, marginTop: 40, }, successIcon: { fontSize: 80, }, addressBox: { backgroundColor: '#F8F9FA', borderRadius: 16, padding: 20, marginBottom: 32, alignItems: 'center', }, addressLabel: { fontSize: 12, color: '#999', marginBottom: 8, }, addressText: { fontSize: 14, fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace', color: KurdistanColors.reş, }, }); export default WalletSetupScreen;