diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs index 1a113ceacb18..4d2848dc8052 100644 --- a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/actions/view.cljs @@ -28,6 +28,9 @@ [keypair]) on-import-seed-phrase (rn/use-callback #(rf/dispatch [:open-modal :screen/settings.import-seed-phrase keypair]) + [keypair]) + on-import-private-key (rn/use-callback + #(rf/dispatch [:open-modal :screen/settings.import-private-key keypair]) [keypair])] [:<> [quo/drawer-top drawer-props] @@ -48,7 +51,11 @@ :seed {:icon :i/seed :accessibility-label :import-seed-phrase :label (i18n/label :t/import-by-entering-recovery-phrase) - :on-press #(on-import-seed-phrase keypair)} + :on-press on-import-seed-phrase} + :key {:icon :i/key + :accessibility-label :import-private-key + :label (i18n/label :t/import-by-entering-private-key) + :on-press on-import-private-key} nil)) {:icon :i/edit :accessibility-label :rename-key-pair diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/import_private_key/style.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/import_private_key/style.cljs new file mode 100644 index 000000000000..97f1d2bb70a4 --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/import_private_key/style.cljs @@ -0,0 +1,25 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.import-private-key.style) + +(def form-container + {:row-gap 8 + :padding-top 8 + :padding-horizontal 20}) + +(def full-layout {:flex 1}) + +(defn page-container + [insets] + {:position :absolute + :top 0 + :bottom (:bottom insets) + :left 0 + :right 0}) + +(def slide-container + {:padding-horizontal 20 + :padding-vertical 12 + :margin-top :auto + :flex-direction :row}) + +(def page-top + {:margin-top 2}) diff --git a/src/status_im/contexts/settings/wallet/keypairs_and_accounts/import_private_key/view.cljs b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/import_private_key/view.cljs new file mode 100644 index 000000000000..baba9ae9b736 --- /dev/null +++ b/src/status_im/contexts/settings/wallet/keypairs_and_accounts/import_private_key/view.cljs @@ -0,0 +1,141 @@ +(ns status-im.contexts.settings.wallet.keypairs-and-accounts.import-private-key.view + (:require + [clojure.string :as string] + [quo.core :as quo] + [react-native.clipboard :as clipboard] + [react-native.core :as rn] + [react-native.safe-area :as safe-area] + [status-im.common.standard-authentication.core :as standard-auth] + [status-im.contexts.settings.wallet.keypairs-and-accounts.import-private-key.style :as style] + [status-im.contexts.wallet.common.validation :as validation] + [utils.debounce :as debounce] + [utils.i18n :as i18n] + [utils.re-frame :as rf] + [utils.security.core :as security])) + +(defn navigate-back + [] + (rf/dispatch [:navigate-back])) + +(defn view + [] + (let [blur? true + insets (safe-area/get-insets) + keypair (rf/sub [:get-screen-params]) + customization-color (rf/sub [:profile/customization-color]) + [private-key set-private-key] (rn/use-state "") + [flow-state set-flow-state] (rn/use-state nil) + error? (case flow-state + (:incorrect-private-key + :invalid-private-key) true + false) + clear-errors (rn/use-callback + #(set-flow-state nil)) + show-invalid (rn/use-callback + #(set-flow-state :invalid-private-key)) + show-correct (rn/use-callback + #(set-flow-state :correct-private-key)) + show-incorrect (rn/use-callback + #(set-flow-state :incorrect-private-key)) + verify-private-key (rn/use-callback + (fn [input] + (rf/dispatch [:wallet/verify-private-key-for-keypair + (:key-uid keypair) + (security/mask-data input) + show-correct + show-incorrect])) + [keypair]) + validate-private-key (rn/use-callback + (debounce/debounce + (fn [input] + (if-not (validation/private-key? input) + (show-invalid) + (do (clear-errors) + (verify-private-key input)))) + 500) + [verify-private-key]) + on-change (rn/use-callback + (fn [input] + (set-private-key input) + (validate-private-key input)) + [validate-private-key]) + on-paste (rn/use-callback + #(clipboard/get-string + (fn [clipboard] + (when-not (empty? clipboard) + (on-change clipboard)))) + [on-change]) + on-import-error (rn/use-callback + (fn [_error] + (rf/dispatch [:hide-bottom-sheet]) + (show-invalid))) + on-import-success (rn/use-callback + (fn [] + (rf/dispatch [:hide-bottom-sheet]) + (rf/dispatch [:navigate-back])) + []) + on-auth-success (rn/use-callback + (fn [password] + (rf/dispatch [:wallet/import-keypair-by-private-key + {:keypair-key-uid (:key-uid keypair) + :private-key (security/mask-data private-key) + :password password + :on-success on-import-success + :on-error on-import-error}])) + [keypair private-key on-import-success on-import-error])] + [quo/overlay {:type :shell} + [rn/view {:style style/full-layout} + [rn/keyboard-avoiding-view {:style (style/page-container insets)} + [quo/page-nav + {:margin-top (:top insets) + :background :blur + :icon-name :i/close + :on-press navigate-back}] + [quo/page-top + {:blur? true + :container-style style/page-top + :title (i18n/label :t/import-private-key) + :description :context-tag + :context-tag {:type :icon + :icon :i/password + :size 24 + :context (:name keypair)}}] + [rn/view {:style style/form-container} + [quo/input + {:accessibility-label :import-private-key + :placeholder (i18n/label :t/enter-private-key-placeholder) + :label (i18n/label :t/private-key) + :type :password + :blur? blur? + :error? error? + :return-key-type :done + :auto-focus true + :on-change-text on-change + :button (when (empty? private-key) + {:on-press on-paste + :text (i18n/label :t/paste)}) + :default-value private-key}] + (when flow-state + [quo/info-message + {:type (if (= flow-state :correct-private-key) + :success + :error) + :size :default + :icon :i/info} + (case flow-state + :correct-private-key (i18n/label :t/correct-private-key) + :invalid-private-key (i18n/label :t/invalid-private-key) + :incorrect-private-key (i18n/label :t/incorrect-private-key {:name (:name keypair)}) + nil)])] + + [rn/view {:style style/slide-container} + [standard-auth/slide-button + {:blur? true + :size :size-48 + :customization-color customization-color + :track-text (i18n/label :t/slide-to-import) + :on-auth-success on-auth-success + :auth-button-label (i18n/label :t/import-key-pair) + :auth-button-icon-left :i/key + :disabled? (or error? (string/blank? private-key)) + :dependencies [on-auth-success]}]]]]])) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index 53335b95269f..a81006ab5dae 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -59,6 +59,8 @@ [status-im.contexts.profile.settings.view :as settings] [status-im.contexts.settings.wallet.keypairs-and-accounts.encrypted-qr.view :as encrypted-key-pair-qr] + [status-im.contexts.settings.wallet.keypairs-and-accounts.import-private-key.view :as + import-private-key] [status-im.contexts.settings.wallet.keypairs-and-accounts.import-seed-phrase.view :as import-seed-phrase] [status-im.contexts.settings.wallet.keypairs-and-accounts.rename.view :as keypair-rename] @@ -553,6 +555,10 @@ :options options/transparent-screen-options :component import-seed-phrase/view} + {:name :screen/settings.import-private-key + :options options/transparent-screen-options + :component import-private-key/view} + {:name :screen/settings.network-settings :options options/transparent-modal-screen-options :component network-settings/view}