From c2815d15ed46dc1214939f66da5eca5b992b5245 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 18 Jul 2019 14:27:42 +0200 Subject: [PATCH] feat(frontend): minor refactor on login and project create form --- frontend/deps.edn | 2 +- frontend/src/uxbox/main.cljs | 33 +--- frontend/src/uxbox/main/data/lightbox.cljs | 7 + frontend/src/uxbox/main/ui.cljs | 34 +++- frontend/src/uxbox/main/ui/auth/login.cljs | 146 +++++++------- .../ui/dashboard/projects_createform.cljs | 185 +++++++++--------- frontend/src/uxbox/main/ui/lightbox.cljs | 70 +++---- frontend/src/uxbox/util/forms.cljs | 15 +- frontend/vendor/deps.cljs | 3 +- 9 files changed, 257 insertions(+), 238 deletions(-) diff --git a/frontend/deps.edn b/frontend/deps.edn index 398f96a4f..70e7f26a5 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -4,7 +4,7 @@ com.cognitect/transit-cljs {:mvn/version "0.8.256"} funcool/rumext {:git/url "https://github.com/funcool/rumext.git" - :sha "1abe07bd1c2ff82c572c2495f4cae082d2b73625"} + :sha "ed2e674cb774153e852cab29f197e8f40ac143d0"} cljsjs/react-dom-server {:mvn/version "16.8.6-0"} diff --git a/frontend/src/uxbox/main.cljs b/frontend/src/uxbox/main.cljs index bd3bed18a..42170a443 100644 --- a/frontend/src/uxbox/main.cljs +++ b/frontend/src/uxbox/main.cljs @@ -6,7 +6,7 @@ (ns ^:figwheel-hooks uxbox.main (:require - [rumext.core :as mx :include-macros true] + [rumext.core :as mx] [uxbox.main.data.auth :refer [logout]] [uxbox.main.data.users :as udu] [uxbox.main.locales.en :as en] @@ -38,40 +38,13 @@ ;; --- Error Handling -(defn- on-error - "A default error handler." - [{:keys [status] :as error}] - (js/console.error "on-error:" (pr-str error)) - (js/console.error (.-stack error)) - (reset! st/loader false) - (cond - ;; Unauthorized or Auth timeout - (and (:status error) - (or (= (:status error) 403) - (= (:status error) 419))) - (ts/schedule 0 #(st/emit! (rt/nav :auth-login))) - - ;; Conflict - (= status 412) - (ts/schedule 100 #(st/emit! (uum/error (tr "errors.conflict")))) - - ;; Network error - (= (:status error) 0) - (ts/schedule 100 #(st/emit! (uum/error (tr "errors.network")))) - - ;; Something else - :else - (ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic")))))) - -(set! st/*on-error* on-error) - (defn- on-navigate [router path] (let [match (rt/match router path)] ;; (prn "on-navigate" path match) (cond - (and (= path "") (nil? match)) - (html-history/set-path! "/dashboard/projects") + #_(and (= path "") (nil? match)) + #_(html-history/set-path! "/dashboard/projects") (nil? match) (prn "TODO 404") diff --git a/frontend/src/uxbox/main/data/lightbox.cljs b/frontend/src/uxbox/main/data/lightbox.cljs index 98e3d64f9..14ec44087 100644 --- a/frontend/src/uxbox/main/data/lightbox.cljs +++ b/frontend/src/uxbox/main/data/lightbox.cljs @@ -31,10 +31,16 @@ (update [_ state] (dissoc state :lightbox))) +;; TODO: revemo this alias (defn hide-lightbox [] (HideLightbox.)) +(defn close-lightbox + [] + (HideLightbox.)) + + ;; --- Direct Call Api (defn open! @@ -42,5 +48,6 @@ (st/emit! (apply show-lightbox args))) (defn close! + {:deperecated true} [& args] (st/emit! (apply hide-lightbox args))) diff --git a/frontend/src/uxbox/main/ui.cljs b/frontend/src/uxbox/main/ui.cljs index dd682e4a9..8ac1ba9ed 100644 --- a/frontend/src/uxbox/main/ui.cljs +++ b/frontend/src/uxbox/main/ui.cljs @@ -11,7 +11,8 @@ [cuerdas.core :as str] [lentes.core :as l] [potok.core :as ptk] - [rumext.core :as mx :include-macros true] + [rumext.core :as mx] + [rumext.func :as mf] [uxbox.builtins.icons :as i] [uxbox.main.data.auth :refer [logout]] [uxbox.main.data.projects :as dp] @@ -51,6 +52,35 @@ ["/colors" :dashboard/colors]] ["/workspace/:project/:page" :workspace/page]]) +;; --- Error Handling + +(defn- on-error + "A default error handler." + [{:keys [status] :as error}] + (js/console.error "on-error:" (pr-str error)) + (js/console.error (.-stack error)) + (reset! st/loader false) + (cond + ;; Unauthorized or Auth timeout + (and (:status error) + (or (= (:status error) 403) + (= (:status error) 419))) + (ts/schedule 0 #(st/emit! (rt/nav :auth/login))) + + ;; Conflict + (= status 412) + (ts/schedule 100 #(st/emit! (uum/error (tr "errors.conflict")))) + + ;; Network error + (= (:status error) 0) + (ts/schedule 100 #(st/emit! (uum/error (tr "errors.network")))) + + ;; Something else + :else + (ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic")))))) + +(set! st/*on-error* on-error) + ;; --- Main App (Component) (defn- parse-dashboard-params @@ -77,7 +107,7 @@ (fn [own props] (let [route (mx/react (::route-ref own))] (case (get-in route [:data :name]) - :auth/login (auth/login-page) + :auth/login (mf/element auth/login-page) :auth/register (auth/register-page) :auth/recovery-request (auth/recovery-request-page) diff --git a/frontend/src/uxbox/main/ui/auth/login.cljs b/frontend/src/uxbox/main/ui/auth/login.cljs index 9c3e22567..1bb8832ab 100644 --- a/frontend/src/uxbox/main/ui/auth/login.cljs +++ b/frontend/src/uxbox/main/ui/auth/login.cljs @@ -6,20 +6,22 @@ ;; Copyright (c) 2015-2016 Juan de la Cruz (ns uxbox.main.ui.auth.login - (:require [cljs.spec.alpha :as s :include-macros true] - [lentes.core :as l] - [cuerdas.core :as str] - [uxbox.builtins.icons :as i] - [uxbox.config :as cfg] - [uxbox.main.store :as st] - [uxbox.main.data.auth :as da] - [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 + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [lentes.core :as l] + [rumext.core :as mx] + [rumext.func :as mf] + [uxbox.builtins.icons :as i] + [uxbox.config :as cfg] + [uxbox.main.data.auth :as da] + [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 :login st/state)) (def form-errors (fm/focus-errors :login st/state)) @@ -34,68 +36,72 @@ (s/def ::login-form (s/keys :req-un [::username ::password])) +(defn- on-change + [event field] + (let [value (dom/event->value event)] + (st/emit! (assoc-value field value)))) -(mx/defc login-form - {:mixins [mx/static mx/reactive]} +(defn- on-submit + [event data] + (dom/prevent-default event) + (st/emit! (da/login {:username (:username data) + :password (:password data)}))) + +(mf/defc demo-warning + [_] + [:div.message-inline + [:p + [:strong "WARNING: "] "this is a " [:strong "demo"] " service." + [:br] + [:strong "DO NOT USE"] " for real work, " [:br] + " the projects will be periodicaly wiped."]]) + +(mf/defc login-form + {:wrap [mf/reactive]} [] - (let [data (mx/react form-data) + (let [data (mf/react form-data) valid? (fm/valid? ::login-form data)] - (letfn [(on-change [event field] - (let [value (dom/event->value event)] - (st/emit! (assoc-value field value)))) - (on-submit [event] - (dom/prevent-default event) - (st/emit! (da/login {:username (:username data) - :password (:password data)})))] - [:form {:on-submit on-submit} - [:div.login-content - (when cfg/isdemo - [:div.message-inline - [:p - [:strong "WARNING: "] "this is a " [:strong "demo"] " service." - [:br] - [:strong "DO NOT USE"] " for real work, " [:br] - " the projects will be periodicaly wiped."]]) - [:input.input-text - {:name "email" - :tab-index "2" - :ref "email" - :value (:username data "") - :on-change #(on-change % :username) - :placeholder (tr "auth.email-or-username") - :type "text"}] - [:input.input-text - {:name "password" - :tab-index "3" - :ref "password" - :value (:password data "") - :on-change #(on-change % :password) - :placeholder (tr "auth.password") - :type "password"}] - [:input.btn-primary - {:name "login" - :tab-index "4" - :class (when-not valid? "btn-disabled") - :disabled (not valid?) - :value (tr "auth.signin") - :type "submit"}] - [:div.login-links - [:a {:on-click #(st/emit! (rt/navigate :auth/recovery-request)) - :tab-index "5"} - (tr "auth.forgot-password")] - [:a {:on-click #(st/emit! (rt/navigate :auth/register)) - :tab-index "6"} - (tr "auth.no-account")]]]]))) + [:form {:on-submit #(on-submit % data)} + [:div.login-content + (when cfg/isdemo + [:& demo-warning]) + [:input.input-text + {:name "email" + :tab-index "2" + :value (:username data "") + :on-change #(on-change % :username) + :placeholder (tr "auth.email-or-username") + :type "text"}] + [:input.input-text + {:name "password" + :tab-index "3" + :value (:password data "") + :on-change #(on-change % :password) + :placeholder (tr "auth.password") + :type "password"}] + [:input.btn-primary + {:name "login" + :tab-index "4" + :class (when-not valid? "btn-disabled") + :disabled (not valid?) + :value (tr "auth.signin") + :type "submit"}] + [:div.login-links + [:a {:on-click #(st/emit! (rt/nav :auth/recovery-request)) + :tab-index "5"} + (tr "auth.forgot-password")] + [:a {:on-click #(st/emit! (rt/nav :auth/register)) + :tab-index "6"} + (tr "auth.no-account")]]]])) -(mx/defc login-page - {:mixins [mx/static (fm/clear-mixin st/store :login)] - :init (fn [own] - (when @st/auth-ref - (st/emit! (rt/navigate :dashboard/projects))) - own)} + +;; {:mixins [mx/static (fm/clear-mixin st/store :login)]} + +(mf/defc login-page [] + (mf/use-effect :end #(st/emit! (fm/clear-form :login))) [:div.login [:div.login-body (messages-widget) [:a i/logo] - (login-form)]]) + [:& login-form]]]) diff --git a/frontend/src/uxbox/main/ui/dashboard/projects_createform.cljs b/frontend/src/uxbox/main/ui/dashboard/projects_createform.cljs index 0e1447ca7..030b2e91d 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects_createform.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects_createform.cljs @@ -6,26 +6,27 @@ ;; Copyright (c) 2015-2017 Juan de la Cruz (ns uxbox.main.ui.dashboard.projects-createform - (: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.constants :as c] - [uxbox.main.data.projects :as udp] - [uxbox.main.data.lightbox :as udl] - [uxbox.main.ui.lightbox :as lbx] - [uxbox.util.data :refer [read-string parse-int]] - [uxbox.util.dom :as dom] - [uxbox.util.forms :as fm] - [uxbox.util.i18n :as t :refer [tr]] - [uxbox.util.router :as r] - [rumext.core :as mx :include-macros true] - [uxbox.util.time :as dt])) + (:require + [cljs.spec.alpha :as s :include-macros true] + [cuerdas.core :as str] + [lentes.core :as l] + [rumext.core :as mx] + [rumext.func :as mxf] + [uxbox.builtins.icons :as i] + [uxbox.main.constants :as c] + [uxbox.main.data.lightbox :as udl] + [uxbox.main.data.projects :as udp] + [uxbox.main.store :as st] + [uxbox.main.ui.lightbox :as lbx] + [uxbox.util.data :refer [read-string parse-int]] + [uxbox.util.dom :as dom] + [uxbox.util.forms :as fm] + [uxbox.util.i18n :as t :refer [tr]] + [uxbox.util.router :as r] + [uxbox.util.time :as dt])) (def form-data (fm/focus-data :create-project st/state)) (def form-errors (fm/focus-errors :create-project st/state)) - (def assoc-value (partial fm/assoc-value :create-project)) (def clear-form (partial fm/clear-form :create-project)) @@ -42,9 +43,8 @@ ;; --- Create Project Form -(mx/defc layout-input - {:mixins [mx/static]} - [data layout-id] +(mxf/defc layout-input + [{:keys [::layout-id] :as data}] (let [layout (get c/page-layouts layout-id)] [:div [:input {:type "radio" @@ -52,7 +52,7 @@ :id layout-id :name "project-layout" :value (:name layout) - :checked (when (= layout-id (:layout data)) "checked") + :checked (= layout-id (:layout data)) :on-change #(st/emit! (assoc-value :layout layout-id) (assoc-value :width (:width layout)) (assoc-value :height (:height layout)))}] @@ -60,89 +60,90 @@ :for layout-id} (:name layout)]])) -(mx/defc layout-selector - {:mixins [mx/static]} - [data] +(mxf/defc layout-selector + [props] [:div.input-radio.radio-primary - (layout-input data "mobile") - (layout-input data "tablet") - (layout-input data "notebook") - (layout-input data "desktop")]) + (layout-input (assoc props ::layout-id "mobile")) + (layout-input (assoc props ::layout-id "tablet")) + (layout-input (assoc props ::layout-id "notebook")) + (layout-input (assoc props ::layout-id "desktop"))]) -(mx/defc create-project-form - {:mixins [mx/reactive mx/static]} - [] - (let [data (merge c/project-defaults (mx/react form-data)) - errors (mx/react form-errors) - valid? (fm/valid? ::project-form data)] - (letfn [(on-submit [event] - (dom/prevent-default event) - (when valid? - (st/emit! (udp/create-project data)) - (udl/close!))) +(mx/def create-project-form + :mixins #{mx/reactive} + :render + (fn [own props] + (let [data (merge c/project-defaults (mx/react form-data)) + valid? (fm/valid? ::project-form data)] + (letfn [(on-submit [event] + (dom/prevent-default event) + (when valid? + (st/emit! (udp/create-project data) + (udl/close-lightbox)))) - (update-size [field e] - (let [value (dom/event->value e) - value (parse-int value)] - (st/emit! (assoc-value field value)))) + (update-size [field e] + (let [value (dom/event->value e) + value (parse-int value)] + (st/emit! (assoc-value field value)))) - (update-name [e] - (let [value (dom/event->value e)] - (st/emit! (assoc-value :name value)))) - (swap-size [] - (st/emit! (assoc-value :width (:height data)) - (assoc-value :height (:width data))))] - [:form {:on-submit on-submit} - [:input#project-name.input-text - {:placeholder "New project name" - :type "text" - :value (:name data) - :auto-focus true - :on-change update-name}] - [:div.project-size - [:div.input-element.pixels - [:span "Width"] - [:input#project-witdh.input-text - {:placeholder "Width" - :type "number" - :min 0 ;;TODO check this value - :max 666666 ;;TODO check this value - :value (:width data) - :on-change (partial update-size :width)}]] - [:a.toggle-layout {:on-click swap-size} i/toggle] - [:div.input-element.pixels - [:span "Height"] - [:input#project-height.input-text - {:placeholder "Height" - :type "number" - :min 0 ;;TODO check this value - :max 666666 ;;TODO check this value - :value (:height data) - :on-change (partial update-size :height)}]]] + (update-name [e] + (let [value (dom/event->value e)] + (st/emit! (assoc-value :name value)))) + (swap-size [] + (st/emit! (assoc-value :width (:height data)) + (assoc-value :height (:width data))))] + [:form {:on-submit on-submit} + [:input#project-name.input-text + {:placeholder "New project name" + :type "text" + :value (:name data) + :auto-focus true + :on-change update-name}] + [:div.project-size + [:div.input-element.pixels + [:span "Width"] + [:input#project-witdh.input-text + {:placeholder "Width" + :type "number" + :min 0 ;;TODO check this value + :max 666666 ;;TODO check this value + :value (str (:width data)) + :on-change (partial update-size :width)}]] + [:a.toggle-layout {:on-click swap-size} i/toggle] + [:div.input-element.pixels + [:span "Height"] + [:input#project-height.input-text + {:placeholder "Height" + :type "number" + :min 0 ;;TODO check this value + :max 666666 ;;TODO check this value + :value (str (:height data)) + :on-change (partial update-size :height)}]]] - ;; Layout selector - (layout-selector data) + ;; Layout selector + (layout-selector data) - ;; Submit - [:input#project-btn.btn-primary - {:value "Go go go!" - :class (when-not valid? "btn-disabled") - :disabled (not valid?) - :type "submit"}]]))) + ;; Submit + [:input#project-btn.btn-primary + {:value "Go go go!" + :class (when-not valid? "btn-disabled") + :disabled (not valid?) + :type "submit"}]])))) ;; --- Create Project Lightbox -(mx/defcs create-project-lightbox - {:mixins [mx/static mx/reactive - (fm/clear-mixin st/store :create-project)]} - [own] - (letfn [(close [] - (udl/close!) - (st/emit! (clear-form)))] +(mx/def create-project-lightbox + :will-unmount + (fn [own] + (st/emit! (fm/clear-form :create-project)) + own) + + :render + (fn [own] [:div.lightbox-body [:h3 "New project"] (create-project-form) - [:a.close {:on-click #(udl/close!)} i/close]])) + [:a.close {:on-click #(st/emit! (udl/close-lightbox))} + i/close]])) (defmethod lbx/render-lightbox :create-project [_] diff --git a/frontend/src/uxbox/main/ui/lightbox.cljs b/frontend/src/uxbox/main/ui/lightbox.cljs index 8b9f6cde6..0b569bdbc 100644 --- a/frontend/src/uxbox/main/ui/lightbox.cljs +++ b/frontend/src/uxbox/main/ui/lightbox.cljs @@ -1,15 +1,16 @@ (ns uxbox.main.ui.lightbox - (:require [lentes.core :as l] - [uxbox.main.store :as st] - [uxbox.main.data.lightbox :as udl] - [rumext.core :as mx :include-macros true] - [uxbox.main.ui.keyboard :as k] - [uxbox.util.dom :as dom] - [uxbox.util.data :refer [classnames]] - [goog.events :as events]) + (:require + [goog.events :as events] + [lentes.core :as l] + [rumext.core :as mx] + [uxbox.main.data.lightbox :as udl] + [uxbox.main.store :as st] + [uxbox.main.ui.keyboard :as k] + [uxbox.util.data :refer [classnames]] + [uxbox.util.dom :as dom]) (:import goog.events.EventType)) -;; --- Lentes +;; --- Refs (def ^:private lightbox-ref (-> (l/key :lightbox) @@ -28,34 +29,33 @@ (defn- on-out-clicked [own event] - (let [parent (mx/ref-node own "parent") + (let [parent (mx/ref-node (::parent own)) current (dom/get-target event)] (when (dom/equals? parent current) - (udl/close!)))) + (st/emit! (udl/hide-lightbox))))) -(defn- lightbox-init - [own] - (let [key (events/listen js/document - EventType.KEYDOWN - on-esc-clicked)] - (assoc own ::key key))) +(mx/def lightbox + :mixins #{mx/reactive} -(defn- lightbox-will-umount - [own] - (events/unlistenByKey (::key own)) - (dissoc own ::key)) + :init + (fn [own props] + (let [key (events/listen js/document EventType.KEYDOWN on-esc-clicked)] + (assoc own + ::key-listener key + ::parent (mx/create-ref)))) -(mx/defcs lightbox - {:mixins [mx/reactive] - :init lightbox-init - :will-unmount lightbox-will-umount} - [own] - (let [data (mx/react lightbox-ref) - classes (classnames - :hide (nil? data) - :transparent (:transparent? data))] - [:div.lightbox - {:class classes - :ref "parent" - :on-click (partial on-out-clicked own)} - (render-lightbox data)])) + :will-unmount + (fn [own] + (events/unlistenByKey (::key-listener own)) + (dissoc own ::key-listener)) + + :render + (fn [own props] + (let [data (mx/react lightbox-ref) + classes (classnames + :hide (nil? data) + :transparent (:transparent? data))] + [:div.lightbox {:class classes + :ref (::parent own) + :on-click (partial on-out-clicked own)} + (render-lightbox data)]))) diff --git a/frontend/src/uxbox/util/forms.cljs b/frontend/src/uxbox/util/forms.cljs index 4a50a721a..b1a81a61a 100644 --- a/frontend/src/uxbox/util/forms.cljs +++ b/frontend/src/uxbox/util/forms.cljs @@ -5,13 +5,14 @@ ;; Copyright (c) 2015-2017 Andrey Antukh (ns uxbox.util.forms - (:require [cljs.spec.alpha :as s :include-macros true] - [cuerdas.core :as str] - [lentes.core :as l] - [beicon.core :as rx] - [potok.core :as ptk] - [rumext.core :as mx :include-macros true] - [uxbox.util.i18n :refer [tr]])) + (:require + [beicon.core :as rx] + [cljs.spec.alpha :as s :include-macros true] + [cuerdas.core :as str] + [lentes.core :as l] + [potok.core :as ptk] + [rumext.core :as mx :include-macros true] + [uxbox.util.i18n :refer [tr]])) ;; --- Form Validation Api diff --git a/frontend/vendor/deps.cljs b/frontend/vendor/deps.cljs index 707c1b8ef..d6cc7ae43 100644 --- a/frontend/vendor/deps.cljs +++ b/frontend/vendor/deps.cljs @@ -7,7 +7,8 @@ :provides ["vendor.jszip"]} {:file "datefns/datefns.js" :file-min "datefns/datefns.min.js" - :provides ["vendor.datefns"]}] + :provides ["vendor.datefns"]} + ] :externs ["main.externs.js" "snapsvg/externs.js" "jszip/externs.js"