1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Extract "create vault" form to separate component (#13461)

The form used for creating a vault on the "Import" page of onboarding
and on the "Restore vault" page is nearly identical, yet the
implementation is totally separate. It has now been extracted to a
separate component, consolidating the two implementations.

There is a "terms of use" checkbox on the import page that isn't on the
restore vault page, so that part has been made optional. The "submit"
button text differs between the two uses as well, so that is
customizable.

There are slight styling differences between the old and new versions
of this form. The fonts and spacing are all using our new standard
design system guidelines, and we're using our standard checkbox now as
well. The spacing and font sizes were chosen somewhat arbitrarily by me
to resemble the old styles, so please feel free to suggest changes if
you think they can be improved upon.

There are some slight copy changes to the "Restore vault" page as well;
the placeholder text and the label for the "Secret Recovery Phrase"
field now matches the "Import" page copy.
This commit is contained in:
Mark Stacey 2022-02-03 10:36:43 -03:30 committed by GitHub
parent 760ed3457d
commit 429451de23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 416 additions and 884 deletions

View File

@ -861,9 +861,6 @@
"sendTokens": {
"message": "ተለዋጭ ስሞችን ላክ"
},
"separateEachWord": {
"message": "እያንዳንዱን ቃል በነጠላ ክፍት ቦታ ይለያዩ"
},
"settings": {
"message": "ቅንብሮች"
},

View File

@ -877,9 +877,6 @@
"sendTokens": {
"message": "إرسال عملات رمزية"
},
"separateEachWord": {
"message": "افصل كل كلمة بمسافة واحدة"
},
"settings": {
"message": "الإعدادات"
},

View File

@ -872,9 +872,6 @@
"sendTokens": {
"message": "Изпращане на жетони"
},
"separateEachWord": {
"message": "Отделете всяка дума с интервал"
},
"settings": {
"message": "Настройки"
},

View File

@ -876,9 +876,6 @@
"sendTokens": {
"message": "টোকেনগুলি পাঠান"
},
"separateEachWord": {
"message": "প্রতিটি শব্দকে একটি স্পেস দিয়ে আলাদা করুন"
},
"settings": {
"message": "সেটিংস"
},

View File

@ -854,9 +854,6 @@
"sendTokens": {
"message": "Enviar Fitxes"
},
"separateEachWord": {
"message": "Separa cada paraula amb un sol espai"
},
"settings": {
"message": "Configuració"
},

View File

@ -854,9 +854,6 @@
"sendTokens": {
"message": "Send tokens"
},
"separateEachWord": {
"message": "Separer hvert ord med et enkelt mellemrum"
},
"settings": {
"message": "Indstillinger "
},

View File

@ -838,9 +838,6 @@
"sendTokens": {
"message": "Token senden"
},
"separateEachWord": {
"message": "Trennen Sie die Wörter mit einem einzelnen Leerzeichen voneinander"
},
"settings": {
"message": "Einstellungen"
},

View File

@ -2460,9 +2460,6 @@
"message": "Αποστολή $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "Διαχωρίστε κάθε λέξη με ένα μόνο κενό"
},
"setAdvancedPrivacySettings": {
"message": "Ορίστε ρυθμίσεις απορρήτου για προχωρημένους"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "Επιτυχής δημιουργία πορτοφολιού"
},
"walletSeedRestore": {
"message": "Μυστικής Φράσης Ανάκτησης Πορτοφολιού"
},
"web3ShimUsageNotification": {
"message": "Παρατηρήσαμε ότι η τρέχουσα ιστοσελίδα προσπάθησε να χρησιμοποιήσει το αφαιρεθέν window.web3 API. Αν η ιστοσελίδα φαίνεται να έχει παραβιαστεί, κάντε κλικ στο $1 για περισσότερες πληροφορίες.",
"description": "$1 is a clickable link."

View File

@ -2582,9 +2582,6 @@
"message": "Sending $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "Separate each word with a single space"
},
"setAdvancedPrivacySettings": {
"message": "Set advanced privacy settings"
},
@ -3503,9 +3500,6 @@
"walletCreationSuccessTitle": {
"message": "Wallet creation successful"
},
"walletSeedRestore": {
"message": "Wallet Secret Recovery Phrase"
},
"web3ShimUsageNotification": {
"message": "We noticed that the current website tried to use the removed window.web3 API. If the site appears to be broken, please click $1 for more information.",
"description": "$1 is a clickable link."

View File

@ -1612,9 +1612,6 @@
"sendTokens": {
"message": "Enviar tokens"
},
"separateEachWord": {
"message": "Separar cada palabra con un solo espacio"
},
"settings": {
"message": "Configuración"
},
@ -2263,9 +2260,6 @@
"walletConnectionGuide": {
"message": "nuestra guía de conexión de la cartera de hardware"
},
"walletSeedRestore": {
"message": "Frase secreta de recuperación de la cartera"
},
"web3ShimUsageNotification": {
"message": "Parece que el sitio web actual intentó utilizar la API de window.web3 que se eliminó. Si el sitio no funciona, haga clic en $1 para obtener más información.",
"description": "$1 is a clickable link."

View File

@ -1624,9 +1624,6 @@
"sendTokens": {
"message": "Enviar tokens"
},
"separateEachWord": {
"message": "Separar cada palabra con un solo espacio"
},
"settings": {
"message": "Configuración"
},
@ -2275,9 +2272,6 @@
"walletConnectionGuide": {
"message": "nuestra guía de conexión de la cartera de hardware"
},
"walletSeedRestore": {
"message": "Frase secreta de recuperación de la cartera"
},
"web3ShimUsageNotification": {
"message": "Parece que el sitio web actual intentó utilizar la API de window.web3 que se eliminó. Si el sitio no funciona, haga clic en $1 para obtener más información.",
"description": "$1 is a clickable link."

View File

@ -866,9 +866,6 @@
"sendTokens": {
"message": "Saada lube"
},
"separateEachWord": {
"message": "Eraldage iga sõna ühe tühikuga"
},
"settings": {
"message": "Seaded"
},

View File

@ -876,9 +876,6 @@
"sendTokens": {
"message": "رمزیاب ها را ارسال کنید"
},
"separateEachWord": {
"message": "هر کلمه را با یک فاصله واحد جدا سازید"
},
"settings": {
"message": "تنظیمات"
},

View File

@ -873,9 +873,6 @@
"sendTokens": {
"message": "Lähetä tietueita"
},
"separateEachWord": {
"message": "Erottele sanat toisistaan yhdellä välilyönnillä"
},
"settings": {
"message": "Asetukset"
},

View File

@ -794,9 +794,6 @@
"sendTokens": {
"message": "Magpadala ng Mga Token"
},
"separateEachWord": {
"message": "Paghiwa-hiwalayin ang bawat salita gamit ang isang space"
},
"settings": {
"message": "Mga Setting"
},

View File

@ -2460,9 +2460,6 @@
"message": "Envoi de $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "Separez chaque mot avec un espace simple"
},
"setAdvancedPrivacySettings": {
"message": "Définir des paramètres de confidentialité avancés"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "Portefeuille créé avec succès"
},
"walletSeedRestore": {
"message": "Phrase secrète de récupération du portefeuille"
},
"web3ShimUsageNotification": {
"message": "Nous avons remarqué que ce site Web a essayé dutiliser lAPI window.web3 supprimée. Si le site semble être défectueux, veuillez cliquer sur $1 pour plus dinformations.",
"description": "$1 is a clickable link."

View File

@ -870,9 +870,6 @@
"sendTokens": {
"message": "שלח טוקנים"
},
"separateEachWord": {
"message": "יש להפריד כל מילה עם רווח יחיד"
},
"settings": {
"message": "הגדרות"
},

View File

@ -2460,9 +2460,6 @@
"message": "$1 भेजा जा रहा है",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "प्रत्येक शब्द को एक रिक्ति से अलग करें"
},
"setAdvancedPrivacySettings": {
"message": "एडवांस गोपनीयता सेटिंग्स निर्धारित करें"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "वॉलेट का निर्माण सफल हुआ"
},
"walletSeedRestore": {
"message": "वॉलेट का गुप्त रिकवरी फ्रेज़"
},
"web3ShimUsageNotification": {
"message": "हमने देखा है कि वर्तमान वेबसाइट ने हटाए गए window.web3 API का उपयोग करने की कोशिश की। यदि साइट में गड़बड़ी लगती है, तो कृपया अधिक जानकारी के लिए $1 पर क्लिक करें।",
"description": "$1 is a clickable link."

View File

@ -869,9 +869,6 @@
"sendTokens": {
"message": "Pošalji tokene"
},
"separateEachWord": {
"message": "Odvojite pojedinačne riječi jednim razmakom"
},
"settings": {
"message": "Postavke"
},

View File

@ -551,9 +551,6 @@
"sendTokens": {
"message": "Voye Tokens"
},
"separateEachWord": {
"message": "Separe chak mo ak yon sèl espas"
},
"settings": {
"message": "Paramèt"
},

View File

@ -869,9 +869,6 @@
"sendTokens": {
"message": "Token küldése"
},
"separateEachWord": {
"message": "Minden egyes szavat szóközzel válasszon el"
},
"settings": {
"message": "Beállítások"
},

View File

@ -2460,9 +2460,6 @@
"message": "Mengirim $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "Pisahkan setiap kata dengan satu spasi"
},
"setAdvancedPrivacySettings": {
"message": "Atur pengaturan privasi lanjutan"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "Dompet berhasil dibuat"
},
"walletSeedRestore": {
"message": "Frasa Pemulihan Rahasia Dompet"
},
"web3ShimUsageNotification": {
"message": "Kami melihat situs web saat ini mencoba menggunakan API window.web3 yang dihapus. Jika situs tersebut tampak bermasalah, silakan klik $1 untuk informasi selengkapnya.",
"description": "$1 is a clickable link."

View File

@ -1298,9 +1298,6 @@
"sendTokens": {
"message": "Invia Tokens"
},
"separateEachWord": {
"message": "Separa ogni parola con un solo spazio"
},
"settings": {
"message": "Impostazioni"
},

View File

@ -2460,9 +2460,6 @@
"message": "$1を送信中",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "単語ごとにスペースを 1 つ置いて分離します"
},
"setAdvancedPrivacySettings": {
"message": "高度なプライバシー設定を設定"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "ウォレットが作成されました"
},
"walletSeedRestore": {
"message": "ウォレット シークレット リカバリー フレーズ"
},
"web3ShimUsageNotification": {
"message": "現在のウェブサイトが、削除済の window.web3 API の使用を検知しました。サイトが壊れているようであれば、$1 をクリックして詳細を確認してください。",
"description": "$1 is a clickable link."

View File

@ -876,9 +876,6 @@
"sendTokens": {
"message": "ಟೋಕನ್‌ಗಳನ್ನು ಕಳುಹಿಸಿ"
},
"separateEachWord": {
"message": "ಒಂದು ಸ್ಪೇಸ್ ಮೂಲಕ ಪ್ರತಿ ಪದವನ್ನು ಬೇರ್ಪಡಿಸಿ"
},
"settings": {
"message": "ಸೆಟ್ಟಿಂಗ್‌ಗಳು"
},

View File

@ -2460,9 +2460,6 @@
"message": "$1 보내기",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "공백 한 칸으로 각 단어를 구분하세요."
},
"setAdvancedPrivacySettings": {
"message": "개인정보 설정 고급 지정"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "지갑 생성 성공"
},
"walletSeedRestore": {
"message": "지갑 비밀 복구 구문"
},
"web3ShimUsageNotification": {
"message": "현재의 웹사이트가 제거된 window.web3 API를 이용하려고 합니다. 이 사이트가 제대로 작동하지 않는 경우, $1을(를) 클릭해 자세히 알아보세요.",
"description": "$1 is a clickable link."

View File

@ -876,9 +876,6 @@
"sendTokens": {
"message": "Siųsti žetonus"
},
"separateEachWord": {
"message": "Kiekvieną žodį atskirkite viengubu tarpu"
},
"settings": {
"message": "Nustatymai"
},

View File

@ -872,9 +872,6 @@
"sendTokens": {
"message": "Nosūtīt marķierus"
},
"separateEachWord": {
"message": "Atdaliet katru vārdu ar vienu atstarpi"
},
"settings": {
"message": "Iestatījumi"
},

View File

@ -856,9 +856,6 @@
"sendTokens": {
"message": "Hantar Token"
},
"separateEachWord": {
"message": "Pisahkan setiap perkataan dengan ruang tunggal"
},
"settings": {
"message": "Tetapan"
},

View File

@ -857,9 +857,6 @@
"sendTokens": {
"message": "Send tokener"
},
"separateEachWord": {
"message": "Del hvert ord med et enkelt mellomrom "
},
"settings": {
"message": "Innstillinger"
},

View File

@ -1637,9 +1637,6 @@
"sendTokens": {
"message": "Magpadala ng Mga Token"
},
"separateEachWord": {
"message": "Paghiwa-hiwalayin ang bawat salita gamit ang isang space"
},
"settings": {
"message": "Mga Setting"
},
@ -2294,9 +2291,6 @@
"walletConnectionGuide": {
"message": "ang aming gabay sa pagkonekta ng hardware wallet"
},
"walletSeedRestore": {
"message": "Recovery Phrase ng Wallet Secret"
},
"web3ShimUsageNotification": {
"message": "Napansin naming sinubukang gamitin ng kasalukuyang website ang tinanggal na window.web3 API. Kung mukhang sira ang site, paki-click ang $1 para sa higit pang impormasyon.",
"description": "$1 is a clickable link."

View File

@ -870,9 +870,6 @@
"sendTokens": {
"message": "Wyślij tokeny"
},
"separateEachWord": {
"message": "Oddziel słowa pojedynczą spacją"
},
"settings": {
"message": "Ustawienia"
},

View File

@ -1612,9 +1612,6 @@
"sendTokens": {
"message": "Enviar tokens"
},
"separateEachWord": {
"message": "Separe cada palavra com um único espaço"
},
"settings": {
"message": "Configurações"
},
@ -2260,9 +2257,6 @@
"walletConnectionGuide": {
"message": "nosso guia de conexão com a carteira de hardware"
},
"walletSeedRestore": {
"message": "Frase de recuperação secreta da carteira"
},
"web3ShimUsageNotification": {
"message": "Percebemos que o site atual tentou usar a API window.web3 removida. Se o site parecer estar corrompido, clique em $1 para obter mais informações.",
"description": "$1 is a clickable link."

View File

@ -863,9 +863,6 @@
"sendTokens": {
"message": "Trimiteți indicative"
},
"separateEachWord": {
"message": "Despărțiți fiecare cuvânt cu un spațiu"
},
"settings": {
"message": "Setări"
},

View File

@ -2460,9 +2460,6 @@
"message": "Отправка $1...",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "Отделяйте каждое слово одним пробелом"
},
"setAdvancedPrivacySettings": {
"message": "Задать дополнительные настройки конфиденциальности"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "Кошелек создан"
},
"walletSeedRestore": {
"message": "Секретная фраза восстановления кошелька"
},
"web3ShimUsageNotification": {
"message": "Мы заметили, что текущий веб-сайт пытался использовать удаленный API window.web3. Если сайт не работает, нажмите $1 для получения дополнительной информации.",
"description": "$1 is a clickable link."

View File

@ -839,9 +839,6 @@
"sendTokens": {
"message": "Odeslat tokeny"
},
"separateEachWord": {
"message": "Každé slovo oddeľte jednou medzerou"
},
"settings": {
"message": "Nastavení"
},

View File

@ -864,9 +864,6 @@
"sendTokens": {
"message": "Pošlji žetone"
},
"separateEachWord": {
"message": "Vsako besedo ločite z enim presledkom"
},
"settings": {
"message": "Nastavitve"
},

View File

@ -867,9 +867,6 @@
"sendTokens": {
"message": "Pošalji tokene"
},
"separateEachWord": {
"message": "Razdvojite svaku reč jednim mestom razmaka"
},
"settings": {
"message": "Podešavanja"
},

View File

@ -860,9 +860,6 @@
"sendTokens": {
"message": "Skicka tokens"
},
"separateEachWord": {
"message": "Lägg in ett mellanslag mellan varje ord"
},
"settings": {
"message": "Inställningar"
},

View File

@ -854,9 +854,6 @@
"sendTokens": {
"message": "Tuma Vianzio"
},
"separateEachWord": {
"message": "Tenganisha kila neno kwa nafasi moja"
},
"settings": {
"message": "Mipangilio"
},

View File

@ -2460,9 +2460,6 @@
"message": "Nagpapadala ng $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "Paghiwa-hiwalayin ang bawat salita gamit ang espasyo"
},
"setAdvancedPrivacySettings": {
"message": "Magtakda ng advanced privacy settings"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "Matagumpay ang paggawa ng wallet"
},
"walletSeedRestore": {
"message": "Recovery Phrase ng Wallet Secret"
},
"web3ShimUsageNotification": {
"message": "Napansin namin na sinubukan ng kasalukuyang website na gamitin ang inalis na window.web3 API. Kung mukhang sira ang site, paki-click ang $1 para sa karagdagang impormasyon.",
"description": "$1 is a clickable link."

View File

@ -2460,9 +2460,6 @@
"message": "$1 gönderiliyor",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "Her kelimeyi tek bir boşluk ile ayırın"
},
"setAdvancedPrivacySettings": {
"message": "Gelişmiş gizlilik ayarlarını ayarlayın"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "Cüzdan oluşturma başarılı"
},
"walletSeedRestore": {
"message": "Cüzdan Gizli Kurtarma İfadesi"
},
"web3ShimUsageNotification": {
"message": "Mevcut web sitesinin kaldırılmış olan window.web3 API'sini kullanmaya çalıştığını fark ettik. Site bozuk olarak görülüyorsa daha fazla bilgi için lütfen şuna tıklayın: $1.",
"description": "$1 is a clickable link."

View File

@ -876,9 +876,6 @@
"sendTokens": {
"message": "Надіслати токени"
},
"separateEachWord": {
"message": "Відділіть кожне слово одним пробілом"
},
"settings": {
"message": "Налаштування"
},

View File

@ -2460,9 +2460,6 @@
"message": "Gửi $1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "Phân tách mỗi từ bằng một dấu cách"
},
"setAdvancedPrivacySettings": {
"message": "Thiết lập cài đặt quyền riêng tư nâng cao"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "Tạo ví thành công"
},
"walletSeedRestore": {
"message": "Cụm mật khẩu khôi phục bí mật của ví"
},
"web3ShimUsageNotification": {
"message": "Chúng tôi nhận thấy rằng trang web hiện tại đã cố dùng API window.web3 đã bị xóa. Nếu trang web có vẻ như đã bị lỗi, vui lòng nhấp vào $1 để biết thêm thông tin.",
"description": "$1 is a clickable link."

View File

@ -2460,9 +2460,6 @@
"message": "正在发送$1",
"description": "$1 represents the native currency symbol for the current network (e.g. ETH or BNB)"
},
"separateEachWord": {
"message": "用空格分隔每个单词"
},
"setAdvancedPrivacySettings": {
"message": "设置高级隐私设置"
},
@ -3375,9 +3372,6 @@
"walletCreationSuccessTitle": {
"message": "钱包创建成功"
},
"walletSeedRestore": {
"message": "钱包账户助记词"
},
"web3ShimUsageNotification": {
"message": "我们发现当前的网站尝试使用已经删除的 window.web3 API。如果这个网站网站已经无法正常使用请点击 $1 获取更多信息。",
"description": "$1 is a clickable link."

View File

@ -855,9 +855,6 @@
"sendTokens": {
"message": "發送代幣"
},
"separateEachWord": {
"message": "單詞之間請以空白間隔"
},
"settings": {
"message": "設定"
},

View File

@ -200,9 +200,10 @@ describe('MetaMask', function () {
await restoreSeedLink.click();
await driver.delay(regularDelayMs);
await driver.clickElement('.import-account__checkbox-container');
await driver.fill('.import-account__secret-phrase', testSeedPhrase);
await driver.fill(
'input[placeholder="Enter your Secret Recovery Phrase"]',
testSeedPhrase,
);
await driver.delay(regularDelayMs);
await driver.fill('#password', 'correct horse battery staple');

View File

@ -87,7 +87,9 @@ describe('Metamask Import UI', function () {
'correct horse battery staple',
);
await driver.clickElement('.first-time-flow__terms');
await driver.clickElement(
'[data-testid="create-new-vault__terms-checkbox"]',
);
await driver.clickElement({ text: 'Import', tag: 'button' });

View File

@ -172,9 +172,10 @@ describe('Metamask Responsive UI', function () {
);
await restoreSeedLink.click();
await driver.clickElement('.import-account__checkbox-container');
await driver.fill('.import-account__secret-phrase', testSeedPhrase);
await driver.fill(
'input[placeholder="Enter your Secret Recovery Phrase"]',
testSeedPhrase,
);
await driver.fill('#password', 'correct horse battery staple');
await driver.fill('#confirm-password', 'correct horse battery staple');

View File

@ -19,6 +19,7 @@
@import 'connected-accounts-permissions/index';
@import 'connected-sites-list/index';
@import 'connected-status-indicator/index';
@import 'create-new-vault/create-new-vault.scss';
@import 'edit-gas-display/index';
@import 'edit-gas-display-education/index';
@import 'edit-gas-fee-button/index';

View File

@ -0,0 +1,249 @@
import { ethers } from 'ethers';
import React, { useCallback, useContext, useState } from 'react';
import PropTypes from 'prop-types';
import { useI18nContext } from '../../../hooks/useI18nContext';
import { MetaMetricsContext } from '../../../contexts/metametrics';
import TextField from '../../ui/text-field';
import Button from '../../ui/button';
import { clearClipboard } from '../../../helpers/utils/util';
import CheckBox from '../../ui/check-box';
import Typography from '../../ui/typography';
import { COLORS } from '../../../helpers/constants/design-system';
import { parseSecretRecoveryPhrase } from './parse-secret-recovery-phrase';
const { isValidMnemonic } = ethers.utils;
export default function CreateNewVault({
disabled = false,
includeTerms = false,
onSubmit,
submitText,
}) {
const [confirmPassword, setConfirmPassword] = useState('');
const [confirmPasswordError, setConfirmPasswordError] = useState('');
const [password, setPassword] = useState('');
const [passwordError, setPasswordError] = useState('');
const [seedPhrase, setSeedPhrase] = useState('');
const [seedPhraseError, setSeedPhraseError] = useState('');
const [showSeedPhrase, setShowSeedPhrase] = useState(false);
const [termsChecked, setTermsChecked] = useState(false);
const t = useI18nContext();
const metricsEvent = useContext(MetaMetricsContext);
const onSeedPhraseChange = useCallback(
(rawSeedPhrase) => {
let newSeedPhraseError = '';
if (rawSeedPhrase) {
const parsedSeedPhrase = parseSecretRecoveryPhrase(rawSeedPhrase);
const wordCount = parsedSeedPhrase.split(/\s/u).length;
if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) {
newSeedPhraseError = t('seedPhraseReq');
} else if (!isValidMnemonic(parsedSeedPhrase)) {
newSeedPhraseError = t('invalidSeedPhrase');
}
}
setSeedPhrase(rawSeedPhrase);
setSeedPhraseError(newSeedPhraseError);
},
[setSeedPhrase, setSeedPhraseError, t],
);
const onPasswordChange = useCallback(
(newPassword) => {
let newConfirmPasswordError = '';
let newPasswordError = '';
if (newPassword && newPassword.length < 8) {
newPasswordError = t('passwordNotLongEnough');
}
if (confirmPassword && newPassword !== confirmPassword) {
newConfirmPasswordError = t('passwordsDontMatch');
}
setPassword(newPassword);
setPasswordError(newPasswordError);
setConfirmPasswordError(newConfirmPasswordError);
},
[confirmPassword, t],
);
const onConfirmPasswordChange = useCallback(
(newConfirmPassword) => {
let newConfirmPasswordError = '';
if (password !== newConfirmPassword) {
newConfirmPasswordError = t('passwordsDontMatch');
}
setConfirmPassword(newConfirmPassword);
setConfirmPasswordError(newConfirmPasswordError);
},
[password, t],
);
const isValid =
!disabled &&
password &&
confirmPassword &&
password === confirmPassword &&
seedPhrase &&
(!includeTerms || termsChecked) &&
!passwordError &&
!confirmPasswordError &&
!seedPhraseError;
const onImport = useCallback(
async (event) => {
event.preventDefault();
if (!isValid) {
return;
}
await onSubmit(password, parseSecretRecoveryPhrase(seedPhrase));
},
[isValid, onSubmit, password, seedPhrase],
);
const toggleTermsCheck = useCallback(() => {
metricsEvent({
eventOpts: {
category: 'Onboarding',
action: 'Import Seed Phrase',
name: 'Check ToS',
},
});
setTermsChecked((currentTermsChecked) => !currentTermsChecked);
}, [metricsEvent]);
const toggleShowSeedPhrase = useCallback(() => {
setShowSeedPhrase((currentShowSeedPhrase) => !currentShowSeedPhrase);
}, []);
const termsOfUse = t('acceptTermsOfUse', [
<a
className="create-new-vault__terms-link"
key="create-new-vault__link-text"
href="https://metamask.io/terms.html"
target="_blank"
rel="noopener noreferrer"
>
{t('terms')}
</a>,
]);
return (
<form className="create-new-vault__form" onSubmit={onImport}>
<div className="create-new-vault__srp-section">
<label
htmlFor="create-new-vault__srp"
className="create-new-vault__srp-label"
>
<Typography>{t('secretRecoveryPhrase')}</Typography>
</label>
{showSeedPhrase ? (
<textarea
id="create-new-vault__srp"
className="create-new-vault__srp-shown"
onChange={(e) => onSeedPhraseChange(e.target.value)}
onPaste={clearClipboard}
value={seedPhrase}
placeholder={t('seedPhrasePlaceholder')}
autoComplete="off"
/>
) : (
<TextField
id="create-new-vault__srp"
type="password"
onChange={(e) => onSeedPhraseChange(e.target.value)}
value={seedPhrase}
placeholder={t('seedPhrasePlaceholderPaste')}
autoComplete="off"
onPaste={clearClipboard}
/>
)}
{seedPhraseError ? (
<Typography
color={COLORS.ERROR1}
tag="span"
className="create-new-vault__srp-error"
>
{seedPhraseError}
</Typography>
) : null}
<div className="create-new-vault__show-srp">
<CheckBox
id="create-new-vault__show-srp-checkbox"
checked={showSeedPhrase}
onClick={toggleShowSeedPhrase}
title={t('showSeedPhrase')}
/>
<label
className="create-new-vault__show-srp-label"
htmlFor="create-new-vault__show-srp-checkbox"
>
<Typography tag="span">{t('showSeedPhrase')}</Typography>
</label>
</div>
</div>
<TextField
id="password"
label={t('newPassword')}
type="password"
value={password}
onChange={(event) => onPasswordChange(event.target.value)}
error={passwordError}
autoComplete="new-password"
margin="normal"
largeLabel
/>
<TextField
id="confirm-password"
label={t('confirmPassword')}
type="password"
value={confirmPassword}
onChange={(event) => onConfirmPasswordChange(event.target.value)}
error={confirmPasswordError}
autoComplete="new-password"
margin="normal"
largeLabel
/>
{includeTerms ? (
<div className="create-new-vault__terms">
<CheckBox
id="create-new-vault__terms-checkbox"
dataTestId="create-new-vault__terms-checkbox"
checked={termsChecked}
onClick={toggleTermsCheck}
/>
<label
className="create-new-vault__terms-label"
htmlFor="create-new-vault__terms-checkbox"
>
<Typography tag="span">{termsOfUse}</Typography>
</label>
</div>
) : null}
<Button
className="create-new-vault__submit-button"
type="primary"
submit
disabled={!isValid}
>
{submitText}
</Button>
</form>
);
}
CreateNewVault.propTypes = {
disabled: PropTypes.bool,
includeTerms: PropTypes.bool,
onSubmit: PropTypes.func.isRequired,
submitText: PropTypes.string.isRequired,
};

View File

@ -0,0 +1,53 @@
.create-new-vault {
&__form {
display: flex;
flex-direction: column;
width: 360px;
}
&__srp-shown {
@include Paragraph;
padding: 8px 16px;
}
&__srp-section {
display: flex;
flex-direction: column;
}
&__srp-label {
margin-bottom: 8px;
}
&__srp-error {
margin-top: 4px;
}
&__show-srp {
margin-top: 16px;
margin-bottom: 16px;
}
&__show-srp-label {
margin-left: 8px;
}
&__terms {
margin-top: 16px;
margin-bottom: 16px;
}
&__terms-label {
margin-left: 8px;
}
&__terms-link {
color: var(--primary-1);
}
&__submit-button#{&}__submit-button {
margin-top: 16px;
width: 170px;
}
}

View File

@ -0,0 +1,29 @@
import React from 'react';
import CreateNewVault from '.';
export default {
title: 'Components/App/CreateNewVault',
id: __filename,
argTypes: {
disabled: { control: 'boolean' },
submitText: { control: 'text' },
},
args: {
submitText: 'Import',
},
};
const Template = (args) => {
return (
<div style={{ width: '600px' }}>
<CreateNewVault {...args} />
</div>
);
};
export const DefaultStory = Template.bind({});
DefaultStory.storyName = 'Default';
export const WithTerms = Template.bind({});
WithTerms.args = { includeTerms: true };

View File

@ -0,0 +1 @@
export { default } from './create-new-vault';

View File

@ -0,0 +1,40 @@
import { parseSecretRecoveryPhrase } from './parse-secret-recovery-phrase';
describe('parseSecretRecoveryPhrase', () => {
it('should handle a regular Secret Recovery Phrase', () => {
expect(parseSecretRecoveryPhrase('foo bar baz')).toStrictEqual(
'foo bar baz',
);
});
it('should handle a mixed-case Secret Recovery Phrase', () => {
expect(parseSecretRecoveryPhrase('FOO bAr baZ')).toStrictEqual(
'foo bar baz',
);
});
it('should handle an upper-case Secret Recovery Phrase', () => {
expect(parseSecretRecoveryPhrase('FOO BAR BAZ')).toStrictEqual(
'foo bar baz',
);
});
it('should trim extraneous whitespace from the given Secret Recovery Phrase', () => {
expect(parseSecretRecoveryPhrase(' foo bar baz ')).toStrictEqual(
'foo bar baz',
);
});
it('should return an empty string when given a whitespace-only string', () => {
expect(parseSecretRecoveryPhrase(' ')).toStrictEqual('');
});
it('should return an empty string when given a string with only symbols', () => {
expect(parseSecretRecoveryPhrase('$')).toStrictEqual('');
});
it('should return an empty string for both null and undefined', () => {
expect(parseSecretRecoveryPhrase(undefined)).toStrictEqual('');
expect(parseSecretRecoveryPhrase(null)).toStrictEqual('');
});
});

View File

@ -0,0 +1,2 @@
export const parseSecretRecoveryPhrase = (seedPhrase) =>
(seedPhrase || '').trim().toLowerCase().match(/\w+/gu)?.join(' ') || '';

View File

@ -1,15 +1,10 @@
import { ethers } from 'ethers';
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import TextField from '../../../../components/ui/text-field';
import Button from '../../../../components/ui/button';
import {
INITIALIZE_SELECT_ACTION_ROUTE,
INITIALIZE_END_OF_FLOW_ROUTE,
} from '../../../../helpers/constants/routes';
import { clearClipboard } from '../../../../helpers/utils/util';
const { isValidMnemonic } = ethers.utils;
import CreateNewVault from '../../../../components/app/create-new-vault';
export default class ImportWithSeedPhrase extends PureComponent {
static contextTypes = {
@ -24,20 +19,6 @@ export default class ImportWithSeedPhrase extends PureComponent {
initializeThreeBox: PropTypes.func,
};
state = {
seedPhrase: '',
showSeedPhrase: false,
password: '',
confirmPassword: '',
seedPhraseError: '',
passwordError: '',
confirmPasswordError: '',
termsChecked: false,
};
parseSeedPhrase = (seedPhrase) =>
(seedPhrase || '').trim().toLowerCase().match(/\w+/gu)?.join(' ') || '';
UNSAFE_componentWillMount() {
this._onBeforeUnload = () =>
this.context.metricsEvent({
@ -58,72 +39,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
window.removeEventListener('beforeunload', this._onBeforeUnload);
}
handleSeedPhraseChange(seedPhrase) {
let seedPhraseError = '';
if (seedPhrase) {
const parsedSeedPhrase = this.parseSeedPhrase(seedPhrase);
const wordCount = parsedSeedPhrase.split(/\s/u).length;
if (wordCount % 3 !== 0 || wordCount > 24 || wordCount < 12) {
seedPhraseError = this.context.t('seedPhraseReq');
} else if (!isValidMnemonic(parsedSeedPhrase)) {
seedPhraseError = this.context.t('invalidSeedPhrase');
}
}
this.setState({ seedPhrase, seedPhraseError });
}
handlePasswordChange(password) {
const { t } = this.context;
this.setState((state) => {
const { confirmPassword } = state;
let confirmPasswordError = '';
let passwordError = '';
if (password && password.length < 8) {
passwordError = t('passwordNotLongEnough');
}
if (confirmPassword && password !== confirmPassword) {
confirmPasswordError = t('passwordsDontMatch');
}
return {
password,
passwordError,
confirmPasswordError,
};
});
}
handleConfirmPasswordChange(confirmPassword) {
const { t } = this.context;
this.setState((state) => {
const { password } = state;
let confirmPasswordError = '';
if (password !== confirmPassword) {
confirmPasswordError = t('passwordsDontMatch');
}
return {
confirmPassword,
confirmPasswordError,
};
});
}
handleImport = async (event) => {
event.preventDefault();
if (!this.isValid()) {
return;
}
const { password, seedPhrase } = this.state;
handleImport = async (password, seedPhrase) => {
const {
history,
onSubmit,
@ -131,88 +47,25 @@ export default class ImportWithSeedPhrase extends PureComponent {
initializeThreeBox,
} = this.props;
try {
await onSubmit(password, this.parseSeedPhrase(seedPhrase));
this.context.metricsEvent({
eventOpts: {
category: 'Onboarding',
action: 'Import Seed Phrase',
name: 'Import Complete',
},
});
setSeedPhraseBackedUp(true).then(async () => {
initializeThreeBox();
history.replace(INITIALIZE_END_OF_FLOW_ROUTE);
});
} catch (error) {
this.setState({ seedPhraseError: error.message });
}
};
isValid() {
const {
seedPhrase,
password,
confirmPassword,
passwordError,
confirmPasswordError,
seedPhraseError,
} = this.state;
if (
!password ||
!confirmPassword ||
!seedPhrase ||
password !== confirmPassword
) {
return false;
}
if (password.length < 8) {
return false;
}
return !passwordError && !confirmPasswordError && !seedPhraseError;
}
onTermsKeyPress = ({ key }) => {
if (key === ' ' || key === 'Enter') {
this.toggleTermsCheck();
}
};
toggleTermsCheck = () => {
await onSubmit(password, seedPhrase);
this.context.metricsEvent({
eventOpts: {
category: 'Onboarding',
action: 'Import Seed Phrase',
name: 'Check ToS',
name: 'Import Complete',
},
});
this.setState((prevState) => ({
termsChecked: !prevState.termsChecked,
}));
};
toggleShowSeedPhrase = () => {
this.setState(({ showSeedPhrase }) => ({
showSeedPhrase: !showSeedPhrase,
}));
await setSeedPhraseBackedUp(true);
initializeThreeBox();
history.replace(INITIALIZE_END_OF_FLOW_ROUTE);
};
render() {
const { t } = this.context;
const {
seedPhraseError,
showSeedPhrase,
passwordError,
confirmPasswordError,
termsChecked,
} = this.state;
return (
<form className="first-time-flow__form" onSubmit={this.handleImport}>
<div className="first-time-flow__import">
<div className="first-time-flow__create-back">
<a
onClick={(e) => {
@ -223,10 +76,6 @@ export default class ImportWithSeedPhrase extends PureComponent {
action: 'Import Seed Phrase',
name: 'Go Back from Onboarding Import',
},
customVariables: {
errorLabel: 'Seed Phrase Error',
errorMessage: seedPhraseError,
},
});
this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE);
}}
@ -239,116 +88,12 @@ export default class ImportWithSeedPhrase extends PureComponent {
{t('importAccountSeedPhrase')}
</div>
<div className="first-time-flow__text-block">{t('secretPhrase')}</div>
<div className="first-time-flow__textarea-wrapper">
<label>{t('secretRecoveryPhrase')}</label>
{showSeedPhrase ? (
<textarea
className="first-time-flow__textarea"
onChange={(e) => this.handleSeedPhraseChange(e.target.value)}
onPaste={clearClipboard}
value={this.state.seedPhrase}
placeholder={t('seedPhrasePlaceholder')}
autoComplete="off"
/>
) : (
<TextField
className="first-time-flow__textarea first-time-flow__seedphrase"
type="password"
onChange={(e) => this.handleSeedPhraseChange(e.target.value)}
value={this.state.seedPhrase}
placeholder={t('seedPhrasePlaceholderPaste')}
autoComplete="off"
onPaste={clearClipboard}
/>
)}
{seedPhraseError ? (
<span className="error">{seedPhraseError}</span>
) : null}
<div
className="first-time-flow__checkbox-container"
onClick={this.toggleShowSeedPhrase}
>
<div
className="first-time-flow__checkbox"
tabIndex="0"
role="checkbox"
onKeyPress={this.toggleShowSeedPhrase}
aria-checked={showSeedPhrase}
aria-labelledby="ftf-chk1-label"
>
{showSeedPhrase ? <i className="fa fa-check fa-2x" /> : null}
</div>
<span
id="ftf-chk1-label"
className="first-time-flow__checkbox-label"
>
{t('showSeedPhrase')}
</span>
</div>
</div>
<TextField
id="password"
label={t('newPassword')}
type="password"
className="first-time-flow__input"
value={this.state.password}
onChange={(event) => this.handlePasswordChange(event.target.value)}
error={passwordError}
autoComplete="new-password"
margin="normal"
largeLabel
<CreateNewVault
includeTerms
onSubmit={this.handleImport}
submitText={t('import')}
/>
<TextField
id="confirm-password"
label={t('confirmPassword')}
type="password"
className="first-time-flow__input"
value={this.state.confirmPassword}
onChange={(event) =>
this.handleConfirmPasswordChange(event.target.value)
}
error={confirmPasswordError}
autoComplete="new-password"
margin="normal"
largeLabel
/>
<div
className="first-time-flow__checkbox-container"
onClick={this.toggleTermsCheck}
>
<div
className="first-time-flow__checkbox first-time-flow__terms"
tabIndex="0"
role="checkbox"
onKeyPress={this.onTermsKeyPress}
aria-checked={termsChecked}
aria-labelledby="ftf-chk1-label"
>
{termsChecked ? <i className="fa fa-check fa-2x" /> : null}
</div>
<span id="ftf-chk1-label" className="first-time-flow__checkbox-label">
{t('acceptTermsOfUse', [
<a
onClick={(e) => e.stopPropagation()}
key="first-time-flow__link-text"
href="https://metamask.io/terms.html"
target="_blank"
rel="noopener noreferrer"
>
<span className="first-time-flow__link-text">{t('terms')}</span>
</a>,
])}
</span>
</div>
<Button
type="primary"
submit
className="first-time-flow__button"
disabled={!this.isValid() || !termsChecked}
>
{t('import')}
</Button>
</form>
</div>
);
}
}

View File

@ -1,99 +0,0 @@
import React from 'react';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import ImportWithSeedPhrase from './import-with-seed-phrase.component';
function shallowRender(props = {}, context = {}) {
return shallow(<ImportWithSeedPhrase {...props} />, {
context: {
t: (str) => `${str}_t`,
metricsEvent: sinon.spy(),
...context,
},
});
}
describe('ImportWithSeedPhrase Component', () => {
it('should render without error', () => {
const component = shallowRender({
onSubmit: sinon.spy(),
});
const textareaCount = component.find('.first-time-flow__textarea').length;
expect(textareaCount).toStrictEqual(1);
});
describe('parseSeedPhrase', () => {
it('should handle a regular Secret Recovery Phrase', () => {
const component = shallowRender({
onSubmit: sinon.spy(),
});
const { parseSeedPhrase } = component.instance();
expect(parseSeedPhrase('foo bar baz')).toStrictEqual('foo bar baz');
});
it('should handle a mixed-case Secret Recovery Phrase', () => {
const component = shallowRender({
onSubmit: sinon.spy(),
});
const { parseSeedPhrase } = component.instance();
expect(parseSeedPhrase('FOO bAr baZ')).toStrictEqual('foo bar baz');
});
it('should handle an upper-case Secret Recovery Phrase', () => {
const component = shallowRender({
onSubmit: sinon.spy(),
});
const { parseSeedPhrase } = component.instance();
expect(parseSeedPhrase('FOO BAR BAZ')).toStrictEqual('foo bar baz');
});
it('should trim extraneous whitespace from the given Secret Recovery Phrase', () => {
const component = shallowRender({
onSubmit: sinon.spy(),
});
const { parseSeedPhrase } = component.instance();
expect(parseSeedPhrase(' foo bar baz ')).toStrictEqual(
'foo bar baz',
);
});
it('should return an empty string when given a whitespace-only string', () => {
const component = shallowRender({
onSubmit: sinon.spy(),
});
const { parseSeedPhrase } = component.instance();
expect(parseSeedPhrase(' ')).toStrictEqual('');
});
it('should return an empty string when given a string with only symbols', () => {
const component = shallowRender({
onSubmit: sinon.spy(),
});
const { parseSeedPhrase } = component.instance();
expect(parseSeedPhrase('$')).toStrictEqual('');
});
it('should return an empty string for both null and undefined', () => {
const component = shallowRender({
onSubmit: sinon.spy(),
});
const { parseSeedPhrase } = component.instance();
expect(parseSeedPhrase(undefined)).toStrictEqual('');
expect(parseSeedPhrase(null)).toStrictEqual('');
});
});
});

View File

@ -29,7 +29,7 @@
}
}
&__form {
&__import {
display: flex;
flex-direction: column;
}
@ -45,51 +45,10 @@
color: black;
}
&__subheader {
margin-bottom: 16px;
}
&__input {
max-width: 350px;
}
&__textarea-wrapper {
margin-bottom: 8px;
display: inline-flex;
padding: 0;
position: relative;
min-width: 0;
flex-direction: column;
max-width: 350px;
}
&__textarea-label {
@include H4;
margin-bottom: 9px;
color: #1b344d;
}
&__textarea {
@include Paragraph;
/*rtl:ignore*/
direction: ltr;
border: 1px solid #cdcdcd;
border-radius: 6px;
background-color: #fff;
padding: 16px;
margin-top: 8px;
}
&__seedphrase {
margin-top: 9px !important;
}
&__breadcrumbs {
margin: 36px 0;
}
&__text-block {
margin-bottom: 24px;
color: black;

View File

@ -25,12 +25,6 @@
margin: 60px 0 30px 0;
position: relative;
max-width: initial;
&__input-label {
padding-bottom: 10px;
font-weight: 400;
display: inline-block;
}
}
@media only screen and (max-width: $break-small) {
@ -60,18 +54,6 @@
.first-time-flow__input {
width: 100%;
}
.import-account__secret-phrase {
width: initial !important;
height: initial !important;
min-height: 190px;
}
}
@media only screen and (max-width: $break-small) {
.import-account__input {
width: 100%;
}
}
.import-account {
@ -91,22 +73,6 @@
top: -25px;
}
&__secret-phrase {
@include Paragraph;
height: 190px;
width: 495px;
border: 1px solid #cdcdcd;
border-radius: 6px;
background-color: #fff;
padding: 17px;
}
&__secret-phrase::placeholder {
color: #9b9b9b;
font-weight: 200;
}
&__selector-label {
@include Paragraph;
@ -119,59 +85,7 @@
align-items: center;
color: #000;
margin-top: 18px;
}
&__input-wrapper {
display: flex;
flex-flow: column nowrap;
margin-top: 18px;
}
&__input {
width: 350px;
}
&__checkbox-container {
display: flex;
align-items: center;
margin-top: 24px;
}
&__checkbox {
background: #fff;
border: 1px solid #cdcdcd;
outline: none;
box-sizing: border-box;
height: 34px;
width: 34px;
display: flex;
justify-content: center;
align-items: center;
&:hover {
border: 1.5px solid #2f9ae0;
}
.fa-check {
color: #2f9ae0;
}
}
&__checkbox-label {
@include H4;
color: #939090;
margin-left: 18px;
}
&__file-input {
display: none;
}
&__file-picker-wrapper {
display: flex;
flex-flow: row nowrap;
align-items: center;
margin-bottom: 16px;
}
}

View File

@ -1,18 +1,13 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { ethers } from 'ethers';
import {
createNewVaultAndRestore,
unMarkPasswordForgotten,
initializeThreeBox,
} from '../../store/actions';
import { DEFAULT_ROUTE } from '../../helpers/constants/routes';
import TextField from '../../components/ui/text-field';
import Button from '../../components/ui/button';
import { clearClipboard } from '../../helpers/utils/util';
const { isValidMnemonic } = ethers.utils;
import CreateNewVault from '../../components/app/create-new-vault';
class RestoreVaultPage extends Component {
static contextTypes = {
@ -28,70 +23,7 @@ class RestoreVaultPage extends Component {
initializeThreeBox: PropTypes.func,
};
state = {
seedPhrase: '',
showSeedPhrase: false,
password: '',
confirmPassword: '',
seedPhraseError: null,
passwordError: null,
confirmPasswordError: null,
};
parseSeedPhrase = (seedPhrase) =>
(seedPhrase || '').trim().toLowerCase().match(/\w+/gu)?.join(' ') || '';
handleSeedPhraseChange(seedPhrase) {
const { t } = this.context;
let seedPhraseError = null;
const parseSeedPhrase = this.parseSeedPhrase(seedPhrase);
const wordCount = parseSeedPhrase.split(/\s/u).length;
if (
parseSeedPhrase &&
(wordCount % 3 !== 0 || wordCount < 12 || wordCount > 24)
) {
seedPhraseError = t('seedPhraseReq');
} else if (!isValidMnemonic(parseSeedPhrase)) {
seedPhraseError = t('invalidSeedPhrase');
}
this.setState({ seedPhrase, seedPhraseError });
}
handlePasswordChange(password) {
const { confirmPassword } = this.state;
let confirmPasswordError = null;
let passwordError = null;
if (password && password.length < 8) {
passwordError = this.context.t('passwordNotLongEnough');
}
if (confirmPassword && password !== confirmPassword) {
confirmPasswordError = this.context.t('passwordsDontMatch');
}
this.setState({ password, passwordError, confirmPasswordError });
}
handleConfirmPasswordChange(confirmPassword) {
const { password } = this.state;
let confirmPasswordError = null;
if (password !== confirmPassword) {
confirmPasswordError = this.context.t('passwordsDontMatch');
}
this.setState({ confirmPassword, confirmPasswordError });
}
handleImport = (event) => {
event.preventDefault();
const { password, seedPhrase, disabled } = this.state;
if (disabled) {
return;
}
handleImport = async (password, seedPhrase) => {
const {
// eslint-disable-next-line no-shadow
createNewVaultAndRestore,
@ -102,50 +34,21 @@ class RestoreVaultPage extends Component {
} = this.props;
leaveImportSeedScreenState();
createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase)).then(
() => {
this.context.metricsEvent({
eventOpts: {
category: 'Retention',
action: 'userEntersSeedPhrase',
name: 'onboardingRestoredVault',
},
});
initializeThreeBox();
history.push(DEFAULT_ROUTE);
await createNewVaultAndRestore(password, seedPhrase);
this.context.metricsEvent({
eventOpts: {
category: 'Retention',
action: 'userEntersSeedPhrase',
name: 'onboardingRestoredVault',
},
);
};
hasError() {
const { passwordError, confirmPasswordError, seedPhraseError } = this.state;
return passwordError || confirmPasswordError || seedPhraseError;
}
toggleShowSeedPhrase = () => {
this.setState(({ showSeedPhrase }) => ({
showSeedPhrase: !showSeedPhrase,
}));
});
initializeThreeBox();
history.push(DEFAULT_ROUTE);
};
render() {
const {
seedPhrase,
showSeedPhrase,
password,
confirmPassword,
seedPhraseError,
passwordError,
confirmPasswordError,
} = this.state;
const { t } = this.context;
const { isLoading } = this.props;
const disabled =
!seedPhrase ||
!password ||
!confirmPassword ||
isLoading ||
this.hasError();
return (
<div className="first-view-main-wrapper">
@ -171,94 +74,11 @@ class RestoreVaultPage extends Component {
<div className="import-account__selector-typography">
{this.context.t('secretPhraseWarning')}
</div>
<form
className="import-account__input-wrapper"
<CreateNewVault
disabled={isLoading}
onSubmit={this.handleImport}
>
<label className="import-account__input-label">
{this.context.t('walletSeedRestore')}
</label>
{showSeedPhrase ? (
<textarea
className="import-account__secret-phrase"
onPaste={clearClipboard}
onChange={(e) => this.handleSeedPhraseChange(e.target.value)}
value={seedPhrase}
autoFocus
placeholder={this.context.t('separateEachWord')}
/>
) : (
<TextField
className="import-account__textarea import-account__seedphrase"
type="password"
onPaste={clearClipboard}
onChange={(e) => this.handleSeedPhraseChange(e.target.value)}
value={seedPhrase}
autoFocus
placeholder={t('seedPhrasePlaceholderPaste')}
/>
)}
<span className="error">{seedPhraseError}</span>
<div
className="import-account__checkbox-container"
onClick={this.toggleShowSeedPhrase}
>
<div
className="import-account__checkbox"
tabIndex="0"
id="seed-checkbox"
role="checkbox"
onKeyPress={this.toggleShowSeedPhrase}
aria-checked={showSeedPhrase}
aria-labelledby="ftf-chk1-label"
>
{showSeedPhrase ? <i className="fa fa-check fa-2x" /> : null}
</div>
<label
htmlFor="seed-checkbox"
id="ftf-chk1-label"
className="import-account__checkbox-label"
>
{t('showSeedPhrase')}
</label>
</div>
<TextField
id="password"
label={t('newPassword')}
type="password"
className="first-time-flow__input"
value={this.state.password}
onChange={(event) =>
this.handlePasswordChange(event.target.value)
}
error={passwordError}
autoComplete="new-password"
margin="normal"
largeLabel
/>
<TextField
id="confirm-password"
label={t('confirmPassword')}
type="password"
className="first-time-flow__input"
value={this.state.confirmPassword}
onChange={(event) =>
this.handleConfirmPasswordChange(event.target.value)
}
error={confirmPasswordError}
autoComplete="confirm-password"
margin="normal"
largeLabel
/>
<Button
type="primary"
submit
className="first-time-flow__button"
disabled={disabled}
>
{this.context.t('restore')}
</Button>
</form>
submitText={t('restore')}
/>
</div>
</div>
</div>