From 9c9b55fe3e685fe716e8c2808e57d69bd2e0a74b Mon Sep 17 00:00:00 2001 From: flexsurfer Date: Mon, 25 Nov 2024 15:22:21 +0100 Subject: [PATCH] [#21573] Keycard - Sign up with a new Keycard (#21623) --- .../contexts/keycard/create/events.cljs | 101 ++++++++++++++++++ .../contexts/keycard/create/view.cljs | 62 +++++++++++ .../contexts/keycard/empty/view.cljs | 26 ++++- src/status_im/contexts/keycard/events.cljs | 1 + .../create_or_sync_profile/view.cljs | 30 +++--- src/status_im/navigation/screens.cljs | 18 +++- translations/en.json | 5 + 7 files changed, 224 insertions(+), 19 deletions(-) create mode 100644 src/status_im/contexts/keycard/create/events.cljs create mode 100644 src/status_im/contexts/keycard/create/view.cljs diff --git a/src/status_im/contexts/keycard/create/events.cljs b/src/status_im/contexts/keycard/create/events.cljs new file mode 100644 index 0000000000..cabebfa800 --- /dev/null +++ b/src/status_im/contexts/keycard/create/events.cljs @@ -0,0 +1,101 @@ +(ns status-im.contexts.keycard.create.events + (:require [clojure.string :as string] + [utils.re-frame :as rf] + [utils.security.core :as security])) + +(rf/reg-event-fx :keycard/create.check-empty-card + (fn [_] + {:fx [[:dispatch + [:keycard/connect + {:on-error + (fn [error] + (if (= error :keycard/error.keycard-blank) + (do + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.empty-create])) + (rf/dispatch [:keycard/on-application-info-error error])))}]]]})) + +(rf/reg-event-fx :keycard/create.get-phrase + (fn [{:keys [db]}] + {:db (assoc-in db [:keycard :create] nil) + :fx [[:dispatch [:navigate-back]] + [:dispatch + [:open-modal :screen/backup-recovery-phrase-dark + {:on-success #(rf/dispatch [:keycard/create.phrase-backed-up %])}]]]})) + +(rf/reg-event-fx :keycard/create.phrase-backed-up + (fn [{:keys [db]} [masked-phrase-vector]] + {:db (assoc-in db + [:keycard :create :masked-phrase] + (->> masked-phrase-vector + security/safe-unmask-data + (string/join " ") + security/mask-data)) + :fx [[:dispatch [:keycard/create.create-or-enter-pin]]]})) + +(rf/reg-event-fx :keycard/create.create-or-enter-pin + (fn [{:keys [db]}] + (let [{:keys [initialized?]} (get-in db [:keycard :application-info])] + {:fx [[:dispatch [:navigate-back]] + (if initialized? + [:dispatch + [:open-modal :screen/keycard.pin.enter + {:on-complete (fn [new-pin] + (rf/dispatch [:keycard/create.save-pin new-pin]) + (rf/dispatch [:keycard/create.start]))}]] + [:dispatch + [:open-modal :screen/keycard.pin.create + {:on-complete (fn [new-pin] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:keycard/create.save-pin new-pin]) + (rf/dispatch [:open-modal :screen/keycard.create.ready-to-add]))}]])]}))) + +(rf/reg-event-fx :keycard/create.save-pin + (fn [{:keys [db]} [pin]] + {:db (assoc-in db [:keycard :create :pin] pin)})) + +(rf/reg-event-fx :keycard/create.start + (fn [_] + {:fx [[:dispatch + [:keycard/connect + {:on-error + (fn [error] + (if (= error :keycard/error.keycard-blank) + (rf/dispatch [:keycard/create.continue]) + (rf/dispatch [:keycard/on-application-info-error error])))}]]]})) + +(defn get-application-info-and-continue + [init?] + (rf/dispatch [:keycard/get-application-info + {:on-success #(rf/dispatch [:keycard/create.continue]) + :on-error + (fn [error] + (if (or (= error :keycard/error.keycard-blank) + (and (not init?) (= error :keycard/error.keycard-wrong-profile))) + (rf/dispatch [:keycard/create.continue]) + (rf/dispatch [:keycard/on-application-info-error error])))}])) + +(rf/reg-event-fx :keycard/create.continue + (fn [{:keys [db]}] + (let [{:keys [initialized? has-master-key?]} (get-in db [:keycard :application-info]) + {:keys [masked-phrase pin]} (get-in db [:keycard :create])] + + (cond + + (not initialized?) + {:fx [[:keycard/init-card + {:pin pin + :on-success #(get-application-info-and-continue true)}]]} + + (not has-master-key?) + {:fx [[:effects.keycard/generate-and-load-key + {:mnemonic (security/safe-unmask-data masked-phrase) + :pin pin + :on-success #(get-application-info-and-continue false) + :on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]]} + + :else + {:fx [[:effects.keycard/get-more-keys + {:pin pin + :on-success #(rf/dispatch [:keycard.login/recover-profile-and-login %]) + :on-failure #(rf/dispatch [:keycard/on-action-with-pin-error %])}]]})))) diff --git a/src/status_im/contexts/keycard/create/view.cljs b/src/status_im/contexts/keycard/create/view.cljs new file mode 100644 index 0000000000..9cd0e3dc34 --- /dev/null +++ b/src/status_im/contexts/keycard/create/view.cljs @@ -0,0 +1,62 @@ +(ns status-im.contexts.keycard.create.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.events-helper :as events-helper] + [status-im.common.resources :as resources] + [status-im.constants :as constants] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn view + [] + [:<> + [quo/page-nav + {:key :header + :background :blur + :icon-name :i/arrow-left + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/create-profile-keycard)}] + [rn/view {:style {:padding-horizontal 20 :padding-top 20}} + [quo/small-option-card + {:variant :main + :title (i18n/label :t/check-keycard) + :subtitle (i18n/label :t/see-keycard-ready) + :button-label (i18n/label :t/scan-keycard) + :accessibility-label :get-keycard + :image (resources/get-image :check-your-keycard) + :on-press #(rf/dispatch [:keycard/create.check-empty-card])}] + [rn/view {:style {:height 12}}] + [quo/small-option-card + {:variant :icon + :title (i18n/label :t/learn-more-keycard) + :subtitle (i18n/label :t/secure-wallet-card) + :accessibility-label :setup-keycard + :image (resources/get-image :use-keycard) + :on-press #(rf/dispatch [:browser.ui/open-url constants/get-keycard-url])}]] + [rn/view {:style {:flex 1}}] + [quo/divider-label (i18n/label :t/tips-scan-keycard)] + [quo/markdown-list {:description (i18n/label :t/remove-phone-case)}] + [quo/markdown-list {:description (i18n/label :t/keep-card-steady)}]]) + +(defn ready-to-add + [] + [:<> + [quo/page-nav + {:icon-name :i/arrow-left + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/ready-add-keypair-keycard) + :description :text + :description-text ""}] + [rn/view {:style {:flex 1 :align-items :center :justify-content :center}} + [rn/image + {:resize-mode :contain + :source (resources/get-image :generate-keys1)}]] + [quo/divider-label (i18n/label :t/tips-scan-keycard)] + [quo/markdown-list {:description (i18n/label :t/remove-phone-case)}] + [quo/markdown-list {:description (i18n/label :t/keep-card-steady)}] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/ready-to-scan) + :button-one-props {:on-press #(rf/dispatch [:keycard/create.start])}}]]) diff --git a/src/status_im/contexts/keycard/empty/view.cljs b/src/status_im/contexts/keycard/empty/view.cljs index 500d2ed162..3d7f4bed82 100644 --- a/src/status_im/contexts/keycard/empty/view.cljs +++ b/src/status_im/contexts/keycard/empty/view.cljs @@ -7,6 +7,30 @@ [utils.i18n :as i18n] [utils.re-frame :as rf])) +(defn create + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/keycard-empty) + :description :text + :description-text (i18n/label :t/what-to-do)}] + [rn/view {:style {:padding-horizontal 28 :padding-top 20}} + [quo/small-option-card + {:variant :main + :title (i18n/label :t/create-new-profile) + :subtitle (i18n/label :t/new-key-pair-keycard) + :button-label (i18n/label :t/lets-go) + :accessibility-label :create-new-profile-keycard + :image (resources/get-image :keycard-buy) + :on-press #(rf/dispatch [:keycard/create.get-phrase])}]] + [quo/information-box + {:type :default + :style {:margin-top 32 :margin-horizontal 28}} + (i18n/label :t/add-key-pair-desktop)]]) + (defn view [] [:<> @@ -23,7 +47,7 @@ :title (i18n/label :t/import-key-pair-keycard) :subtitle (i18n/label :t/use-keycard-login-sign) :button-label (i18n/label :t/import-profile-key-pair) - :accessibility-label :get-keycard + :accessibility-label :import-key-pair-keycard :image (resources/get-image :keycard-buy) :on-press (fn [] (rf/dispatch diff --git a/src/status_im/contexts/keycard/events.cljs b/src/status_im/contexts/keycard/events.cljs index 60ee834124..a1af7520a7 100644 --- a/src/status_im/contexts/keycard/events.cljs +++ b/src/status_im/contexts/keycard/events.cljs @@ -1,5 +1,6 @@ (ns status-im.contexts.keycard.events (:require [re-frame.core :as rf] + status-im.contexts.keycard.create.events status-im.contexts.keycard.login.events status-im.contexts.keycard.migrate.events status-im.contexts.keycard.migrate.re-encrypting.events diff --git a/src/status_im/contexts/onboarding/create_or_sync_profile/view.cljs b/src/status_im/contexts/onboarding/create_or_sync_profile/view.cljs index 4ab8494e4c..d74f4074ad 100644 --- a/src/status_im/contexts/onboarding/create_or_sync_profile/view.cljs +++ b/src/status_im/contexts/onboarding/create_or_sync_profile/view.cljs @@ -6,7 +6,6 @@ [react-native.safe-area :as safe-area] [status-im.common.check-before-syncing.view :as check-before-syncing] [status-im.common.metrics-confirmation-modal.view :as metrics-modal] - [status-im.common.not-implemented :as not-implemented] [status-im.common.resources :as resources] [status-im.config :as config] [status-im.contexts.onboarding.create-or-sync-profile.style :as style] @@ -44,11 +43,6 @@ :screen/onboarding.new-to-status :screen/onboarding.sync-or-recover-profile)])) -(defn- check-your-keycard - [] - (rf/dispatch [:open-modal :screen/keycard.check - {:on-press #(rf/dispatch [:keycard.login/check-card])}])) - (defn- option-card-max-height [window-height] (- window-height @@ -90,16 +84,6 @@ :image (resources/get-image :ethereum-address) :on-press #(navigate-to-sign-in-by-recovery-phrase true)}]) -(defn- use-empty-keycard-icon-card - [] - [quo/small-option-card - {:variant :icon - :title (i18n/label :t/use-an-empty-keycard) - :subtitle (i18n/label :t/use-an-empty-keycard-subtitle) - :accessibility-label :use-an-empty-keycard-icon-card - :image (resources/get-image :use-keycard) - :on-press status-im.common.not-implemented/alert}]) - (defn- log-in-by-syncing-icon-card [] [quo/small-option-card @@ -110,6 +94,16 @@ :image (resources/get-image :ethereum-address) :on-press show-check-before-syncing}]) +(defn- use-empty-keycard-icon-card + [] + [quo/small-option-card + {:variant :icon + :title (i18n/label :t/use-an-empty-keycard) + :subtitle (i18n/label :t/use-an-empty-keycard-subtitle) + :accessibility-label :use-an-empty-keycard-icon-card + :image (resources/get-image :use-keycard) + :on-press #(rf/dispatch [:open-modal :screen/keycard.create-profile])}]) + (defn- log-in-with-keycard-icon-card [] [quo/small-option-card @@ -118,7 +112,9 @@ :subtitle (i18n/label :t/log-in-with-keycard-subtitle) :accessibility-label :log-in-with-keycard :image (resources/get-image :use-keycard) - :on-press check-your-keycard}]) + :on-press (fn [] + (rf/dispatch [:open-modal :screen/keycard.check + {:on-press #(rf/dispatch [:keycard.login/check-card])}]))}]) (defn sign-in-options [create-profile?] diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index 17bff0b10a..f7ac4b8a28 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -30,6 +30,7 @@ [status-im.contexts.communities.overview.view :as communities.overview] [status-im.contexts.keycard.authorise.view :as keycard.authorise] [status-im.contexts.keycard.check.view :as keycard.check] + [status-im.contexts.keycard.create.view :as keycard.create] [status-im.contexts.keycard.empty.view :as keycard.empty] [status-im.contexts.keycard.error.view :as keycard.error] [status-im.contexts.keycard.migrate.fail.view :as keycard.migrate.fail] @@ -891,6 +892,11 @@ :insets {:top? true :bottom? true}} :component keycard.empty/view} + {:name :screen/keycard.empty-create + :metrics {:track? :true} + :options {:insets {:top? true :bottom? true}} + :component keycard.empty/create} + {:name :screen/keycard.error :metrics {:track? :true} :options {:theme :dark @@ -958,7 +964,17 @@ :metrics {:track? :true} :options {:theme :dark :insets {:top? true :bottom? true}} - :component keycard.migrate.profile-keys/view}]) + :component keycard.migrate.profile-keys/view} + + {:name :screen/keycard.create-profile + :metrics {:track? :true} + :options {:insets {:top? true :bottom? true}} + :component keycard.create/view} + + {:name :screen/keycard.create.ready-to-add + :metrics {:track? :true} + :options {:insets {:top? true :bottom? true}} + :component keycard.create/ready-to-add}]) (defn screens [] diff --git a/translations/en.json b/translations/en.json index b77bbfaa5c..9f4aff9e56 100644 --- a/translations/en.json +++ b/translations/en.json @@ -57,6 +57,7 @@ "add-custom-token": "Add custom token", "add-eth": "Add ETH", "add-favourite": "Add favourite", + "add-key-pair-desktop": "To add existing key pair to the Keycard please use Status Desktop. If you'd like this feature on mobile, feel free to upvote them.", "add-mailserver": "Add Status node", "add-me-to-your-contacts": "Please add me to your contacts", "add-members": "Add members", @@ -550,6 +551,7 @@ "create-pin": "Create 6-digit passcode", "create-pin-description": "You'll need your card + this 6-digit passcode to unlock Status and to confirm transactions", "create-profile": "Create profile", + "create-profile-keycard": "Create profile on empty Keycard", "create-profile-password-info-box-description": "Your Status keys are the foundation of your self-sovereign identity in Web3. You have complete control over these keys, which you can use to sign transactions, access your data, and interact with Web3 services.\n\nYour keys are always securely stored on your device and protected by your Status profile password. Status doesn't know your password and can't reset it for you. If you forget your password, you may lose access to your Status profile and wallet funds.\n\nRemember your Status password and don't share it with anyone.", "create-profile-password-info-box-title": "About your profile password", "created-group-chat-description": "You created the group {{group-name}}", @@ -1430,6 +1432,7 @@ "layer-2": "Layer 2", "learn-more": "Learn more", "learn-more-about-keycard": "Learn more about Keycard", + "learn-more-keycard": "Learn more about Keycard", "leave": "Leave", "leave-chat": "Leave chat", "leave-chat-confirmation": "Chat history will be removed from your device. After rejoining you won't be able to retrieve any of your history.", @@ -1708,6 +1711,7 @@ "new-group": "New group", "new-group-chat": "New group chat", "new-group-limit": "You can only add {{max-contacts}} contacts to the group chat", + "new-key-pair-keycard": "New key pair will be stored on Keycard", "new-messages-header": "New Messages", "new-network": "New network", "new-password": "New password", @@ -2058,6 +2062,7 @@ "re-encrypting-data": "Re-encrypting data", "read": "Read", "read-more": "Read more", + "ready-add-keypair-keycard": "Ready to add key pair to Keycard", "ready-keycard": "Get your Keycard ready", "ready-to-migrate-key-pair": "Ready to migrate profile key pair to the Keycard", "ready-to-scan": "Ready to Scan",