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.
feature/default_network_editable
Mark Stacey 3 years ago committed by GitHub
parent 760ed3457d
commit 429451de23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      app/_locales/am/messages.json
  2. 3
      app/_locales/ar/messages.json
  3. 3
      app/_locales/bg/messages.json
  4. 3
      app/_locales/bn/messages.json
  5. 3
      app/_locales/ca/messages.json
  6. 3
      app/_locales/da/messages.json
  7. 3
      app/_locales/de/messages.json
  8. 6
      app/_locales/el/messages.json
  9. 6
      app/_locales/en/messages.json
  10. 6
      app/_locales/es/messages.json
  11. 6
      app/_locales/es_419/messages.json
  12. 3
      app/_locales/et/messages.json
  13. 3
      app/_locales/fa/messages.json
  14. 3
      app/_locales/fi/messages.json
  15. 3
      app/_locales/fil/messages.json
  16. 6
      app/_locales/fr/messages.json
  17. 3
      app/_locales/he/messages.json
  18. 6
      app/_locales/hi/messages.json
  19. 3
      app/_locales/hr/messages.json
  20. 3
      app/_locales/ht/messages.json
  21. 3
      app/_locales/hu/messages.json
  22. 6
      app/_locales/id/messages.json
  23. 3
      app/_locales/it/messages.json
  24. 6
      app/_locales/ja/messages.json
  25. 3
      app/_locales/kn/messages.json
  26. 6
      app/_locales/ko/messages.json
  27. 3
      app/_locales/lt/messages.json
  28. 3
      app/_locales/lv/messages.json
  29. 3
      app/_locales/ms/messages.json
  30. 3
      app/_locales/no/messages.json
  31. 6
      app/_locales/ph/messages.json
  32. 3
      app/_locales/pl/messages.json
  33. 6
      app/_locales/pt_BR/messages.json
  34. 3
      app/_locales/ro/messages.json
  35. 6
      app/_locales/ru/messages.json
  36. 3
      app/_locales/sk/messages.json
  37. 3
      app/_locales/sl/messages.json
  38. 3
      app/_locales/sr/messages.json
  39. 3
      app/_locales/sv/messages.json
  40. 3
      app/_locales/sw/messages.json
  41. 6
      app/_locales/tl/messages.json
  42. 6
      app/_locales/tr/messages.json
  43. 3
      app/_locales/uk/messages.json
  44. 6
      app/_locales/vi/messages.json
  45. 6
      app/_locales/zh_CN/messages.json
  46. 3
      app/_locales/zh_TW/messages.json
  47. 7
      test/e2e/metamask-ui.spec.js
  48. 4
      test/e2e/tests/from-import-ui.spec.js
  49. 7
      test/e2e/tests/metamask-responsive-ui.spec.js
  50. 1
      ui/components/app/app-components.scss
  51. 249
      ui/components/app/create-new-vault/create-new-vault.js
  52. 53
      ui/components/app/create-new-vault/create-new-vault.scss
  53. 29
      ui/components/app/create-new-vault/create-new-vault.stories.js
  54. 1
      ui/components/app/create-new-vault/index.js
  55. 40
      ui/components/app/create-new-vault/parse-secret-recovery-phrase'.test.js
  56. 2
      ui/components/app/create-new-vault/parse-secret-recovery-phrase.js
  57. 281
      ui/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
  58. 99
      ui/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.test.js
  59. 43
      ui/pages/first-time-flow/index.scss
  60. 88
      ui/pages/keychains/index.scss
  61. 210
      ui/pages/keychains/restore-vault.js

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

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

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

@ -876,9 +876,6 @@
"sendTokens": {
"message": "টনগিন"
},
"separateEachWord": {
"message": "পরতিি শবদক একটিস দি আল করন"
},
"settings": {
"message": "সিস"
},

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

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

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

@ -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."

@ -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."

@ -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."

@ -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."

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

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

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

@ -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"
},

@ -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é d’utiliser l’API window.web3 supprimée. Si le site semble être défectueux, veuillez cliquer sur $1 pour plus d’informations.",
"description": "$1 is a clickable link."

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

@ -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."

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

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

@ -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"
},

@ -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."

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

@ -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."

@ -876,9 +876,6 @@
"sendTokens": {
"message": "ಟಕನಗಳನ ಕಳಿಿ"
},
"separateEachWord": {
"message": "ಒಲಕ ಪರತಿ ಪದವನಪಡಿಿ"
},
"settings": {
"message": "ಸಿಗಳ"
},

@ -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."

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

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

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

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

@ -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."

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

@ -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."

@ -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"
},

@ -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."

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

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

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

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

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

@ -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."

@ -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."

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

@ -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."

@ -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."

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

@ -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');

@ -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' });

@ -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');

@ -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';

@ -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,
};

@ -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;
}
}

@ -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 };

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

@ -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('');
});
});

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

@ -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>
);
}
}

@ -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('');
});
});
});

@ -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;

@ -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;
}
}

@ -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>

Loading…
Cancel
Save