mirror of
https://github.com/penpot/penpot.git
synced 2025-03-15 17:21:17 -05:00
🚧 More work on better forms and data validation.
This commit is contained in:
parent
04a5038ff4
commit
689cc5f3e7
21 changed files with 641 additions and 618 deletions
|
@ -5,154 +5,144 @@
|
|||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(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))))))
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -5,17 +5,18 @@
|
|||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]]])
|
||||
|
|
|
@ -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)]])
|
||||
|
|
|
@ -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)]])
|
||||
|
|
|
@ -6,118 +6,131 @@
|
|||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(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]]])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -40,7 +40,6 @@
|
|||
(-> (l/key :projects)
|
||||
(l/derive st/state)))
|
||||
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn sort-projects-by
|
||||
|
|
|
@ -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)}]]]
|
||||
|
|
|
@ -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}]))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")]]]))
|
||||
|
||||
|
|
|
@ -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)}]
|
||||
|
|
|
@ -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 <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2019 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Reference in a new issue