feat(frontend): minor refactor on login and project create form

This commit is contained in:
Andrey Antukh 2019-07-18 14:27:42 +02:00
parent 321c8d14e1
commit c2815d15ed
9 changed files with 257 additions and 238 deletions

View file

@ -4,7 +4,7 @@
com.cognitect/transit-cljs {:mvn/version "0.8.256"} com.cognitect/transit-cljs {:mvn/version "0.8.256"}
funcool/rumext {:git/url "https://github.com/funcool/rumext.git" funcool/rumext {:git/url "https://github.com/funcool/rumext.git"
:sha "1abe07bd1c2ff82c572c2495f4cae082d2b73625"} :sha "ed2e674cb774153e852cab29f197e8f40ac143d0"}
cljsjs/react-dom-server {:mvn/version "16.8.6-0"} cljsjs/react-dom-server {:mvn/version "16.8.6-0"}

View file

@ -6,7 +6,7 @@
(ns ^:figwheel-hooks uxbox.main (ns ^:figwheel-hooks uxbox.main
(:require (:require
[rumext.core :as mx :include-macros true] [rumext.core :as mx]
[uxbox.main.data.auth :refer [logout]] [uxbox.main.data.auth :refer [logout]]
[uxbox.main.data.users :as udu] [uxbox.main.data.users :as udu]
[uxbox.main.locales.en :as en] [uxbox.main.locales.en :as en]
@ -38,40 +38,13 @@
;; --- Error Handling ;; --- 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 (defn- on-navigate
[router path] [router path]
(let [match (rt/match router path)] (let [match (rt/match router path)]
;; (prn "on-navigate" path match) ;; (prn "on-navigate" path match)
(cond (cond
(and (= path "") (nil? match)) #_(and (= path "") (nil? match))
(html-history/set-path! "/dashboard/projects") #_(html-history/set-path! "/dashboard/projects")
(nil? match) (nil? match)
(prn "TODO 404") (prn "TODO 404")

View file

@ -31,10 +31,16 @@
(update [_ state] (update [_ state]
(dissoc state :lightbox))) (dissoc state :lightbox)))
;; TODO: revemo this alias
(defn hide-lightbox (defn hide-lightbox
[] []
(HideLightbox.)) (HideLightbox.))
(defn close-lightbox
[]
(HideLightbox.))
;; --- Direct Call Api ;; --- Direct Call Api
(defn open! (defn open!
@ -42,5 +48,6 @@
(st/emit! (apply show-lightbox args))) (st/emit! (apply show-lightbox args)))
(defn close! (defn close!
{:deperecated true}
[& args] [& args]
(st/emit! (apply hide-lightbox args))) (st/emit! (apply hide-lightbox args)))

View file

@ -11,7 +11,8 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[lentes.core :as l] [lentes.core :as l]
[potok.core :as ptk] [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.builtins.icons :as i]
[uxbox.main.data.auth :refer [logout]] [uxbox.main.data.auth :refer [logout]]
[uxbox.main.data.projects :as dp] [uxbox.main.data.projects :as dp]
@ -51,6 +52,35 @@
["/colors" :dashboard/colors]] ["/colors" :dashboard/colors]]
["/workspace/:project/:page" :workspace/page]]) ["/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) ;; --- Main App (Component)
(defn- parse-dashboard-params (defn- parse-dashboard-params
@ -77,7 +107,7 @@
(fn [own props] (fn [own props]
(let [route (mx/react (::route-ref own))] (let [route (mx/react (::route-ref own))]
(case (get-in route [:data :name]) (case (get-in route [:data :name])
:auth/login (auth/login-page) :auth/login (mf/element auth/login-page)
:auth/register (auth/register-page) :auth/register (auth/register-page)
:auth/recovery-request (auth/recovery-request-page) :auth/recovery-request (auth/recovery-request-page)

View file

@ -6,19 +6,21 @@
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.auth.login (ns uxbox.main.ui.auth.login
(:require [cljs.spec.alpha :as s :include-macros true] (:require
[lentes.core :as l] [cljs.spec.alpha :as s]
[cuerdas.core :as str] [cuerdas.core :as str]
[lentes.core :as l]
[rumext.core :as mx]
[rumext.func :as mf]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.config :as cfg] [uxbox.config :as cfg]
[uxbox.main.store :as st]
[uxbox.main.data.auth :as da] [uxbox.main.data.auth :as da]
[uxbox.main.store :as st]
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.ui.navigation :as nav] [uxbox.main.ui.navigation :as nav]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.forms :as fm] [uxbox.util.forms :as fm]
[rumext.core :as mx :include-macros true] [uxbox.util.i18n :refer (tr)]
[uxbox.util.router :as rt])) [uxbox.util.router :as rt]))
(def form-data (fm/focus-data :login st/state)) (def form-data (fm/focus-data :login st/state))
@ -34,32 +36,38 @@
(s/def ::login-form (s/def ::login-form
(s/keys :req-un [::username ::password])) (s/keys :req-un [::username ::password]))
(defn- on-change
(mx/defc login-form [event field]
{:mixins [mx/static mx/reactive]}
[]
(let [data (mx/react form-data)
valid? (fm/valid? ::login-form data)]
(letfn [(on-change [event field]
(let [value (dom/event->value event)] (let [value (dom/event->value event)]
(st/emit! (assoc-value field value)))) (st/emit! (assoc-value field value))))
(on-submit [event]
(defn- on-submit
[event data]
(dom/prevent-default event) (dom/prevent-default event)
(st/emit! (da/login {:username (:username data) (st/emit! (da/login {:username (:username data)
:password (:password data)})))] :password (:password data)})))
[:form {:on-submit on-submit}
[:div.login-content (mf/defc demo-warning
(when cfg/isdemo [_]
[:div.message-inline [:div.message-inline
[:p [:p
[:strong "WARNING: "] "this is a " [:strong "demo"] " service." [:strong "WARNING: "] "this is a " [:strong "demo"] " service."
[:br] [:br]
[:strong "DO NOT USE"] " for real work, " [:br] [:strong "DO NOT USE"] " for real work, " [:br]
" the projects will be periodicaly wiped."]]) " the projects will be periodicaly wiped."]])
(mf/defc login-form
{:wrap [mf/reactive]}
[]
(let [data (mf/react form-data)
valid? (fm/valid? ::login-form data)]
[:form {:on-submit #(on-submit % data)}
[:div.login-content
(when cfg/isdemo
[:& demo-warning])
[:input.input-text [:input.input-text
{:name "email" {:name "email"
:tab-index "2" :tab-index "2"
:ref "email"
:value (:username data "") :value (:username data "")
:on-change #(on-change % :username) :on-change #(on-change % :username)
:placeholder (tr "auth.email-or-username") :placeholder (tr "auth.email-or-username")
@ -67,7 +75,6 @@
[:input.input-text [:input.input-text
{:name "password" {:name "password"
:tab-index "3" :tab-index "3"
:ref "password"
:value (:password data "") :value (:password data "")
:on-change #(on-change % :password) :on-change #(on-change % :password)
:placeholder (tr "auth.password") :placeholder (tr "auth.password")
@ -80,22 +87,21 @@
:value (tr "auth.signin") :value (tr "auth.signin")
:type "submit"}] :type "submit"}]
[:div.login-links [:div.login-links
[:a {:on-click #(st/emit! (rt/navigate :auth/recovery-request)) [:a {:on-click #(st/emit! (rt/nav :auth/recovery-request))
:tab-index "5"} :tab-index "5"}
(tr "auth.forgot-password")] (tr "auth.forgot-password")]
[:a {:on-click #(st/emit! (rt/navigate :auth/register)) [:a {:on-click #(st/emit! (rt/nav :auth/register))
:tab-index "6"} :tab-index "6"}
(tr "auth.no-account")]]]]))) (tr "auth.no-account")]]]]))
(mx/defc login-page
{:mixins [mx/static (fm/clear-mixin st/store :login)] ;; {:mixins [mx/static (fm/clear-mixin st/store :login)]}
:init (fn [own]
(when @st/auth-ref (mf/defc login-page
(st/emit! (rt/navigate :dashboard/projects)))
own)}
[] []
(mf/use-effect :end #(st/emit! (fm/clear-form :login)))
[:div.login [:div.login
[:div.login-body [:div.login-body
(messages-widget) (messages-widget)
[:a i/logo] [:a i/logo]
(login-form)]]) [:& login-form]]])

View file

@ -6,26 +6,27 @@
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.dashboard.projects-createform (ns uxbox.main.ui.dashboard.projects-createform
(:require [cljs.spec.alpha :as s :include-macros true] (:require
[lentes.core :as l] [cljs.spec.alpha :as s :include-macros true]
[cuerdas.core :as str] [cuerdas.core :as str]
[lentes.core :as l]
[rumext.core :as mx]
[rumext.func :as mxf]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.store :as st]
[uxbox.main.constants :as c] [uxbox.main.constants :as c]
[uxbox.main.data.projects :as udp]
[uxbox.main.data.lightbox :as udl] [uxbox.main.data.lightbox :as udl]
[uxbox.main.data.projects :as udp]
[uxbox.main.store :as st]
[uxbox.main.ui.lightbox :as lbx] [uxbox.main.ui.lightbox :as lbx]
[uxbox.util.data :refer [read-string parse-int]] [uxbox.util.data :refer [read-string parse-int]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.forms :as fm] [uxbox.util.forms :as fm]
[uxbox.util.i18n :as t :refer [tr]] [uxbox.util.i18n :as t :refer [tr]]
[uxbox.util.router :as r] [uxbox.util.router :as r]
[rumext.core :as mx :include-macros true]
[uxbox.util.time :as dt])) [uxbox.util.time :as dt]))
(def form-data (fm/focus-data :create-project st/state)) (def form-data (fm/focus-data :create-project st/state))
(def form-errors (fm/focus-errors :create-project st/state)) (def form-errors (fm/focus-errors :create-project st/state))
(def assoc-value (partial fm/assoc-value :create-project)) (def assoc-value (partial fm/assoc-value :create-project))
(def clear-form (partial fm/clear-form :create-project)) (def clear-form (partial fm/clear-form :create-project))
@ -42,9 +43,8 @@
;; --- Create Project Form ;; --- Create Project Form
(mx/defc layout-input (mxf/defc layout-input
{:mixins [mx/static]} [{:keys [::layout-id] :as data}]
[data layout-id]
(let [layout (get c/page-layouts layout-id)] (let [layout (get c/page-layouts layout-id)]
[:div [:div
[:input {:type "radio" [:input {:type "radio"
@ -52,7 +52,7 @@
:id layout-id :id layout-id
:name "project-layout" :name "project-layout"
:value (:name 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) :on-change #(st/emit! (assoc-value :layout layout-id)
(assoc-value :width (:width layout)) (assoc-value :width (:width layout))
(assoc-value :height (:height layout)))}] (assoc-value :height (:height layout)))}]
@ -60,26 +60,25 @@
:for layout-id} :for layout-id}
(:name layout)]])) (:name layout)]]))
(mx/defc layout-selector (mxf/defc layout-selector
{:mixins [mx/static]} [props]
[data]
[:div.input-radio.radio-primary [:div.input-radio.radio-primary
(layout-input data "mobile") (layout-input (assoc props ::layout-id "mobile"))
(layout-input data "tablet") (layout-input (assoc props ::layout-id "tablet"))
(layout-input data "notebook") (layout-input (assoc props ::layout-id "notebook"))
(layout-input data "desktop")]) (layout-input (assoc props ::layout-id "desktop"))])
(mx/defc create-project-form (mx/def create-project-form
{:mixins [mx/reactive mx/static]} :mixins #{mx/reactive}
[] :render
(fn [own props]
(let [data (merge c/project-defaults (mx/react form-data)) (let [data (merge c/project-defaults (mx/react form-data))
errors (mx/react form-errors)
valid? (fm/valid? ::project-form data)] valid? (fm/valid? ::project-form data)]
(letfn [(on-submit [event] (letfn [(on-submit [event]
(dom/prevent-default event) (dom/prevent-default event)
(when valid? (when valid?
(st/emit! (udp/create-project data)) (st/emit! (udp/create-project data)
(udl/close!))) (udl/close-lightbox))))
(update-size [field e] (update-size [field e]
(let [value (dom/event->value e) (let [value (dom/event->value e)
@ -107,7 +106,7 @@
:type "number" :type "number"
:min 0 ;;TODO check this value :min 0 ;;TODO check this value
:max 666666 ;;TODO check this value :max 666666 ;;TODO check this value
:value (:width data) :value (str (:width data))
:on-change (partial update-size :width)}]] :on-change (partial update-size :width)}]]
[:a.toggle-layout {:on-click swap-size} i/toggle] [:a.toggle-layout {:on-click swap-size} i/toggle]
[:div.input-element.pixels [:div.input-element.pixels
@ -117,7 +116,7 @@
:type "number" :type "number"
:min 0 ;;TODO check this value :min 0 ;;TODO check this value
:max 666666 ;;TODO check this value :max 666666 ;;TODO check this value
:value (:height data) :value (str (:height data))
:on-change (partial update-size :height)}]]] :on-change (partial update-size :height)}]]]
;; Layout selector ;; Layout selector
@ -128,21 +127,23 @@
{:value "Go go go!" {:value "Go go go!"
:class (when-not valid? "btn-disabled") :class (when-not valid? "btn-disabled")
:disabled (not valid?) :disabled (not valid?)
:type "submit"}]]))) :type "submit"}]]))))
;; --- Create Project Lightbox ;; --- Create Project Lightbox
(mx/defcs create-project-lightbox (mx/def create-project-lightbox
{:mixins [mx/static mx/reactive :will-unmount
(fm/clear-mixin st/store :create-project)]} (fn [own]
[own] (st/emit! (fm/clear-form :create-project))
(letfn [(close [] own)
(udl/close!)
(st/emit! (clear-form)))] :render
(fn [own]
[:div.lightbox-body [:div.lightbox-body
[:h3 "New project"] [:h3 "New project"]
(create-project-form) (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 (defmethod lbx/render-lightbox :create-project
[_] [_]

View file

@ -1,15 +1,16 @@
(ns uxbox.main.ui.lightbox (ns uxbox.main.ui.lightbox
(:require [lentes.core :as l] (:require
[uxbox.main.store :as st] [goog.events :as events]
[lentes.core :as l]
[rumext.core :as mx]
[uxbox.main.data.lightbox :as udl] [uxbox.main.data.lightbox :as udl]
[rumext.core :as mx :include-macros true] [uxbox.main.store :as st]
[uxbox.main.ui.keyboard :as k] [uxbox.main.ui.keyboard :as k]
[uxbox.util.dom :as dom]
[uxbox.util.data :refer [classnames]] [uxbox.util.data :refer [classnames]]
[goog.events :as events]) [uxbox.util.dom :as dom])
(:import goog.events.EventType)) (:import goog.events.EventType))
;; --- Lentes ;; --- Refs
(def ^:private lightbox-ref (def ^:private lightbox-ref
(-> (l/key :lightbox) (-> (l/key :lightbox)
@ -28,34 +29,33 @@
(defn- on-out-clicked (defn- on-out-clicked
[own event] [own event]
(let [parent (mx/ref-node own "parent") (let [parent (mx/ref-node (::parent own))
current (dom/get-target event)] current (dom/get-target event)]
(when (dom/equals? parent current) (when (dom/equals? parent current)
(udl/close!)))) (st/emit! (udl/hide-lightbox)))))
(defn- lightbox-init (mx/def lightbox
[own] :mixins #{mx/reactive}
(let [key (events/listen js/document
EventType.KEYDOWN
on-esc-clicked)]
(assoc own ::key key)))
(defn- lightbox-will-umount :init
[own] (fn [own props]
(events/unlistenByKey (::key own)) (let [key (events/listen js/document EventType.KEYDOWN on-esc-clicked)]
(dissoc own ::key)) (assoc own
::key-listener key
::parent (mx/create-ref))))
(mx/defcs lightbox :will-unmount
{:mixins [mx/reactive] (fn [own]
:init lightbox-init (events/unlistenByKey (::key-listener own))
:will-unmount lightbox-will-umount} (dissoc own ::key-listener))
[own]
:render
(fn [own props]
(let [data (mx/react lightbox-ref) (let [data (mx/react lightbox-ref)
classes (classnames classes (classnames
:hide (nil? data) :hide (nil? data)
:transparent (:transparent? data))] :transparent (:transparent? data))]
[:div.lightbox [:div.lightbox {:class classes
{:class classes :ref (::parent own)
:ref "parent"
:on-click (partial on-out-clicked own)} :on-click (partial on-out-clicked own)}
(render-lightbox data)])) (render-lightbox data)])))

View file

@ -5,10 +5,11 @@
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.util.forms (ns uxbox.util.forms
(:require [cljs.spec.alpha :as s :include-macros true] (:require
[beicon.core :as rx]
[cljs.spec.alpha :as s :include-macros true]
[cuerdas.core :as str] [cuerdas.core :as str]
[lentes.core :as l] [lentes.core :as l]
[beicon.core :as rx]
[potok.core :as ptk] [potok.core :as ptk]
[rumext.core :as mx :include-macros true] [rumext.core :as mx :include-macros true]
[uxbox.util.i18n :refer [tr]])) [uxbox.util.i18n :refer [tr]]))

View file

@ -7,7 +7,8 @@
:provides ["vendor.jszip"]} :provides ["vendor.jszip"]}
{:file "datefns/datefns.js" {:file "datefns/datefns.js"
:file-min "datefns/datefns.min.js" :file-min "datefns/datefns.min.js"
:provides ["vendor.datefns"]}] :provides ["vendor.datefns"]}
]
:externs ["main.externs.js" :externs ["main.externs.js"
"snapsvg/externs.js" "snapsvg/externs.js"
"jszip/externs.js" "jszip/externs.js"