diff --git a/src/quo/components/info/info_message.cljs b/src/quo/components/info/info_message.cljs index 7609e49e4f..92896b7079 100644 --- a/src/quo/components/info/info_message.cljs +++ b/src/quo/components/info/info_message.cljs @@ -23,7 +23,8 @@ :text-color colors/white ;; text color override :icon-color colors/white ;; icon color override :no-icon-color? false ;; disable tint color for icon" - [{:keys [type size theme icon text-color icon-color no-icon-color? style accessibility-label]} message] + [{:keys [type size theme icon text-color icon-color no-icon-color? style accessibility-label + container-style]} message] (let [weight (if (= size :default) :regular :medium) icon-size (if (= size :default) 16 12) size (if (= size :default) :paragraph-2 :label) @@ -32,7 +33,8 @@ [rn/view {:style (merge {:flex-direction :row :align-items :center} - style)} + style + container-style)} [quo.icons/icon icon {:color icon-color :no-color no-icon-color? diff --git a/src/quo/components/inputs/input/style.cljs b/src/quo/components/inputs/input/style.cljs index 584ddc60fb..5755c936df 100644 --- a/src/quo/components/inputs/input/style.cljs +++ b/src/quo/components/inputs/input/style.cljs @@ -134,9 +134,9 @@ :align-items :flex-end}) (defn counter-color - [current-chars char-limit variant-colors] + [{:keys [current-chars char-limit variant-colors theme]}] {:color (if (> current-chars char-limit) - colors/danger-60 + (colors/resolve-color :danger theme) (:label variant-colors))}) (defn button diff --git a/src/quo/components/inputs/input/view.cljs b/src/quo/components/inputs/input/view.cljs index 71c89d88f3..3d1c7e450f 100644 --- a/src/quo/components/inputs/input/view.cljs +++ b/src/quo/components/inputs/input/view.cljs @@ -8,7 +8,7 @@ [react-native.platform :as platform])) (defn- label-&-counter - [{:keys [label current-chars char-limit variant-colors]}] + [{:keys [label current-chars char-limit variant-colors theme]}] [rn/view {:accessibility-label :input-labels :style style/texts-container} @@ -22,7 +22,10 @@ (str current-chars "/"))] [rn/view {:style style/counter-container} [text/text - {:style (style/counter-color current-chars char-limit variant-colors) + {:style (style/counter-color {:current-chars current-chars + :char-limit char-limit + :variant-colors variant-colors + :theme theme}) :weight :regular :size :paragraph-2} count-text]])]) @@ -114,7 +117,8 @@ {:variant-colors variant-colors :label label :current-chars char-count - :char-limit char-limit}]) + :char-limit char-limit + :theme theme}]) [rn/view {:style (style/input-container colors-by-status small? disabled?)} (when-let [{:keys [icon-name]} left-icon] [left-accessory diff --git a/src/status_im/common/router.cljs b/src/status_im/common/router.cljs index 170aa20413..08d7f4d97b 100644 --- a/src/status_im/common/router.cljs +++ b/src/status_im/common/router.cljs @@ -5,7 +5,7 @@ [legacy.status-im.ethereum.ens :as ens] [native-module.core :as native-module] [re-frame.core :as re-frame] - [status-im.common.validators :as validators] + [status-im.common.validation.general :as validators] [status-im.constants :as constants] [status-im.contexts.chat.events :as chat.events] [taoensso.timbre :as log] diff --git a/src/status_im/common/validation/general.cljs b/src/status_im/common/validation/general.cljs new file mode 100644 index 0000000000..b1de07cd61 --- /dev/null +++ b/src/status_im/common/validation/general.cljs @@ -0,0 +1,25 @@ +(ns status-im.common.validation.general + (:require + [status-im.constants :as constants] + [utils.emojilib :as emoji])) + +(defn valid-public-key? + [s] + (and (string? s) + (not-empty s) + (boolean (re-matches constants/regx-public-key s)))) + +(defn valid-compressed-key? + [s] + (and (string? s) + (not-empty s) + (boolean (re-matches constants/regx-compressed-key s)))) + +(defn has-emojis? [s] (boolean (re-find emoji/emoji-regex s))) + +(def no-special-chars-regex #"^[a-zA-Z0-9\-_ ]+$") +(defn has-special-characters? + [s] + (and (not (= s "")) + (not (re-find no-special-chars-regex s)))) + diff --git a/src/status_im/common/validators_test.cljs b/src/status_im/common/validation/general_test.cljs similarity index 91% rename from src/status_im/common/validators_test.cljs rename to src/status_im/common/validation/general_test.cljs index 48c7c90ef8..c9e28c6463 100644 --- a/src/status_im/common/validators_test.cljs +++ b/src/status_im/common/validation/general_test.cljs @@ -1,7 +1,7 @@ -(ns status-im.common.validators-test +(ns status-im.common.validation.general-test (:require [cljs.test :refer-macros [deftest testing is]] - [status-im.common.validators :refer [valid-compressed-key?]])) + [status-im.common.validation.general :refer [valid-compressed-key?]])) (deftest test-valid-compressed-key (testing "valid" @@ -32,3 +32,4 @@ (testing "contains l" (is (not (valid-compressed-key? "zQ3shWj4WaBdf2zYKCkXe6PHxDxNTzZyid1i75879Ue9cX9gl"))))) + diff --git a/src/status_im/common/validation/profile.cljs b/src/status_im/common/validation/profile.cljs index daea4d0d81..fd07526150 100644 --- a/src/status_im/common/validation/profile.cljs +++ b/src/status_im/common/validation/profile.cljs @@ -1,5 +1,6 @@ (ns status-im.common.validation.profile (:require [clojure.string :as string] + [status-im.common.validation.general :as validators] [status-im.constants :as constants] utils.emojilib [utils.i18n :as i18n])) @@ -8,16 +9,10 @@ ;; https://github.com/status-im/status-desktop/blob/2ba96803168461088346bf5030df750cb226df4c/ui/imports/utils/Constants.qml#L468 (def min-length 5) -(def status-regex #"^[a-zA-Z0-9\-_ ]+$") - (def common-names ["Ethereum" "Bitcoin"]) -(defn has-emojis? [s] (boolean (re-find utils.emojilib/emoji-regex s))) - (defn has-common-names? [s] (pos? (count (filter #(string/includes? s %) common-names)))) -(defn has-special-characters? [s] (not (re-find status-regex s))) - (defn name-too-short? [s] (< (count (string/trim (str s))) min-length)) (defn name-too-long? [s] (> (count (string/trim (str s))) constants/profile-name-max-length)) @@ -27,33 +22,37 @@ (defn validation-name [s] (cond - (string/blank? s) nil - (string/ends-with? s "-eth") (i18n/label :t/ending-not-allowed {:ending "-eth"}) - (string/ends-with? s "_eth") (i18n/label :t/ending-not-allowed {:ending "_eth"}) - (string/ends-with? s ".eth") (i18n/label :t/ending-not-allowed {:ending ".eth"}) - (string/starts-with? s " ") (i18n/label :t/start-with-space) - (string/ends-with? s " ") (i18n/label :t/ends-with-space) - (has-common-names? s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/common-names)}) - (has-emojis? s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/emojis)}) - (has-special-characters? s) (i18n/label :t/are-not-allowed - {:check (i18n/label :t/special-characters)}) - (name-too-short? s) (i18n/label :t/minimum-characters {:min-chars min-length}) - (name-too-long? s) (i18n/label :t/profile-name-is-too-long))) + (string/blank? s) nil + (string/ends-with? s "-eth") (i18n/label :t/ending-not-allowed {:ending "-eth"}) + (string/ends-with? s "_eth") (i18n/label :t/ending-not-allowed {:ending "_eth"}) + (string/ends-with? s ".eth") (i18n/label :t/ending-not-allowed {:ending ".eth"}) + (string/starts-with? s " ") (i18n/label :t/start-with-space) + (string/ends-with? s " ") (i18n/label :t/ends-with-space) + (has-common-names? s) (i18n/label :t/are-not-allowed + {:check (i18n/label :t/common-names)}) + (validators/has-emojis? s) (i18n/label :t/are-not-allowed + {:check (i18n/label :t/emojis)}) + (validators/has-special-characters? s) (i18n/label :t/are-not-allowed + {:check (i18n/label :t/special-characters)}) + (name-too-short? s) (i18n/label :t/minimum-characters {:min-chars min-length}) + (name-too-long? s) (i18n/label :t/profile-name-is-too-long))) (defn validation-bio [s] (cond - (string/blank? s) nil - (has-emojis? s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/emojis)}) - (has-special-characters? s) (i18n/label :t/are-not-allowed - {:check (i18n/label :t/special-characters)}) - (bio-too-long? s) (i18n/label :t/bio-is-too-long))) + (string/blank? s) nil + (validators/has-emojis? s) (i18n/label :t/are-not-allowed + {:check (i18n/label :t/emojis)}) + (validators/has-special-characters? s) (i18n/label :t/are-not-allowed + {:check (i18n/label :t/special-characters)}) + (bio-too-long? s) (i18n/label :t/bio-is-too-long))) (defn validation-nickname [s] (cond - (string/blank? s) nil - (has-emojis? s) (i18n/label :t/are-not-allowed {:check (i18n/label :t/emojis)}) - (has-special-characters? s) (i18n/label :t/are-not-allowed - {:check (i18n/label :t/special-characters)}) - (name-too-long? s) (i18n/label :t/nickname-is-too-long))) + (string/blank? s) nil + (validators/has-emojis? s) (i18n/label :t/are-not-allowed + {:check (i18n/label :t/emojis)}) + (validators/has-special-characters? s) (i18n/label :t/are-not-allowed + {:check (i18n/label :t/special-characters)}) + (name-too-long? s) (i18n/label :t/nickname-is-too-long))) diff --git a/src/status_im/common/validation/profile_test.cljs b/src/status_im/common/validation/profile_test.cljs index b12fe7a231..84fd4c9873 100644 --- a/src/status_im/common/validation/profile_test.cljs +++ b/src/status_im/common/validation/profile_test.cljs @@ -1,12 +1,13 @@ (ns status-im.common.validation.profile-test (:require [cljs.test :refer-macros [deftest are]] + [status-im.common.validation.general :as validator] [status-im.common.validation.profile :as profile-validator] [utils.i18n :as i18n])) (deftest has-emojis-test (are [arg expected] - (expected (profile-validator/has-emojis? arg)) + (expected (validator/has-emojis? arg)) "Hello 😊" true? "Hello" false?)) @@ -18,7 +19,7 @@ (deftest has-special-characters-test (are [arg expected] - (expected (profile-validator/has-special-characters? arg)) + (expected (validator/has-special-characters? arg)) "@name" true? "name" false?)) diff --git a/src/status_im/common/validators.cljs b/src/status_im/common/validators.cljs deleted file mode 100644 index 0c758ac6d7..0000000000 --- a/src/status_im/common/validators.cljs +++ /dev/null @@ -1,15 +0,0 @@ -(ns status-im.common.validators - (:require - [status-im.constants :as constants])) - -(defn valid-public-key? - [s] - (and (string? s) - (not-empty s) - (boolean (re-matches constants/regx-public-key s)))) - -(defn valid-compressed-key? - [s] - (and (string? s) - (not-empty s) - (boolean (re-matches constants/regx-compressed-key s)))) diff --git a/src/status_im/contexts/chat/home/add_new_contact/events.cljs b/src/status_im/contexts/chat/home/add_new_contact/events.cljs index 043d7153ee..49a7a07087 100644 --- a/src/status_im/contexts/chat/home/add_new_contact/events.cljs +++ b/src/status_im/contexts/chat/home/add_new_contact/events.cljs @@ -2,7 +2,7 @@ (:require [clojure.string :as string] [re-frame.core :as re-frame] - [status-im.common.validators :as validators] + [status-im.common.validation.general :as validators] [status-im.contexts.chat.contacts.events :as data-store.contacts] status-im.contexts.chat.home.add-new-contact.effects [utils.ens.stateofus :as stateofus] diff --git a/src/status_im/contexts/shell/qr_reader/view.cljs b/src/status_im/contexts/shell/qr_reader/view.cljs index fe79efc73b..b63b2e481f 100644 --- a/src/status_im/contexts/shell/qr_reader/view.cljs +++ b/src/status_im/contexts/shell/qr_reader/view.cljs @@ -5,7 +5,7 @@ [react-native.hooks :as hooks] [status-im.common.router :as router] [status-im.common.scan-qr-code.view :as scan-qr-code] - [status-im.common.validators :as validators] + [status-im.common.validation.general :as validators] [status-im.contexts.communities.events] [status-im.contexts.wallet.common.validation :as wallet-validation] [utils.debounce :as debounce] diff --git a/src/status_im/contexts/wallet/create_account/edit_derivation_path/view.cljs b/src/status_im/contexts/wallet/create_account/edit_derivation_path/view.cljs index 4255fc305f..46ff3ebaac 100644 --- a/src/status_im/contexts/wallet/create_account/edit_derivation_path/view.cljs +++ b/src/status_im/contexts/wallet/create_account/edit_derivation_path/view.cljs @@ -143,4 +143,3 @@ :delete-key? true}])])))) (def view (quo.theme/with-theme view-internal)) - diff --git a/src/status_im/contexts/wallet/create_account/new_keypair/keypair_name/style.cljs b/src/status_im/contexts/wallet/create_account/new_keypair/keypair_name/style.cljs index ac38696374..d5792ba0a1 100644 --- a/src/status_im/contexts/wallet/create_account/new_keypair/keypair_name/style.cljs +++ b/src/status_im/contexts/wallet/create_account/new_keypair/keypair_name/style.cljs @@ -7,3 +7,7 @@ (def bottom-action {:margin-horizontal -20}) + +(def error-container + {:margin-left 20 + :margin-vertical 8}) diff --git a/src/status_im/contexts/wallet/create_account/new_keypair/keypair_name/view.cljs b/src/status_im/contexts/wallet/create_account/new_keypair/keypair_name/view.cljs index 4b25c5e52a..8d44dce839 100644 --- a/src/status_im/contexts/wallet/create_account/new_keypair/keypair_name/view.cljs +++ b/src/status_im/contexts/wallet/create_account/new_keypair/keypair_name/view.cljs @@ -2,44 +2,72 @@ (:require [quo.core :as quo] [react-native.core :as rn] - [reagent.core :as reagent] [status-im.common.floating-button-page.view :as floating-button-page] + [status-im.common.validation.general :as validators] [status-im.contexts.wallet.create-account.new-keypair.keypair-name.style :as style] [utils.i18n :as i18n] [utils.re-frame :as rf])) (def keypair-name-max-length 15) +(def keypair-name-min-length 5) + +(def error-messages + {:length (i18n/label :t/key-name-error-length) + :emoji (i18n/label :t/key-name-error-emoji) + :special-char (i18n/label :t/key-name-error-special-char)}) + +(defn navigate-back [] (rf/dispatch [:navigate-back])) (defn view [] - (let [keypair-name (reagent/atom "")] - (fn [] - (let [customization-color (rf/sub [:profile/customization-color])] - [rn/view {:style {:flex 1}} - [floating-button-page/view - {:header [quo/page-nav - {:icon-name :i/arrow-left - :on-press #(rf/dispatch [:navigate-back]) - :accessibility-label :top-bar}] - :footer [quo/bottom-actions - {:actions :one-action - :button-one-label (i18n/label :t/continue) - :button-one-props {:disabled? (or (zero? (count @keypair-name)) - (> (count @keypair-name) - keypair-name-max-length)) - :customization-color customization-color - :on-press #(rf/dispatch [:wallet/new-keypair-continue - {:keypair-name - @keypair-name}])} - :container-style style/bottom-action}]} - [quo/text-combinations - {:container-style style/header-container - :title (i18n/label :t/keypair-name) - :description (i18n/label :t/keypair-name-description)}] - [quo/input - {:container-style {:margin-horizontal 20} - :placeholder (i18n/label :t/keypair-name-input-placeholder) - :label (i18n/label :t/keypair-name) - :char-limit keypair-name-max-length - :auto-focus true - :on-change-text #(reset! keypair-name %)}]]])))) + (let [[keypair-name set-keypair-name] (rn/use-state "") + customization-color (rf/sub [:profile/customization-color]) + [error set-error] (rn/use-state false) + on-change-text (rn/use-callback (fn [value] + (set-keypair-name value) + (cond + (> (count value) keypair-name-max-length) + (set-error :length) + (validators/has-emojis? value) (set-error + :emoji) + (validators/has-special-characters? value) + (set-error :special-char) + :else (set-error nil)))) + on-continue (rn/use-callback #(rf/dispatch [:wallet/new-keypair-continue + {:keypair-name + keypair-name}]) + [keypair-name])] + [rn/view {:style {:flex 1}} + [floating-button-page/view + {:header [quo/page-nav + {:icon-name :i/arrow-left + :on-press navigate-back + :accessibility-label :top-bar}] + :footer [quo/bottom-actions + {:actions :one-action + :button-one-label (i18n/label :t/continue) + :button-one-props {:disabled? (or (pos? error) + (<= (count keypair-name) + keypair-name-min-length)) + :customization-color customization-color + :on-press on-continue} + :container-style style/bottom-action}]} + [quo/text-combinations + {:container-style style/header-container + :title (i18n/label :t/keypair-name) + :description (i18n/label :t/keypair-name-description)}] + [quo/input + {:container-style {:margin-horizontal 20} + :placeholder (i18n/label :t/keypair-name-input-placeholder) + :label (i18n/label :t/keypair-name) + :char-limit keypair-name-max-length + :auto-focus true + :on-change-text on-change-text + :error error}] + (when error + [quo/info-message + {:type :error + :size :default + :icon :i/info + :container-style style/error-container} + (get error-messages error)])]])) diff --git a/translations/en.json b/translations/en.json index 465af28497..7ac10da317 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2562,5 +2562,8 @@ "one": "1 address", "other": "{{count}} addresses" }, - "max": "Max: {{number}}" + "max": "Max: {{number}}", + "key-name-error-length": "Key name too long", + "key-name-error-emoji": "Emojis are not allowed", + "key-name-error-special-char": "Special characters are not allowed" }