mirror of
https://github.com/status-im/status-react.git
synced 2025-01-09 10:42:53 +00:00
parent
006824e0ed
commit
6be1adb40e
BIN
resources/images/icons2/12x12/seed-phrase@2x.png
Normal file
BIN
resources/images/icons2/12x12/seed-phrase@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 221 B |
BIN
resources/images/icons2/12x12/seed-phrase@3x.png
Normal file
BIN
resources/images/icons2/12x12/seed-phrase@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 348 B |
23
src/status_im/common/validation/keypair.cljs
Normal file
23
src/status_im/common/validation/keypair.cljs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
(ns status-im.common.validation.keypair
|
||||||
|
(:require [clojure.string :as string]
|
||||||
|
[status-im.common.validation.general :as validators]
|
||||||
|
[status-im.constants :as constants]
|
||||||
|
utils.emojilib
|
||||||
|
[utils.i18n :as i18n]))
|
||||||
|
|
||||||
|
(defn keypair-too-short?
|
||||||
|
[s]
|
||||||
|
(< (-> s str string/trim count) constants/key-pair-name-min-length))
|
||||||
|
|
||||||
|
(defn keypair-too-long?
|
||||||
|
[s]
|
||||||
|
(> (-> s str string/trim count) constants/key-pair-name-max-length))
|
||||||
|
|
||||||
|
(defn validation-keypair-name
|
||||||
|
[s]
|
||||||
|
(cond
|
||||||
|
(string/blank? s) nil
|
||||||
|
(validators/has-emojis? s) (i18n/label :t/key-name-error-emoji)
|
||||||
|
(validators/has-special-characters? s) (i18n/label :t/key-name-error-special-char)
|
||||||
|
(keypair-too-short? s) (i18n/label :t/your-key-pair-name-is-too-short)
|
||||||
|
(keypair-too-long? s) (i18n/label :t/your-key-pair-name-is-too-long)))
|
27
src/status_im/common/validation/keypair_test.cljs
Normal file
27
src/status_im/common/validation/keypair_test.cljs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
(ns status-im.common.validation.keypair-test
|
||||||
|
(:require
|
||||||
|
[cljs.test :refer-macros [deftest are]]
|
||||||
|
[status-im.common.validation.keypair :as keypair-validator]
|
||||||
|
[utils.i18n :as i18n]))
|
||||||
|
|
||||||
|
(deftest keypair-name-too-short-test
|
||||||
|
(are [arg expected]
|
||||||
|
(expected (keypair-validator/keypair-too-short? arg))
|
||||||
|
"abc" true?
|
||||||
|
"abcdef" false?))
|
||||||
|
|
||||||
|
(deftest keypair-name-too-long-test
|
||||||
|
(are [arg expected]
|
||||||
|
(expected (keypair-validator/keypair-too-long? arg))
|
||||||
|
(apply str (repeat 25 "a")) true?
|
||||||
|
"abcdef" false?))
|
||||||
|
|
||||||
|
(deftest validation-keypair-name-test
|
||||||
|
(are [arg expected]
|
||||||
|
(= (keypair-validator/validation-keypair-name arg) expected)
|
||||||
|
nil nil
|
||||||
|
"" nil
|
||||||
|
"name !" (i18n/label :t/key-name-error-special-char)
|
||||||
|
"Hello 😊" (i18n/label :t/key-name-error-emoji)
|
||||||
|
"abc" (i18n/label :t/your-key-pair-name-is-too-short)
|
||||||
|
(apply str (repeat 25 "a")) (i18n/label :t/your-key-pair-name-is-too-long)))
|
@ -77,6 +77,9 @@
|
|||||||
(def ^:const contact-request-message-state-declined 3)
|
(def ^:const contact-request-message-state-declined 3)
|
||||||
(def ^:const contact-request-message-max-length 280)
|
(def ^:const contact-request-message-max-length 280)
|
||||||
|
|
||||||
|
(def ^:const key-pair-name-max-length 20)
|
||||||
|
(def ^:const key-pair-name-min-length 5)
|
||||||
|
|
||||||
(def request-to-join-pending-state 1)
|
(def request-to-join-pending-state 1)
|
||||||
|
|
||||||
(def reactions
|
(def reactions
|
||||||
|
32
src/status_im/contexts/settings/wallet/events.cljs
Normal file
32
src/status_im/contexts/settings/wallet/events.cljs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
(ns status-im.contexts.settings.wallet.events
|
||||||
|
(:require
|
||||||
|
[taoensso.timbre :as log]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(rf/reg-event-fx
|
||||||
|
:wallet/rename-keypair-success
|
||||||
|
(fn [{:keys [db]} [key-uid name]]
|
||||||
|
{:db (update-in db
|
||||||
|
[:wallet :keypairs]
|
||||||
|
(fn [keypairs]
|
||||||
|
(map (fn [keypair]
|
||||||
|
(if (= (keypair :key-uid) key-uid)
|
||||||
|
(assoc keypair :name name)
|
||||||
|
keypair))
|
||||||
|
keypairs)))
|
||||||
|
:fx [[:dispatch [:navigate-back]]
|
||||||
|
[:dispatch
|
||||||
|
[:toasts/upsert
|
||||||
|
{:type :positive
|
||||||
|
:text (i18n/label :t/key-pair-name-updated)}]]]}))
|
||||||
|
|
||||||
|
(defn rename-keypair
|
||||||
|
[_ [{:keys [key-uid keypair-name]}]]
|
||||||
|
{:fx [[:json-rpc/call
|
||||||
|
[{:method "accounts_updateKeypairName"
|
||||||
|
:params [key-uid keypair-name]
|
||||||
|
:on-success [:wallet/rename-keypair-success key-uid keypair-name]
|
||||||
|
:on-error #(log/info "failed to rename keypair " %)}]]]})
|
||||||
|
|
||||||
|
(rf/reg-event-fx :wallet/rename-keypair rename-keypair)
|
20
src/status_im/contexts/settings/wallet/events_test.cljs
Normal file
20
src/status_im/contexts/settings/wallet/events_test.cljs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
(ns status-im.contexts.settings.wallet.events-test
|
||||||
|
(:require
|
||||||
|
[cljs.test :refer-macros [deftest is]]
|
||||||
|
matcher-combinators.test
|
||||||
|
[status-im.contexts.settings.wallet.events :as sut]))
|
||||||
|
|
||||||
|
(def key-uid "0xfef454bb492ee4677594f8e05921c84f336fa811deb99b8d922477cc87a38b98")
|
||||||
|
|
||||||
|
(deftest rename-keypair-test
|
||||||
|
(let [new-keypair-name "key pair new"
|
||||||
|
cofx {:db {}}
|
||||||
|
expected {:fx [[:json-rpc/call
|
||||||
|
[{:method "accounts_updateKeypairName"
|
||||||
|
:params [key-uid new-keypair-name]
|
||||||
|
:on-success [:wallet/rename-keypair-success key-uid new-keypair-name]
|
||||||
|
:on-error fn?}]]]}]
|
||||||
|
(is (match? expected
|
||||||
|
(sut/rename-keypair cofx
|
||||||
|
[{:key-uid key-uid
|
||||||
|
:keypair-name new-keypair-name}])))))
|
@ -1,6 +1,19 @@
|
|||||||
(ns status-im.contexts.settings.wallet.keypairs-and-accounts.actions.view
|
(ns status-im.contexts.settings.wallet.keypairs-and-accounts.actions.view
|
||||||
(:require [quo.core :as quo]))
|
(:require [quo.core :as quo]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn on-rename-request
|
||||||
|
[data]
|
||||||
|
(rf/dispatch [:open-modal :screen/settings.rename-keypair data]))
|
||||||
|
|
||||||
(defn view
|
(defn view
|
||||||
[props]
|
[props data]
|
||||||
[quo/drawer-top props])
|
[:<>
|
||||||
|
[quo/drawer-top props]
|
||||||
|
[quo/action-drawer
|
||||||
|
[(when (= (:type props) :keypair)
|
||||||
|
[{:icon :i/edit
|
||||||
|
:accessibility-label :rename-key-pair
|
||||||
|
:label (i18n/label :t/rename-key-pair)
|
||||||
|
:on-press #(on-rename-request data)}])]]])
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
(ns status-im.contexts.settings.wallet.keypairs-and-accounts.rename.style)
|
||||||
|
|
||||||
|
(def header-container
|
||||||
|
{:margin-bottom 8})
|
||||||
|
|
||||||
|
(def bottom-action
|
||||||
|
{:margin-horizontal -20})
|
||||||
|
|
||||||
|
(def error-container
|
||||||
|
{:margin-left 20
|
||||||
|
:margin-vertical 8})
|
@ -0,0 +1,84 @@
|
|||||||
|
(ns status-im.contexts.settings.wallet.keypairs-and-accounts.rename.view
|
||||||
|
(:require [clojure.string :as string]
|
||||||
|
[quo.core :as quo]
|
||||||
|
[react-native.core :as rn]
|
||||||
|
[status-im.common.floating-button-page.view :as floating-button-page]
|
||||||
|
[status-im.common.validation.keypair :as keypair-validator]
|
||||||
|
[status-im.constants :as constants]
|
||||||
|
[status-im.contexts.settings.wallet.keypairs-and-accounts.rename.style :as style]
|
||||||
|
[utils.debounce :as debounce]
|
||||||
|
[utils.i18n :as i18n]
|
||||||
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
|
(defn navigate-back [] (rf/dispatch [:navigate-back]))
|
||||||
|
|
||||||
|
(defn view
|
||||||
|
[]
|
||||||
|
(let [{:keys [name key-uid]} (rf/sub [:get-screen-params])
|
||||||
|
customization-color (rf/sub [:profile/customization-color])
|
||||||
|
[unsaved-keypair-name set-unsaved-keypair-name] (rn/use-state name)
|
||||||
|
[error-msg set-error-msg] (rn/use-state nil)
|
||||||
|
[typing? set-typing?] (rn/use-state false)
|
||||||
|
validate-keypair-name (rn/use-callback
|
||||||
|
(debounce/debounce
|
||||||
|
(fn [name]
|
||||||
|
(set-error-msg
|
||||||
|
(keypair-validator/validation-keypair-name
|
||||||
|
name))
|
||||||
|
(set-typing? false))
|
||||||
|
300))
|
||||||
|
on-change-text (rn/use-callback (fn [text]
|
||||||
|
(set-typing? true)
|
||||||
|
(set-unsaved-keypair-name
|
||||||
|
text)
|
||||||
|
(validate-keypair-name text)))
|
||||||
|
on-clear (rn/use-callback (fn []
|
||||||
|
(on-change-text "")))
|
||||||
|
on-save (rn/use-callback #(rf/dispatch
|
||||||
|
[:wallet/rename-keypair
|
||||||
|
{:key-uid key-uid
|
||||||
|
:keypair-name
|
||||||
|
unsaved-keypair-name}])
|
||||||
|
[unsaved-keypair-name key-uid])]
|
||||||
|
[floating-button-page/view
|
||||||
|
{:header [quo/page-nav
|
||||||
|
{:icon-name :i/close
|
||||||
|
:on-press navigate-back
|
||||||
|
:accessibility-label :top-bar}]
|
||||||
|
:footer [quo/bottom-actions
|
||||||
|
{:actions :one-action
|
||||||
|
:button-one-label (i18n/label :t/save)
|
||||||
|
:button-one-props {:disabled? (or typing?
|
||||||
|
(string/blank? unsaved-keypair-name)
|
||||||
|
(not (string/blank? error-msg)))
|
||||||
|
:customization-color customization-color
|
||||||
|
:on-press on-save}
|
||||||
|
:container-style style/bottom-action}]}
|
||||||
|
[quo/page-top
|
||||||
|
{:container-style style/header-container
|
||||||
|
:title (i18n/label :t/rename-key-pair)
|
||||||
|
:description :context-tag
|
||||||
|
:context-tag {:type :icon
|
||||||
|
:size 24
|
||||||
|
:context name
|
||||||
|
:icon :i/seed-phrase}}]
|
||||||
|
[quo/input
|
||||||
|
{:container-style {:margin-horizontal 20}
|
||||||
|
:placeholder (i18n/label :t/keypair-name-input-placeholder)
|
||||||
|
:label (i18n/label :t/keypair-name)
|
||||||
|
:default-value unsaved-keypair-name
|
||||||
|
:char-limit constants/key-pair-name-max-length
|
||||||
|
:max-length constants/key-pair-name-max-length
|
||||||
|
:auto-focus true
|
||||||
|
:clearable? (not (string/blank? unsaved-keypair-name))
|
||||||
|
:on-clear on-clear
|
||||||
|
:on-change-text on-change-text
|
||||||
|
:error? (not (string/blank? error-msg))}]
|
||||||
|
(when-not (string/blank? error-msg)
|
||||||
|
[quo/info-message
|
||||||
|
{:type :error
|
||||||
|
:size :default
|
||||||
|
:icon :i/info
|
||||||
|
:container-style style/error-container}
|
||||||
|
error-msg])]))
|
||||||
|
|
@ -15,14 +15,15 @@
|
|||||||
|
|
||||||
(defn on-options-press
|
(defn on-options-press
|
||||||
[{:keys [theme]
|
[{:keys [theme]
|
||||||
:as props}]
|
:as props} keypair]
|
||||||
(rf/dispatch [:show-bottom-sheet
|
(rf/dispatch [:show-bottom-sheet
|
||||||
{:content (fn [] [actions/view props])
|
{:content (fn [] [actions/view props keypair])
|
||||||
:theme theme}]))
|
:theme theme}]))
|
||||||
|
|
||||||
(defn- keypair
|
(defn- keypair
|
||||||
[{keypair-type :type
|
[{keypair-type :type
|
||||||
:keys [accounts name]}
|
:keys [accounts name]
|
||||||
|
:as item}
|
||||||
_ _
|
_ _
|
||||||
{:keys [profile-picture compressed-key customization-color]}]
|
{:keys [profile-picture compressed-key customization-color]}]
|
||||||
(let [theme (quo.theme/use-theme)
|
(let [theme (quo.theme/use-theme)
|
||||||
@ -42,7 +43,8 @@
|
|||||||
:profile-picture profile-picture)
|
:profile-picture profile-picture)
|
||||||
(not default-keypair?)
|
(not default-keypair?)
|
||||||
(assoc :type :keypair
|
(assoc :type :keypair
|
||||||
:icon-avatar :i/seed))))
|
:icon-avatar :i/seed))
|
||||||
|
item))
|
||||||
[customization-color default-keypair? name
|
[customization-color default-keypair? name
|
||||||
profile-picture shortened-key theme])]
|
profile-picture shortened-key theme])]
|
||||||
[quo/keypair
|
[quo/keypair
|
||||||
|
@ -6,16 +6,14 @@
|
|||||||
[status-im.common.floating-button-page.view :as floating-button-page]
|
[status-im.common.floating-button-page.view :as floating-button-page]
|
||||||
[status-im.common.not-implemented :as not-implemented]
|
[status-im.common.not-implemented :as not-implemented]
|
||||||
[status-im.common.validation.general :as validators]
|
[status-im.common.validation.general :as validators]
|
||||||
|
[status-im.constants :as constants]
|
||||||
[status-im.contexts.wallet.add-account.create-account.key-pair-name.style :as style]
|
[status-im.contexts.wallet.add-account.create-account.key-pair-name.style :as style]
|
||||||
[utils.i18n :as i18n]
|
[utils.i18n :as i18n]
|
||||||
[utils.re-frame :as rf]))
|
[utils.re-frame :as rf]))
|
||||||
|
|
||||||
(def ^:private key-pair-name-max-length 15)
|
|
||||||
(def ^:private key-pair-name-min-length 5)
|
|
||||||
|
|
||||||
(def error-messages
|
(def error-messages
|
||||||
{:too-long (i18n/label :t/key-name-error-length)
|
{:too-long (i18n/label :t/key-name-error-length)
|
||||||
:too-short (i18n/label :t/key-name-error-too-short {:count key-pair-name-min-length})
|
:too-short (i18n/label :t/key-name-error-too-short {:count constants/key-pair-name-min-length})
|
||||||
:emoji (i18n/label :t/key-name-error-emoji)
|
:emoji (i18n/label :t/key-name-error-emoji)
|
||||||
:special-char (i18n/label :t/key-name-error-special-char)})
|
:special-char (i18n/label :t/key-name-error-special-char)})
|
||||||
|
|
||||||
@ -33,10 +31,10 @@
|
|||||||
(fn [value]
|
(fn [value]
|
||||||
(set-key-pair-name value)
|
(set-key-pair-name value)
|
||||||
(cond
|
(cond
|
||||||
(> (count value) key-pair-name-max-length)
|
(> (count value) constants/key-pair-name-max-length)
|
||||||
(set-error :too-long)
|
(set-error :too-long)
|
||||||
|
|
||||||
(< 0 (count value) key-pair-name-min-length)
|
(< 0 (count value) constants/key-pair-name-min-length)
|
||||||
(set-error :too-short)
|
(set-error :too-short)
|
||||||
|
|
||||||
(validators/has-emojis? value)
|
(validators/has-emojis? value)
|
||||||
@ -81,7 +79,7 @@
|
|||||||
{:container-style style/input
|
{:container-style style/input
|
||||||
:placeholder (i18n/label :t/keypair-name-input-placeholder)
|
:placeholder (i18n/label :t/keypair-name-input-placeholder)
|
||||||
:label (i18n/label :t/keypair-name)
|
:label (i18n/label :t/keypair-name)
|
||||||
:char-limit key-pair-name-max-length
|
:char-limit constants/key-pair-name-max-length
|
||||||
:auto-focus true
|
:auto-focus true
|
||||||
:on-change-text on-change-text
|
:on-change-text on-change-text
|
||||||
:error error}]
|
:error error}]
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
[clojure.string :as string]
|
[clojure.string :as string]
|
||||||
[react-native.platform :as platform]
|
[react-native.platform :as platform]
|
||||||
[status-im.constants :as constants]
|
[status-im.constants :as constants]
|
||||||
|
[status-im.contexts.settings.wallet.events]
|
||||||
[status-im.contexts.wallet.common.utils.networks :as network-utils]
|
[status-im.contexts.wallet.common.utils.networks :as network-utils]
|
||||||
[status-im.contexts.wallet.data-store :as data-store]
|
[status-im.contexts.wallet.data-store :as data-store]
|
||||||
[status-im.contexts.wallet.db :as db]
|
[status-im.contexts.wallet.db :as db]
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
[status-im.contexts.profile.settings.screens.password.change-password.view :as change-password]
|
[status-im.contexts.profile.settings.screens.password.change-password.view :as change-password]
|
||||||
[status-im.contexts.profile.settings.screens.password.view :as settings-password]
|
[status-im.contexts.profile.settings.screens.password.view :as settings-password]
|
||||||
[status-im.contexts.profile.settings.view :as settings]
|
[status-im.contexts.profile.settings.view :as settings]
|
||||||
|
[status-im.contexts.settings.wallet.keypairs-and-accounts.rename.view :as keypair-rename]
|
||||||
[status-im.contexts.settings.wallet.keypairs-and-accounts.view :as keypairs-and-accounts]
|
[status-im.contexts.settings.wallet.keypairs-and-accounts.view :as keypairs-and-accounts]
|
||||||
[status-im.contexts.settings.wallet.saved-addresses.view :as saved-addresses-settings]
|
[status-im.contexts.settings.wallet.saved-addresses.view :as saved-addresses-settings]
|
||||||
[status-im.contexts.settings.wallet.wallet-options.view :as wallet-options]
|
[status-im.contexts.settings.wallet.wallet-options.view :as wallet-options]
|
||||||
@ -503,6 +504,10 @@
|
|||||||
:options options/transparent-modal-screen-options
|
:options options/transparent-modal-screen-options
|
||||||
:component wallet-options/view}
|
:component wallet-options/view}
|
||||||
|
|
||||||
|
{:name :screen/settings.rename-keypair
|
||||||
|
:options (assoc options/dark-screen :sheet? true)
|
||||||
|
:component keypair-rename/view}
|
||||||
|
|
||||||
{:name :screen/settings.saved-addresses
|
{:name :screen/settings.saved-addresses
|
||||||
:options options/transparent-modal-screen-options
|
:options options/transparent-modal-screen-options
|
||||||
:component saved-addresses-settings/view}
|
:component saved-addresses-settings/view}
|
||||||
|
@ -183,9 +183,10 @@
|
|||||||
:<- [:wallet/keypairs]
|
:<- [:wallet/keypairs]
|
||||||
(fn [keypairs [_ format-options]]
|
(fn [keypairs [_ format-options]]
|
||||||
(->> keypairs
|
(->> keypairs
|
||||||
(map (fn [{:keys [accounts name type]}]
|
(map (fn [{:keys [accounts name type key-uid]}]
|
||||||
{:type (keyword type)
|
{:type (keyword type)
|
||||||
:name name
|
:name name
|
||||||
|
:key-uid key-uid
|
||||||
:accounts (format-settings-keypair-accounts accounts format-options)})))))
|
:accounts (format-settings-keypair-accounts accounts format-options)})))))
|
||||||
|
|
||||||
(rf/reg-sub
|
(rf/reg-sub
|
||||||
|
@ -1255,6 +1255,7 @@
|
|||||||
"remove-network": "Remove network",
|
"remove-network": "Remove network",
|
||||||
"remove-token": "Remove token",
|
"remove-token": "Remove token",
|
||||||
"removed": "removed",
|
"removed": "removed",
|
||||||
|
"rename-key-pair": "Rename key pair",
|
||||||
"repeat-pin": "Repeat new 6-digit passcode",
|
"repeat-pin": "Repeat new 6-digit passcode",
|
||||||
"repeat-puk": "Repeat new 12-digit PUK",
|
"repeat-puk": "Repeat new 12-digit PUK",
|
||||||
"report-bug-email-template": "1. Issue Description\n{{description}}\n\n\n2. Steps to reproduce\n{{steps}}\n\n\n3. Attach screenshots that can demo the problem, please\n",
|
"report-bug-email-template": "1. Issue Description\n{{description}}\n\n\n2. Steps to reproduce\n{{steps}}\n\n\n3. Attach screenshots that can demo the problem, please\n",
|
||||||
@ -2570,6 +2571,7 @@
|
|||||||
"keypair-name": "Key pair name",
|
"keypair-name": "Key pair name",
|
||||||
"keypair-name-description": "Name key pair for your own personal reference",
|
"keypair-name-description": "Name key pair for your own personal reference",
|
||||||
"keypair-name-input-placeholder": "Collectibles account, Old vault....",
|
"keypair-name-input-placeholder": "Collectibles account, Old vault....",
|
||||||
|
"key-pair-name-updated": "Key pair name updated",
|
||||||
"goerli-testnet-toggle-confirmation": "Are you sure you want to toggle Goerli? This will log you out and you will have to login again.",
|
"goerli-testnet-toggle-confirmation": "Are you sure you want to toggle Goerli? This will log you out and you will have to login again.",
|
||||||
"bridged-to": "Bridged to {{network}}",
|
"bridged-to": "Bridged to {{network}}",
|
||||||
"slide-to-bridge": "Slide to bridge",
|
"slide-to-bridge": "Slide to bridge",
|
||||||
@ -2590,6 +2592,8 @@
|
|||||||
"other": "{{count}} addresses"
|
"other": "{{count}} addresses"
|
||||||
},
|
},
|
||||||
"max": "Max: {{number}}",
|
"max": "Max: {{number}}",
|
||||||
|
"your-key-pair-name-is-too-long": "Your key pair name is too long",
|
||||||
|
"your-key-pair-name-is-too-short": "Your key pair name is too short",
|
||||||
"key-name-error-length": "Key name too long",
|
"key-name-error-length": "Key name too long",
|
||||||
"key-name-error-emoji": "Emojis are not allowed",
|
"key-name-error-emoji": "Emojis are not allowed",
|
||||||
"key-name-error-special-char": "Special characters are not allowed",
|
"key-name-error-special-char": "Special characters are not allowed",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user