diff --git a/project.clj b/project.clj index 28cc3c4415..6151364f57 100644 --- a/project.clj +++ b/project.clj @@ -20,13 +20,13 @@ [cljsjs/react "15.0.0-rc.2-0"] [cljsjs/react-dom "15.0.0-rc.2-0"] [cljsjs/moment "2.10.6-3"] + [funcool/struct "0.1.0-SNAPSHOT"] [funcool/lentes "1.0.1"] [funcool/httpurr "0.6.0-SNAPSHOT"] [funcool/promesa "1.1.1"] [funcool/beicon "1.1.1"] [funcool/cuerdas "0.7.1"] [funcool/hodgepodge "0.1.4"] - [bouncer "1.0.0"] [bidi "2.0.4"]] :plugins [[lein-ancient "0.6.7"]] :clean-targets ^{:protect false} ["resources/public/js" "target"] diff --git a/src/uxbox/data/auth.cljs b/src/uxbox/data/auth.cljs index 9b70b6437d..664b275d13 100644 --- a/src/uxbox/data/auth.cljs +++ b/src/uxbox/data/auth.cljs @@ -64,13 +64,8 @@ (dp/fetch-projects) (udu/fetch-profile)))))))) -(def ^:const ^:private +login-schema+ - {:username [sc/required sc/string] - :password [sc/required sc/string]}) - (defn login [params] - (sc/validate! +login-schema+ params) (map->Login params)) ;; --- Logout diff --git a/src/uxbox/data/dashboard.cljs b/src/uxbox/data/dashboard.cljs index 72acd89a5c..4a0e888726 100644 --- a/src/uxbox/data/dashboard.cljs +++ b/src/uxbox/data/dashboard.cljs @@ -10,25 +10,9 @@ [uxbox.router :as r] [uxbox.state :as st] [uxbox.schema :as sc] - [uxbox.repo :as rp] - [bouncer.validators :as v])) + [uxbox.repo :as rp])) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Schemas -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(def ^:static +color-replace-schema+ - {:id [v/required sc/uuid] - :from [sc/color] - :to [v/required sc/color]}) - -(def ^:static +remove-color-schema+ - {:id [v/required sc/uuid] - :color [v/required sc/color]}) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Helpers -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; --- Helpers (defn assoc-page "A reduce function for assoc the page @@ -37,9 +21,7 @@ (let [uuid (:id page)] (update-in state [:pages-by-id] assoc uuid page))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Events -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; --- Events (defn merge-if-not-exists [map & maps] @@ -140,7 +122,6 @@ (defn replace-color "Add or replace color in a collection." [{:keys [id from to] :as params}] - (sc/validate! +color-replace-schema+ params) (reify rs/UpdateEvent (-apply-update [_ state] @@ -154,7 +135,6 @@ (defn remove-color "Remove color in a collection." [{:keys [id color] :as params}] - (sc/validate! +remove-color-schema+ params) (reify rs/UpdateEvent (-apply-update [_ state] diff --git a/src/uxbox/data/forms.cljs b/src/uxbox/data/forms.cljs new file mode 100644 index 0000000000..cc7c6b8a91 --- /dev/null +++ b/src/uxbox/data/forms.cljs @@ -0,0 +1,40 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; 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 Andrey Antukh + +(ns uxbox.data.forms + (:require [beicon.core :as rx] + [promesa.core :as p] + [uxbox.repo :as rp] + [uxbox.rstore :as rs] + [uxbox.state :as st] + [uxbox.schema :as sc] + [uxbox.locales :refer (tr)] + [uxbox.ui.messages :as uum])) + +;; --- Assign Errors + +(defrecord AssignErrors [type errors] + rs/UpdateEvent + (-apply-update [_ state] + (assoc-in state [:errors type] errors))) + +(defn assign-errors + ([type] (assign-errors type nil)) + ([type errors] + (AssignErrors. type errors))) + +;; --- Assign Field Value + +(defrecord AssignFieldValue [type field value] + rs/UpdateEvent + (-apply-update [_ state] + (assoc-in state [:forms type field] value))) + +(defn assign-field-value + [type field value] + (AssignFieldValue. type field value)) + + diff --git a/src/uxbox/data/pages.cljs b/src/uxbox/data/pages.cljs index 6495445526..acae8ebdb5 100644 --- a/src/uxbox/data/pages.cljs +++ b/src/uxbox/data/pages.cljs @@ -74,7 +74,7 @@ (rx/mapcat on-created) (rx/catch on-failed)))))) -(def ^:static +create-page-schema+ +(def ^:private create-page-schema {:name [sc/required sc/string] :layout [sc/required sc/string] :width [sc/required sc/integer] @@ -83,8 +83,8 @@ (defn create-page [data] - (sc/validate! +create-page-schema+ data) - (map->CreatePage data)) + (-> (sc/validate! data create-page-schema) + (map->CreatePage))) ;; --- Sync Page @@ -171,16 +171,20 @@ (rx/map on-success) (rx/catch on-failure))))) -(def ^:const +update-page-schema+ - {:name [sc/required sc/string] +(def ^:private update-page-schema + {:id [sc/required] + :project [sc/required] + :version [sc/required] + :name [sc/required sc/string] :width [sc/required sc/integer] :height [sc/required sc/integer] :layout [sc/required sc/string]}) (defn update-page-metadata [data] - (sc/validate! +update-page-schema+ data) - (map->UpdatePageMetadata (dissoc data :data))) + (-> (sc/validate! data update-page-schema {:strip false}) + (dissoc data :data) + (map->UpdatePageMetadata))) ;; --- Delete Page (by id) diff --git a/src/uxbox/data/projects.cljs b/src/uxbox/data/projects.cljs index b1e9e0dd2d..38d9112149 100644 --- a/src/uxbox/data/projects.cljs +++ b/src/uxbox/data/projects.cljs @@ -70,33 +70,48 @@ [] (FetchProjects.)) +;; --- Project Created + +(defrecord ProjectCreated [project] + rs/UpdateEvent + (-apply-update [_ state] + (stpr/assoc-project state project))) + +(defn project-created + [data] + (ProjectCreated. data)) + ;; --- Create Project (defrecord CreateProject [name width height layout] rs/WatchEvent (-apply-watch [this state s] - (letfn [(on-success [project] - (rx/of (rs/swap #(stpr/assoc-project % project)) - (udp/create-page (assoc (into {} this) - :project (:id project) - :name "Page 1" - :data nil)))) + (letfn [(on-success [{project :payload}] + (rx/of + (project-created project) + (udp/create-page {:width width + :height height + :layout layout + :project (:id project) + :name "Page 1" + :data nil}))) (on-failure [err] - (uum/error (tr "errors.create-project")))] + (uum/error (tr "errors.create-project")) + (rx/empty))] (->> (rp/req :create/project {:name name}) - (rx/mapcat on-success) - (rx/catch on-failure))))) + (rx/catch on-failure) + (rx/mapcat on-success))))) -(def ^:static +project-schema+ +(def ^:private create-project-schema {:name [sc/required sc/string] :width [sc/required sc/integer] :height [sc/required sc/integer] :layout [sc/required sc/string]}) (defn create-project - [{:keys [name width height layout] :as data}] - (sc/validate! +project-schema+ data) - (map->CreateProject data)) + [params] + (-> (sc/validate! params create-project-schema) + (map->CreateProject))) ;; --- Delete Project (by id) diff --git a/src/uxbox/data/shapes.cljs b/src/uxbox/data/shapes.cljs index d59429aa03..c7de4ab5e0 100644 --- a/src/uxbox/data/shapes.cljs +++ b/src/uxbox/data/shapes.cljs @@ -6,8 +6,7 @@ ;; Copyright (c) 2015-2016 Juan de la Cruz (ns uxbox.data.shapes - (:require [bouncer.validators :as v] - [beicon.core :as rx] + (:require [beicon.core :as rx] [uxbox.shapes :as sh] [uxbox.rstore :as rs] [uxbox.router :as r] @@ -20,63 +19,9 @@ [uxbox.util.geom.point :as gpt] [uxbox.util.data :refer (index-of)])) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Schemas -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(def ^:static +shape-schema+ - {:x [sc/integer] - :y [sc/integer] - :width [sc/integer] - :height [sc/integer] - :type [sc/required sc/shape-type]}) - -(def ^:static +shape-size-schema+ - {:width [sc/integer] - :height [sc/integer] - :lock [sc/boolean]}) - -(def ^:static +shape-fill-attrs-schema+ - {:color [sc/color] - :opacity [sc/number]}) - -(def ^:static +shape-stroke-attrs-schema+ - {:color [sc/color] - :width [sc/integer] - :type [sc/keyword] - :opacity [sc/number]}) - -(def ^:static +shape-line-attrs-schema+ - {:x1 [sc/integer] - :y1 [sc/integer] - :x2 [sc/integer] - :y2 [sc/integer]}) - -(def ^:static +shape-font-attrs-schema+ - {:family [sc/string] - :style [sc/string] - :weight [sc/string] - :align [sc/string] - :size [sc/number] - :letter-spacing [sc/number] - :line-height [sc/number]}) - -(def ^:static +shape-radius-attrs-schema+ - {:rx [sc/integer] - :ry [sc/integer]}) - -(def ^:static +shape-position-schema+ - {:x [sc/integer] - :y [sc/integer]}) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Events (explicit) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (defn add-shape "Create and add shape to the current selected page." [shape] - (sc/validate! +shape-schema+ shape) (reify udp/IPageUpdate rs/UpdateEvent @@ -114,7 +59,6 @@ (defn update-line-attrs [sid {:keys [x1 y1 x2 y2] :as opts}] - (sc/validate! +shape-line-attrs-schema+ opts) (reify udp/IPageUpdate rs/UpdateEvent @@ -145,7 +89,6 @@ WARN: only works with shapes that works with height and width such are ::rect" [sid {:keys [width height] :as opts}] - (sc/validate! +shape-size-schema+ opts) (reify udp/IPageUpdate rs/UpdateEvent @@ -165,7 +108,6 @@ (defn update-position "Update the start position coordenate of the shape." [sid {:keys [x y] :as opts}] - (sc/validate! +shape-position-schema+ opts) (reify rs/UpdateEvent (-apply-update [_ state] @@ -183,7 +125,6 @@ (defn update-fill-attrs [sid {:keys [color opacity] :as opts}] - (sc/validate! +shape-fill-attrs-schema+ opts) (reify udp/IPageUpdate rs/UpdateEvent @@ -196,7 +137,6 @@ (defn update-font-attrs [sid {:keys [family style weight size align letter-spacing line-height] :as opts}] - (sc/validate! +shape-font-attrs-schema+ opts) (reify udp/IPageUpdate rs/UpdateEvent @@ -213,7 +153,6 @@ (defn update-stroke-attrs [sid {:keys [color opacity type width] :as opts}] - (sc/validate! +shape-stroke-attrs-schema+ opts) (reify udp/IPageUpdate rs/UpdateEvent @@ -227,7 +166,6 @@ (defn update-radius-attrs [sid {:keys [rx ry] :as opts}] - (sc/validate! +shape-radius-attrs-schema+ opts) (reify udp/IPageUpdate rs/UpdateEvent @@ -464,7 +402,6 @@ "Update the fill related attributed on selected shapes." [opts] - (sc/validate! +shape-fill-attrs-schema+ opts) (reify rs/WatchEvent (-apply-watch [_ state s] @@ -477,7 +414,6 @@ "Update the fill related attributed on selected shapes." [opts] - (sc/validate! +shape-stroke-attrs-schema+ opts) (reify rs/WatchEvent (-apply-watch [_ state s] diff --git a/src/uxbox/data/users.cljs b/src/uxbox/data/users.cljs index 237610b599..67f2a94369 100644 --- a/src/uxbox/data/users.cljs +++ b/src/uxbox/data/users.cljs @@ -12,6 +12,7 @@ [uxbox.state :as st] [uxbox.schema :as sc] [uxbox.locales :refer (tr)] + [uxbox.data.forms :as forms] [uxbox.ui.messages :as uum])) ;; --- Profile Fetched @@ -59,17 +60,44 @@ [data] (UpdateProfile. data)) +;; --- Password Updated + +(defrecord PasswordUpdated [] + rs/UpdateEvent + (-apply-update [_ state] + (assoc-in state [:forms :profile/password] {})) + + rs/EffectEvent + (-apply-effect [_ state] + (uum/info (tr "profile.password-saved")))) + ;; --- Update Password -(defrecord UpdatePassword [old-password password] +(defrecord UpdatePassword [data] rs/WatchEvent (-apply-watch [_ state s] - (letfn [(on-error [err] - (uum/error (tr "errors.update-password")) - (rx/empty))] - (->> (rp/req :update/password {:old-password old-password :password password}) - (rx/catch on-error))))) + (letfn [(on-error [{payload :payload :as data}] + (if (= (:type payload) :form/validation) + (rx/of + (forms/assign-errors :profile/password (:payload payload))) + (do + (uum/error (tr "errors.profile.update-password") {:timeout 3000}) + (rx/empty))))] + (let [params {:old-password (:old-password data) + :password (:password-1 data)}] + (->> (rp/req :update/password params) + (rx/map #(->PasswordUpdated)) + (rx/catch on-error)))))) + +(def update-password-schema + [[:password-1 sc/required sc/string [sc/min-len 6]] + [:password-2 sc/required sc/string + [sc/identical-to :password-1 :message "errors.form.password-not-match"]] + [:old-password sc/required sc/string]]) (defn update-password - [old-password password] - (UpdatePassword. old-password password)) + [data] + (let [[errors data] (sc/validate data update-password-schema)] + (if errors + (forms/assign-errors :profile/password errors) + (UpdatePassword. data)))) diff --git a/src/uxbox/data/workspace.cljs b/src/uxbox/data/workspace.cljs index b4ed057205..bfcce6a654 100644 --- a/src/uxbox/data/workspace.cljs +++ b/src/uxbox/data/workspace.cljs @@ -6,8 +6,7 @@ ;; Copyright (c) 2015-2016 Juan de la Cruz (ns uxbox.data.workspace - (:require [bouncer.validators :as v] - [beicon.core :as rx] + (:require [beicon.core :as rx] [uxbox.constants :as c] [uxbox.shapes :as sh] [uxbox.rstore :as rs] diff --git a/src/uxbox/locales.cljs b/src/uxbox/locales.cljs index 90658ab2a8..630b624ae3 100644 --- a/src/uxbox/locales.cljs +++ b/src/uxbox/locales.cljs @@ -17,6 +17,9 @@ (defonce +locale+ (get local-storage ::locale :en)) +;; A marker type that is used just for mark +;; a parameter that reprsentes the counter. + (deftype C [val] IDeref (-deref [o] val)) @@ -29,6 +32,8 @@ [r] (instance? C r)) +;; A main public api for translate strings. + (defn tr "Translate the string." ([t] diff --git a/src/uxbox/locales/en.cljs b/src/uxbox/locales/en.cljs index ce2f1e2349..7f4bf4d949 100644 --- a/src/uxbox/locales/en.cljs +++ b/src/uxbox/locales/en.cljs @@ -37,8 +37,22 @@ "ds.help.circle" "Circle (Ctrl + E)" "ds.help.line" "Line (Ctrl + L)" + "profile.password-saved" "Password saved successfully!" + "history.alert-message" "You are seeng version %s" - "errors.auth" "Username or passwords seems to be wrong." + "errors.api.form.old-password-not-match" "Incorrect old password" + "errors.form.required" "This field is mandatory" + "errors.form.string" "Should be string" + "errors.form.number" "Invalid number" + "errors.form.integer" "Invalid integer" + "errors.form.bool" "Should be bool" + "errors.form.min-len" "Should be great than %s" + "errors.form.max-len" "Should be less than %s" + "errors.form.color" "Should be a valid color string" + "errors.form.password-not-match" "Password does not match" + + "errors.auth" "Username or passwords seems to be wrong." + "errors.profile.update-password" "Error updating password, probably your old password is wrong." }) diff --git a/src/uxbox/schema.cljs b/src/uxbox/schema.cljs index 14c7da155c..1aba210cfa 100644 --- a/src/uxbox/schema.cljs +++ b/src/uxbox/schema.cljs @@ -6,89 +6,87 @@ ;; Copyright (c) 2015-2016 Juan de la Cruz (ns uxbox.schema - (:refer-clojure :exclude [keyword uuid vector boolean]) - (:require [bouncer.core :as b] - [bouncer.validators :as v] - [cuerdas.core :as str] + (:refer-clojure :exclude [keyword uuid vector boolean map set]) + (:require [struct.core :as st] + [uxbox.locales :refer (tr)] [uxbox.shapes :refer (shape?)])) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Validators -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; (def datetime +;; {:message "must be an instant" +;; :optional true +;; :validate #(instance? Instant %)}) -(v/defvalidator keyword - "Validates maybe-an-int is a valid integer. - For use with validation functions such as `validate` or `valid?`" - {:default-message-format "%s must be a keyword"} - [v] - (cljs.core/keyword? v)) +(def required + (assoc st/required :message "errors.form.required")) -(v/defvalidator uuid - "Validates maybe-an-int is a valid integer. - For use with validation functions such as `validate` or `valid?`" - {:default-message-format "%s must be a uuid instance"} - [v] - (instance? cljs.core.UUID v)) +(def string + (assoc st/string :message "errors.form.string")) -(v/defvalidator color - "Validates if a string is a valid color." - {:default-message-format "%s must be a valid hex color"} - [v] - (not (nil? (re-find #"^#[0-9A-Fa-f]{6}$" v)))) +(def number + (assoc st/number :message "errors.form.number")) -(v/defvalidator shape-type - "Validates if a keyword is a shape type." - {:default-message-format "%s must be a shape type keyword."} - [v] - (shape? v)) +(def integer + (assoc st/integer :message "errors.form.integer")) -(v/defvalidator vector - "Validats if `v` is vector." - {:default-message-format "%s must be a vector instance."} - [v] - (vector? v)) +(def boolean + (assoc st/boolean :message "errors.form.bool")) -(v/defvalidator function - "Validats if `v` is function." - {:default-message-format "%s must be a function."} - [v] - (fn? v)) +(def identical-to + (assoc st/identical-to :message "errors.form.identical-to")) -(def ^:const +email-re+ - #"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") +;; (def in-range st/in-range) +;; (def uuid-like st/uuid-like) +(def uuid st/uuid) +(def keyword st/keyword) +;; (def integer-like st/integer-like) +;; (def boolean-like st/boolean-like) +;; (def email st/email) +;; (def function st/function) +;; (def positive st/positive) +;; (def validate st/validate) +;; (def validate! st/validate!) -(v/defvalidator email - "Validate if `v` is a valid email." - {:default-message-format "% must be a valid email."} - [v] - (clojure.core/boolean (re-seq +email-re+ v))) +(def max-len + {:message "errors.form.max-len" + :optional true + :validate (fn [v n] + (let [len (count v)] + (>= len v)))}) -(def required v/required) -(def number v/number) -(def integer v/integer) -(def boolean v/boolean) -(def string v/string) +(def min-len + {:message "errors.form.min-len" + :optional true + :validate (fn [v n] + (>= (count v) n))}) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Public Api -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(def color + {:message "errors.form.color" + :optional true + :validate #(not (nil? (re-find #"^#[0-9A-Fa-f]{6}$" %)))}) + +(def shape-type + {:message "should be shape" + :optional true + :validate #(shape? %)}) (defn validate - ([schema] #(validate schema %)) - ([schema data] (first (b/validate data schema)))) + ([data schema] + (validate data schema nil)) + ([data schema opts] + (let [opts (merge {:strip true} + opts)] + (st/validate data schema opts)))) (defn validate! - ([schema] #(validate! schema %)) - ([schema data] - (when-let [errors (validate schema data)] - (throw (ex-info "Invalid data" errors))))) + ([data schema] + (validate! data schema nil)) + ([data schema opts] + (let [[errors data] (validate data schema opts)] + (if errors + (throw (ex-info "Invalid data" errors)) + data)))) (defn valid? - [validator data] - (let [result (validator data)] - (if result - result - (let [message (:default-message-format (meta validator)) - message (str/format message data)] - (throw (ex-info message {})))))) - + [data schema] + (let [[errors data] (validate data schema)] + (not errors))) diff --git a/src/uxbox/ui/dashboard/colors.cljs b/src/uxbox/ui/dashboard/colors.cljs index 5cb798bcdd..35e9881d01 100644 --- a/src/uxbox/ui/dashboard/colors.cljs +++ b/src/uxbox/ui/dashboard/colors.cljs @@ -17,7 +17,7 @@ [uxbox.library :as library] [uxbox.data.dashboard :as dd] [uxbox.ui.icons :as i] - [uxbox.ui.form :as form] + [uxbox.ui.forms :as form] [uxbox.ui.lightbox :as lightbox] [uxbox.ui.colorpicker :refer (colorpicker)] [uxbox.ui.mixins :as mx] @@ -187,44 +187,43 @@ ;; --- Colors Create / Edit Lightbox -(def ^:const ^:private +color-form-schema+ - {:hex [sc/required sc/color]}) - (defn- color-lightbox-render [own {:keys [coll color]}] - (let [local (:rum/local own)] - (letfn [(submit [e] - (if-let [errors (sc/validate +color-form-schema+ @local)] - (swap! local assoc :errors errors) - (let [params {:id (:id coll) :from color - :to (:hex @local)}] - (rs/emit! (dd/replace-color params)) - (lightbox/close!)))) - (on-change [e] - (let [value (str/trim (dom/event->value e))] - (swap! local assoc :hex value)))] - (html - [:div.lightbox-body - [:h3 "New color"] - [:form - [:div.row-flex - [:input#color-hex.input-text - {:placeholder "#" - :class (form/error-class local :hex) - :on-change on-change - :value (or (:hex @local) color "") - :type "text"}]] - [:div.row-flex.center.color-picker-default - (colorpicker - :value (or (:hex @local) color "#00ccff") - :on-change #(swap! local assoc :hex %))] + (html + [:p "TODO"])) + ;; (let [local (:rum/local own)] + ;; (letfn [(submit [e] + ;; (if-let [errors (sc/validate +color-form-schema+ @local)] + ;; (swap! local assoc :errors errors) + ;; (let [params {:id (:id coll) :from color + ;; :to (:hex @local)}] + ;; (rs/emit! (dd/replace-color params)) + ;; (lightbox/close!)))) + ;; (on-change [e] + ;; (let [value (str/trim (dom/event->value e))] + ;; (swap! local assoc :hex value)))] + ;; (html + ;; [:div.lightbox-body + ;; [:h3 "New color"] + ;; [:form + ;; [:div.row-flex + ;; [:input#color-hex.input-text + ;; {:placeholder "#" + ;; :class (form/error-class local :hex) + ;; :on-change on-change + ;; :value (or (:hex @local) color "") + ;; :type "text"}]] + ;; [:div.row-flex.center.color-picker-default + ;; (colorpicker + ;; :value (or (:hex @local) color "#00ccff") + ;; :on-change #(swap! local assoc :hex %))] - [:input#project-btn.btn-primary - {:value "+ Add color" - :on-click submit - :type "button"}]] - [:a.close {:on-click #(lightbox/close!)} - i/close]])))) + ;; [:input#project-btn.btn-primary + ;; {:value "+ Add color" + ;; :on-click submit + ;; :type "button"}]] + ;; [:a.close {:on-click #(lightbox/close!)} + ;; i/close]])))) (def color-lightbox (mx/component diff --git a/src/uxbox/ui/dashboard/icons.cljs b/src/uxbox/ui/dashboard/icons.cljs index 8aec7ccc76..b397cef288 100644 --- a/src/uxbox/ui/dashboard/icons.cljs +++ b/src/uxbox/ui/dashboard/icons.cljs @@ -15,7 +15,6 @@ [uxbox.library :as library] [uxbox.data.dashboard :as dd] [uxbox.ui.icons :as i] - [uxbox.ui.form :as form] [uxbox.ui.shapes.core :as uusc] [uxbox.ui.lightbox :as lightbox] [uxbox.ui.mixins :as mx] diff --git a/src/uxbox/ui/form.cljs b/src/uxbox/ui/form.cljs deleted file mode 100644 index 6816007e0c..0000000000 --- a/src/uxbox/ui/form.cljs +++ /dev/null @@ -1,22 +0,0 @@ -(ns uxbox.ui.form - (:require [sablono.core :refer-macros [html]] - [uxbox.schema :as sc])) - -(defn validate! - [local schema] - (if-let [errors (sc/validate schema @local)] - (swap! local assoc :errors errors) - (swap! local assoc :errors nil))) - -(defn input-error - [local name] - (when-let [errors (get-in @local [:errors name])] - [:div.errors - [:ul {} - (for [error errors] - [:li error])]])) - -(defn error-class - [local name] - (when (get-in @local [:errors name]) - "invalid")) diff --git a/src/uxbox/ui/forms.cljs b/src/uxbox/ui/forms.cljs new file mode 100644 index 0000000000..a6ec2cffd2 --- /dev/null +++ b/src/uxbox/ui/forms.cljs @@ -0,0 +1,17 @@ +(ns uxbox.ui.forms + (:require [sablono.core :refer-macros [html]] + [uxbox.locales :refer (tr)] + [uxbox.schema :as sc])) + +(defn input-error + [errors field] + (when-let [errors (get errors field)] + (html + [:ul.form-errors + (for [error errors] + [:li {:key error} (tr error)])]))) + +(defn error-class + [errors field] + (when (get errors field) + "invalid")) diff --git a/src/uxbox/ui/messages.cljs b/src/uxbox/ui/messages.cljs index 98a59781a7..220cecd77f 100644 --- a/src/uxbox/ui/messages.cljs +++ b/src/uxbox/ui/messages.cljs @@ -54,6 +54,20 @@ :tsem tsem :content message})))) +(defn info + ([message] (info message nil)) + ([message {:keys [timeout] :or {timeout 6000}}] + (when-let [prev @+message+] + (clean-prev-msgstate! prev)) + (let [timeout' (+ timeout +animation-timeout+) + tsem-main (set-timeout! timeout' #(reset! +message+ nil)) + tsem (set-timeout! timeout #(swap! +message+ assoc :state :hide))] + (reset! +message+ {:type :notification/info + :state :normal + :tsem-main tsem-main + :tsem tsem + :content message})))) + (defn dialog [& {:keys [message on-accept on-cancel] :or {on-cancel (constantly nil)} diff --git a/src/uxbox/ui/settings/password.cljs b/src/uxbox/ui/settings/password.cljs index 363e9bf1e4..8fb06436c7 100644 --- a/src/uxbox/ui/settings/password.cljs +++ b/src/uxbox/ui/settings/password.cljs @@ -8,52 +8,69 @@ (ns uxbox.ui.settings.password (:require [sablono.core :as html :refer-macros [html]] [rum.core :as rum] + [lentes.core :as l] [cuerdas.core :as str] + [uxbox.schema :as sc] + [uxbox.state :as st] + [uxbox.locales :as t :refer (tr)] [uxbox.router :as r] [uxbox.rstore :as rs] - [uxbox.ui.icons :as i] - [uxbox.ui.mixins :as mx] - [uxbox.util.dom :as dom] [uxbox.data.users :as udu] - [uxbox.ui.dashboard.header :refer (header)])) + [uxbox.data.forms :as udf] + [uxbox.ui.icons :as i] + [uxbox.ui.forms :as forms] + [uxbox.ui.messages :as uum] + [uxbox.ui.mixins :as mx] + [uxbox.ui.dashboard.header :refer (header)] + [uxbox.util.dom :as dom])) ;; --- Password Form +(def formdata + (-> (l/in [:forms :profile/password]) + (l/focus-atom st/state))) + +(def formerrors + (-> (l/in [:errors :profile/password]) + (l/focus-atom st/state))) + +(def assign-field-value + (partial udf/assign-field-value :profile/password)) + (defn password-form-render [own] - (let [local (:rum/local own) - valid? (and (not (str/empty? (:password-1 @local))) - (not (str/empty? (:password-2 @local))) - (= 6 (count (:password-1 @local ""))) - (= (:password-1 @local) - (:password-2 @local)))] - (println "valid?" valid?) + (let [form (rum/react formdata) + errors (rum/react formerrors) + valid? (sc/valid? form udu/update-password-schema)] (letfn [(on-field-change [field event] (let [value (dom/event->value event)] - (swap! local assoc field value))) + (rs/emit! (assign-field-value field value)))) (on-submit [event] - (let [password (:password-1 @local) - old-password (:old-password @local)] - (rs/emit! (udu/update-password old-password password))))] - + (rs/emit! (udu/update-password form)))] (html [:form.password-form [:span.user-settings-label "Change password"] [:input.input-text {:type "password" - :value (:old-password @local "") + :class (forms/error-class errors :old-password) + :value (:old-password form "") :on-change (partial on-field-change :old-password) :placeholder "Old password"}] + (forms/input-error errors :old-password) [:input.input-text {:type "password" - :value (:password-1 @local "") + :class (forms/error-class errors :password-1) + :value (:password-1 form "") :on-change (partial on-field-change :password-1) :placeholder "New password"}] + (forms/input-error errors :password-1) [:input.input-text {:type "password" - :value (:password-2 @local "") + :class (forms/error-class errors :password-2) + :value (:password-2 form "") :on-change (partial on-field-change :password-2) :placeholder "Confirm password"}] + (forms/input-error errors :password-2) [:input.btn-primary {:type "button" :class (when-not valid? "btn-disabled") @@ -65,7 +82,7 @@ (mx/component {:render password-form-render :name "password-form" - :mixins [mx/static (mx/local)]})) + :mixins [mx/static (mx/local) rum/reactive]})) ;; --- Password Page @@ -74,6 +91,7 @@ (html [:main.dashboard-main (header) + (uum/messages) [:section.dashboard-content.user-settings [:div.user-settings-nav [:ul.user-settings-nav-inside