diff --git a/frontend/src/uxbox/main/data/auth.cljs b/frontend/src/uxbox/main/data/auth.cljs index eb05e6cbf..1450dd6b9 100644 --- a/frontend/src/uxbox/main/data/auth.cljs +++ b/frontend/src/uxbox/main/data/auth.cljs @@ -5,154 +5,144 @@ ;; Copyright (c) 2015-2016 Andrey Antukh (ns uxbox.main.data.auth - (:require [cljs.spec.alpha :as s] - [beicon.core :as rx] - [potok.core :as ptk] - [uxbox.main.store :as st] - [uxbox.main.repo :as rp] - [uxbox.main.store :refer [initial-state]] - [uxbox.main.data.projects :as udp] - [uxbox.main.data.users :as udu] - [uxbox.util.messages :as uum] - [uxbox.util.router :as rt] - [uxbox.util.spec :as us] - [uxbox.util.i18n :as i18n :refer [tr]] - [uxbox.util.storage :refer [storage]])) - -(s/def ::username string?) -(s/def ::password string?) -(s/def ::fullname string?) -(s/def ::email us/email?) -(s/def ::token string?) + (:require + [struct.alpha :as st] + [beicon.core :as rx] + [potok.core :as ptk] + [uxbox.main.repo :as rp] + [uxbox.main.store :refer [initial-state]] + [uxbox.main.data.users :as du] + [uxbox.util.messages :as um] + [uxbox.util.router :as rt] + [uxbox.util.i18n :as i18n :refer [tr]] + [uxbox.util.storage :refer [storage]])) ;; --- Logged In -(defrecord LoggedIn [data] - ptk/UpdateEvent - (update [this state] - (assoc state :auth data)) - - ptk/WatchEvent - (watch [this state s] - (swap! storage assoc :auth data) - (rx/of (udu/fetch-profile) - (rt/navigate :dashboard/projects)))) - -(defn logged-in? - [v] - (instance? LoggedIn v)) +;; TODO: add spec (defn logged-in [data] - (LoggedIn. data)) + (reify + ptk/EventType + (type [_] ::logged-in) + + ptk/UpdateEvent + (update [this state] + (assoc state :auth data)) + + ptk/WatchEvent + (watch [this state s] + (swap! storage assoc :auth data) + (rx/of (du/fetch-profile) + (rt/navigate :dashboard/projects))))) + +(defn logged-in? + [v] + (= (ptk/type v) ::logged-in)) ;; --- Login -(defrecord Login [username password] - ptk/UpdateEvent - (update [_ state] - (merge state (dissoc initial-state :route :router))) - - ptk/WatchEvent - (watch [this state s] - (let [params {:username username - :password password - :scope "webapp"} - on-error #(rx/of (uum/error (tr "errors.auth.unauthorized")))] - (->> (rp/req :auth/login params) - (rx/map :payload) - (rx/map logged-in) - (rx/catch rp/client-error? on-error))))) - -(s/def ::login-event - (s/keys :req-un [::username ::password])) +(st/defs ::login + (st/dict :username ::st/string + :password ::st/string)) (defn login - [params] - {:pre [(us/valid? ::login-event params)]} - (map->Login params)) + [{:keys [username password] :as data}] + (assert (st/valid? ::login data)) + (reify + ptk/UpdateEvent + (update [_ state] + (merge state (dissoc initial-state :route :router))) + + ptk/WatchEvent + (watch [this state s] + (let [params {:username username + :password password + :scope "webapp"} + on-error #(rx/of (um/error (tr "errors.auth.unauthorized")))] + (->> (rp/req :auth/login params) + (rx/map :payload) + (rx/map logged-in) + (rx/catch rp/client-error? on-error)))))) ;; --- Logout -(defrecord ClearUserData [] - ptk/UpdateEvent - (update [_ state] - (merge state (dissoc initial-state :route :router))) +(def clear-user-data + (reify + ptk/UpdateEvent + (update [_ state] + (merge state (dissoc initial-state :route :router))) - ptk/WatchEvent - (watch [_ state s] - (->> (rp/req :auth/logout) - (rx/ignore))) + ptk/WatchEvent + (watch [_ state s] + (->> (rp/req :auth/logout) + (rx/ignore))) - ptk/EffectEvent - (effect [_ state s] - (reset! storage {}) - (i18n/set-default-locale!))) + ptk/EffectEvent + (effect [_ state s] + (reset! storage {}) + (i18n/set-default-locale!)))) -(defrecord Logout [] - ptk/WatchEvent - (watch [_ state s] - (rx/of (rt/nav :auth/login) - (->ClearUserData)))) - -(defn logout - [] - (->Logout)) +(def logout + (reify + ptk/WatchEvent + (watch [_ state s] + (rx/of (rt/nav :auth/login) + clear-user-data)))) ;; --- Register -;; TODO: clean form on success - -(defrecord Register [data on-error] - ptk/WatchEvent - (watch [_ state stream] - (letfn [(handle-error [{payload :payload}] - (on-error payload) - (rx/empty))] - (rx/merge - (->> (rp/req :auth/register data) - (rx/map :payload) - (rx/map (constantly ::registered)) - (rx/catch rp/client-error? handle-error)) - (->> stream - (rx/filter #(= % ::registered)) - (rx/take 1) - (rx/map #(login data))))))) - -(s/def ::register-event - (s/keys :req-un [::fullname ::username ::email ::password])) +(st/defs ::register + (st/dict :fullname ::st/string + :username ::st/string + :password ::st/string + :email ::st/email)) (defn register "Create a register event instance." [data on-error] - {:pre [(us/valid? ::register-event data) - (fn? on-error)]} - (Register. data on-error)) + (assert (st/valid? ::register data)) + (assert (fn? on-error)) + (reify + ptk/WatchEvent + (watch [_ state stream] + (letfn [(handle-error [{payload :payload}] + (on-error payload) + (rx/empty))] + (rx/merge + (->> (rp/req :auth/register data) + (rx/map :payload) + (rx/map (constantly ::registered)) + (rx/catch rp/client-error? handle-error)) + (->> stream + (rx/filter #(= % ::registered)) + (rx/take 1) + (rx/map #(login data)))))))) ;; --- Recovery Request -(defrecord RecoveryRequest [data] - ptk/WatchEvent - (watch [_ state stream] - (letfn [(on-error [{payload :payload}] - (println "on-error" payload) - (rx/empty))] - (rx/merge - (->> (rp/req :auth/recovery-request data) - (rx/map (constantly ::recovery-requested)) - (rx/catch rp/client-error? on-error)) - (->> stream - (rx/filter #(= % ::recovery-requested)) - (rx/take 1) - (rx/map #(uum/info (tr "auth.message.recovery-token-sent")))))))) - -(s/def ::recovery-request-event - (s/keys :req-un [::username])) +(st/defs ::recovery-request + (st/dict :username ::st/string)) (defn recovery-request [data] - {:pre [(us/valid? ::recovery-request-event data)]} - (RecoveryRequest. data)) + (assert (st/valid? ::recovery-request data)) + (reify + ptk/WatchEvent + (watch [_ state stream] + (letfn [(on-error [{payload :payload}] + (println "on-error" payload) + (rx/empty))] + (rx/merge + (->> (rp/req :auth/recovery-request data) + (rx/map (constantly ::recovery-requested)) + (rx/catch rp/client-error? on-error)) + (->> stream + (rx/filter #(= % ::recovery-requested)) + (rx/take 1) + ;; TODO: this should be moved to the UI part + (rx/map #(um/info (tr "auth.message.recovery-token-sent"))))))))) ;; --- Check Recovery Token @@ -162,7 +152,7 @@ (letfn [(on-error [{payload :payload}] (rx/of (rt/navigate :auth/login) - (uum/error (tr "errors.auth.invalid-recovery-token"))))] + (um/error (tr "errors.auth.invalid-recovery-token"))))] (->> (rp/req :auth/validate-recovery-token token) (rx/ignore) (rx/catch rp/client-error? on-error))))) @@ -174,23 +164,22 @@ ;; --- Recovery (Password) -(defrecord Recovery [token password] - ptk/WatchEvent - (watch [_ state stream] - (letfn [(on-error [{payload :payload}] - (rx/of (uum/error (tr "errors.auth.invalid-recovery-token")))) - (on-success [{payload :payload}] - (rx/of - (rt/navigate :auth/login) - (uum/info (tr "auth.message.password-recovered"))))] - (->> (rp/req :auth/recovery {:token token :password password}) - (rx/mapcat on-success) - (rx/catch rp/client-error? on-error))))) - -(s/def ::recovery-event - (s/keys :req-un [::username ::token])) +(st/defs ::recovery + (st/dict :username ::st/string + :token ::st/string)) (defn recovery [{:keys [token password] :as data}] - {:pre [(us/valid? ::recovery-event data)]} - (Recovery. token password)) + (assert (st/valid? ::recovery data)) + (reify + ptk/WatchEvent + (watch [_ state stream] + (letfn [(on-error [{payload :payload}] + (rx/of (um/error (tr "errors.auth.invalid-recovery-token")))) + (on-success [{payload :payload}] + (rx/of + (rt/navigate :auth/login) + (um/info (tr "auth.message.password-recovered"))))] + (->> (rp/req :auth/recovery {:token token :password password}) + (rx/mapcat on-success) + (rx/catch rp/client-error? on-error)))))) diff --git a/frontend/src/uxbox/main/data/pages.cljs b/frontend/src/uxbox/main/data/pages.cljs index 1da8b76d6..b1edf61c2 100644 --- a/frontend/src/uxbox/main/data/pages.cljs +++ b/frontend/src/uxbox/main/data/pages.cljs @@ -7,62 +7,65 @@ (ns uxbox.main.data.pages (:require [beicon.core :as rx] - [cljs.spec.alpha :as s] [cuerdas.core :as str] [potok.core :as ptk] + [struct.alpha :as st] [uxbox.main.repo :as rp] - [uxbox.main.store :as st] [uxbox.util.data :refer [index-by-id]] [uxbox.util.spec :as us] [uxbox.util.timers :as ts] [uxbox.util.uuid :as uuid])) -;; --- Specs +;; --- Struct -(s/def ::grid-x-axis number?) -(s/def ::grid-y-axis number?) -(s/def ::grid-color string?) -(s/def ::background string?) -(s/def ::background-opacity number?) -(s/def ::grid-alignment boolean?) -(s/def ::width number?) -(s/def ::height number?) -(s/def ::layout string?) +(st/defs ::inst inst?) +(st/defs ::width (st/&& ::st/number ::st/positive)) +(st/defs ::height (st/&& ::st/number ::st/positive)) -(s/def ::metadata - (s/keys :req-un [::width ::height] - :opt-un [::grid-y-axis - ::grid-x-axis - ::grid-color - ::grid-alignment - ::order - ::background - ::background-opacity - ::layout])) +(st/defs ::metadata + (st/dict :width ::width + :height ::height + :grid-y-axis (st/opt ::st/number) + :grid-x-axis (st/opt ::st/number) + :grid-color (st/opt ::st/string) + :order (st/opt ::st/number) + :background (st/opt ::st/string) + :background-opacity (st/opt ::st/number))) -(s/def ::id uuid?) -(s/def ::name string?) -(s/def ::version integer?) -(s/def ::project uuid?) -(s/def ::user uuid?) -(s/def ::created-at inst?) -(s/def ::modified-at inst?) -(s/def ::shapes - (-> (s/coll-of uuid? :kind vector?) - (s/nilable))) +(st/defs ::shapes-list + (st/coll-of ::st/uuid)) -(s/def ::page-entity - (s/keys :req-un [::id - ::name - ::project - ::version - ::created-at - ::modified-at - ::user - ::metadata - ::shapes])) +(st/defs ::page-entity + (st/dict :id ::st/uuid + :name ::st/string + :project ::st/uuid + :created-at ::inst + :modified-at ::inst + :user ::st/uuid + :metadata ::metadata + :shapes ::shapes-list)) -;; TODO: add interactions to spec +(st/defs ::minimal-shape + (st/dict :id ::st/uuid + :type ::st/keyword + :name ::st/string)) + +(st/defs ::server-page-data-sapes + (st/coll-of ::minimal-shape)) + +(st/defs ::server-page-data + (st/dict :shapes ::server-page-data-sapes)) + +(st/defs ::server-page + (st/dict :id ::st/uuid + :name ::st/string + :project ::st/uuid + :version ::st/integer + :created-at ::inst + :modified-at ::inst + :user ::st/uuid + :metadata ::metadata + :data ::server-page-data)) ;; --- Protocols @@ -170,44 +173,39 @@ (declare rehash-pages) -(deftype PageCreated [data] - ptk/UpdateEvent - (update [_ state] - (let [project-id (:project data)] - (-> (update-in state [:projects project-id :pages] conj (:id data)) - (unpack-page data) - (assoc-packed-page data)))) - - ptk/WatchEvent - (watch [_ state stream] - (rx/of (rehash-pages (:project data))))) - -(s/def ::page-created - (s/keys :req-un [::id - ::name - ::project - ::metadata])) +(st/defs ::page-created + (st/dict :id ::st/uuid + :name ::st/string + :project ::st/uuid + :metadata ::metadata)) (defn page-created [data] - {:pre [(us/valid? ::page-created data)]} - (PageCreated. data)) + (assert (st/valid? ::page-created data) "invalid parameters") + (reify + ptk/UpdateEvent + (update [_ state] + (let [pid (:project data)] + (-> state + (update-in [:projects pid :pages] (fnil conj []) (:id data)) + (unpack-page data) + (assoc-packed-page data)))) -(defn page-created? - [o] - (instance? PageCreated o)) + ptk/WatchEvent + (watch [_ state stream] + (rx/of (rehash-pages (:project data)))))) ;; --- Create Page -(s/def ::create-page-params - (s/keys :req-un [::name - ::project - ::width - ::height])) +(st/defs ::create-page + (st/dict :name ::st/string + :project ::st/uuid + :width ::width + :height ::height)) (defn create-page [{:keys [name project width height layout] :as data}] - {:pre [(us/valid? ::create-page-params data)]} + (assert (st/valid? ::create-page data)) (reify ptk/WatchEvent (watch [this state s] @@ -231,11 +229,9 @@ ;; --- Page Persisted -;; TODO: add page spec - (defn page-persisted [data] - {:pre [(map? data)]} + (assert (st/valid? ::server-page data)) (reify cljs.core/IDeref (-deref [_] data) @@ -245,6 +241,7 @@ ptk/UpdateEvent (update [_ state] + (prn "page-persisted" data) (let [{:keys [id version]} data] (-> state (assoc-in [:pages id :version] version) @@ -256,30 +253,30 @@ ;; --- Persist Page -(deftype PersistPage [id on-success] - ptk/WatchEvent - (watch [this state s] - (let [page (get-in state [:pages id])] - (if (:history page) - (rx/empty) - (let [page (pack-page state id)] - (->> (rp/req :update/page page) - (rx/map :payload) - (rx/do #(when (fn? on-success) - (ts/schedule-on-idle on-success))) - (rx/map page-persisted))))))) +(defn persist-page + ([id] (persist-page id identity)) + ([id on-success] + (assert (uuid? id)) + (reify + ptk/EventType + (type [_] ::persist-page) + + ptk/WatchEvent + (watch [this state s] + (prn "persist-page" id) + (let [page (get-in state [:pages id])] + (if (:history page) + (rx/empty) + (let [page (pack-page state id)] + (->> (rp/req :update/page page) + (rx/map :payload) + (rx/do #(when (fn? on-success) + (ts/schedule-on-idle on-success))) + (rx/map page-persisted))))))))) (defn persist-page? [v] - (instance? PersistPage v)) - -(defn persist-page - ([id] - {:pre [(uuid? id)]} - (PersistPage. id (constantly nil))) - ([id on-success] - {:pre [(uuid? id)]} - (PersistPage. id on-success))) + (= ::persist-page (ptk/type v))) ;; --- Page Metadata Persisted @@ -288,8 +285,10 @@ (update [_ state] (assoc-in state [:pages id :version] (:version data)))) -(s/def ::metadata-persisted-event - (s/keys :req-un [::id ::version])) +(st/defs ::version integer?) +(st/defs ::metadata-persisted-event + (st/dict :id ::st/uuid + :version ::version)) (defn metadata-persisted? [v] @@ -297,7 +296,7 @@ (defn metadata-persisted [{:keys [id] :as data}] - {:pre [(us/valid? ::metadata-persisted-event data)]} + {:pre [(st/valid? ::metadata-persisted-event data)]} (MetadataPersisted. id data)) ;; --- Persist Page Metadata @@ -327,7 +326,7 @@ (defn update-page [id data] - {:pre [(uuid? id) (us/valid? ::page-entity data)]} + {:pre [(uuid? id) (st/valid? ::page-entity data)]} (UpdatePage. id data)) ;; --- Update Page Metadata @@ -340,7 +339,7 @@ (defn update-metadata [id metadata] - {:pre [(uuid? id) (us/valid? ::metadata metadata)]} + {:pre [(uuid? id) (st/valid? ::metadata metadata)]} (UpdateMetadata. id metadata)) ;; --- Rehash Pages @@ -385,12 +384,15 @@ ;; A specialized event for persist data ;; from the update page form. -(s/def ::persist-page-update-form-params - (s/keys :req-un [::id ::name ::width ::height])) +(st/defs ::persist-page-update-form + (st/dict :id ::st/uuid + :name ::st/string + :width ::width + :height ::height)) (defn persist-page-update-form [{:keys [id name width height] :as data}] - {:pre [(us/valid? ::persist-page-update-form-params data)]} + (assert (st/valid? ::persist-page-update-form data)) (reify ptk/WatchEvent (watch [_ state stream] diff --git a/frontend/src/uxbox/main/data/projects.cljs b/frontend/src/uxbox/main/data/projects.cljs index 743447167..7c4edfa19 100644 --- a/frontend/src/uxbox/main/data/projects.cljs +++ b/frontend/src/uxbox/main/data/projects.cljs @@ -5,17 +5,18 @@ ;; Copyright (c) 2015-2017 Andrey Antukh (ns uxbox.main.data.projects - (:require [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [beicon.core :as rx] - [potok.core :as ptk] - [uxbox.main.store :as st] - [uxbox.main.repo :as rp] - [uxbox.main.data.pages :as udp] - [uxbox.util.uuid :as uuid] - [uxbox.util.spec :as us] - [uxbox.util.time :as dt] - [uxbox.util.router :as rt])) + (:require + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [beicon.core :as rx] + [potok.core :as ptk] + [struct.core :as st] + [uxbox.main.repo :as rp] + [uxbox.main.data.pages :as udp] + [uxbox.util.uuid :as uuid] + [uxbox.util.spec :as us] + [uxbox.util.time :as dt] + [uxbox.util.router :as rt])) ;; --- Specs @@ -34,12 +35,21 @@ ::created-at ::modified-at])) +(st/defs project-spec + {:id [st/required st/uuid] + :name [st/required st/string] + :version [st/required st/integer] + :user [st/required st/uuid] + :created-at [st/required inst?] + :modified-at [st/required inst?]}) + ;; --- Helpers (defn assoc-project "A reduce function for assoc the project to the state map." [state {:keys [id] :as project}] - {:pre [(us/valid? ::project-entity project)]} + (assert (st/valid? project-spec project) + "invalid project instance") (update-in state [:projects id] merge project)) (defn dissoc-project @@ -160,24 +170,23 @@ ;; --- Create Project -(s/def ::create-project-params - (s/keys :req-un [::name ::udp/width ::udp/height])) +(st/defs create-project-spec + {:name [st/required st/string] + :width [st/required st/number st/positive] + :height [st/required st/number st/positive]}) (defn create-project [{:keys [name] :as params}] - {:pre [(us/valid? ::create-project-params params)]} + (assert (st/valid? create-project-spec params) + "invalid params for create project event") (reify ptk/WatchEvent (watch [this state stream] - (rx/merge - (->> (rp/req :create/project {:name name}) - (rx/map :payload) - (rx/map (fn [{:keys [id] :as project}] - (udp/create-page (assoc params :project id))))) - (->> stream - (rx/filter udp/page-created?) - (rx/take 1) - (rx/map #(fetch-projects))))))) + (->> (rp/req :create/project {:name name}) + (rx/map :payload) + (rx/mapcat (fn [{:keys [id] :as project}] + (rx/of #(assoc-project % project) + (udp/create-page (assoc params :project id))))))))) ;; --- Go To Project diff --git a/frontend/src/uxbox/main/ui.cljs b/frontend/src/uxbox/main/ui.cljs index 1d2345065..a570faf25 100644 --- a/frontend/src/uxbox/main/ui.cljs +++ b/frontend/src/uxbox/main/ui.cljs @@ -92,12 +92,12 @@ (let [route (mf/deref route-iref)] (case (get-in route [:data :name]) :auth/login (mf/element auth/login-page) - :auth/register (auth/register-page) - :auth/recovery-request (auth/recovery-request-page) + :auth/register (mf/element auth/register-page) + ;; :auth/recovery-request (auth/recovery-request-page) - :auth/recovery - (let [token (get-in route [:params :path :token])] - (auth/recovery-page token)) + ;; :auth/recovery + ;; (let [token (get-in route [:params :path :token])] + ;; (auth/recovery-page token)) (:settings/profile :settings/password diff --git a/frontend/src/uxbox/main/ui/auth.cljs b/frontend/src/uxbox/main/ui/auth.cljs index 8f6c840a3..72359a9ca 100644 --- a/frontend/src/uxbox/main/ui/auth.cljs +++ b/frontend/src/uxbox/main/ui/auth.cljs @@ -7,10 +7,10 @@ (ns uxbox.main.ui.auth (:require [uxbox.main.ui.auth.login :as login] [uxbox.main.ui.auth.register :as register] - [uxbox.main.ui.auth.recovery-request :as recovery-request] - [uxbox.main.ui.auth.recovery :as recovery])) + #_[uxbox.main.ui.auth.recovery-request :as recovery-request] + #_[uxbox.main.ui.auth.recovery :as recovery])) (def login-page login/login-page) (def register-page register/register-page) -(def recovery-page recovery/recovery-page) -(def recovery-request-page recovery-request/recovery-request-page) +;; (def recovery-page recovery/recovery-page) +;; (def recovery-request-page recovery-request/recovery-request-page) diff --git a/frontend/src/uxbox/main/ui/auth/login.cljs b/frontend/src/uxbox/main/ui/auth/login.cljs index f2dfefafb..94d995fc1 100644 --- a/frontend/src/uxbox/main/ui/auth/login.cljs +++ b/frontend/src/uxbox/main/ui/auth/login.cljs @@ -8,7 +8,7 @@ (ns uxbox.main.ui.auth.login (:require [rumext.alpha :as mf] - [struct.core :as s] + [struct.alpha :as s] [uxbox.builtins.icons :as i] [uxbox.config :as cfg] [uxbox.main.data.auth :as da] @@ -19,9 +19,9 @@ [uxbox.util.i18n :refer [tr]] [uxbox.util.router :as rt])) -(s/defs login-form-spec - {:username [fm/required fm/string] - :password [fm/required fm/string]}) +(s/defs ::login-form + (s/dict :username (s/&& ::s/string ::fm/not-empty-string) + :password (s/&& ::s/string ::fm/not-empty-string))) (defn- on-submit [event form] @@ -42,7 +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 ::login-form {})] [:form {:on-submit #(on-submit % form)} [:div.login-content (when cfg/isdemo @@ -84,6 +84,6 @@ [] [:div.login [:div.login-body - (messages-widget) + [:& messages-widget] [:a i/logo] [:& login-form]]]) diff --git a/frontend/src/uxbox/main/ui/auth/recovery.cljs b/frontend/src/uxbox/main/ui/auth/recovery.cljs index d333b91e5..c444767bb 100644 --- a/frontend/src/uxbox/main/ui/auth/recovery.cljs +++ b/frontend/src/uxbox/main/ui/auth/recovery.cljs @@ -21,62 +21,62 @@ [uxbox.util.i18n :refer (tr)] [uxbox.util.router :as rt])) -(def form-data (fm/focus-data :recovery st/state)) -(def form-errors (fm/focus-errors :recovery st/state)) +;; (def form-data (fm/focus-data :recovery st/state)) +;; (def form-errors (fm/focus-errors :recovery st/state)) -(def assoc-value (partial fm/assoc-value :recovery)) -(def assoc-errors (partial fm/assoc-errors :recovery)) -(def clear-form (partial fm/clear-form :recovery)) +;; (def assoc-value (partial fm/assoc-value :recovery)) +;; (def assoc-errors (partial fm/assoc-errors :recovery)) +;; (def clear-form (partial fm/clear-form :recovery)) -;; --- Recovery Form +;; ;; --- Recovery Form -(s/def ::password ::fm/non-empty-string) -(s/def ::recovery-form - (s/keys :req-un [::password])) +;; (s/def ::password ::fm/non-empty-string) +;; (s/def ::recovery-form +;; (s/keys :req-un [::password])) -(mx/defc recovery-form - {:mixins [mx/static mx/reactive]} - [token] - (let [data (merge (mx/react form-data) {:token token}) - valid? (fm/valid? ::recovery-form data)] - (letfn [(on-change [field event] - (let [value (dom/event->value event)] - (st/emit! (assoc-value field value)))) - (on-submit [event] - (dom/prevent-default event) - (st/emit! (uda/recovery data) - (clear-form)))] - [:form {:on-submit on-submit} - [:div.login-content - [:input.input-text - {:name "password" - :value (:password data "") - :on-change (partial on-change :password) - :placeholder (tr "recover.password.placeholder") - :type "password"}] - [:input.btn-primary - {:name "login" - :class (when-not valid? "btn-disabled") - :disabled (not valid?) - :value (tr "recover.recover-password") - :type "submit"}] - [:div.login-links - [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recover.go-back")]]]]))) +;; (mx/defc recovery-form +;; {:mixins [mx/static mx/reactive]} +;; [token] +;; (let [data (merge (mx/react form-data) {:token token}) +;; valid? (fm/valid? ::recovery-form data)] +;; (letfn [(on-change [field event] +;; (let [value (dom/event->value event)] +;; (st/emit! (assoc-value field value)))) +;; (on-submit [event] +;; (dom/prevent-default event) +;; (st/emit! (uda/recovery data) +;; (clear-form)))] +;; [:form {:on-submit on-submit} +;; [:div.login-content +;; [:input.input-text +;; {:name "password" +;; :value (:password data "") +;; :on-change (partial on-change :password) +;; :placeholder (tr "recover.password.placeholder") +;; :type "password"}] +;; [:input.btn-primary +;; {:name "login" +;; :class (when-not valid? "btn-disabled") +;; :disabled (not valid?) +;; :value (tr "recover.recover-password") +;; :type "submit"}] +;; [:div.login-links +;; [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recover.go-back")]]]]))) -;; --- Recovery Page +;; ;; --- Recovery Page -(defn- recovery-page-init - [own] - (let [[token] (::mx/args own)] - (st/emit! (uda/validate-recovery-token token)) - own)) +;; (defn- recovery-page-init +;; [own] +;; (let [[token] (::mx/args own)] +;; (st/emit! (uda/validate-recovery-token token)) +;; own)) -(mx/defc recovery-page - {:mixins [mx/static (fm/clear-mixin st/store :recovery)] - :init recovery-page-init} - [token] - [:div.login - [:div.login-body - (messages-widget) - [:a i/logo] - (recovery-form token)]]) +;; (mx/defc recovery-page +;; {:mixins [mx/static (fm/clear-mixin st/store :recovery)] +;; :init recovery-page-init} +;; [token] +;; [:div.login +;; [:div.login-body +;; (messages-widget) +;; [:a i/logo] +;; (recovery-form token)]]) diff --git a/frontend/src/uxbox/main/ui/auth/recovery_request.cljs b/frontend/src/uxbox/main/ui/auth/recovery_request.cljs index 66e37c948..156c7df6b 100644 --- a/frontend/src/uxbox/main/ui/auth/recovery_request.cljs +++ b/frontend/src/uxbox/main/ui/auth/recovery_request.cljs @@ -20,52 +20,52 @@ [rumext.core :as mx :include-macros true] [uxbox.util.router :as rt])) -(def form-data (fm/focus-data :recovery-request st/state)) -(def form-errors (fm/focus-errors :recovery-request st/state)) +;; (def form-data (fm/focus-data :recovery-request st/state)) +;; (def form-errors (fm/focus-errors :recovery-request st/state)) -(def assoc-value (partial fm/assoc-value :profile-password)) -(def assoc-errors (partial fm/assoc-errors :profile-password)) -(def clear-form (partial fm/clear-form :profile-password)) +;; (def assoc-value (partial fm/assoc-value :profile-password)) +;; (def assoc-errors (partial fm/assoc-errors :profile-password)) +;; (def clear-form (partial fm/clear-form :profile-password)) -(s/def ::username ::fm/non-empty-string) -(s/def ::recovery-request-form (s/keys :req-un [::username])) +;; (s/def ::username ::fm/non-empty-string) +;; (s/def ::recovery-request-form (s/keys :req-un [::username])) -(mx/defc recovery-request-form - {:mixins [mx/static mx/reactive]} - [] - (let [data (mx/react form-data) - valid? (fm/valid? ::recovery-request-form data)] - (letfn [(on-change [event] - (let [value (dom/event->value event)] - (st/emit! (assoc-value :username value)))) - (on-submit [event] - (dom/prevent-default event) - (st/emit! (uda/recovery-request data) - (clear-form)))] - [:form {:on-submit on-submit} - [:div.login-content - [:input.input-text - {:name "username" - :value (:username data "") - :on-change on-change - :placeholder (tr "recovery-request.username-or-email.placeholder") - :type "text"}] - [:input.btn-primary - {:name "login" - :class (when-not valid? "btn-disabled") - :disabled (not valid?) - :value (tr "recovery-request.recover-password") - :type "submit"}] - [:div.login-links - [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recovery-request.go-back")]]]]))) +;; (mx/defc recovery-request-form +;; {:mixins [mx/static mx/reactive]} +;; [] +;; (let [data (mx/react form-data) +;; valid? (fm/valid? ::recovery-request-form data)] +;; (letfn [(on-change [event] +;; (let [value (dom/event->value event)] +;; (st/emit! (assoc-value :username value)))) +;; (on-submit [event] +;; (dom/prevent-default event) +;; (st/emit! (uda/recovery-request data) +;; (clear-form)))] +;; [:form {:on-submit on-submit} +;; [:div.login-content +;; [:input.input-text +;; {:name "username" +;; :value (:username data "") +;; :on-change on-change +;; :placeholder (tr "recovery-request.username-or-email.placeholder") +;; :type "text"}] +;; [:input.btn-primary +;; {:name "login" +;; :class (when-not valid? "btn-disabled") +;; :disabled (not valid?) +;; :value (tr "recovery-request.recover-password") +;; :type "submit"}] +;; [:div.login-links +;; [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "recovery-request.go-back")]]]]))) -;; --- Recovery Request Page +;; ;; --- Recovery Request Page -(mx/defc recovery-request-page - {:mixins [mx/static (fm/clear-mixin st/store :recovery-request)]} - [] - [:div.login - [:div.login-body - (messages-widget) - [:a i/logo] - (recovery-request-form)]]) +;; (mx/defc recovery-request-page +;; {:mixins [mx/static (fm/clear-mixin st/store :recovery-request)]} +;; [] +;; [:div.login +;; [:div.login-body +;; (messages-widget) +;; [:a i/logo] +;; (recovery-request-form)]]) diff --git a/frontend/src/uxbox/main/ui/auth/register.cljs b/frontend/src/uxbox/main/ui/auth/register.cljs index f02afe659..261a2a98a 100644 --- a/frontend/src/uxbox/main/ui/auth/register.cljs +++ b/frontend/src/uxbox/main/ui/auth/register.cljs @@ -6,118 +6,131 @@ ;; Copyright (c) 2015-2017 Juan de la Cruz (ns uxbox.main.ui.auth.register - (:require [cljs.spec.alpha :as s :include-macros true] - [lentes.core :as l] - [cuerdas.core :as str] - [uxbox.builtins.icons :as i] - [uxbox.main.store :as st] - [uxbox.main.data.auth :as uda] - [uxbox.main.ui.messages :refer [messages-widget]] - [uxbox.main.ui.navigation :as nav] - [uxbox.util.i18n :refer (tr)] - [uxbox.util.dom :as dom] - [uxbox.util.forms :as fm] - [rumext.core :as mx :include-macros true] - [uxbox.util.router :as rt])) + (:require + [cuerdas.core :as str] + [lentes.core :as l] + [rumext.alpha :as mf] + [struct.alpha :as s] + [uxbox.builtins.icons :as i] + [uxbox.main.data.auth :as uda] + [uxbox.main.store :as st] + [uxbox.main.ui.messages :refer [messages-widget]] + [uxbox.main.ui.navigation :as nav] + [uxbox.util.dom :as dom] + [uxbox.util.forms :as fm] + [uxbox.util.i18n :refer [tr]] + [uxbox.util.router :as rt])) -(def form-data (fm/focus-data :register st/state)) -(def form-errors (fm/focus-errors :register st/state)) +(s/defs ::register-form + (s/dict :username (s/&& ::s/string ::fm/not-empty-string) + :fullname (s/&& ::s/string ::fm/not-empty-string) + :password (s/&& ::s/string ::fm/not-empty-string) + :email ::s/email)) -(def assoc-value (partial fm/assoc-value :register)) -(def assoc-error (partial fm/assoc-error :register)) -(def clear-form (partial fm/clear-form :register)) +(defn- on-error + [error form] + (case (:code error) + :uxbox.services.users/registration-disabled + (st/emit! (tr "errors.api.form.registration-disabled")) -;; TODO: add better password validation + :uxbox.services.users/email-already-exists + (swap! form assoc-in [:errors :email] + {:type ::api + :message "errors.api.form.email-already-exists"}) -(s/def ::username ::fm/non-empty-string) -(s/def ::fullname ::fm/non-empty-string) -(s/def ::password ::fm/non-empty-string) -(s/def ::email ::fm/email) + :uxbox.services.users/username-already-exists + (swap! form assoc-in [:errors :username] + {:type ::api + :message "errors.api.form.username-already-exists"}))) -(s/def ::register-form - (s/keys :req-un [::username - ::fullname - ::email - ::password])) +(defn- on-submit + [event form] + (dom/prevent-default event) + (let [data (:clean-data form) + on-error #(on-error % form)] + (st/emit! (uda/register data on-error)))) -(mx/defc register-form - {:mixins [mx/static mx/reactive - (fm/clear-mixin st/store :register)]} - [] - (let [data (mx/react form-data) - errors (mx/react form-errors) - valid? (fm/valid? ::register-form data)] - (letfn [(on-change [field event] - (let [value (dom/event->value event)] - (st/emit! (assoc-value field value)))) - (on-error [{:keys [type code] :as payload}] - (case code - :uxbox.services.users/registration-disabled - (st/emit! (tr "errors.api.form.registration-disabled")) - :uxbox.services.users/email-already-exists - (st/emit! (assoc-error :email (tr "errors.api.form.email-already-exists"))) - :uxbox.services.users/username-already-exists - (st/emit! (assoc-error :username (tr "errors.api.form.username-already-exists"))))) - (on-submit [event] - (dom/prevent-default event) - (st/emit! (uda/register data on-error)))] - [:form {:on-submit on-submit} - [:div.login-content - [:input.input-text - {:name "fullname" - :tab-index "2" - :value (:fullname data "") - :on-change (partial on-change :fullname) - :placeholder (tr "register.fullname.placeholder") - :type "text"}] - (fm/input-error errors :fullname) +(mf/defc register-form + [props] + (let [{:keys [data] :as form} (fm/use-form ::register-form {})] + (prn "register-form" form) + [:form {:on-submit #(on-submit % form)} + [:div.login-content + [:input.input-text + {:name "fullname" + :tab-index "1" + :value (:fullname data "") + :class (fm/error-class form :fullname) + :on-blur (fm/on-input-blur form :fullname) + :on-change (fm/on-input-change form :fullname) + :placeholder (tr "register.fullname.placeholder") + :type "text"}] - [:input.input-text - {:name "username" - :tab-index "3" - :value (:username data "") - :on-change (partial on-change :username) - :placeholder (tr "register.username.placeholder") - :type "text"}] - (fm/input-error errors :username) + [:& fm/field-error {:form form + :type #{::api} + :field :fullname}] - [:input.input-text - {:name "email" - :tab-index "4" - :ref "email" - :value (:email data "") - :on-change (partial on-change :email) - :placeholder (tr "register.email.placeholder") - :type "text"}] - (fm/input-error errors :email) + [:input.input-text + {:type "text" + :name "username" + :tab-index "2" + :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")}] - [:input.input-text - {:name "password" - :tab-index "5" - :ref "password" - :value (:password data "") - :on-change (partial on-change :password) - :placeholder (tr "register.password.placeholder") - :type "password"}] - (fm/input-error errors :password) + [:& fm/field-error {:form form + :type #{::api} + :field :username}] - [:input.btn-primary - {:name "login" - :tab-index "6" - :class (when-not valid? "btn-disabled") - :disabled (not valid?) - :value (tr "register.get-started") - :type "submit"}] - [:div.login-links - [:a {:on-click #(st/emit! (rt/navigate :auth/login))} (tr "register.already-have-account")]]]]))) + [:input.input-text + {:type "email" + :name "email" + :tab-index "3" + :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/field-error {:form form + :type #{::api} + :field :email}] + + + [:input.input-text + {:name "password" + :tab-index "4" + :value (:password data "") + :class (fm/error-class form :password) + :on-blur (fm/on-input-blur form :password) + :on-change (fm/on-input-change form :password) + :placeholder (tr "register.password.placeholder") + :type "password"}] + + [:& fm/field-error {:form form + :type #{::api} + :field :email}] + + + [:input.btn-primary + {:type "submit" + :tab-index "5" + :class (when-not (:valid form) "btn-disabled") + :disabled (not (:valid form)) + :value (tr "register.get-started")}] + + [:div.login-links + [:a {:on-click #(st/emit! (rt/nav :auth/login))} + (tr "register.already-have-account")]]]])) ;; --- Register Page -(mx/defc register-page - {:mixins [mx/static]} - [own] +(mf/defc register-page + [props] [:div.login [:div.login-body (messages-widget) [:a i/logo] - (register-form)]]) + [:& register-form]]]) diff --git a/frontend/src/uxbox/main/ui/dashboard.cljs b/frontend/src/uxbox/main/ui/dashboard.cljs index 8d86362ea..a48a82194 100644 --- a/frontend/src/uxbox/main/ui/dashboard.cljs +++ b/frontend/src/uxbox/main/ui/dashboard.cljs @@ -25,7 +25,7 @@ [{:keys [route] :as props}] (let [[section type id] (parse-route route)] [:main.dashboard-main - (messages-widget) + [:& messages-widget] [:& header {:section section}] (case section :dashboard/icons diff --git a/frontend/src/uxbox/main/ui/dashboard/projects.cljs b/frontend/src/uxbox/main/ui/dashboard/projects.cljs index 0b09c3424..f17f5badb 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects.cljs @@ -40,7 +40,6 @@ (-> (l/key :projects) (l/derive st/state))) - ;; --- Helpers (defn sort-projects-by diff --git a/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs index 944bb8f18..119161ccf 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs @@ -7,7 +7,7 @@ (ns uxbox.main.ui.dashboard.projects-forms (:require - [cljs.spec.alpha :as s] + [struct.alpha :as s] [rumext.alpha :as mf] [uxbox.builtins.icons :as i] [uxbox.main.data.projects :as udp] @@ -17,10 +17,10 @@ [uxbox.util.forms :as fm] [uxbox.util.i18n :as t :refer [tr]])) -(def project-form-spec - {:name [fm/required fm/string] - :width [fm/required fm/number-str] - :height [fm/required fm/number-str]}) +(s/defs ::project-form + (s/dict :name (s/&& ::s/string ::fm/not-empty-string) + :width ::s/number-str + :height ::s/number-str)) (def defaults {:name "" @@ -44,13 +44,14 @@ (mf/defc create-project-form [props] - (let [{:keys [data errors] :as form} (fm/use-form {:initial defaults :spec project-form-spec})] + (let [{:keys [data] :as form} (fm/use-form ::project-form defaults)] [:form {:on-submit #(on-submit % form)} [:input.input-text {:placeholder "New project name" :type "text" :name "name" :value (:name data) + :class (fm/error-class form :name) :on-blur (fm/on-input-blur form :name) :on-change (fm/on-input-change form :name) :auto-focus true}] @@ -63,6 +64,7 @@ :type "number" :min 0 :max 5000 + :class (fm/error-class form :width) :on-blur (fm/on-input-blur form :width) :on-change (fm/on-input-change form :width) :value (:width data)}]] @@ -75,6 +77,7 @@ :name "height" :min 0 :max 5000 + :class (fm/error-class form :height) :on-blur (fm/on-input-blur form :height) :on-change (fm/on-input-change form :height) :value (:height data)}]]] diff --git a/frontend/src/uxbox/main/ui/messages.cljs b/frontend/src/uxbox/main/ui/messages.cljs index ae72ed269..6b2e263a8 100644 --- a/frontend/src/uxbox/main/ui/messages.cljs +++ b/frontend/src/uxbox/main/ui/messages.cljs @@ -1,16 +1,17 @@ (ns uxbox.main.ui.messages - (:require [lentes.core :as l] - [uxbox.main.store :as st] - [uxbox.util.messages :as uum] - [rumext.core :as mx :include-macros true])) + (:require + [lentes.core :as l] + [rumext.alpha :as mf] + [uxbox.main.store :as st] + [uxbox.util.messages :as um])) -(def ^:private message-ref +(def ^:private message-iref (-> (l/key :message) (l/derive st/state))) -(mx/defc messages-widget - {:mixins [mx/static mx/reactive]} +(mf/defc messages-widget [] - (let [message (mx/react message-ref) - on-close #(st/emit! (uum/hide))] - (uum/messages-widget (assoc message :on-close on-close)))) + (let [message (mf/deref message-iref) + on-close #(st/emit! (um/hide))] + [:& um/messages-widget {:message message + :on-close on-close}])) diff --git a/frontend/src/uxbox/main/ui/settings.cljs b/frontend/src/uxbox/main/ui/settings.cljs index 3da1a4f6b..62b4073d9 100644 --- a/frontend/src/uxbox/main/ui/settings.cljs +++ b/frontend/src/uxbox/main/ui/settings.cljs @@ -22,7 +22,7 @@ [{:keys [route] :as props}] (let [section (get-in route [:data :name])] [:main.dashboard-main - (messages-widget) + [:& messages-widget] [:& header {:section section}] (case section :settings/profile (mf/element profile/profile-page) diff --git a/frontend/src/uxbox/main/ui/settings/password.cljs b/frontend/src/uxbox/main/ui/settings/password.cljs index 090974eba..9737f6725 100644 --- a/frontend/src/uxbox/main/ui/settings/password.cljs +++ b/frontend/src/uxbox/main/ui/settings/password.cljs @@ -8,7 +8,7 @@ (ns uxbox.main.ui.settings.password (:require [rumext.alpha :as mf] - [struct.core :as s] + [struct.alpha :as s] [uxbox.builtins.icons :as i] [uxbox.main.data.users :as udu] [uxbox.main.store :as st] @@ -36,14 +36,14 @@ :on-error on-error}] (st/emit! (udu/update-password data opts))))) -(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]}) +(s/defs ::password-form + (s/dict :password-1 (s/&& ::s/string ::fm/not-empty-string) + :password-2 (s/&& ::s/string ::fm/not-empty-string) + :password-old (s/&& ::s/string ::fm/not-empty-string))) (mf/defc password-form [props] - (let [{:keys [data] :as form} (fm/use-form {:initial {} :spec password-form-spec})] + (let [{:keys [data] :as form} (fm/use-form ::password-form {})] [:form.password-form {:on-submit #(on-submit % form)} [:span.user-settings-label (tr "settings.password.change-password")] [:input.input-text diff --git a/frontend/src/uxbox/main/ui/settings/profile.cljs b/frontend/src/uxbox/main/ui/settings/profile.cljs index ed336db08..814d36e52 100644 --- a/frontend/src/uxbox/main/ui/settings/profile.cljs +++ b/frontend/src/uxbox/main/ui/settings/profile.cljs @@ -10,7 +10,7 @@ [cuerdas.core :as str] [lentes.core :as l] [rumext.alpha :as mf] - [struct.core :as s] + [struct.alpha :as s] [uxbox.builtins.icons :as i] [uxbox.main.data.users :as udu] [uxbox.main.store :as st] @@ -18,27 +18,28 @@ [uxbox.util.dom :as dom] [uxbox.util.forms :as fm] [uxbox.util.i18n :as i18n :refer [tr]] - [uxbox.util.interop :refer [iterable->seq]])) + [uxbox.util.interop :refer [iterable->seq]] + [uxbox.util.messages :as um])) -(defn profile->form + +(defn- profile->form [profile] (let [language (get-in profile [:metadata :language])] (-> (select-keys profile [:fullname :username :email]) (cond-> language (assoc :language language))))) -(def profile-ref +(def ^:private profile-ref (-> (l/key :profile) (l/derive st/state))) -(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]}) +(s/defs ::profile-form + (s/dict :fullname (s/&& ::s/string ::fm/not-empty-string) + :username (s/&& ::s/string ::fm/not-empty-string) + :language (s/&& ::s/string ::fm/not-empty-string) + :email ::s/email)) (defn- on-error [error form] - (prn "on-error" error form) (case (:code error) :uxbox.services.users/email-already-exists (swap! form assoc-in [:errors :email] @@ -57,18 +58,20 @@ (defn- on-submit [event form] + (prn "on-submit" form) (dom/prevent-default event) (let [data (:clean-data form) - opts {:on-success #(prn "On Success" %) - :on-error #(on-error % form)}] + on-success #(st/emit! (um/info (tr "settings.profile.profile-saved"))) + on-error #(on-error % form) + opts {:on-success on-success + :on-error on-error}] (st/emit! (udu/update-profile data opts)))) ;; --- Profile Form + (mf/defc profile-form [props] - (let [{:keys [data] :as form} (fm/use-form {:initial initial-data - :spec profile-form-spec})] - (prn "profile-form" form) + (let [{:keys [data] :as form} (fm/use-form ::profile-form initial-data)] [:form.profile-form {:on-submit #(on-submit % form)} [:span.user-settings-label (tr "settings.profile.section-basic-data")] [:input.input-text diff --git a/frontend/src/uxbox/main/ui/users.cljs b/frontend/src/uxbox/main/ui/users.cljs index cd0cf3912..6283c8706 100644 --- a/frontend/src/uxbox/main/ui/users.cljs +++ b/frontend/src/uxbox/main/ui/users.cljs @@ -38,7 +38,7 @@ [:li {:on-click #(on-click % :settings/notifications)} i/mail [:span (tr "ds.user.notifications")]] - [:li {:on-click #(on-click % (da/logout))} + [:li {:on-click #(on-click % da/logout)} i/exit [:span (tr "ds.user.exit")]]])) diff --git a/frontend/src/uxbox/main/ui/workspace.cljs b/frontend/src/uxbox/main/ui/workspace.cljs index 296c6d7b7..99a22402c 100644 --- a/frontend/src/uxbox/main/ui/workspace.cljs +++ b/frontend/src/uxbox/main/ui/workspace.cljs @@ -9,7 +9,6 @@ (:require [beicon.core :as rx] [lentes.core :as l] - [rumext.core :as mx] [rumext.alpha :as mf] [uxbox.main.constants :as c] [uxbox.main.data.history :as udh] @@ -86,7 +85,7 @@ (mf/use-effect #(subscribe canvas page) #js [(:id page)]) [:* - (messages-widget) + [:& messages-widget] [:& header {:page page :flags flags :key (:id page)}] 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 408c58b44..8e3c410c4 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs @@ -2,12 +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) 2015-2016 Andrey Antukh -;; Copyright (c) 2015-2016 Juan de la Cruz +;; Copyright (c) 2015-2019 Andrey Antukh +;; Copyright (c) 2015-2019 Juan de la Cruz (ns uxbox.main.ui.workspace.sidebar.sitemap-forms (:require [rumext.alpha :as mf] + [struct.alpha :as s] [uxbox.builtins.icons :as i] [uxbox.main.constants :as c] [uxbox.main.data.pages :as udp] @@ -17,12 +18,12 @@ [uxbox.util.forms :as fm] [uxbox.util.i18n :refer [tr]])) -(def page-form-spec - {:id [fm/uuid] - :project [fm/uuid] - :name [fm/required fm/string] - :width [fm/required fm/number-str] - :height [fm/required fm/number-str]}) +(s/defs ::page-form + (s/dict :id (s/opt ::s/uuid) + :project ::s/uuid + :name (s/&& ::s/string ::fm/not-empty-string) + :width ::s/number-str + :height ::s/number-str)) (def defaults {:name "" @@ -52,13 +53,13 @@ (mf/defc page-form [{:keys [page] :as props}] - (let [{:keys [data errors] :as form} (fm/use-form {:initial #(initial-data page) - :spec page-form-spec})] + (let [{:keys [data] :as form} (fm/use-form ::page-form #(initial-data page))] [:form {:on-submit #(on-submit % form)} [:input.input-text {:placeholder "Page name" :type "text" :name "name" + :class (fm/error-class form :name) :on-blur (fm/on-input-blur form :name) :on-change (fm/on-input-change form :name) :value (:name data) @@ -72,6 +73,7 @@ :type "number" :min 0 :max 5000 + :class (fm/error-class form :width) :on-blur (fm/on-input-blur form :width) :on-change (fm/on-input-change form :width) :value (:width data)}]] @@ -84,12 +86,14 @@ :type "number" :min 0 :max 5000 + :class (fm/error-class form :height) :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!" :type "submit" + :class (when-not (:valid form) "btn-disabled") :disabled (not (:valid form))}]])) (mf/defc page-form-dialog diff --git a/frontend/src/uxbox/util/forms.cljs b/frontend/src/uxbox/util/forms.cljs index 90d9a4d73..70e5db543 100644 --- a/frontend/src/uxbox/util/forms.cljs +++ b/frontend/src/uxbox/util/forms.cljs @@ -8,26 +8,16 @@ (:refer-clojure :exclude [uuid]) (:require [beicon.core :as rx] - [cljs.spec.alpha :as s :include-macros true] + [cljs.spec.alpha :as s] [cuerdas.core :as str] [lentes.core :as l] [potok.core :as ptk] [rumext.alpha :as mf] [rumext.core :as mx] - [struct.core :as st] + [struct.alpha :as st] [uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]])) -;; --- Main Api - -(defn validate - [data spec] - (st/validate data spec)) - -(defn valid? - [data spec] - (st/valid? data spec)) - ;; --- Handlers Helpers (defn- impl-mutator @@ -45,8 +35,8 @@ ([self f x y more] (update-fn #(apply f % x y more)))))) (defn- translate-error-type - [code] - (case code + [name] + (case name ::st/string "errors.form.string" ::st/number "errors.form.number" ::st/number-str "errors.form.number" @@ -54,31 +44,35 @@ ::st/integer-str "errors.form.integer" ::st/required "errors.form.required" ::st/email "errors.form.email" - ::st/identical-to "errors.form.does-not-match" + ;; ::st/identical-to "errors.form.does-not-match" "errors.undefined-error")) -(defn- translate-errors +(defn- process-errors [errors] - (reduce-kv (fn [acc key val] - (if (string? (:message val)) - (assoc acc key val) - (->> (translate-error-type (:code val)) - (assoc val :message) - (assoc acc key)))) - {} errors)) + (reduce (fn [acc {:keys [path name] :as error}] + (let [message (translate-error-type name)] + (assoc-in acc path + (-> (assoc error :message message) + (dissoc :path))))) + {} errors)) (defn use-form - [{:keys [initial spec] :as opts}] + [spec initial] (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) + cdata (st/conform spec (:data state)) + errors' (when (= ::st/invalid cdata) + (st/explain spec (:data state))) + + errors (merge (process-errors errors') (:errors state))] + (-> (assoc state :errors errors - :clean-data clean-data - :valid (not (seq errors))) + :clean-data (when (not= cdata ::st/invalid) cdata) + :valid (and (empty? errors) + (not= cdata ::st/invalid))) (impl-mutator update-state)))) (defn on-input-change @@ -123,15 +117,17 @@ ;; --- Additional Validators -(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")) +(st/defs ::not-empty-string #(not (empty? %))) + +;; (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 @@ -154,6 +150,9 @@ (s/def ::non-empty-string (s/and string? #(not (str/empty? %)))) +(s/def ::not-empty #(not (str/empty? %))) + + (defn- parse-number [v] (cond @@ -167,6 +166,8 @@ (s/def ::color (s/and string? #(boolean (re-matches color-re %)))) + + ;; --- Form State Events ;; --- Assoc Error diff --git a/frontend/src/uxbox/util/messages.cljs b/frontend/src/uxbox/util/messages.cljs index e31239729..edd1103d4 100644 --- a/frontend/src/uxbox/util/messages.cljs +++ b/frontend/src/uxbox/util/messages.cljs @@ -6,15 +6,16 @@ (ns uxbox.util.messages "Messages notifications." - (:require [lentes.core :as l] - [cuerdas.core :as str] - [beicon.core :as rx] - [potok.core :as ptk] - [uxbox.builtins.icons :as i] - [uxbox.util.timers :as ts] - [rumext.core :as mx :include-macros true] - [uxbox.util.data :refer [classnames]] - [uxbox.util.dom :as dom])) + (:require + [beicon.core :as rx] + [cuerdas.core :as str] + [lentes.core :as l] + [potok.core :as ptk] + [rumext.alpha :as mf] + [uxbox.builtins.icons :as i] + [uxbox.util.data :refer [classnames]] + [uxbox.util.dom :as dom] + [uxbox.util.timers :as ts])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Events @@ -29,27 +30,28 @@ (declare hide) (declare show?) -(deftype Show [data] - ptk/UpdateEvent - (update [_ state] - (let [message (assoc data :state :visible)] - (assoc state :message message))) - - ptk/WatchEvent - (watch [_ state s] - (let [stoper (->> (rx/filter show? s) - (rx/take 1))] - (->> (rx/of (hide)) - (rx/delay (:timeout data)) - (rx/take-until stoper))))) - (defn show - [message] - (Show. message)) + [data] + (reify + ptk/EventType + (type [_] ::show) + + ptk/UpdateEvent + (update [_ state] + (let [message (assoc data :state :visible)] + (assoc state :message message))) + + ptk/WatchEvent + (watch [_ state s] + (let [stoper (->> (rx/filter show? s) + (rx/take 1))] + (->> (rx/of (hide)) + (rx/delay (:timeout data)) + (rx/take-until stoper)))))) (defn show? [v] - (instance? Show v)) + (= ::show (ptk/type v))) (defn error [message & {:keys [timeout] :or {timeout 3000}}] @@ -73,25 +75,25 @@ ;; --- Hide Message -(deftype Hide [^:mutable canceled?] - ptk/UpdateEvent - (update [_ state] - (update state :message - (fn [v] - (if (nil? v) - (do (set! canceled? true) nil) - (assoc v :state :hide))))) - - ptk/WatchEvent - (watch [_ state stream] - (if canceled? - (rx/empty) - (->> (rx/of #(dissoc state :message)) - (rx/delay +animation-timeout+))))) - (defn hide [] - (Hide. false)) + (let [canceled? (volatile! {})] + (reify + ptk/UpdateEvent + (update [_ state] + (update state :message + (fn [v] + (if (nil? v) + (do (vreset! canceled? true) nil) + (assoc v :state :hide))))) + + ptk/WatchEvent + (watch [_ state stream] + (if @canceled? + (rx/empty) + (->> (rx/of #(dissoc % :message)) + (rx/delay +animation-timeout+))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; UI Components @@ -99,10 +101,10 @@ ;; --- Notification Component -(mx/defc notification-box - {:mixins [mx/static]} - [{:keys [type on-close] :as message}] - (let [classes (classnames :error (= type :error) +(mf/defc notification-box + [{:keys [message on-close] :as message}] + (let [type (:type message) + classes (classnames :error (= type :error) :info (= type :info) :hide-message (= (:state message) :hide) :quick true)] @@ -113,9 +115,8 @@ ;; --- Dialog Component -(mx/defc dialog-box - {:mixins [mx/static mx/reactive]} - [{:keys [on-accept on-cancel on-close] :as message}] +(mf/defc dialog-box + [{:keys [on-accept on-cancel on-close message] :as props}] (let [classes (classnames :info true :hide-message (= (:state message) :hide))] (letfn [(accept [event] @@ -142,11 +143,10 @@ ;; --- Main Component (entry point) -(mx/defc messages-widget - {:mixins [mx/static mx/reactive]} - [message] +(mf/defc messages-widget + [{:keys [message] :as props}] (case (:type message) - :error (notification-box message) - :info (notification-box message) - :dialog (dialog-box message) + :error (mf/element notification-box props) + :info (mf/element notification-box props) + :dialog (mf/element dialog-box props) nil))