mirror of
https://github.com/status-im/status-react.git
synced 2025-01-24 18:00:45 +00:00
Standardized in-app authentication (#16916)
* chore: move password input to connected component --------- Co-authored-by: Jamie Caprani <jamiecaprani@gmail.com> Co-authored-by: frank <lovefree103@gmail.com>
This commit is contained in:
parent
6b9b5fa300
commit
0522120c66
@ -70,7 +70,7 @@
|
||||
[sliding-complete?]
|
||||
(reset! sliding-complete? true))
|
||||
|
||||
(defn- reset-track-position
|
||||
(defn reset-track-position
|
||||
[x-pos]
|
||||
(animate-spring x-pos 0))
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
(ns quo2.components.buttons.slide-button.component-spec
|
||||
(:require [quo2.components.buttons.slide-button.view :as slide-button]
|
||||
[quo2.components.buttons.slide-button.constants :as constants]
|
||||
[quo2.components.buttons.slide-button.utils :as utils]
|
||||
["@testing-library/react-native" :as rtl]
|
||||
["react-native-gesture-handler/jest-utils" :as gestures-jest]
|
||||
[reagent.core :as r]
|
||||
@ -82,17 +81,11 @@
|
||||
(h/has-style track-mock {:opacity constants/disable-opacity})))
|
||||
|
||||
(h/test "render the small button"
|
||||
(h/render [slide-button/view (assoc default-props :size :small)])
|
||||
(h/render [slide-button/view (assoc default-props :size :size/s-40)])
|
||||
(let [mock (h/get-by-test-id :slide-button-track)
|
||||
small-height (:track-height constants/small-dimensions)]
|
||||
(h/has-style mock {:height small-height})))
|
||||
|
||||
(h/test "render with the correct customization-color"
|
||||
(h/render [slide-button/view (assoc default-props :customization-color :purple)])
|
||||
(let [track-mock (h/get-by-test-id :slide-button-track)
|
||||
purple-color (utils/slider-color :track :purple)]
|
||||
(h/has-style track-mock {:backgroundColor purple-color})))
|
||||
|
||||
(h/test
|
||||
"calls on-complete when dragged"
|
||||
(let [props (merge default-props {:on-complete (h/mock-fn)})
|
||||
|
@ -13,10 +13,10 @@
|
||||
:right 0})
|
||||
|
||||
(defn thumb-container
|
||||
[interpolate-track thumb-size customization-color]
|
||||
[{:keys [interpolate-track thumb-size customization-color theme]}]
|
||||
(reanimated/apply-animations-to-style
|
||||
{:transform [{:translate-x (interpolate-track :track-clamp)}]}
|
||||
{:background-color (utils/slider-color :main customization-color)
|
||||
{:background-color (utils/slider-color :main customization-color theme)
|
||||
:border-radius 12
|
||||
:height thumb-size
|
||||
:width thumb-size
|
||||
@ -46,7 +46,7 @@
|
||||
:justify-content :space-around}))
|
||||
|
||||
(defn track
|
||||
[disabled? customization-color height]
|
||||
[{:keys [disabled? customization-color height theme]}]
|
||||
{:align-items :flex-start
|
||||
:justify-content :center
|
||||
:border-radius 14
|
||||
@ -54,7 +54,7 @@
|
||||
:align-self :stretch
|
||||
:padding constants/track-padding
|
||||
:opacity (if disabled? 0.3 1)
|
||||
:background-color (utils/slider-color :track customization-color)})
|
||||
:background-color (utils/slider-color :track customization-color theme)})
|
||||
|
||||
(defn track-cover
|
||||
[interpolate-track]
|
||||
@ -74,7 +74,7 @@
|
||||
:width track-width})
|
||||
|
||||
(defn track-text
|
||||
[customization-color]
|
||||
[customization-color theme]
|
||||
(-> typography/paragraph-1
|
||||
(merge typography/font-medium)
|
||||
(assoc :color (utils/slider-color :main customization-color))))
|
||||
(assoc :color (utils/slider-color :main customization-color theme))))
|
||||
|
@ -6,9 +6,15 @@
|
||||
(defn slider-color
|
||||
"- `color-key` `:main`/`:track`
|
||||
- `customization-color` Customization color"
|
||||
[color-key customization-color]
|
||||
(let [colors-by-key {:main (colors/custom-color-by-theme customization-color 50 60)
|
||||
:track (colors/custom-color-by-theme customization-color 50 60 10 10)}]
|
||||
[color-key customization-color theme]
|
||||
(let [colors-by-key {:main (colors/theme-colors
|
||||
(colors/custom-color customization-color 50)
|
||||
(colors/custom-color customization-color 60)
|
||||
theme)
|
||||
:track (colors/theme-colors
|
||||
(colors/custom-color customization-color 50 10)
|
||||
(colors/custom-color customization-color 60 10)
|
||||
theme)}]
|
||||
(color-key colors-by-key)))
|
||||
|
||||
(defn clamp-value
|
||||
@ -28,8 +34,8 @@
|
||||
(defn get-dimensions
|
||||
[track-width size dimension-key]
|
||||
(let [default-dimensions (case size
|
||||
:small constants/small-dimensions
|
||||
:large constants/large-dimensions
|
||||
:size/s-40 constants/small-dimensions
|
||||
:size/s-48 constants/large-dimensions
|
||||
constants/large-dimensions)]
|
||||
(-> default-dimensions
|
||||
(merge {:usable-track (calc-usable-track
|
||||
|
@ -10,7 +10,8 @@
|
||||
[reagent.core :as reagent]
|
||||
[oops.core :as oops]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[quo2.components.buttons.slide-button.constants :as constants]))
|
||||
[quo2.components.buttons.slide-button.constants :as constants]
|
||||
[quo2.theme :as quo.theme]))
|
||||
|
||||
(defn- f-slider
|
||||
[{:keys [disabled?]}]
|
||||
@ -20,14 +21,15 @@
|
||||
on-track-layout (fn [evt]
|
||||
(let [width (oops/oget evt "nativeEvent.layout.width")]
|
||||
(reset! track-width width)))]
|
||||
|
||||
(fn [{:keys [on-complete
|
||||
(fn [{:keys [on-reset
|
||||
on-complete
|
||||
track-text
|
||||
track-icon
|
||||
disabled?
|
||||
customization-color
|
||||
size
|
||||
container-style]}]
|
||||
container-style
|
||||
theme]}]
|
||||
(let [x-pos (reanimated/use-shared-value 0)
|
||||
dimensions (partial utils/get-dimensions
|
||||
(or @track-width constants/default-width)
|
||||
@ -36,12 +38,19 @@
|
||||
x-pos
|
||||
(dimensions :usable-track)
|
||||
(dimensions :thumb))]
|
||||
|
||||
(rn/use-effect (fn []
|
||||
(when @sliding-complete?
|
||||
(on-complete)))
|
||||
[@sliding-complete?])
|
||||
|
||||
(rn/use-effect (fn []
|
||||
(when on-reset
|
||||
(reset! sliding-complete? false)
|
||||
(reset! gestures-disabled? false)
|
||||
(animations/reset-track-position x-pos)
|
||||
(on-reset)))
|
||||
[on-reset])
|
||||
|
||||
[gesture/gesture-detector
|
||||
{:gesture (animations/drag-gesture x-pos
|
||||
gestures-disabled?
|
||||
@ -50,21 +59,25 @@
|
||||
sliding-complete?)}
|
||||
[reanimated/view
|
||||
{:test-ID :slide-button-track
|
||||
:style (merge (style/track disabled? customization-color (dimensions :track-height))
|
||||
:style (merge (style/track {:disabled? disabled?
|
||||
:customization-color customization-color
|
||||
:height (dimensions :track-height)
|
||||
:theme theme})
|
||||
container-style)
|
||||
:on-layout (when-not (some? @track-width)
|
||||
on-track-layout)}
|
||||
[reanimated/view {:style (style/track-cover interpolate-track)}
|
||||
[rn/view {:style (style/track-cover-text-container @track-width)}
|
||||
[icon/icon track-icon
|
||||
{:color (utils/slider-color :main customization-color)
|
||||
{:color (utils/slider-color :main customization-color theme)
|
||||
:size 20}]
|
||||
[rn/view {:width 4}]
|
||||
[rn/text {:style (style/track-text customization-color)} track-text]]]
|
||||
[rn/text {:style (style/track-text customization-color theme)} track-text]]]
|
||||
[reanimated/view
|
||||
{:style (style/thumb-container interpolate-track
|
||||
(dimensions :thumb)
|
||||
customization-color)}
|
||||
{:style (style/thumb-container {:interpolate-track interpolate-track
|
||||
:thumb-size (dimensions :thumb)
|
||||
:customization-color customization-color
|
||||
:theme theme})}
|
||||
[reanimated/view {:style (style/arrow-icon-container interpolate-track)}
|
||||
[icon/icon :arrow-right
|
||||
{:color colors/white
|
||||
@ -76,16 +89,19 @@
|
||||
{:color colors/white
|
||||
:size 20}]]]]]))))
|
||||
|
||||
(defn view
|
||||
(defn- view-internal
|
||||
"Options
|
||||
- `on-complete` Callback called when the sliding is complete
|
||||
- `disabled?` Boolean that disables the button
|
||||
(_and gestures_)
|
||||
- `size` `:small`/`:large`
|
||||
- `size` :size/s-40`/`:size/s-48`
|
||||
- `track-text` Text that is shown on the track
|
||||
- `track-icon` Key of the icon shown on the track
|
||||
(e.g. `:face-id`)
|
||||
- `customization-color` Customization color
|
||||
- `on-reset` A callback which can be used to reset the component and run required functionality
|
||||
"
|
||||
[props]
|
||||
[:f> f-slider props])
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
||||
|
@ -15,5 +15,5 @@
|
||||
(defn authenticate
|
||||
[{:keys [on-success on-fail reason options]}]
|
||||
(-> (.authenticate ^js touchid reason (clj->js options))
|
||||
(.then #(when on-success (on-success)))
|
||||
(.then #(when on-success (on-success %)))
|
||||
(.catch #(when on-fail (on-fail (aget % "code"))))))
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
(defn hide
|
||||
[translate-y bg-opacity window-height on-close]
|
||||
(rf/dispatch [:dismiss-keyboard])
|
||||
(when (fn? on-close)
|
||||
(on-close))
|
||||
;; it will be better to use animation callback, but it doesn't work
|
||||
|
@ -0,0 +1,20 @@
|
||||
(ns status-im2.common.standard-authentication.enter-password.style)
|
||||
|
||||
(def enter-password-container
|
||||
{:margin-horizontal 20
|
||||
:border-top-left-radius 12
|
||||
:border-top-right-radius 12})
|
||||
|
||||
(def error-message
|
||||
{:margin-top 8
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
||||
|
||||
(def enter-password-button
|
||||
{:margin-top 45
|
||||
:margin-bottom 12})
|
||||
|
||||
(def context-tag
|
||||
{:flex-direction :row
|
||||
:margin-bottom 20
|
||||
:margin-top 8})
|
@ -0,0 +1,53 @@
|
||||
(ns status-im2.common.standard-authentication.enter-password.view
|
||||
(:require [utils.i18n :as i18n]
|
||||
[quo2.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.common.standard-authentication.enter-password.style :as style]
|
||||
[status-im2.common.standard-authentication.password-input.view :as password-input]
|
||||
[status-im.multiaccounts.core :as multiaccounts]))
|
||||
|
||||
(defn view
|
||||
[{:keys [on-enter-password button-label]}]
|
||||
(let [{:keys [key-uid display-name
|
||||
customization-color]
|
||||
:as account} (rf/sub [:profile/multiaccount])
|
||||
{:keys [error processing password]} (rf/sub [:profile/login])
|
||||
sign-in-enabled? (rf/sub [:sign-in-enabled?])
|
||||
profile-picture (multiaccounts/displayed-photo account)]
|
||||
[:<>
|
||||
[rn/view {:style style/enter-password-container}
|
||||
[rn/view
|
||||
[quo/text
|
||||
{:accessibility-label :sync-code-generated
|
||||
:weight :bold
|
||||
:size :heading-1
|
||||
:style {:margin-bottom 4}}
|
||||
(i18n/label :t/enter-password)]
|
||||
[rn/view
|
||||
{:style style/context-tag}
|
||||
[quo/context-tag
|
||||
{:type :default
|
||||
:blur? true
|
||||
:profile-picture profile-picture
|
||||
:full-name display-name
|
||||
:customization-color customization-color
|
||||
:size 24}]]
|
||||
[password-input/view
|
||||
{:processing processing
|
||||
:error error
|
||||
:default-password password
|
||||
:sign-in-enabled? sign-in-enabled?}]
|
||||
[rn/view {:style style/enter-password-button}
|
||||
[quo/button
|
||||
{:size 40
|
||||
:type :primary
|
||||
:customization-color (or customization-color :primary)
|
||||
:accessibility-label :login-button
|
||||
:icon-left :i/reveal
|
||||
:disabled? (or (not sign-in-enabled?) processing)
|
||||
:on-press (fn []
|
||||
(rf/dispatch [:set-in [:profile/login :key-uid] key-uid])
|
||||
(rf/dispatch [:profile.login/verify-database-password password
|
||||
#(on-enter-password password)]))}
|
||||
button-label]]]]]))
|
@ -0,0 +1,6 @@
|
||||
(ns status-im2.common.standard-authentication.forgot-password-doc.style)
|
||||
|
||||
(def container {:margin-right 16})
|
||||
(def step-container {:flex-direction :row :margin-top 14})
|
||||
(def step-content {:margin-left 10})
|
||||
(def step-title {:flex-direction :row})
|
@ -0,0 +1,52 @@
|
||||
(ns status-im2.common.standard-authentication.forgot-password-doc.view
|
||||
(:require [status-im2.common.standard-authentication.forgot-password-doc.style :as style]
|
||||
[quo2.core :as quo]
|
||||
[react-native.core :as rn]
|
||||
[utils.i18n :as i18n]))
|
||||
|
||||
(defn view
|
||||
[{:keys [shell?]}]
|
||||
[quo/documentation-drawers
|
||||
{:title (i18n/label :t/forgot-your-password-info-title)
|
||||
:shell? shell?}
|
||||
[rn/view
|
||||
{:style style/container}
|
||||
[quo/text {:size :paragraph-2} (i18n/label :t/forgot-your-password-info-description)]
|
||||
|
||||
[rn/view {:style style/step-container}
|
||||
[quo/step {:in-blur-view? shell?} 1]
|
||||
[rn/view
|
||||
{:style style/step-content}
|
||||
[quo/text {:size :paragraph-2 :weight :semi-bold}
|
||||
(i18n/label :t/forgot-your-password-info-remove-app)]
|
||||
[quo/text {:size :paragraph-2} (i18n/label :t/forgot-your-password-info-remove-app-description)]]]
|
||||
|
||||
[rn/view {:style style/step-container}
|
||||
[quo/step {:in-blur-view? shell?} 2]
|
||||
[rn/view
|
||||
{:style style/step-content}
|
||||
[quo/text {:size :paragraph-2 :weight :semi-bold}
|
||||
(i18n/label :t/forgot-your-password-info-reinstall-app)]
|
||||
[quo/text {:size :paragraph-2}
|
||||
(i18n/label :t/forgot-your-password-info-reinstall-app-description)]]]
|
||||
|
||||
[rn/view {:style style/step-container}
|
||||
[quo/step {:in-blur-view? shell?} 3]
|
||||
[rn/view
|
||||
{:style style/step-content}
|
||||
[rn/view
|
||||
{:style style/step-title}
|
||||
[quo/text {:size :paragraph-2} (str (i18n/label :t/sign-up) " ")]
|
||||
[quo/text {:size :paragraph-2 :weight :semi-bold}
|
||||
(i18n/label :t/forgot-your-password-info-signup-with-key)]]
|
||||
[quo/text {:size :paragraph-2}
|
||||
(i18n/label :t/forgot-your-password-info-signup-with-key-description)]]]
|
||||
|
||||
[rn/view {:style style/step-container}
|
||||
[quo/step {:in-blur-view? shell?} 4]
|
||||
[rn/view
|
||||
{:style style/step-content}
|
||||
[quo/text {:size :paragraph-2 :weight :semi-bold}
|
||||
(i18n/label :t/forgot-your-password-info-create-new-password)]
|
||||
[quo/text {:size :paragraph-2}
|
||||
(i18n/label :t/forgot-your-password-info-create-new-password-description)]]]]])
|
@ -0,0 +1,6 @@
|
||||
(ns status-im2.common.standard-authentication.password-input.style)
|
||||
|
||||
(def error-message
|
||||
{:margin-top 8
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
@ -0,0 +1,70 @@
|
||||
(ns status-im2.common.standard-authentication.password-input.view
|
||||
(:require [quo2.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
[status-im2.common.standard-authentication.password-input.style :as style]
|
||||
[status-im2.common.standard-authentication.forgot-password-doc.view :as forgot-password-doc]
|
||||
[quo2.theme :as quo.theme]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(defn get-error-message
|
||||
[error]
|
||||
(if (and (some? error)
|
||||
(or (= error "file is not a database")
|
||||
(string/starts-with? error "failed to set ")
|
||||
(string/starts-with? error "Failed")))
|
||||
(i18n/label :t/oops-wrong-password)
|
||||
error))
|
||||
|
||||
(defn- on-change-password
|
||||
[entered-password]
|
||||
(rf/dispatch [:set-in [:profile/login :password]
|
||||
(security/mask-data entered-password)])
|
||||
(rf/dispatch [:set-in [:profile/login :error] ""]))
|
||||
|
||||
(defn- view-internal
|
||||
[{:keys [default-password theme shell?]}]
|
||||
(let [{:keys [error processing]} (rf/sub [:profile/login])
|
||||
error-message (get-error-message error)
|
||||
error? (boolean (seq error-message))]
|
||||
[:<>
|
||||
[quo/input
|
||||
{:type :password
|
||||
:blur? true
|
||||
:disabled? processing
|
||||
:placeholder (i18n/label :t/type-your-password)
|
||||
:auto-focus true
|
||||
:error? error?
|
||||
:label (i18n/label :t/profile-password)
|
||||
:on-change-text on-change-password
|
||||
:default-value (security/safe-unmask-data default-password)}]
|
||||
(when error?
|
||||
[rn/view {:style style/error-message}
|
||||
[quo/info-message
|
||||
{:type :error
|
||||
:size :default
|
||||
:icon :i/info}
|
||||
error-message]
|
||||
[rn/pressable
|
||||
{:hit-slop {:top 6 :bottom 20 :left 0 :right 0}
|
||||
:disabled processing
|
||||
:on-press (fn []
|
||||
(rn/dismiss-keyboard!)
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content #(forgot-password-doc/view {:shell? shell?})
|
||||
:theme theme
|
||||
:shell? shell?}]))}
|
||||
[rn/text
|
||||
{:style {:text-decoration-line :underline
|
||||
:color (colors/theme-colors
|
||||
(colors/custom-color :danger 50)
|
||||
(colors/custom-color :danger 60)
|
||||
theme)}
|
||||
:size :paragraph-2
|
||||
:suppress-highlighting true}
|
||||
(i18n/label :t/forgot-password)]]])]))
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
@ -0,0 +1,79 @@
|
||||
(ns status-im2.common.standard-authentication.standard-auth.view
|
||||
(:require
|
||||
[quo2.core :as quo]
|
||||
[quo2.theme :as quo.theme]
|
||||
[reagent.core :as reagent]
|
||||
[utils.re-frame :as rf]
|
||||
[react-native.touch-id :as biometric]
|
||||
[utils.i18n :as i18n]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im2.common.standard-authentication.enter-password.view :as enter-password]
|
||||
[react-native.core :as rn]))
|
||||
|
||||
(defn reset-password
|
||||
[]
|
||||
(rf/dispatch [:set-in [:profile/login :password] nil])
|
||||
(rf/dispatch [:set-in [:profile/login :error] ""]))
|
||||
|
||||
(defn authorize
|
||||
[{:keys [on-enter-password biometric-auth? on-auth-success on-auth-fail on-close
|
||||
fallback-button-label theme blur?]}]
|
||||
(biometric/get-supported-type
|
||||
(fn [biometric-type]
|
||||
(if (and biometric-auth? biometric-type)
|
||||
(biometric/authenticate
|
||||
{:reason (i18n/label :t/biometric-auth-confirm-message)
|
||||
:on-success (fn [response]
|
||||
(when on-auth-success (on-auth-success response))
|
||||
(log/info "response" response))
|
||||
:on-fail (fn [error]
|
||||
(log/error "Authentication Failed. Error:" error)
|
||||
(when on-auth-fail (on-auth-fail error))
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:theme theme
|
||||
:shell? blur?
|
||||
:content (fn []
|
||||
[enter-password/view
|
||||
{:on-enter-password on-enter-password}])}]))})
|
||||
(do
|
||||
(reset-password)
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:on-close on-close
|
||||
:theme theme
|
||||
:shell? blur?
|
||||
:content (fn []
|
||||
[enter-password/view
|
||||
{:on-enter-password on-enter-password
|
||||
:button-label fallback-button-label}])}]))))))
|
||||
|
||||
(defn- view-internal
|
||||
[_]
|
||||
(let [reset-slider? (reagent/atom false)
|
||||
on-close #(reset! reset-slider? true)]
|
||||
(fn [{:keys [biometric-auth?
|
||||
track-text
|
||||
customization-color
|
||||
fallback-button-label
|
||||
on-enter-password
|
||||
on-auth-success
|
||||
on-auth-fail
|
||||
size
|
||||
theme
|
||||
blur?]}]
|
||||
[rn/view {:style {:flex 1}}
|
||||
[quo/slide-button
|
||||
{:size size
|
||||
:customization-color customization-color
|
||||
:on-reset (when @reset-slider? #(reset! reset-slider? false))
|
||||
:on-complete #(authorize {:on-close on-close
|
||||
:theme theme
|
||||
:blur? blur?
|
||||
:on-enter-password on-enter-password
|
||||
:biometric-auth? biometric-auth?
|
||||
:on-auth-success on-auth-success
|
||||
:on-auth-fail on-auth-fail
|
||||
:fallback-button-label fallback-button-label})
|
||||
:track-icon :i/face-id
|
||||
:track-text track-text}]])))
|
||||
|
||||
(def view (quo.theme/with-theme view-internal))
|
@ -181,3 +181,29 @@
|
||||
(rf/merge cofx
|
||||
(navigation/init-root :profiles)
|
||||
(biometric/show-message code)))
|
||||
|
||||
(rf/defn verify-database-password
|
||||
{:events [:profile.login/verify-database-password]}
|
||||
[_ entered-password cb]
|
||||
(let [hashed-password (-> entered-password
|
||||
security/safe-unmask-data
|
||||
native-module/sha3)]
|
||||
{:json-rpc/call [{:method "accounts_verifyPassword"
|
||||
:params [hashed-password]
|
||||
:on-success #(do (rf/dispatch [:profile.login/verified-database-password %]) (cb))
|
||||
:on-error #(log/error "accounts_verifyPassword error" %)}]}))
|
||||
|
||||
(rf/defn verify-database-password-success
|
||||
{:events [:profile.login/verified-database-password]}
|
||||
[{:keys [db] :as cofx} valid?]
|
||||
(if valid?
|
||||
(do
|
||||
{:db (update db
|
||||
:profile/login
|
||||
dissoc
|
||||
:processing :error)})
|
||||
{:db (update db
|
||||
:profile/login
|
||||
#(-> %
|
||||
(dissoc :processing)
|
||||
(assoc :error "Invalid password")))}))
|
||||
|
@ -53,12 +53,3 @@
|
||||
(def login-profile-card
|
||||
{:margin-bottom 20})
|
||||
|
||||
(def error-message
|
||||
{:margin-top 8
|
||||
:flex-direction :row
|
||||
:align-items :center})
|
||||
|
||||
(def forget-password-doc-container {:margin-right 16})
|
||||
(def forget-password-step-container {:flex-direction :row :margin-top 14})
|
||||
(def forget-password-step-content {:margin-left 10})
|
||||
(def forget-password-step-title {:flex-direction :row})
|
||||
|
@ -1,8 +1,6 @@
|
||||
(ns status-im2.contexts.profile.profiles.view
|
||||
(:require [clojure.string :as string]
|
||||
[native-module.core :as native-module]
|
||||
(:require [native-module.core :as native-module]
|
||||
[quo2.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[react-native.reanimated :as reanimated]
|
||||
[react-native.safe-area :as safe-area]
|
||||
@ -15,8 +13,8 @@
|
||||
[taoensso.timbre :as log]
|
||||
[utils.i18n :as i18n]
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
[utils.transforms :as transforms]))
|
||||
[utils.transforms :as transforms]
|
||||
[status-im2.common.standard-authentication.password-input.view :as password-input]))
|
||||
|
||||
(defonce push-animation-fn-atom (atom nil))
|
||||
(defonce pop-animation-fn-atom (atom nil))
|
||||
@ -166,69 +164,12 @@
|
||||
[props]
|
||||
[:f> f-profiles-section props])
|
||||
|
||||
(defn forget-password-doc
|
||||
[]
|
||||
[quo/documentation-drawers
|
||||
{:title (i18n/label :t/forgot-your-password-info-title)
|
||||
:shell? true}
|
||||
[rn/view
|
||||
{:style style/forget-password-doc-container}
|
||||
[quo/text {:size :paragraph-2} (i18n/label :t/forgot-your-password-info-description)]
|
||||
|
||||
[rn/view {:style style/forget-password-step-container}
|
||||
[quo/step {:in-blur-view? true} 1]
|
||||
[rn/view
|
||||
{:style style/forget-password-step-content}
|
||||
[quo/text {:size :paragraph-2 :weight :semi-bold}
|
||||
(i18n/label :t/forgot-your-password-info-remove-app)]
|
||||
[quo/text {:size :paragraph-2} (i18n/label :t/forgot-your-password-info-remove-app-description)]]]
|
||||
|
||||
[rn/view {:style style/forget-password-step-container}
|
||||
[quo/step {:in-blur-view? true} 2]
|
||||
[rn/view
|
||||
{:style style/forget-password-step-content}
|
||||
[quo/text {:size :paragraph-2 :weight :semi-bold}
|
||||
(i18n/label :t/forgot-your-password-info-reinstall-app)]
|
||||
[quo/text {:size :paragraph-2}
|
||||
(i18n/label :t/forgot-your-password-info-reinstall-app-description)]]]
|
||||
|
||||
[rn/view {:style style/forget-password-step-container}
|
||||
[quo/step {:in-blur-view? true} 3]
|
||||
[rn/view
|
||||
{:style style/forget-password-step-content}
|
||||
[rn/view
|
||||
{:style style/forget-password-step-title}
|
||||
[quo/text {:size :paragraph-2} (str (i18n/label :t/sign-up) " ")]
|
||||
[quo/text {:size :paragraph-2 :weight :semi-bold}
|
||||
(i18n/label :t/forgot-your-password-info-signup-with-key)]]
|
||||
[quo/text {:size :paragraph-2}
|
||||
(i18n/label :t/forgot-your-password-info-signup-with-key-description)]]]
|
||||
|
||||
[rn/view {:style style/forget-password-step-container}
|
||||
[quo/step {:in-blur-view? true} 4]
|
||||
[rn/view
|
||||
{:style style/forget-password-step-content}
|
||||
[quo/text {:size :paragraph-2 :weight :semi-bold}
|
||||
(i18n/label :t/forgot-your-password-info-create-new-password)]
|
||||
[quo/text {:size :paragraph-2}
|
||||
(i18n/label :t/forgot-your-password-info-create-new-password-description)]]]]])
|
||||
|
||||
(defn- get-error-message
|
||||
[error]
|
||||
(if (and (some? error)
|
||||
(or (= error "file is not a database")
|
||||
(string/starts-with? error "failed to set ")
|
||||
(string/starts-with? error "Failed")))
|
||||
(i18n/label :t/oops-wrong-password)
|
||||
error))
|
||||
|
||||
(defn login-section
|
||||
[{:keys [set-show-profiles]}]
|
||||
(let [{:keys [error processing password]} (rf/sub [:profile/login])
|
||||
(let [{:keys [processing password]} (rf/sub [:profile/login])
|
||||
{:keys [key-uid name customization-color]} (rf/sub [:profile/login-profile])
|
||||
sign-in-enabled? (rf/sub [:sign-in-enabled?])
|
||||
profile-picture (rf/sub [:profile/login-profiles-picture key-uid])
|
||||
error (get-error-message error)
|
||||
login-multiaccount #(rf/dispatch [:profile.login/login])]
|
||||
[rn/keyboard-avoiding-view
|
||||
{:style style/login-container
|
||||
@ -263,41 +204,9 @@
|
||||
:customization-color (or customization-color :primary)
|
||||
:profile-picture profile-picture
|
||||
:card-style style/login-profile-card}]
|
||||
[quo/input
|
||||
{:type :password
|
||||
:blur? true
|
||||
:disabled? processing
|
||||
:placeholder (i18n/label :t/type-your-password)
|
||||
:auto-focus true
|
||||
:error? (seq error)
|
||||
:label (i18n/label :t/profile-password)
|
||||
:on-change-text (fn [password]
|
||||
(rf/dispatch [:set-in [:profile/login :password]
|
||||
(security/mask-data password)])
|
||||
(rf/dispatch [:set-in [:profile/login :error] ""]))
|
||||
:default-value (security/safe-unmask-data password)
|
||||
:on-submit-editing (when sign-in-enabled? login-multiaccount)}]
|
||||
(when (seq error)
|
||||
[rn/view {:style style/error-message}
|
||||
[quo/info-message
|
||||
{:type :error
|
||||
:size :default
|
||||
:icon :i/info}
|
||||
error]
|
||||
[rn/touchable-opacity
|
||||
{:hit-slop {:top 6 :bottom 20 :left 0 :right 0}
|
||||
:disabled processing
|
||||
:active-opacity 1
|
||||
:on-press (fn []
|
||||
(rn/dismiss-keyboard!)
|
||||
(rf/dispatch [:show-bottom-sheet
|
||||
{:content forget-password-doc :shell? true}]))}
|
||||
[rn/text
|
||||
{:style {:text-decoration-line :underline
|
||||
:color colors/danger-60}
|
||||
:size :paragraph-2
|
||||
:suppress-highlighting true}
|
||||
(i18n/label :t/forgot-password)]]])]
|
||||
[password-input/view
|
||||
{:shell? true
|
||||
:default-password password}]]
|
||||
[quo/button
|
||||
{:size 40
|
||||
:type :primary
|
||||
|
@ -6,8 +6,8 @@
|
||||
(def descriptor
|
||||
[{:key :size
|
||||
:type :select
|
||||
:options [{:key :large}
|
||||
{:key :small}]}
|
||||
:options [{:key :size/s-48}
|
||||
{:key :size/s-40}]}
|
||||
{:key :disabled?
|
||||
:type :boolean}
|
||||
(preview/customization-color-option {:key :color})])
|
||||
@ -16,10 +16,8 @@
|
||||
[]
|
||||
(let [state (reagent/atom {:disabled? false
|
||||
:color :blue
|
||||
:size :large})
|
||||
:size :size/s-48})
|
||||
color (reagent/cursor state [:color])
|
||||
disabled? (reagent/cursor state [:disabled?])
|
||||
size (reagent/cursor state [:size])
|
||||
complete? (reagent/atom false)]
|
||||
(fn []
|
||||
[preview/preview-container
|
||||
@ -31,8 +29,8 @@
|
||||
{:track-text "We gotta slide"
|
||||
:track-icon :face-id
|
||||
:customization-color @color
|
||||
:size @size
|
||||
:disabled? @disabled?
|
||||
:size (:size @state)
|
||||
:disabled? (:disabled? @state)
|
||||
:on-complete (fn []
|
||||
(js/setTimeout (fn [] (reset! complete? true))
|
||||
1000)
|
||||
|
@ -1,6 +1,7 @@
|
||||
(ns status-im2.contexts.syncing.events
|
||||
(:require [native-module.core :as native-module]
|
||||
[re-frame.core :as re-frame]
|
||||
[clojure.string :as string]
|
||||
[status-im.data-store.settings :as data-store.settings]
|
||||
[status-im.node.core :as node]
|
||||
[status-im2.config :as config]
|
||||
@ -9,6 +10,7 @@
|
||||
[utils.re-frame :as rf]
|
||||
[utils.security.core :as security]
|
||||
[utils.transforms :as transforms]
|
||||
[status-im2.contexts.syncing.utils :as sync-utils]
|
||||
[react-native.platform :as platform]))
|
||||
|
||||
(rf/defn local-pairing-update-role
|
||||
@ -73,16 +75,17 @@
|
||||
(native-module/prepare-dir-and-update-config "" default-node-config-string callback)))
|
||||
|
||||
(rf/defn preparations-for-connection-string
|
||||
{:events [:syncing/get-connection-string-for-bootstrapping-another-device]}
|
||||
[{:keys [db]} entered-password set-code]
|
||||
(let [valid-password? (>= (count entered-password) constants/min-password-length)
|
||||
show-sheet (fn [connection-string]
|
||||
(set-code connection-string)
|
||||
(rf/dispatch [:syncing/update-role constants/local-pairing-role-sender])
|
||||
(rf/dispatch [:bottom-sheet/hide]))]
|
||||
(if valid-password?
|
||||
(let [sha3-pwd (native-module/sha3 (str (security/safe-unmask-data entered-password)))
|
||||
key-uid (get-in db [:profile/profile :key-uid])
|
||||
{:events [:syncing/get-connection-string]}
|
||||
[{:keys [db] :as cofx} entered-password on-valid-connection-string]
|
||||
(let [error (get-in db [:profile/login :error])
|
||||
handle-connection (fn [response]
|
||||
(when (sync-utils/valid-connection-string? response)
|
||||
(on-valid-connection-string response)
|
||||
(rf/dispatch [:syncing/update-role constants/local-pairing-role-sender])
|
||||
(rf/dispatch [:hide-bottom-sheet])))]
|
||||
(when-not (and error (string/blank? error))
|
||||
(let [key-uid (get-in db [:profile/login :key-uid])
|
||||
sha3-pwd (native-module/sha3 (str (security/safe-unmask-data entered-password)))
|
||||
config-map (.stringify js/JSON
|
||||
(clj->js {:senderConfig {:keyUID key-uid
|
||||
:keystorePath ""
|
||||
@ -91,5 +94,4 @@
|
||||
:serverConfig {:timeout 0}}))]
|
||||
(native-module/get-connection-string-for-bootstrapping-another-device
|
||||
config-map
|
||||
#(show-sheet %)))
|
||||
(show-sheet ""))))
|
||||
handle-connection)))))
|
||||
|
@ -6,7 +6,8 @@
|
||||
:flex 1})
|
||||
|
||||
(def page-container
|
||||
{:margin-horizontal 20})
|
||||
{:margin-top 14
|
||||
:margin-horizontal 20})
|
||||
|
||||
(def title-container
|
||||
{:flex-direction :row
|
||||
@ -17,17 +18,18 @@
|
||||
{:height 56})
|
||||
|
||||
(def sync-code
|
||||
{:margin-top 36})
|
||||
{:margin-top 20})
|
||||
|
||||
(defn qr-container
|
||||
[valid-code?]
|
||||
(merge {:margin-top 12
|
||||
:background-color colors/white-opa-5
|
||||
:border-radius 20
|
||||
:padding 12}
|
||||
(if valid-code?
|
||||
{:flex 1}
|
||||
{:aspect-ratio 1})))
|
||||
(def standard-auth
|
||||
{:margin-top 12
|
||||
:flex 1})
|
||||
|
||||
(def qr-container
|
||||
{:margin-top 12
|
||||
:background-color colors/white-opa-5
|
||||
:border-radius 20
|
||||
:flex 1
|
||||
:padding 12})
|
||||
|
||||
(def sub-text-container
|
||||
{:margin-bottom 8
|
||||
@ -39,10 +41,3 @@
|
||||
{:flex 1
|
||||
:margin 12})
|
||||
|
||||
(def generate-button
|
||||
{:position :absolute
|
||||
:top "50%"
|
||||
:bottom 0
|
||||
:left 0
|
||||
:right 0
|
||||
:margin-horizontal 60})
|
||||
|
@ -3,12 +3,12 @@
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.clipboard :as clipboard]
|
||||
[react-native.core :as rn]
|
||||
[react-native.hooks :as hooks]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.common.qr-code-viewer.view :as qr-code-viewer]
|
||||
[reagent.core :as reagent]
|
||||
[status-im2.common.resources :as resources]
|
||||
[status-im2.common.standard-authentication.standard-auth.view :as standard-auth]
|
||||
[react-native.hooks :as hooks]
|
||||
[status-im2.contexts.syncing.setup-syncing.style :as style]
|
||||
[status-im2.contexts.syncing.sheets.enter-password.view :as enter-password]
|
||||
[status-im2.contexts.syncing.utils :as sync-utils]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.i18n :as i18n]
|
||||
@ -24,31 +24,37 @@
|
||||
|
||||
(defn view
|
||||
[]
|
||||
(let [profile-color (rf/sub [:profile/customization-color])
|
||||
valid-for-ms (reagent/atom code-valid-for-ms)
|
||||
code (reagent/atom nil)
|
||||
delay-ms (reagent/atom nil)
|
||||
timestamp (reagent/atom nil)
|
||||
set-code (fn [connection-string]
|
||||
(when (sync-utils/valid-connection-string? connection-string)
|
||||
(reset! timestamp (* 1000 (js/Math.ceil (/ (datetime/timestamp) 1000))))
|
||||
(reset! delay-ms 1000)
|
||||
(reset! code connection-string)))
|
||||
clock (fn []
|
||||
(if (pos? (- code-valid-for-ms
|
||||
(- (* 1000 (js/Math.ceil (/ (datetime/timestamp) 1000)))
|
||||
@timestamp)))
|
||||
(swap! valid-for-ms (fn [_]
|
||||
(- code-valid-for-ms
|
||||
(- (* 1000
|
||||
(js/Math.ceil (/ (datetime/timestamp) 1000)))
|
||||
@timestamp))))
|
||||
(reset! delay-ms nil)))
|
||||
cleanup-clock (fn []
|
||||
(reset! code nil)
|
||||
(reset! timestamp nil)
|
||||
(reset! valid-for-ms code-valid-for-ms))]
|
||||
|
||||
(let [{:keys [customization-color]} (rf/sub [:profile/multiaccount])
|
||||
valid-for-ms (reagent/atom code-valid-for-ms)
|
||||
code (reagent/atom nil)
|
||||
delay-ms (reagent/atom nil)
|
||||
timestamp (reagent/atom nil)
|
||||
set-code (fn [connection-string]
|
||||
(when (sync-utils/valid-connection-string? connection-string)
|
||||
(reset! timestamp (* 1000
|
||||
(js/Math.ceil (/ (datetime/timestamp)
|
||||
1000))))
|
||||
(reset! delay-ms 1000)
|
||||
(reset! code connection-string)))
|
||||
clock (fn []
|
||||
(if (pos? (- code-valid-for-ms
|
||||
(- (* 1000
|
||||
(js/Math.ceil (/ (datetime/timestamp) 1000)))
|
||||
@timestamp)))
|
||||
(swap! valid-for-ms (fn [_]
|
||||
(- code-valid-for-ms
|
||||
(- (* 1000
|
||||
(js/Math.ceil
|
||||
(/ (datetime/timestamp) 1000)))
|
||||
@timestamp))))
|
||||
(reset! delay-ms nil)))
|
||||
cleanup-clock (fn []
|
||||
(reset! code nil)
|
||||
(reset! timestamp nil)
|
||||
(reset! valid-for-ms code-valid-for-ms))
|
||||
on-enter-password (fn [entered-password]
|
||||
(rf/dispatch [:syncing/get-connection-string entered-password
|
||||
set-code]))]
|
||||
(fn []
|
||||
[rn/view {:style style/container-main}
|
||||
[:f> f-use-interval clock cleanup-clock @delay-ms]
|
||||
@ -68,29 +74,14 @@
|
||||
:weight :semi-bold
|
||||
:style {:color colors/white}}
|
||||
(i18n/label :t/setup-syncing)]]
|
||||
[rn/view {:style (style/qr-container (sync-utils/valid-connection-string? @code))}
|
||||
[rn/view {:style style/qr-container}
|
||||
(if (sync-utils/valid-connection-string? @code)
|
||||
[qr-code-viewer/qr-code-view 331 @code]
|
||||
[rn/view {:style {:margin-horizontal 12}}
|
||||
[qr-code-viewer/qr-code-view 311 @code]]
|
||||
[quo/qr-code
|
||||
{:source (resources/get-image :qr-code)
|
||||
:height 220
|
||||
:width "100%"}])
|
||||
(when-not (sync-utils/valid-connection-string? @code)
|
||||
[quo/button
|
||||
{:on-press (fn []
|
||||
;TODO https://github.com/status-im/status-mobile/issues/15570
|
||||
;remove old bottom sheet when Authentication process design is
|
||||
;created.
|
||||
(rf/dispatch [:bottom-sheet/hide-old])
|
||||
(rf/dispatch [:bottom-sheet/show-sheet-old
|
||||
{:content (fn []
|
||||
[enter-password/sheet set-code])}]))
|
||||
:type :primary
|
||||
:customization-color profile-color
|
||||
:size 40
|
||||
:container-style style/generate-button
|
||||
:icon-left :i/reveal}
|
||||
(i18n/label :t/reveal-sync-code)])
|
||||
(when (sync-utils/valid-connection-string? @code)
|
||||
[rn/view
|
||||
{:style style/valid-cs-container}
|
||||
@ -122,7 +113,17 @@
|
||||
:type :grey
|
||||
:container-style {:margin-top 12}
|
||||
:icon-left :i/copy}
|
||||
(i18n/label :t/copy-qr)]])]]
|
||||
(i18n/label :t/copy-qr)]])
|
||||
(when-not (sync-utils/valid-connection-string? @code)
|
||||
[rn/view {:style style/standard-auth}
|
||||
[standard-auth/view
|
||||
{:blur? true
|
||||
:size :size/s-40
|
||||
:track-text (i18n/label :t/slide-to-reveal-code)
|
||||
:customization-color customization-color
|
||||
:on-enter-password on-enter-password
|
||||
:biometric-auth? false
|
||||
:fallback-button-label (i18n/label :t/reveal-sync-code)}]])]]
|
||||
[rn/view {:style style/sync-code}
|
||||
[quo/divider-label {:tight? false} (i18n/label :t/have-a-sync-code?)]
|
||||
[quo/action-drawer
|
||||
|
@ -1,42 +0,0 @@
|
||||
(ns status-im2.contexts.syncing.sheets.enter-password.view
|
||||
(:require [utils.i18n :as i18n]
|
||||
[quo.core :as quo-old]
|
||||
[quo2.core :as quo]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[react-native.core :as rn]
|
||||
[utils.re-frame :as rf]))
|
||||
|
||||
;;TODO : this file is temporary and will be removed for new design auth method
|
||||
(defn sheet
|
||||
[set-code]
|
||||
(let [entered-password (atom "")]
|
||||
[:<>
|
||||
[rn/view {:margin 20}
|
||||
[rn/view
|
||||
[quo/text
|
||||
{:accessibility-label :sync-code-generated
|
||||
:weight :bold
|
||||
:size :heading-1
|
||||
:style {:color colors/neutral-100
|
||||
:margin 20}}
|
||||
(i18n/label :t/enter-your-password)]
|
||||
[rn/view {:flex-direction :row :align-items :center}
|
||||
[rn/view {:flex 1}
|
||||
[quo-old/text-input
|
||||
{:placeholder (i18n/label :t/enter-your-password)
|
||||
:auto-focus true
|
||||
:accessibility-label :password-input
|
||||
:show-cancel false
|
||||
:on-change-text #(reset! entered-password %)
|
||||
:secure-text-entry true}]]]
|
||||
[rn/view
|
||||
{:padding-horizontal 18
|
||||
:margin-top 20}
|
||||
[quo/button
|
||||
{:on-press (fn []
|
||||
;TODO https://github.com/status-im/status-mobile/issues/15570
|
||||
;remove old bottom sheet when Authentication process design is created.
|
||||
(rf/dispatch [:bottom-sheet/hide-old])
|
||||
(rf/dispatch [:syncing/get-connection-string-for-bootstrapping-another-device
|
||||
@entered-password set-code]))}
|
||||
(i18n/label :t/generate-scan-sync-code)]]]]]))
|
@ -1892,6 +1892,7 @@
|
||||
"swap": "Swap",
|
||||
"select-token-to-swap": "Select token to Swap",
|
||||
"select-token-to-receive": "Select token to receive",
|
||||
"slide-to-reveal-code": "Slide to reveal code",
|
||||
"minimum-received": "Minimum received",
|
||||
"powered-by-paraswap": "Powered by Paraswap",
|
||||
"priority": "Priority",
|
||||
@ -2050,7 +2051,7 @@
|
||||
"local-pairing-experimental-mode": "Local Pairing Mode (alpha)",
|
||||
"syncing": "Syncing",
|
||||
"synced-devices": "Synced Devices",
|
||||
"setup-syncing": "Setup Syncing",
|
||||
"setup-syncing": "Pair devices to sync",
|
||||
"sync-code": "Sync Code",
|
||||
"sync-code-generated": "Sync code generated",
|
||||
"generate-scan-sync-code": "Generate Scan Sync Code",
|
||||
|
Loading…
x
Reference in New Issue
Block a user