diff --git a/frontend/src/uxbox/main/data/users.cljs b/frontend/src/uxbox/main/data/users.cljs index f2840287a..a00abc355 100644 --- a/frontend/src/uxbox/main/data/users.cljs +++ b/frontend/src/uxbox/main/data/users.cljs @@ -7,11 +7,10 @@ (ns uxbox.main.data.users (:require [beicon.core :as rx] - [cljs.spec.alpha :as s] - [struct.core :as stt] + [struct.core :as s] [potok.core :as ptk] [uxbox.main.repo :as rp] - [uxbox.util.i18n :as i18n :refer (tr)] + [uxbox.util.i18n :as i18n :refer [tr]] [uxbox.util.messages :as uum] [uxbox.util.spec :as us] [uxbox.util.storage :refer [storage]])) @@ -59,15 +58,15 @@ ;; --- Update Profile -(stt/defs update-profile-spec - {:fullname [stt/required stt/string] - :email [stt/required stt/email] - :username [stt/required stt/string] - :language [stt/required stt/string]}) +(s/defs update-profile-spec + {:fullname [s/required s/string] + :email [s/required s/email] + :username [s/required s/string] + :language [s/required s/string]}) (defn update-profile [data {:keys [on-success on-error]}] - {:pre [(stt/valid? update-profile-spec data) + {:pre [(s/valid? update-profile-spec data) (fn? on-error) (fn? on-success)]} (reify @@ -90,33 +89,28 @@ ;; --- Update Password (Form) -(deftype UpdatePassword [data on-success on-error] - ptk/WatchEvent - (watch [_ state s] - (let [params {:old-password (:password-old data) - :password (:password-1 data)}] - (->> (rp/req :update/profile-password params) - (rx/catch rp/client-error? (fn [e] - (on-error (:payload e)) - (rx/empty))) - (rx/do on-success) - (rx/ignore))))) - -(s/def ::password-1 string?) -(s/def ::password-2 string?) -(s/def ::password-old string?) - -(s/def ::update-password - (s/keys :req-un [::password-1 - ::password-2 - ::password-old])) +(s/defs update-password-spec + {:password-1 [s/required s/string] + :password-2 [s/required s/string [s/identical-to :password-1]] + :password-old [s/required s/string]}) (defn update-password - [data & {:keys [on-success on-error]}] - {:pre [(us/valid? ::update-password data) + [data {:keys [on-success on-error]}] + {:pre [(s/valid? update-password-spec data) (fn? on-success) (fn? on-error)]} - (UpdatePassword. data on-success on-error)) + (reify + ptk/WatchEvent + (watch [_ state s] + (let [params {:old-password (:password-old data) + :password (:password-1 data)}] + (->> (rp/req :update/profile-password params) + (rx/catch rp/client-error? (fn [e] + (on-error (:payload e)) + (rx/empty))) + (rx/do on-success) + (rx/ignore)))))) + ;; --- Update Photo diff --git a/frontend/src/uxbox/main/ui/auth/login.cljs b/frontend/src/uxbox/main/ui/auth/login.cljs index 039bf2044..f2dfefafb 100644 --- a/frontend/src/uxbox/main/ui/auth/login.cljs +++ b/frontend/src/uxbox/main/ui/auth/login.cljs @@ -8,6 +8,7 @@ (ns uxbox.main.ui.auth.login (:require [rumext.alpha :as mf] + [struct.core :as s] [uxbox.builtins.icons :as i] [uxbox.config :as cfg] [uxbox.main.data.auth :as da] @@ -18,9 +19,9 @@ [uxbox.util.i18n :refer [tr]] [uxbox.util.router :as rt])) -(def login-form-spec - {:username [fm/required fm/string fm/non-empty-string] - :password [fm/required fm/string fm/non-empty-string]}) +(s/defs login-form-spec + {:username [fm/required fm/string] + :password [fm/required fm/string]}) (defn- on-submit [event form] @@ -41,8 +42,7 @@ (mf/defc login-form [] - (let [{:keys [data] :as form} (fm/use-form {:initial {} - :spec login-form-spec})] + (let [{:keys [data] :as form} (fm/use-form {:initial {} :spec login-form-spec})] [:form {:on-submit #(on-submit % form)} [:div.login-content (when cfg/isdemo @@ -52,25 +52,26 @@ {:name "username" :tab-index "2" :value (:username data "") - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :on-blur (fm/on-input-blur form :username) + :on-change (fm/on-input-change form :username) :placeholder (tr "auth.email-or-username") :type "text"}] [:input.input-text {:name "password" :tab-index "3" :value (:password data "") - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :on-blur (fm/on-input-blur form :password) + :on-change (fm/on-input-change form :password) :placeholder (tr "auth.password") :type "password"}] [:input.btn-primary {:name "login" :tab-index "4" - :class (when (:errors form) "btn-disabled") - :disabled (boolean (:errors form)) + :class (when-not (:valid form) "btn-disabled") + :disabled (not (:valid form)) :value (tr "auth.signin") :type "submit"}] + [:div.login-links [:a {:on-click #(st/emit! (rt/nav :auth/recovery-request)) :tab-index "5"} diff --git a/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs index 67e0d1b77..944bb8f18 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs @@ -18,7 +18,7 @@ [uxbox.util.i18n :as t :refer [tr]])) (def project-form-spec - {:name [fm/required fm/string fm/non-empty-string] + {:name [fm/required fm/string] :width [fm/required fm/number-str] :height [fm/required fm/number-str]}) @@ -51,8 +51,8 @@ :type "text" :name "name" :value (:name data) - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :on-blur (fm/on-input-blur form :name) + :on-change (fm/on-input-change form :name) :auto-focus true}] [:div.project-size [:div.input-element.pixels @@ -63,8 +63,8 @@ :type "number" :min 0 :max 5000 - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :on-blur (fm/on-input-blur form :width) + :on-change (fm/on-input-change form :width) :value (:width data)}]] [:a.toggle-layout {:on-click #(swap-size % form)} i/toggle] [:div.input-element.pixels @@ -75,8 +75,8 @@ :name "height" :min 0 :max 5000 - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :on-blur (fm/on-input-blur form :height) + :on-change (fm/on-input-change form :height) :value (:height data)}]]] ;; Submit diff --git a/frontend/src/uxbox/main/ui/settings/password.cljs b/frontend/src/uxbox/main/ui/settings/password.cljs index f6a014435..090974eba 100644 --- a/frontend/src/uxbox/main/ui/settings/password.cljs +++ b/frontend/src/uxbox/main/ui/settings/password.cljs @@ -2,15 +2,13 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) 2016-2017 Andrey Antukh -;; Copyright (c) 2016-2017 Juan de la Cruz +;; Copyright (c) 2016-2019 Andrey Antukh +;; Copyright (c) 2016-2019 Juan de la Cruz (ns uxbox.main.ui.settings.password (:require - [cuerdas.core :as str] - [lentes.core :as l] [rumext.alpha :as mf] - [struct.core :as stt] + [struct.core :as s] [uxbox.builtins.icons :as i] [uxbox.main.data.users :as udu] [uxbox.main.store :as st] @@ -19,79 +17,65 @@ [uxbox.util.i18n :refer [tr]] [uxbox.util.messages :as um])) -(stt/defs password-form-spec - {:password-1 [stt/required stt/string] - :password-2 [stt/required stt/string] - :password-old [stt/required stt/string]}) - (defn- on-submit [event form] - (dom/prevent-default event) - (prn "on-submit" form) - #_(let [data (:clean-data form) - opts {:on-success #(prn "On Success" %) - :on-error #(on-error % form)}] - (st/emit! (udu/update-profile data opts)))) + (letfn [(on-error [error] + (case (:code error) + :uxbox.services.users/old-password-not-match + (swap! form assoc-in [:errors :password-old] + {:type ::api :message "settings.password.wrong-old-password"}) + :else (throw (ex-info "unexpected" {:error error})))) + (on-success [_] + (st/emit! (um/info (tr "settings.password.password-saved"))))] + (dom/prevent-default event) + (let [data (:clean-data form) + opts {:on-success on-success + :on-error on-error}] + (st/emit! (udu/update-password data opts))))) - ;; #_(let [data (mx/deref form-data) - ;; errors (mx/react form-errors) - ;; valid? (fm/valid? ::password-form data)] - ;; (letfn [(on-change [field event] - ;; (let [value (dom/event->value event)] - ;; (st/emit! (assoc-value field value)))) - ;; (on-success [] - ;; (st/emit! (um/info (tr "settings.password.password-saved")))) - ;; (on-error [{:keys [code] :as payload}] - ;; (case code - ;; :uxbox.services.users/old-password-not-match - ;; (st/emit! (assoc-error :password-old (tr "settings.password.wrong-old-password"))) - - ;; :else - ;; (throw (ex-info "unexpected" {:error payload})))) - ;; (on-submit [event] - ;; (st/emit! (udu/update-password data - ;; :on-success on-success - ;; :on-error on-error)))] - - +(s/defs password-form-spec + {:password-1 [s/required s/string] + :password-2 [s/required s/string [s/identical-to :password-1]] + :password-old [s/required s/string]}) (mf/defc password-form [props] (let [{:keys [data] :as form} (fm/use-form {:initial {} :spec password-form-spec})] - (prn "password-form" form) [:form.password-form {:on-submit #(on-submit % form)} [:span.user-settings-label (tr "settings.password.change-password")] [:input.input-text {:type "password" :name "password-old" - :class (fm/error-class form :password-old) :value (:password-old data "") - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :class (fm/error-class form :password-old) + :on-blur (fm/on-input-blur form :password-old) + :on-change (fm/on-input-change form :password-old) :placeholder (tr "settings.password.old-password")}] - [:& fm/error-input {:form form :field :password-old}] + [:& fm/field-error {:form form :field :password-old}] [:input.input-text {:type "password" :name "password-1" - :class (fm/error-class form :password-1) :value (:password-1 data "") - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :class (fm/error-class form :password-1) + :on-blur (fm/on-input-blur form :password-1) + :on-change (fm/on-input-change form :password-1) :placeholder (tr "settings.password.new-password")}] - [:& fm/error-input {:form form :field :password-1}] + [:& fm/field-error {:form form :field :password-1}] + [:input.input-text {:type "password" :name "password-2" - :class (fm/error-class form :password-2) :value (:password-2 data "") - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :class (fm/error-class form :password-2) + :on-blur (fm/on-input-blur form :password-2) + :on-change (fm/on-input-change form :password-2) :placeholder (tr "settings.password.confirm-password")}] - [:& fm/error-input {:form form :field :password-2}] + [:& fm/field-error {:form form :field :password-2}] + [:input.btn-primary {:type "submit" :class (when-not (:valid form) "btn-disabled") diff --git a/frontend/src/uxbox/main/ui/settings/profile.cljs b/frontend/src/uxbox/main/ui/settings/profile.cljs index 06f83f14b..ed336db08 100644 --- a/frontend/src/uxbox/main/ui/settings/profile.cljs +++ b/frontend/src/uxbox/main/ui/settings/profile.cljs @@ -7,15 +7,15 @@ (ns uxbox.main.ui.settings.profile (:require - [cljs.spec.alpha :as s] [cuerdas.core :as str] [lentes.core :as l] [rumext.alpha :as mf] + [struct.core :as s] [uxbox.builtins.icons :as i] [uxbox.main.data.users :as udu] [uxbox.main.store :as st] - [uxbox.util.dom :as dom] [uxbox.util.data :refer [read-string]] + [uxbox.util.dom :as dom] [uxbox.util.forms :as fm] [uxbox.util.i18n :as i18n :refer [tr]] [uxbox.util.interop :refer [iterable->seq]])) @@ -30,23 +30,25 @@ (-> (l/key :profile) (l/derive st/state))) -(def profile-form-spec - {:fullname [fm/required fm/string fm/non-empty-string] - :username [fm/required fm/string fm/non-empty-string] +(s/defs profile-form-spec + {:fullname [fm/required fm/string] + :username [fm/required fm/string] :email [fm/required fm/email] :language [fm/required fm/string]}) (defn- on-error - [error {:keys [errors] :as form}] + [error form] (prn "on-error" error form) (case (:code error) :uxbox.services.users/email-already-exists (swap! form assoc-in [:errors :email] - {:message "errors.api.form.email-already-exists"}) + {:type ::api + :message "errors.api.form.email-already-exists"}) :uxbox.services.users/username-already-exists (swap! form assoc-in [:errors :username] - {:message "errors.api.form.username-already-exists"}))) + {:type ::api + :message "errors.api.form.username-already-exists"}))) (defn- initial-data [] @@ -66,39 +68,53 @@ [props] (let [{:keys [data] :as form} (fm/use-form {:initial initial-data :spec profile-form-spec})] + (prn "profile-form" form) [:form.profile-form {:on-submit #(on-submit % form)} [:span.user-settings-label (tr "settings.profile.section-basic-data")] [:input.input-text {:type "text" :name "fullname" - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :class (fm/error-class form :fullname) + :on-blur (fm/on-input-blur form :fullname) + :on-change (fm/on-input-change form :fullname) :value (:fullname data "") :placeholder (tr "settings.profile.your-name")}] - [:& fm/error-input {:form form :field :fullname}] + + [:& fm/field-error {:form form + :type #{::api} + :field :fullname}] [:input.input-text {:type "text" :name "username" - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :class (fm/error-class form :username) + :on-blur (fm/on-input-blur form :username) + :on-change (fm/on-input-change form :username) :value (:username data "") :placeholder (tr "settings.profile.your-username")}] - [:& fm/error-input {:form form :field :username}] + + [:& fm/field-error {:form form + :type #{::api} + :field :username}] [:input.input-text {:type "email" :name "email" - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :class (fm/error-class form :email) + :on-blur (fm/on-input-blur form :email) + :on-change (fm/on-input-change form :email) :value (:email data "") :placeholder (tr "settings.profile.your-email")}] - [:& fm/error-input {:form form :field :email}] + + [:& fm/field-error {:form form + :type #{::api} + :field :email}] [:span.user-settings-label (tr "settings.profile.section-i18n-data")] [:select.input-select {:value (:language data) :name "language" - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form)} + :class (fm/error-class form :language) + :on-blur (fm/on-input-blur form :language) + :on-change (fm/on-input-change form :language)} [:option {:value "en"} "English"] [:option {:value "fr"} "Français"]] diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs index d01c3b0b3..408c58b44 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs @@ -20,7 +20,7 @@ (def page-form-spec {:id [fm/uuid] :project [fm/uuid] - :name [fm/required fm/string fm/non-empty-string] + :name [fm/required fm/string] :width [fm/required fm/number-str] :height [fm/required fm/number-str]}) @@ -59,8 +59,8 @@ {:placeholder "Page name" :type "text" :name "name" - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :on-blur (fm/on-input-blur form :name) + :on-change (fm/on-input-change form :name) :value (:name data) :auto-focus true}] [:div.project-size @@ -72,8 +72,8 @@ :type "number" :min 0 :max 5000 - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :on-blur (fm/on-input-blur form :width) + :on-change (fm/on-input-change form :width) :value (:width data)}]] [:a.toggle-layout {:on-click #(swap-size % form)} i/toggle] [:div.input-element.pixels @@ -84,8 +84,8 @@ :type "number" :min 0 :max 5000 - :on-blur (fm/on-input-blur form) - :on-change (fm/on-input-change form) + :on-blur (fm/on-input-blur form :height) + :on-change (fm/on-input-change form :height) :value (:height data)}]]] [:input.btn-primary {:value "Go go go!" diff --git a/frontend/src/uxbox/util/forms.cljs b/frontend/src/uxbox/util/forms.cljs index b244ef450..90d9a4d73 100644 --- a/frontend/src/uxbox/util/forms.cljs +++ b/frontend/src/uxbox/util/forms.cljs @@ -14,7 +14,7 @@ [potok.core :as ptk] [rumext.alpha :as mf] [rumext.core :as mx] - [struct.core :as stt] + [struct.core :as st] [uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]])) @@ -22,11 +22,11 @@ (defn validate [data spec] - (stt/validate data spec)) + (st/validate data spec)) (defn valid? [data spec] - (stt/valid? data spec)) + (st/valid? data spec)) ;; --- Handlers Helpers @@ -45,17 +45,16 @@ ([self f x y more] (update-fn #(apply f % x y more)))))) (defn- translate-error-type - [type] - (case type - ::stt/string "errors.should-be-string" - ::stt/number "errors.should-be-number" - ::stt/number-str "errors.should-be-number" - ::stt/integer "errors.should-be-integer" - ::stt/integer-str "errors.should-be-integer" - ::stt/required "errors.required" - ::stt/email "errors.should-be-valid-email" - ::stt/uuid "errors.should-be-uuid" - ::stt/uuid-str "errors.should-be-valid-uuid" + [code] + (case code + ::st/string "errors.form.string" + ::st/number "errors.form.number" + ::st/number-str "errors.form.number" + ::st/integer "errors.form.integer" + ::st/integer-str "errors.form.integer" + ::st/required "errors.form.required" + ::st/email "errors.form.email" + ::st/identical-to "errors.form.does-not-match" "errors.undefined-error")) (defn- translate-errors @@ -63,7 +62,7 @@ (reduce-kv (fn [acc key val] (if (string? (:message val)) (assoc acc key val) - (->> (translate-error-type (:type val)) + (->> (translate-error-type (:code val)) (assoc val :message) (assoc acc key)))) {} errors)) @@ -73,8 +72,8 @@ (let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial) :errors {} :touched {}}) - [errors' clean-data] (validate spec (:data state)) - errors (merge (translate-errors errors') + [errors clean-data] (validate spec (:data state)) + errors (merge (translate-errors errors) (:errors state))] (-> (assoc state :errors errors @@ -83,10 +82,9 @@ (impl-mutator update-state)))) (defn on-input-change - [{:keys [data] :as form}] + [{:keys [data] :as form} field] (fn [event] (let [target (dom/get-target event) - field (keyword (.-name target)) value (dom/get-value target)] (swap! form (fn [state] (-> state @@ -94,22 +92,28 @@ (update :errors dissoc field))))))) (defn on-input-blur - [{:keys [touched] :as form}] + [{:keys [touched] :as form} field] (fn [event] - (let [target (dom/get-target event) - field (keyword (.-name target))] + (let [target (dom/get-target event)] (when-not (get touched field) (swap! form assoc-in [:touched field] true))))) ;; --- Helper Components -(mf/defc error-input - [{:keys [form field] :as props}] +(mf/defc field-error + [{:keys [form field type] + :or {only (constantly true)} + :as props}] (let [touched? (get-in form [:touched field]) - error (get-in form [:errors field])] - (when (and touched? error) + {:keys [message code] :as error} (get-in form [:errors field])] + (when (and touched? error + (cond + (nil? type) true + (ifn? type) (type (:type error)) + (keyword? type) (= (:type error) type) + :else false)) [:ul.form-errors - [:li {:key (:type error)} (tr (:message error))]]))) + [:li {:key code} (tr message)]]))) (defn error-class [form field] @@ -119,50 +123,20 @@ ;; --- Additional Validators -(def non-empty-string - {:message "errors.empty-string" - :optional true - :validate #(not (str/empty? %))}) - -(def string (assoc stt/string :message "errors.should-be-string")) -(def number (assoc stt/number :message "errors.should-be-number")) -(def number-str (assoc stt/number-str :message "errors.should-be-number")) -(def integer (assoc stt/integer :message "errors.should-be-integer")) -(def integer-str (assoc stt/integer-str :message "errors.should-be-integer")) -(def required (assoc stt/required :message "errors.required")) -(def email (assoc stt/email :message "errors.should-be-valid-email")) -(def uuid (assoc stt/uuid :message "errors.should-be-uuid")) -(def uuid-str (assoc stt/uuid-str :message "errors.should-be-valid-uuid")) +(def string (assoc st/string :message "errors.should-be-string")) +(def number (assoc st/number :message "errors.should-be-number")) +(def number-str (assoc st/number-str :message "errors.should-be-number")) +(def integer (assoc st/integer :message "errors.should-be-integer")) +(def integer-str (assoc st/integer-str :message "errors.should-be-integer")) +(def required (assoc st/required :message "errors.required")) +(def email (assoc st/email :message "errors.should-be-valid-email")) +(def uuid (assoc st/uuid :message "errors.should-be-uuid")) +(def uuid-str (assoc st/uuid-str :message "errors.should-be-valid-uuid")) ;; DEPRECATED ;; --- Form Validation Api -(defn- interpret-problem - [acc {:keys [path pred val via in] :as problem}] - (cond - (and (empty? path) - (= (first pred) 'contains?)) - (let [path (conj path (last pred))] - (update-in acc path assoc :missing)) - - (and (seq path) - (= 1 (count path))) - (update-in acc path assoc :invalid) - - :else acc)) - -;; (defn validate -;; [spec data] -;; (when-not (s/valid? spec data) -;; (let [report (s/explain-data spec data)] -;; (reduce interpret-problem {} (::s/problems report))))) - -;; (defn valid? -;; [spec data] -;; (s/valid? spec data)) - - ;; --- Form Specs and Conformers (def ^:private email-re