diff --git a/src/status_im/contexts/keycard/change_pin/events.cljs b/src/status_im/contexts/keycard/change_pin/events.cljs new file mode 100644 index 00000000000..92a9c7c2551 --- /dev/null +++ b/src/status_im/contexts/keycard/change_pin/events.cljs @@ -0,0 +1,72 @@ +(ns status-im.contexts.keycard.change-pin.events + (:require [status-im.contexts.keycard.utils :as utils] + [utils.i18n :as i18n] + [utils.re-frame :as rf] + [utils.security.core :as security])) + +(rf/reg-event-fx :keycard/change-pin.enter-current-pin + (fn [{:keys [db]}] + {:db (update db :keycard dissoc :change-pin) + :fx [[:dispatch [:navigate-back]] + [:dispatch + [:open-modal :screen/keycard.pin.enter + {:title (i18n/label :t/enter-current-keycard-pin) + :on-complete (fn [current-pin] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:keycard/change-pin.save-current-pin current-pin]) + (rf/dispatch [:keycard/change-pin.enter-new-pin]))}]]]})) + +(rf/reg-event-fx :keycard/change-pin.save-current-pin + (fn [{:keys [db]} [pin]] + {:db (assoc-in db [:keycard :change-pin :current-pin] (security/mask-data pin))})) + +(rf/reg-event-fx :keycard/change-pin.enter-new-pin + (fn [_] + {:fx [[:dispatch + [:open-modal :screen/keycard.pin.create + {:title (i18n/label :t/create-new-keycard-pin) + :repeat-stage-title (i18n/label :t/repeat-new-keycard-pin) + :on-complete (fn [new-pin] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:keycard/change-pin.save-new-pin new-pin]) + (rf/dispatch [:open-modal :screen/keycard.ready-to-change-pin]))}]]]})) + +(rf/reg-event-fx :keycard/change-pin.save-new-pin + (fn [{:keys [db]} [pin]] + {:db (assoc-in db [:keycard :change-pin :new-pin] (security/mask-data pin))})) + +(defn- change-pin-and-continue + [current-pin new-pin] + (rf/dispatch [:keycard/change-pin + {:on-success (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.pin-change-success])) + :on-failure (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:open-modal :screen/keycard.pin-change-failed])) + :current-pin current-pin + :new-pin new-pin}])) + +(defn- verify-pin-and-continue + [current-pin new-pin] + (rf/dispatch + [:keycard/verify-pin + {:pin current-pin + :on-success #(change-pin-and-continue current-pin new-pin) + :on-failure (fn [error] + (when-not (utils/tag-lost? (:error error)) + (rf/dispatch [:keycard/disconnect]) + (rf/dispatch [:keycard/on-action-with-pin-error error]) + (rf/dispatch [:keycard/change-pin.enter-current-pin])))}])) + +(rf/reg-event-fx :keycard/change-pin.verify-current-pin-and-continue + (fn [{:keys [db]}] + (let [{:keys [current-pin new-pin]} (get-in db [:keycard :change-pin]) + unmasked-current-pin (security/safe-unmask-data current-pin) + unmasked-new-pin (security/safe-unmask-data new-pin)] + {:fx [[:dispatch + [:keycard/connect + {:key-uid (get-in db [:profile/profile :key-uid]) + :on-success #(verify-pin-and-continue unmasked-current-pin unmasked-new-pin)}]]]}))) diff --git a/src/status_im/contexts/keycard/change_pin/view.cljs b/src/status_im/contexts/keycard/change_pin/view.cljs new file mode 100644 index 00000000000..80657a1956d --- /dev/null +++ b/src/status_im/contexts/keycard/change_pin/view.cljs @@ -0,0 +1,82 @@ +(ns status-im.contexts.keycard.change-pin.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [status-im.common.events-helper :as events-helper] + [status-im.contexts.keycard.common.view :as common.view] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn change-pin-confirmation-sheet + [] + [quo/documentation-drawers + {:title (i18n/label :t/change-pin-keycard) + :shell? true} + [rn/view + [quo/text {:size :paragraph-2} + (i18n/label :t/change-pin-keycard-message)] + [quo/bottom-actions + {:actions :two-actions + :container-style {:margin-horizontal -20} + :blur? true + :button-one-label (i18n/label :t/continue) + :button-one-props {:on-press #(rf/dispatch [:keycard/change-pin.enter-current-pin])} + :button-two-label (i18n/label :t/cancel) + :button-two-props {:on-press events-helper/hide-bottom-sheet + :type :grey}}]]]) + +(defn ready-to-change-pin + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/ready-to-change-pin)}] + [rn/view {:style {:flex 1 :align-items :center :justify-content :center}} + [rn/image + {:resize-mode :contain}]] + [common.view/tips] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/scan-keycard) + :button-one-props {:on-press #(rf/dispatch + [:keycard/change-pin.verify-current-pin-and-continue])}}]]) + +(defn pin-change-success + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/keycard-pin-changed) + :description :text + :description-text (i18n/label :t/keycard-pin-changed-description)}] + [rn/view {:style {:flex 1 :align-items :center :justify-content :center}} + [rn/image + {:resize-mode :contain}]] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/done) + :button-one-props {:on-press events-helper/navigate-back}}]]) + +(defn pin-change-failed + [] + [:<> + [quo/page-nav + {:icon-name :i/close + :on-press events-helper/navigate-back}] + [quo/page-top + {:title (i18n/label :t/keycard-pin-change-failed) + :description :text + :description-text (i18n/label :t/keycard-pin-change-failed-description)}] + [rn/view {:style {:flex 1 :align-items :center :justify-content :center}} + [rn/image + {:resize-mode :contain}]] + [common.view/tips] + [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/try-again) + :button-one-props {:on-press (fn [] + (rf/dispatch [:navigate-back]) + (rf/dispatch [:open-modal :screen/keycard.ready-to-change-pin]))}}]]) diff --git a/src/status_im/contexts/keycard/effects.cljs b/src/status_im/contexts/keycard/effects.cljs index a58fc3151f3..5c6e61133d3 100644 --- a/src/status_im/contexts/keycard/effects.cljs +++ b/src/status_im/contexts/keycard/effects.cljs @@ -60,6 +60,10 @@ (fn [args] (keycard/verify-pin (keycard.utils/wrap-handlers args)))) +(rf/reg-fx :effects.keycard/change-pin + (fn [args] + (keycard/change-pin (keycard.utils/wrap-handlers args)))) + (rf/reg-fx :effects.keycard/sign (fn [args] (-> (keycard/sign args) diff --git a/src/status_im/contexts/keycard/events.cljs b/src/status_im/contexts/keycard/events.cljs index cfa9c2af8b7..9e8db25f04d 100644 --- a/src/status_im/contexts/keycard/events.cljs +++ b/src/status_im/contexts/keycard/events.cljs @@ -82,6 +82,10 @@ (fn [_ [data]] {:effects.keycard/verify-pin data})) +(rf/reg-event-fx :keycard/change-pin + (fn [_ [data]] + {:effects.keycard/change-pin data})) + (rf/reg-event-fx :keycard/connect-derive-address-and-add-account (fn [_ [{:keys [pin derivation-path key-uid account-preferences]}]] {:fx [[:dispatch diff --git a/src/status_im/contexts/keycard/manage/profile_keys/view.cljs b/src/status_im/contexts/keycard/manage/profile_keys/view.cljs index 78711a228bf..06022b6cdeb 100644 --- a/src/status_im/contexts/keycard/manage/profile_keys/view.cljs +++ b/src/status_im/contexts/keycard/manage/profile_keys/view.cljs @@ -3,6 +3,7 @@ [react-native.core :as rn] [status-im.common.events-helper :as events-helper] [status-im.contexts.keycard.backup.view :as backup.view] + [status-im.contexts.keycard.change-pin.view :as change-pin.view] [utils.i18n :as i18n] [utils.re-frame :as rf])) @@ -29,10 +30,22 @@ [quo/keycard {:holder-name ""}]] [quo/section-label {:section (i18n/label :t/what-you-can-do) :container-style {:padding-vertical 8}}] + [quo/settings-item + {:title (i18n/label :t/change-pin-keycard) + :image :icon + :blur? true + :image-props :i/keycard + :action :arrow + :description :text + :description-props {:text (i18n/label :t/change-pin-keycard-description)} + :on-press (fn [] + (rf/dispatch [:show-bottom-sheet + {:content change-pin.view/change-pin-confirmation-sheet}]))}] [quo/settings-item {:title (i18n/label :t/backup-keycard) :image :icon - :image-props :i/profile + :blur? true + :image-props :i/keycard :action :arrow :description :text :description-props {:text (i18n/label :t/create-backup-profile-keycard)} diff --git a/src/status_im/contexts/keycard/pin/create/view.cljs b/src/status_im/contexts/keycard/pin/create/view.cljs index fbb9fd78270..6ebfb0a7461 100644 --- a/src/status_im/contexts/keycard/pin/create/view.cljs +++ b/src/status_im/contexts/keycard/pin/create/view.cljs @@ -9,45 +9,47 @@ (defn view [] - (let [{:keys [on-complete]} (rf/sub [:get-screen-params]) - [pin set-pin] (rn/use-state "") - [first-pin set-first-pin] (rn/use-state "") - [error? set-error] (rn/use-state false) - [stage set-stage] (rn/use-state :create) - pin-empty? (string/blank? pin) - on-delete (rn/use-callback - (fn [] - (set-error false) - (if (= (count pin) 1) - (do - (set-pin "") - (set-stage :create)) - (when (and pin (pos? (count pin))) - (set-pin (.slice pin 0 -1))))) - [pin]) - on-press (rn/use-callback - (fn [new-symbol] - (let [new-pin (str pin new-symbol)] - (when (<= (count new-pin) constants/pincode-length) - (set-pin new-pin) - (when (= constants/pincode-length (count new-pin)) - (if (= :repeat stage) - (if (= first-pin new-pin) - (on-complete new-pin) - (set-error true)) - (do - (set-pin "") - (set-first-pin new-pin) - (set-stage :repeat))))))) - [pin stage first-pin])] + (let [{:keys [on-complete title repeat-stage-title]} (rf/sub [:get-screen-params]) + [pin set-pin] (rn/use-state "") + [first-pin set-first-pin] (rn/use-state "") + [error? set-error] (rn/use-state false) + [stage set-stage] (rn/use-state :create) + pin-empty? (string/blank? pin) + on-delete (rn/use-callback + (fn [] + (set-error false) + (if (= (count pin) 1) + (do + (set-pin "") + (set-stage :create)) + (when (and pin (pos? (count pin))) + (set-pin (.slice pin 0 -1))))) + [pin]) + on-press (rn/use-callback + (fn [new-symbol] + (let [new-pin (str pin new-symbol)] + (when (<= (count new-pin) + constants/pincode-length) + (set-pin new-pin) + (when (= constants/pincode-length + (count new-pin)) + (if (= :repeat stage) + (if (= first-pin new-pin) + (on-complete new-pin) + (set-error true)) + (do + (set-pin "") + (set-first-pin new-pin) + (set-stage :repeat))))))) + [pin stage first-pin])] [rn/view {:style {:padding-bottom 12 :flex 1}} [quo/page-nav {:icon-name :i/close :on-press events-helper/navigate-back}] [quo/page-top {:title (if (= :create stage) - (i18n/label :t/create-keycard-pin) - (i18n/label :t/repeat-keycard-pin)) + (or title (i18n/label :t/create-keycard-pin)) + (or repeat-stage-title (i18n/label :t/repeat-keycard-pin))) :description :text :description-text (i18n/label :t/pin-needed-login-sign)}] [rn/view {:style {:flex 1 :justify-content :center :align-items :center :padding-vertical 34}} diff --git a/src/status_im/contexts/keycard/pin/enter/view.cljs b/src/status_im/contexts/keycard/pin/enter/view.cljs index e42b8939778..c9d9100c312 100644 --- a/src/status_im/contexts/keycard/pin/enter/view.cljs +++ b/src/status_im/contexts/keycard/pin/enter/view.cljs @@ -8,10 +8,10 @@ (defn view [] - (let [{:keys [on-complete]} (rf/sub [:get-screen-params])] + (let [{:keys [on-complete title]} (rf/sub [:get-screen-params])] [rn/view {:style {:padding-bottom 12 :flex 1}} [quo/page-nav {:icon-name :i/close :on-press events-helper/navigate-back}] - [quo/page-top {:title (i18n/label :t/enter-keycard-pin)}] + [quo/page-top {:title (or title (i18n/label :t/enter-keycard-pin))}] [keycard.pin/auth {:on-complete on-complete}]])) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index e1eee0fabfb..b0e983d9521 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -24,6 +24,7 @@ status-im.contexts.communities.overview.events status-im.contexts.communities.sharing.events status-im.contexts.contact.blocking.events + status-im.contexts.keycard.change-pin.events status-im.contexts.keycard.effects status-im.contexts.keycard.events status-im.contexts.keycard.feature-unavailable.events diff --git a/src/status_im/navigation/effects.cljs b/src/status_im/navigation/effects.cljs index b2fe251af25..2ca476f2ac7 100644 --- a/src/status_im/navigation/effects.cljs +++ b/src/status_im/navigation/effects.cljs @@ -164,9 +164,9 @@ theme)}) options (when sheet? - options/sheet-options))}}]}}))) - (state/navigation-state-push {:id component - :type :modal}))) + options/sheet-options))}}]}}) + (state/navigation-state-push {:id component + :type :modal}))))) (rf/reg-fx :open-modal-fx open-modal) diff --git a/src/status_im/navigation/screens.cljs b/src/status_im/navigation/screens.cljs index 046e43c7128..63caa42b888 100644 --- a/src/status_im/navigation/screens.cljs +++ b/src/status_im/navigation/screens.cljs @@ -32,6 +32,7 @@ [status-im.contexts.communities.overview.view :as communities.overview] [status-im.contexts.keycard.authorise.view :as keycard.authorise] [status-im.contexts.keycard.backup.view :as keycard.backup] + [status-im.contexts.keycard.change-pin.view :as keycard.change-pin] [status-im.contexts.keycard.check.view :as keycard.check] [status-im.contexts.keycard.create.view :as keycard.create] [status-im.contexts.keycard.different-card.view :as keycard.different-card] @@ -1032,6 +1033,24 @@ :modalPresentationStyle :fullScreen} :component keycard.create/ready-to-add} + {:name :screen/keycard.ready-to-change-pin + :metrics {:track? true} + :options {:insets {:top? true :bottom? true} + :modalPresentationStyle :fullScreen} + :component keycard.change-pin/ready-to-change-pin} + + {:name :screen/keycard.pin-change-success + :metrics {:track? true} + :options {:insets {:top? true :bottom? true} + :modalPresentationStyle :fullScreen} + :component keycard.change-pin/pin-change-success} + + {:name :screen/keycard.pin-change-failed + :metrics {:track? true} + :options {:insets {:top? true :bottom? true} + :modalPresentationStyle :fullScreen} + :component keycard.change-pin/pin-change-failed} + {:name :screen/keycard.factory-reset.success :metrics {:track? true} :options {:theme :dark diff --git a/translations/en.json b/translations/en.json index ddb985e7ca9..10419d33dac 100644 --- a/translations/en.json +++ b/translations/en.json @@ -335,6 +335,9 @@ "change-password-old-password-placeholder": "Enter current password", "change-password-repeat-password-placeholder": "Repeat new password", "change-pin": "Change 6-digit passcode", + "change-pin-keycard": "Change PIN", + "change-pin-keycard-description": "Use different PIN for this Keycard", + "change-pin-keycard-message": "It’s very important that you will not forget new PIN. Status is unable to restore PIN. If you forget it, Keycard will be blocked and you will need to restore it via recovery phrase or PUK.", "change-puk": "Change 12-digit PUK", "change-testnet-mode-logout-info": "You’ll be logged out of the app", "change-tip": "Change tip", @@ -562,6 +565,7 @@ "create-keycard-pin": "Create Keycard PIN", "create-multiaccount": "Generate keys", "create-new-key": "Get new keys", + "create-new-keycard-pin": "Create new Keycard PIN", "create-new-profile": "Create new profile", "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", @@ -814,6 +818,7 @@ "do-not-cheat": "Don't try to cheat", "do-not-cheat-description": "These 12 words give access to all of your funds so it is important that you write them in the correct order, take it seriously.", "do-not-quit": "Do not quit the application or turn off your device. Doing so will lead to data corruption, loss of your Status profile and the inability to use Status.", + "do-not-quit-the-application": "Do not quit the application", "do-not-share": "Do not share", "documents": "Documents", "done": "Done", @@ -994,6 +999,7 @@ "enter-channel": "Enter channel", "enter-chat-key": "Enter chat key or scan a QR", "enter-contact-code": "ENS (vitalik94) or chat key (0x04…)", + "enter-current-keycard-pin": "Enter current Keycard PIN", "enter-eth": "Enter any ETH address or ENS name.", "enter-keycard-pin": "Enter Keycard PIN", "enter-pair-code": "Enter your pairing code", @@ -1435,6 +1441,10 @@ "keycard-onboarding-start-step3": "Back up the seed phrase", "keycard-onboarding-start-step3-text": "Around 1 minute. Also a piece of paper and a pencil are necessary", "keycard-onboarding-start-text": "And maintain card to phone contact\n during the setup. The setup will take around 4 minutes", + "keycard-pin-change-failed": "Failed to change Keycard PIN", + "keycard-pin-change-failed-description": "Keycard PIN was not updated", + "keycard-pin-changed": "Keycard PIN changed successfully", + "keycard-pin-changed-description": "Now you required to use new PIN for this Keycard", "keycard-processing-description": "Try keeping the card still", "keycard-processing-title": "Processing...", "keycard-recover": "lost or frozen card?", @@ -2122,6 +2132,7 @@ "read-more": "Read more", "ready-add-keypair-keycard": "Ready to add key pair to Keycard", "ready-keycard": "Get your Keycard ready", + "ready-to-change-pin": "Ready to change PIN", "ready-to-migrate-key-pair": "Ready to migrate profile key pair to the Keycard", "ready-to-scan": "Ready to Scan", "rearrange-categories": "Rearrange Categories", @@ -2197,6 +2208,7 @@ "rename": "Rename", "rename-key-pair": "Rename key pair", "repeat-keycard-pin": "Repeat Keycard PIN", + "repeat-new-keycard-pin": "Repeat new Keycard PIN", "repeat-pin": "Repeat new 6-digit passcode", "repeat-puk": "Repeat new 12-digit PUK", "replied": "Replied", @@ -2953,7 +2965,7 @@ "what-to-do": "What you would like to do?", "what-we-will-receive": "What we will receive:", "what-we-wont-receive": "What we won't receive:", - "what-you-can-do": "What you can do:", + "what-you-can-do": "What you can do?", "whats-on-your-mind": "What’s on your mind…", "which-connection-to-use": "Which connection to use for syncing?", "who-are-you-looking-for": "Who are you looking for ?",