mirror of
https://github.com/penpot/penpot.git
synced 2025-06-13 16:51:38 +02:00
♻️ Initial profile and auth refactor.
This commit is contained in:
parent
d0defe5d93
commit
7d5f9c1078
59 changed files with 2712 additions and 1407 deletions
|
@ -41,7 +41,7 @@
|
|||
(and (or (= path "")
|
||||
(nil? match))
|
||||
(not authed?))
|
||||
(st/emit! (rt/nav :login))
|
||||
(st/emit! (rt/nav :auth-login))
|
||||
|
||||
(and (nil? match) authed?)
|
||||
(st/emit! (rt/nav :dashboard-team {:team-id (:default-team-id profile)}))
|
||||
|
|
|
@ -51,14 +51,18 @@
|
|||
|
||||
ptk/WatchEvent
|
||||
(watch [this state s]
|
||||
(let [params {:email email
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error identity
|
||||
on-success identity}} (meta data)
|
||||
params {:email email
|
||||
:password password
|
||||
:scope "webapp"}
|
||||
on-error #(rx/of (dm/error (tr "errors.auth.unauthorized")))]
|
||||
:scope "webapp"}]
|
||||
(->> (rp/mutation :login params)
|
||||
(rx/map logged-in)
|
||||
(rx/catch rp/client-error? on-error))))))
|
||||
|
||||
(rx/tap on-success)
|
||||
(rx/catch (fn [err]
|
||||
(on-error err)
|
||||
(rx/empty)))
|
||||
(rx/map logged-in))))))
|
||||
;; --- Logout
|
||||
|
||||
(def clear-user-data
|
||||
|
@ -81,8 +85,8 @@
|
|||
(ptk/reify ::logout
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (rt/nav :login)
|
||||
clear-user-data))))
|
||||
(rx/of clear-user-data
|
||||
(rt/nav :auth-login)))))
|
||||
|
||||
;; --- Register
|
||||
|
||||
|
@ -93,18 +97,37 @@
|
|||
|
||||
(defn register
|
||||
"Create a register event instance."
|
||||
[data on-error]
|
||||
[data]
|
||||
(s/assert ::register data)
|
||||
(s/assert fn? on-error)
|
||||
(ptk/reify ::register
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(letfn [(handle-error [{payload :payload}]
|
||||
(on-error payload)
|
||||
(rx/empty))]
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error identity
|
||||
on-success identity}} (meta data)]
|
||||
(->> (rp/mutation :register-profile data)
|
||||
(rx/map (fn [_] (login data)))
|
||||
(rx/catch rp/client-error? handle-error))))))
|
||||
(rx/tap on-success)
|
||||
(rx/map #(login data))
|
||||
(rx/catch (fn [err]
|
||||
(on-error err)
|
||||
(rx/empty))))))))
|
||||
|
||||
|
||||
;; --- Request Account Deletion
|
||||
|
||||
(def request-account-deletion
|
||||
(letfn [(on-error [{:keys [code] :as error}]
|
||||
(if (= :uxbox.services.mutations.profile/owner-teams-with-people code)
|
||||
(let [msg (tr "settings.notifications.profile-deletion-not-allowed")]
|
||||
(rx/of (dm/error msg)))
|
||||
(rx/empty)))]
|
||||
(ptk/reify ::request-account-deletion
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/concat
|
||||
(->> (rp/mutation :delete-profile {})
|
||||
(rx/map #(rt/nav :auth-goodbye))
|
||||
(rx/catch on-error)))))))
|
||||
|
||||
;; --- Recovery Request
|
||||
|
||||
|
@ -112,38 +135,43 @@
|
|||
(s/keys :req-un [::email]))
|
||||
|
||||
(defn request-profile-recovery
|
||||
[data on-success]
|
||||
[data]
|
||||
(us/verify ::recovery-request data)
|
||||
(us/verify fn? on-success)
|
||||
(ptk/reify ::request-profile-recovery
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(letfn [(on-error [{payload :payload}]
|
||||
(rx/empty))]
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error identity
|
||||
on-success identity}} (meta data)]
|
||||
|
||||
(->> (rp/mutation :request-profile-recovery data)
|
||||
(rx/tap on-success)
|
||||
(rx/catch rp/client-error? on-error))))))
|
||||
(rx/catch (fn [err]
|
||||
(on-error err)
|
||||
(rx/empty))))))))
|
||||
|
||||
|
||||
;; --- Recovery (Password)
|
||||
|
||||
(s/def ::token string?)
|
||||
(s/def ::on-error fn?)
|
||||
(s/def ::on-success fn?)
|
||||
|
||||
(s/def ::recover-profile
|
||||
(s/keys :req-un [::password ::token ::on-error ::on-success]))
|
||||
(s/keys :req-un [::password ::token]))
|
||||
|
||||
(defn recover-profile
|
||||
[{:keys [token password on-error on-success] :as data}]
|
||||
[{:keys [token password] :as data}]
|
||||
(us/verify ::recover-profile data)
|
||||
(ptk/reify ::recover-profile
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/mutation :recover-profile {:token token :password password})
|
||||
(rx/tap on-success)
|
||||
(rx/catch (fn [err]
|
||||
(on-error)
|
||||
(rx/empty)))))))
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error identity
|
||||
on-success identity}} (meta data)]
|
||||
(->> (rp/mutation :recover-profile data)
|
||||
(rx/tap on-success)
|
||||
(rx/catch (fn [err]
|
||||
(on-error)
|
||||
(rx/empty))))))))
|
||||
|
||||
|
||||
;; --- Create Demo Profile
|
||||
|
||||
|
|
|
@ -143,7 +143,7 @@
|
|||
(->> (rp/query :projects-by-team {:team-id team-id})
|
||||
(rx/map projects-fetched)
|
||||
(rx/catch (fn [error]
|
||||
(rx/of (rt/nav' :not-authorized))))))))
|
||||
(rx/of (rt/nav' :auth-login))))))))
|
||||
|
||||
(defn projects-fetched
|
||||
[projects]
|
||||
|
@ -212,7 +212,7 @@
|
|||
(->> (rp/query :recent-files params)
|
||||
(rx/map recent-files-fetched)
|
||||
(rx/catch (fn [e]
|
||||
(rx/of (rt/nav' :not-authorized)))))))))
|
||||
(rx/of (rt/nav' :auth-login)))))))))
|
||||
|
||||
(defn recent-files-fetched
|
||||
[recent-files]
|
||||
|
|
|
@ -61,3 +61,9 @@
|
|||
(show {:content message
|
||||
:type :info
|
||||
:timeout timeout}))
|
||||
|
||||
(defn success
|
||||
[message & {:keys [timeout] :or {timeout 3000}}]
|
||||
(show {:content message
|
||||
:type :info
|
||||
:timeout timeout}))
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[uxbox.common.spec :as us]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.main.repo :as rp]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.i18n :as i18n :refer [tr]]
|
||||
[uxbox.util.storage :refer [storage]]
|
||||
[uxbox.util.avatars :as avatars]
|
||||
|
@ -74,7 +75,11 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(->> (rp/query! :profile)
|
||||
(rx/map profile-fetched)))))
|
||||
(rx/map profile-fetched)
|
||||
(rx/catch (fn [error]
|
||||
(if (= (:type error) :not-found)
|
||||
(rx/of (rt/nav :auth-login))
|
||||
(rx/empty))))))))
|
||||
|
||||
;; --- Update Profile
|
||||
|
||||
|
@ -91,9 +96,35 @@
|
|||
(rx/empty))]
|
||||
(->> (rp/mutation :update-profile data)
|
||||
(rx/do on-success)
|
||||
(rx/map profile-fetched)
|
||||
(rx/map (constantly fetch-profile))
|
||||
(rx/catch rp/client-error? handle-error))))))
|
||||
|
||||
;; --- Request Email Change
|
||||
|
||||
(defn request-email-change
|
||||
[{:keys [email] :as data}]
|
||||
(ptk/reify ::request-email-change
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error identity
|
||||
on-success identity}} (meta data)]
|
||||
(->> (rp/mutation :request-email-change data)
|
||||
(rx/tap on-success)
|
||||
(rx/map (constantly fetch-profile))
|
||||
(rx/catch (fn [err]
|
||||
(on-error err)
|
||||
(rx/empty))))))))
|
||||
|
||||
;; --- Cancel Email Change
|
||||
|
||||
(def cancel-email-change
|
||||
(ptk/reify ::cancel-email-change
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (rp/mutation :cancel-email-change {})
|
||||
(rx/map (constantly fetch-profile))))))
|
||||
|
||||
;; --- Update Password (Form)
|
||||
|
||||
(s/def ::update-password
|
||||
|
@ -107,15 +138,16 @@
|
|||
(ptk/reify ::update-password
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [mdata (meta data)
|
||||
on-success (:on-success mdata identity)
|
||||
on-error (:on-error mdata identity)
|
||||
(let [{:keys [on-error on-success]
|
||||
:or {on-error identity
|
||||
on-success identity}} (meta data)
|
||||
params {:old-password (:password-old data)
|
||||
:password (:password-1 data)}]
|
||||
(->> (rp/mutation :update-profile-password params)
|
||||
(rx/catch rp/client-error? #(do (on-error (:payload %))
|
||||
(rx/empty)))
|
||||
(rx/do on-success)
|
||||
(rx/tap on-success)
|
||||
(rx/catch (fn [err]
|
||||
(on-error err)
|
||||
(rx/empty)))
|
||||
(rx/ignore))))))
|
||||
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
(defmethod mutation :logout
|
||||
[id params]
|
||||
(let [url (str url "/api/logout")]
|
||||
(->> (http/send! {:method :post :url url :body params :auth false})
|
||||
(->> (http/send! {:method :post :url url :body params})
|
||||
(rx/mapcat handle-response))))
|
||||
|
||||
(def client-error? http/client-error?)
|
||||
|
|
|
@ -21,11 +21,8 @@
|
|||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.dashboard :refer [dashboard]]
|
||||
[uxbox.main.ui.login :refer [login-page]]
|
||||
[uxbox.main.ui.static :refer [not-found-page not-authorized-page]]
|
||||
[uxbox.main.ui.profile.recovery :refer [profile-recovery-page]]
|
||||
[uxbox.main.ui.profile.recovery-request :refer [profile-recovery-request-page]]
|
||||
[uxbox.main.ui.profile.register :refer [profile-register-page]]
|
||||
[uxbox.main.ui.auth :refer [auth verify-token]]
|
||||
[uxbox.main.ui.settings :as settings]
|
||||
[uxbox.main.ui.viewer :refer [viewer-page]]
|
||||
[uxbox.main.ui.workspace :as workspace]
|
||||
|
@ -35,14 +32,18 @@
|
|||
;; --- Routes
|
||||
|
||||
(def routes
|
||||
[["/login" :login]
|
||||
["/register" :profile-register]
|
||||
["/recovery/request" :profile-recovery-request]
|
||||
["/recovery" :profile-recovery]
|
||||
[["/auth"
|
||||
["/login" :auth-login]
|
||||
["/register" :auth-register]
|
||||
["/recovery/request" :auth-recovery-request]
|
||||
["/recovery" :auth-recovery]
|
||||
["/verify-token" :auth-verify-token]
|
||||
["/goodbye" :auth-goodbye]]
|
||||
|
||||
["/settings"
|
||||
["/profile" :settings-profile]
|
||||
["/password" :settings-password]]
|
||||
["/password" :settings-password]
|
||||
["/options" :settings-options]]
|
||||
|
||||
["/view/:page-id" :viewer]
|
||||
["/not-found" :not-found]
|
||||
|
@ -84,20 +85,20 @@
|
|||
{::mf/wrap [#(mf/catch % {:fallback app-error})]}
|
||||
[{:keys [route] :as props}]
|
||||
(case (get-in route [:data :name])
|
||||
:login
|
||||
[:& login-page]
|
||||
|
||||
:profile-register
|
||||
[:& profile-register-page]
|
||||
(:auth-login
|
||||
:auth-register
|
||||
:auth-goodbye
|
||||
:auth-recovery-request
|
||||
:auth-recovery)
|
||||
[:& auth {:route route}]
|
||||
|
||||
:profile-recovery-request
|
||||
[:& profile-recovery-request-page]
|
||||
|
||||
:profile-recovery
|
||||
[:& profile-recovery-page]
|
||||
:auth-verify-token
|
||||
[:& verify-token {:route route}]
|
||||
|
||||
(:settings-profile
|
||||
:settings-password)
|
||||
:settings-password
|
||||
:settings-options)
|
||||
[:& settings/settings {:route route}]
|
||||
|
||||
:debug-icons-preview
|
||||
|
|
93
frontend/src/uxbox/main/ui/auth.cljs
Normal file
93
frontend/src/uxbox/main/ui/auth.cljs
Normal file
|
@ -0,0 +1,93 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.auth
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[beicon.core :as rx]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.users :as du]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.messages :refer [messages]]
|
||||
[uxbox.main.ui.auth.login :refer [login-page]]
|
||||
[uxbox.main.ui.auth.recovery :refer [recovery-page]]
|
||||
[uxbox.main.ui.auth.recovery-request :refer [recovery-request-page]]
|
||||
[uxbox.main.ui.auth.register :refer [register-page]]
|
||||
[uxbox.main.repo :as rp]
|
||||
[uxbox.util.timers :as ts]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(mf/defc goodbye-page
|
||||
[{:keys [locale] :as props}]
|
||||
[:div.goodbay
|
||||
[:h1 (t locale "auth.goodbye-title")]])
|
||||
|
||||
(mf/defc auth
|
||||
[{:keys [route] :as props}]
|
||||
(let [section (get-in route [:data :name])
|
||||
locale (mf/deref i18n/locale)]
|
||||
[:*
|
||||
[:& messages]
|
||||
[:div.auth
|
||||
[:section.auth-sidebar
|
||||
[:a.logo i/logo]
|
||||
[:span.tagline (t locale "auth.sidebar-tagline")]]
|
||||
|
||||
[:section.auth-content
|
||||
(case section
|
||||
:auth-register [:& register-page {:locale locale}]
|
||||
:auth-login [:& login-page {:locale locale}]
|
||||
:auth-goodbye [:& goodbye-page {:locale locale}]
|
||||
:auth-recovery-request [:& recovery-request-page {:locale locale}]
|
||||
:auth-recovery [:& recovery-page {:locale locale
|
||||
:params (:query-params route)}])]]]))
|
||||
|
||||
(defn- handle-email-verified
|
||||
[data]
|
||||
(let [msg (tr "settings.notifications.email-verified-successfully")]
|
||||
(ts/schedule 100 #(st/emit! (dm/success msg)))
|
||||
(st/emit! (rt/nav :settings-profile)
|
||||
du/fetch-profile)))
|
||||
|
||||
(defn- handle-email-changed
|
||||
[data]
|
||||
(let [msg (tr "settings.notifications.email-changed-successfully")]
|
||||
(ts/schedule 100 #(st/emit! (dm/success msg)))
|
||||
(st/emit! (rt/nav :settings-profile)
|
||||
du/fetch-profile)))
|
||||
|
||||
(mf/defc verify-token
|
||||
[{:keys [route] :as props}]
|
||||
(let [token (get-in route [:query-params :token])]
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(->> (rp/mutation :verify-profile-token {:token token})
|
||||
(rx/subs
|
||||
(fn [response]
|
||||
(case (:type response)
|
||||
:verify-email (handle-email-verified response)
|
||||
:change-email (handle-email-changed response)
|
||||
nil))
|
||||
(fn [error]
|
||||
(case (:code error)
|
||||
:uxbox.services.mutations.profile/email-already-exists
|
||||
(let [msg (tr "errors.email-already-exists")]
|
||||
(ts/schedule 100 #(st/emit! (dm/error msg)))
|
||||
(st/emit! (rt/nav :settings-profile)))
|
||||
|
||||
(let [msg (tr "errors.generic")]
|
||||
(ts/schedule 100 #(st/emit! (dm/error msg)))
|
||||
(st/emit! (rt/nav :settings-profile)))))))))
|
||||
|
||||
[:div.verify-token
|
||||
i/loader-pencil]))
|
86
frontend/src/uxbox/main/ui/auth/login.cljs
Normal file
86
frontend/src/uxbox/main/ui/auth/login.cljs
Normal file
|
@ -0,0 +1,86 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.auth.login
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.ui.components.forms :refer [input submit-button form]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer [tr t]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::password ::us/not-empty-string)
|
||||
|
||||
(s/def ::login-form
|
||||
(s/keys :req-un [::email ::password]))
|
||||
|
||||
(defn- on-error
|
||||
[form error]
|
||||
(st/emit! (dm/error (tr "errors.auth.unauthorized"))))
|
||||
|
||||
(defn- on-submit
|
||||
[form event]
|
||||
(let [params (with-meta (:clean-data form)
|
||||
{:on-error (partial on-error form)})]
|
||||
(st/emit! (da/login params))))
|
||||
|
||||
(mf/defc login-form
|
||||
[{:keys [locale] :as props}]
|
||||
[:& form {:on-submit on-submit
|
||||
:spec ::login-form
|
||||
:initial {}}
|
||||
[:& input
|
||||
{:name :email
|
||||
:type "text"
|
||||
:tab-index "2"
|
||||
:help-icon i/at
|
||||
:label (t locale "auth.email-label")}]
|
||||
[:& input
|
||||
{:type "password"
|
||||
:name :password
|
||||
:tab-index "3"
|
||||
:help-icon i/eye
|
||||
:label (t locale "auth.password-label")}]
|
||||
[:& submit-button
|
||||
{:label (t locale "auth.login-submit-label")}]])
|
||||
|
||||
(mf/defc login-page
|
||||
[{:keys [locale] :as props}]
|
||||
[:div.generic-form.login-form
|
||||
[:div.form-container
|
||||
[:h1 (t locale "auth.login-title")]
|
||||
[:div.subtitle (t locale "auth.login-subtitle")]
|
||||
|
||||
[:& login-form {:locale locale}]
|
||||
|
||||
[:div.links
|
||||
[:div.link-entry
|
||||
[:a {:on-click #(st/emit! (rt/nav :auth-recovery-request))
|
||||
:tab-index "5"}
|
||||
(t locale "auth.forgot-password")]]
|
||||
|
||||
[:div.link-entry
|
||||
[:span (t locale "auth.register-label") " "]
|
||||
[:a {:on-click #(st/emit! (rt/nav :auth-register))
|
||||
:tab-index "6"}
|
||||
(t locale "auth.register")]]
|
||||
|
||||
[:div.link-entry
|
||||
[:span (t locale "auth.create-demo-profile-label") " "]
|
||||
[:a {:on-click #(st/emit! da/create-demo-profile)
|
||||
:tab-index "6"}
|
||||
(t locale "auth.create-demo-profile")]]]]])
|
98
frontend/src/uxbox/main/ui/auth/recovery.cljs
Normal file
98
frontend/src/uxbox/main/ui/auth/recovery.cljs
Normal file
|
@ -0,0 +1,98 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.auth.recovery
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.components.forms :refer [input submit-button form]]
|
||||
[uxbox.main.ui.messages :refer [messages]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(s/def ::password-1 ::fm/not-empty-string)
|
||||
(s/def ::password-2 ::fm/not-empty-string)
|
||||
(s/def ::token ::fm/not-empty-string)
|
||||
|
||||
(s/def ::recovery-form
|
||||
(s/keys :req-un [::password-1
|
||||
::password-2]))
|
||||
|
||||
(defn- password-equality
|
||||
[data]
|
||||
(let [password-1 (:password-1 data)
|
||||
password-2 (:password-2 data)]
|
||||
(cond-> {}
|
||||
(and password-1 password-2
|
||||
(not= password-1 password-2))
|
||||
(assoc :password-2 {:message "errors.password-invalid-confirmation"})
|
||||
|
||||
(and password-1 (> 8 (count password-1)))
|
||||
(assoc :password-1 {:message "errors.password-too-short"}))))
|
||||
|
||||
(defn- on-error
|
||||
[form error]
|
||||
(st/emit! (dm/error (tr "auth.notifications.invalid-token-error"))))
|
||||
|
||||
(defn- on-success
|
||||
[_]
|
||||
(st/emit! (dm/info (tr "auth.notifications.password-changed-succesfully"))
|
||||
(rt/nav :auth-login)))
|
||||
|
||||
(defn- on-submit
|
||||
[form event]
|
||||
(let [params (with-meta {:token (get-in form [:clean-data :token])
|
||||
:password (get-in form [:clean-data :password-2])}
|
||||
{:on-error (partial on-error form)
|
||||
:on-success (partial on-success form)})]
|
||||
(st/emit! (uda/recover-profile params))))
|
||||
|
||||
(mf/defc recovery-form
|
||||
[{:keys [locale params] :as props}]
|
||||
[:& form {:on-submit on-submit
|
||||
:spec ::recovery-form
|
||||
:validators [password-equality]
|
||||
:initial params}
|
||||
|
||||
[:& input {:type "password"
|
||||
:name :password-1
|
||||
:label (t locale "auth.new-password-label")}]
|
||||
|
||||
[:& input {:type "password"
|
||||
:name :password-2
|
||||
:label (t locale "auth.confirm-password-label")}]
|
||||
|
||||
[:& submit-button
|
||||
{:label (t locale "auth.recovery-submit-label")}]])
|
||||
|
||||
;; --- Recovery Request Page
|
||||
|
||||
(mf/defc recovery-page
|
||||
[{:keys [locale params] :as props}]
|
||||
[:section.generic-form
|
||||
[:div.form-container
|
||||
[:h1 "Forgot your password?"]
|
||||
[:div.subtitle "Please enter your new password"]
|
||||
|
||||
[:& recovery-form {:locale locale :params params}]
|
||||
|
||||
[:div.links
|
||||
[:div.link-entry
|
||||
[:a {:on-click #(st/emit! (rt/nav :auth-login))}
|
||||
(t locale "profile.recovery.go-to-login")]]]]])
|
||||
|
68
frontend/src/uxbox/main/ui/auth/recovery_request.cljs
Normal file
68
frontend/src/uxbox/main/ui/auth/recovery_request.cljs
Normal file
|
@ -0,0 +1,68 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.auth.recovery-request
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.components.forms :refer [input submit-button form]]
|
||||
[uxbox.main.ui.messages :refer [messages]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::recovery-request-form (s/keys :req-un [::email]))
|
||||
|
||||
(defn- on-submit
|
||||
[form event]
|
||||
(let [on-success #(st/emit! (dm/info (tr "auth.notifications.recovery-token-sent"))
|
||||
(rt/nav :auth-login))
|
||||
params (with-meta (:clean-data form)
|
||||
{:on-success on-success})]
|
||||
(st/emit! (uda/request-profile-recovery params))))
|
||||
|
||||
(mf/defc recovery-form
|
||||
[{:keys [locale] :as props}]
|
||||
[:& form {:on-submit on-submit
|
||||
:spec ::recovery-request-form
|
||||
:initial {}}
|
||||
|
||||
[:& input {:name :email
|
||||
:label (t locale "auth.email-label")
|
||||
:help-icon i/at
|
||||
:type "text"}]
|
||||
|
||||
[:& submit-button
|
||||
{:label (t locale "auth.recovery-request-submit-label")}]])
|
||||
|
||||
;; --- Recovery Request Page
|
||||
|
||||
(mf/defc recovery-request-page
|
||||
[{:keys [locale] :as props}]
|
||||
[:section.generic-form
|
||||
[:div.form-container
|
||||
[:h1 (t locale "auth.recovery-request-title")]
|
||||
[:div.subtitle (t locale "auth.recovery-request-subtitle")]
|
||||
|
||||
[:& recovery-form {:locale locale}]
|
||||
|
||||
[:div.links
|
||||
[:div.link-entry
|
||||
[:a {:on-click #(st/emit! (rt/nav :auth-login))}
|
||||
(t locale "auth.go-back-to-login")]]]]])
|
120
frontend/src/uxbox/main/ui/auth/register.cljs
Normal file
120
frontend/src/uxbox/main/ui/auth/register.cljs
Normal file
|
@ -0,0 +1,120 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.auth.register
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.ui.messages :refer [messages]]
|
||||
[uxbox.main.ui.components.forms :refer [input submit-button form]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer [tr t]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
|
||||
(mf/defc demo-warning
|
||||
[_]
|
||||
[:div.featured-note.warning
|
||||
[:span
|
||||
[:strong "WARNING: "]
|
||||
"This is a " [:strong "demo"] " service, "
|
||||
[:strong "DO NOT USE"] " for real work, "
|
||||
" the projects will be periodicaly wiped."]])
|
||||
|
||||
(s/def ::fullname ::fm/not-empty-string)
|
||||
(s/def ::password ::fm/not-empty-string)
|
||||
(s/def ::email ::fm/email)
|
||||
|
||||
(s/def ::register-form
|
||||
(s/keys :req-un [::password
|
||||
::fullname
|
||||
::email]))
|
||||
|
||||
(defn- on-error
|
||||
[form error]
|
||||
(case (:code error)
|
||||
:uxbox.services.mutations.profile/registration-disabled
|
||||
(st/emit! (tr "errors.registration-disabled"))
|
||||
|
||||
:uxbox.services.mutations.profile/email-already-exists
|
||||
(swap! form assoc-in [:errors :email]
|
||||
{:message "errors.email-already-exists"})
|
||||
|
||||
(st/emit! (tr "errors.unexpected-error"))))
|
||||
|
||||
(defn- validate
|
||||
[data]
|
||||
(let [password (:password data)]
|
||||
(when (> 8 (count password))
|
||||
{:password {:message "errors.password-too-short"}})))
|
||||
|
||||
(defn- on-submit
|
||||
[form event]
|
||||
(let [data (with-meta (:clean-data form)
|
||||
{:on-error (partial on-error form)})]
|
||||
(st/emit! (uda/register data))))
|
||||
|
||||
(mf/defc register-form
|
||||
[{:keys [locale] :as props}]
|
||||
[:& form {:on-submit on-submit
|
||||
:spec ::register-form
|
||||
:validators [validate]
|
||||
:initial {}}
|
||||
[:& input {:name :fullname
|
||||
:tab-index "1"
|
||||
:label (t locale "auth.fullname-label")
|
||||
:type "text"}]
|
||||
[:& input {:type "email"
|
||||
:name :email
|
||||
:tab-index "2"
|
||||
:help-icon i/at
|
||||
:label (t locale "auth.email-label")}]
|
||||
[:& input {:name :password
|
||||
:tab-index "3"
|
||||
:hint (t locale "auth.password-length-hint")
|
||||
:label (t locale "auth.password-label")
|
||||
:type "password"}]
|
||||
|
||||
[:& submit-button
|
||||
{:label (t locale "auth.register-submit-label")}]])
|
||||
|
||||
;; --- Register Page
|
||||
|
||||
(mf/defc register-page
|
||||
[{:keys [locale] :as props}]
|
||||
[:section.generic-form
|
||||
[:div.form-container
|
||||
[:h1 (t locale "auth.register-title")]
|
||||
[:div.subtitle (t locale "auth.register-subtitle")]
|
||||
(when cfg/demo-warning
|
||||
[:& demo-warning])
|
||||
|
||||
[:& register-form {:locale locale}]
|
||||
|
||||
[:div.links
|
||||
[:div.link-entry
|
||||
[:span (t locale "auth.already-have-account") " "]
|
||||
[:a {:on-click #(st/emit! (rt/nav :auth-login))
|
||||
:tab-index "4"}
|
||||
(t locale "auth.login-here")]]
|
||||
|
||||
[:div.link-entry
|
||||
[:span (t locale "auth.create-demo-profile-label") " "]
|
||||
[:a {:on-click #(st/emit! da/create-demo-profile)
|
||||
:tab-index "5"}
|
||||
(t locale "auth.create-demo-profile")]]]]])
|
150
frontend/src/uxbox/main/ui/components/forms.cljs
Normal file
150
frontend/src/uxbox/main/ui/components/forms.cljs
Normal file
|
@ -0,0 +1,150 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.components.forms
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[cuerdas.core :as str]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.util.object :as obj]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as i18n :refer [t]]
|
||||
["react" :as react]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
(def form-ctx (mf/create-context nil))
|
||||
|
||||
(mf/defc input
|
||||
[{:keys [type label help-icon disabled name form hint] :as props}]
|
||||
(let [form (mf/use-ctx form-ctx)
|
||||
|
||||
type' (mf/use-state type)
|
||||
focus? (mf/use-state false)
|
||||
locale (mf/deref i18n/locale)
|
||||
|
||||
touched? (get-in form [:touched name])
|
||||
error (get-in form [:errors name])
|
||||
|
||||
klass (dom/classnames
|
||||
:focus @focus?
|
||||
:valid (and touched? (not error))
|
||||
:invalid (and touched? error)
|
||||
:disabled disabled)
|
||||
|
||||
swap-text-password
|
||||
(fn []
|
||||
(swap! type' (fn [type]
|
||||
(if (= "password" type)
|
||||
"text"
|
||||
"password"))))
|
||||
|
||||
on-focus #(reset! focus? true)
|
||||
on-change (fm/on-input-change form name)
|
||||
|
||||
on-blur
|
||||
(fn [event]
|
||||
(reset! focus? false)
|
||||
(when-not (get-in form [:touched name])
|
||||
(swap! form assoc-in [:touched name] true)))
|
||||
|
||||
value (get-in form [:data name] "")
|
||||
|
||||
props (-> props
|
||||
(dissoc :help-icon :form)
|
||||
(assoc :value value
|
||||
:on-focus on-focus
|
||||
:on-blur on-blur
|
||||
:placeholder label
|
||||
:on-change on-change
|
||||
:type @type')
|
||||
(obj/clj->props))]
|
||||
|
||||
[:div.custom-input
|
||||
[:div.input-container {:class klass}
|
||||
[:div.main-content
|
||||
(when-not (str/empty? value)
|
||||
[:label label])
|
||||
[:> :input props]]
|
||||
[:div.help-icon
|
||||
{:style {:cursor "pointer"}
|
||||
:on-click (when (= "password" type)
|
||||
swap-text-password)}
|
||||
(cond
|
||||
(and (= type "password")
|
||||
(= @type' "password"))
|
||||
i/eye
|
||||
|
||||
(and (= type "password")
|
||||
(= @type' "text"))
|
||||
i/eye-closed
|
||||
|
||||
:else
|
||||
help-icon)]]
|
||||
(cond
|
||||
(and touched? (:message error))
|
||||
[:span.error (t locale (:message error))]
|
||||
|
||||
(string? hint)
|
||||
[:span.hint hint])]))
|
||||
|
||||
(mf/defc select
|
||||
[{:keys [options label name form default]
|
||||
:or {default ""}}]
|
||||
(let [form (mf/use-ctx form-ctx)
|
||||
value (get-in form [:data name] default)
|
||||
cvalue (d/seek #(= value (:value %)) options)
|
||||
on-change (fm/on-input-change form name)]
|
||||
|
||||
[:div.custom-select
|
||||
[:select {:value value
|
||||
:on-change on-change}
|
||||
(for [item options]
|
||||
[:option {:key (:value item) :value (:value item)} (:label item)])]
|
||||
|
||||
[:div.input-container
|
||||
[:div.main-content
|
||||
[:label label]
|
||||
[:span.value (:label cvalue "")]]
|
||||
|
||||
[:div.icon
|
||||
i/arrow-slide]]]))
|
||||
|
||||
(mf/defc submit-button
|
||||
[{:keys [label form] :as props}]
|
||||
(let [form (mf/use-ctx form-ctx)]
|
||||
[:input.btn-primary.btn-large
|
||||
{:name "submit"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value label
|
||||
:type "submit"}]))
|
||||
|
||||
(mf/defc form
|
||||
[{:keys [on-submit spec validators initial children class] :as props}]
|
||||
(let [frm (fm/use-form :spec spec
|
||||
:validators validators
|
||||
:initial initial)]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps initial)
|
||||
(fn []
|
||||
(if (fn? initial)
|
||||
(swap! frm update :data merge (initial))
|
||||
(swap! frm update :data merge initial))))
|
||||
|
||||
[:& (mf/provider form-ctx) {:value frm}
|
||||
[:form {:class class
|
||||
:on-submit (fn [event]
|
||||
(dom/prevent-default event)
|
||||
(on-submit frm event))}
|
||||
children]]))
|
||||
|
||||
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
(def arrow-end (icon-xref :arrow-end))
|
||||
(def arrow-slide (icon-xref :arrow-slide))
|
||||
(def artboard (icon-xref :artboard))
|
||||
(def at (icon-xref :at))
|
||||
(def auto-fix (icon-xref :auto-fix))
|
||||
(def auto-height (icon-xref :auto-height))
|
||||
(def auto-width (icon-xref :auto-width))
|
||||
|
@ -60,6 +61,7 @@
|
|||
(def lock (icon-xref :lock))
|
||||
(def lock-open (icon-xref :lock-open))
|
||||
(def logo (icon-xref :uxbox-logo))
|
||||
(def logout (icon-xref :logout))
|
||||
(def logo-icon (icon-xref :uxbox-logo-icon))
|
||||
(def lowercase (icon-xref :lowercase))
|
||||
(def mail (icon-xref :mail))
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2015-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2020 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.login
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.messages :refer [messages]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer [tr]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::password ::us/not-empty-string)
|
||||
|
||||
(s/def ::login-form
|
||||
(s/keys :req-un [::email ::password]))
|
||||
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
(dom/prevent-default event)
|
||||
(let [{:keys [email password]} (:clean-data form)]
|
||||
(st/emit! (da/login {:email email :password password}))))
|
||||
|
||||
(mf/defc demo-warning
|
||||
[_]
|
||||
[:div.message-inline
|
||||
[:p
|
||||
[:strong "WARNING: "]
|
||||
"This is a " [:strong "demo"] " service, "
|
||||
[:strong "DO NOT USE"] " for real work, "
|
||||
" the projects will be periodicaly wiped."]])
|
||||
|
||||
(mf/defc login-form
|
||||
[]
|
||||
(let [{:keys [data] :as form} (fm/use-form ::login-form {})]
|
||||
[:form {:on-submit #(on-submit % form)}
|
||||
[:div.login-content
|
||||
(when cfg/demo-warning
|
||||
[:& demo-warning])
|
||||
|
||||
[:input.input-text
|
||||
{:name "email"
|
||||
:tab-index "2"
|
||||
:value (:email data "")
|
||||
:class (fm/error-class form :email)
|
||||
:on-blur (fm/on-input-blur form :email)
|
||||
:on-change (fm/on-input-change form :email)
|
||||
:placeholder (tr "login.email")
|
||||
:type "text"}]
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
:tab-index "3"
|
||||
: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 "login.password")
|
||||
:type "password"}]
|
||||
[:input.btn-primary.btn-large
|
||||
{:name "login"
|
||||
:tab-index "4"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (tr "login.submit")
|
||||
:type "submit"}]
|
||||
|
||||
[:div.login-links
|
||||
[:a {:on-click #(st/emit! (rt/nav :profile-recovery-request))
|
||||
:tab-index "5"}
|
||||
(tr "login.forgot-password")]
|
||||
[:a {:on-click #(st/emit! (rt/nav :profile-register))
|
||||
:tab-index "6"}
|
||||
(tr "login.register")]]
|
||||
[:a.btn-secondary.btn-small {:on-click #(st/emit! da/create-demo-profile)
|
||||
:tab-index "7"
|
||||
:title (tr "login.create-demo-profile-description")}
|
||||
(tr "login.create-demo-profile")]]]))
|
||||
|
||||
(mf/defc login-page
|
||||
[]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
[:& messages]
|
||||
[:a i/logo]
|
||||
[:& login-form]]])
|
|
@ -27,13 +27,12 @@
|
|||
|
||||
(defn- on-parent-clicked
|
||||
[event parent-ref]
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(let [parent (mf/ref-val parent-ref)
|
||||
current (dom/get-target event)]
|
||||
(when (dom/equals? parent current)
|
||||
(reset! state nil)
|
||||
#_(st/emit! (udl/hide-lightbox)))))
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(reset! state nil))))
|
||||
|
||||
(mf/defc modal-wrapper
|
||||
[{:keys [component props]}]
|
||||
|
@ -46,7 +45,8 @@
|
|||
parent-ref (mf/use-ref nil)]
|
||||
[:div.lightbox {:class classes
|
||||
:ref parent-ref
|
||||
:on-click #(on-parent-clicked % parent-ref)}
|
||||
:on-click #(on-parent-clicked % parent-ref)
|
||||
}
|
||||
(mf/element component props)]))
|
||||
|
||||
(mf/defc modal
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2015-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2020 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.profile.recovery
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.messages :refer [messages]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as i18n :refer [t]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(s/def ::token ::us/not-empty-string)
|
||||
(s/def ::password ::us/not-empty-string)
|
||||
(s/def ::recovery-form (s/keys :req-un [::token ::password]))
|
||||
|
||||
(mf/defc recovery-form
|
||||
[]
|
||||
(let [{:keys [data] :as form} (fm/use-form ::recovery-form {})
|
||||
locale (i18n/use-locale)
|
||||
|
||||
on-success
|
||||
(fn []
|
||||
(st/emit! (dm/info (t locale "profile.recovery.password-changed"))
|
||||
(rt/nav :login)))
|
||||
|
||||
on-error
|
||||
(fn []
|
||||
(st/emit! (dm/error (t locale "profile.recovery.invalid-token"))))
|
||||
|
||||
on-submit
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (uda/recover-profile (assoc (:clean-data form)
|
||||
:on-error on-error
|
||||
:on-success on-success))))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "token"
|
||||
:value (:token data "")
|
||||
:class (fm/error-class form :token)
|
||||
:on-blur (fm/on-input-blur form :token)
|
||||
:on-change (fm/on-input-change form :token)
|
||||
:placeholder (t locale "profile.recovery.token")
|
||||
:auto-complete "off"
|
||||
:type "text"}]
|
||||
[:input.input-text
|
||||
{:name "password"
|
||||
: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 (t locale "profile.recovery.password")
|
||||
:type "password"}]
|
||||
[:input.btn-primary
|
||||
{:name "recover"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (t locale "profile.recovery.submit-recover")
|
||||
:type "submit"}]
|
||||
|
||||
[:div.login-links
|
||||
[:a {:on-click #(st/emit! (rt/nav :login))}
|
||||
(t locale "profile.recovery.go-to-login")]]]]))
|
||||
|
||||
;; --- Recovery Request Page
|
||||
|
||||
(mf/defc profile-recovery-page
|
||||
[]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
[:& messages]
|
||||
[:a i/logo]
|
||||
[:& recovery-form]]])
|
|
@ -1,75 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2015-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2020 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.profile.recovery-request
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.common.spec :as us]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.messages :refer [messages]]
|
||||
[uxbox.main.ui.navigation :as nav]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as i18n :refer [t]]
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
(s/def ::email ::us/email)
|
||||
(s/def ::recovery-request-form (s/keys :req-un [::email]))
|
||||
|
||||
(mf/defc recovery-form
|
||||
[]
|
||||
(let [{:keys [data] :as form} (fm/use-form ::recovery-request-form {})
|
||||
locale (i18n/use-locale)
|
||||
|
||||
on-success
|
||||
(fn []
|
||||
(st/emit! (dm/info (t locale "profile.recovery.recovery-token-sent"))
|
||||
(rt/nav :profile-recovery)))
|
||||
|
||||
on-submit
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(st/emit! (uda/request-profile-recovery (:clean-data form) on-success)))]
|
||||
[:form {:on-submit on-submit}
|
||||
[:div.login-content
|
||||
[:input.input-text
|
||||
{:name "email"
|
||||
:value (:email data "")
|
||||
:class (fm/error-class form :email)
|
||||
:on-blur (fm/on-input-blur form :email)
|
||||
:on-change (fm/on-input-change form :email)
|
||||
:placeholder (t locale "profile.recovery.email")
|
||||
:type "text"}]
|
||||
[:input.btn-primary
|
||||
{:name "login"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (t locale "profile.recovery.submit-request")
|
||||
:type "submit"}]
|
||||
|
||||
[:div.login-links
|
||||
[:a {:on-click #(st/emit! (rt/nav :login))}
|
||||
(t locale "profile.recovery.go-to-login")]]]]))
|
||||
|
||||
;; --- Recovery Request Page
|
||||
|
||||
(mf/defc profile-recovery-request-page
|
||||
[]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
[:& messages]
|
||||
[:a i/logo]
|
||||
[:& recovery-form]]])
|
|
@ -1,120 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.profile.register
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.auth :as uda]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.messages :refer [messages]]
|
||||
[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]))
|
||||
|
||||
(s/def ::fullname ::fm/not-empty-string)
|
||||
(s/def ::password ::fm/not-empty-string)
|
||||
(s/def ::email ::fm/email)
|
||||
|
||||
(s/def ::register-form
|
||||
(s/keys :req-un [::password
|
||||
::fullname
|
||||
::email]))
|
||||
|
||||
(defn- on-error
|
||||
[error form]
|
||||
(case (:code error)
|
||||
:uxbox.services.users/registration-disabled
|
||||
(st/emit! (tr "errors.api.form.registration-disabled"))
|
||||
|
||||
:uxbox.services.users/email-already-exists
|
||||
(swap! form assoc-in [:errors :email]
|
||||
{:type ::api
|
||||
:message "errors.api.form.email-already-exists"})
|
||||
|
||||
(st/emit! (tr "errors.api.form.unexpected-error"))))
|
||||
|
||||
(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))))
|
||||
|
||||
(mf/defc register-form
|
||||
[props]
|
||||
(let [{:keys [data] :as form} (fm/use-form ::register-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 "profile.register.fullname")
|
||||
:type "text"}]
|
||||
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :fullname}]
|
||||
|
||||
[: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 "profile.register.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 "profile.register.password")
|
||||
: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 "profile.register.get-started")}]
|
||||
|
||||
[:div.login-links
|
||||
[:a {:on-click #(st/emit! (rt/nav :login))}
|
||||
(tr "profile.register.already-have-account")]]]]))
|
||||
|
||||
;; --- Register Page
|
||||
|
||||
(mf/defc profile-register-page
|
||||
[props]
|
||||
[:div.login
|
||||
[:div.login-body
|
||||
[:& messages]
|
||||
[:a i/logo]
|
||||
[:& register-form]]])
|
|
@ -2,8 +2,10 @@
|
|||
;; 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>
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.settings
|
||||
(:require
|
||||
|
@ -18,6 +20,7 @@
|
|||
[uxbox.main.ui.messages :refer [messages]]
|
||||
[uxbox.main.ui.settings.header :refer [header]]
|
||||
[uxbox.main.ui.settings.password :refer [password-page]]
|
||||
[uxbox.main.ui.settings.options :refer [options-page]]
|
||||
[uxbox.main.ui.settings.profile :refer [profile-page]]))
|
||||
|
||||
(mf/defc settings
|
||||
|
@ -30,7 +33,8 @@
|
|||
[:& header {:section section :profile profile}]
|
||||
(case section
|
||||
:settings-profile (mf/element profile-page)
|
||||
:settings-password (mf/element password-page))]]))
|
||||
:settings-password (mf/element password-page)
|
||||
:settings-options (mf/element options-page))]]))
|
||||
|
||||
|
||||
|
||||
|
|
102
frontend/src/uxbox/main/ui/settings/change_email.cljs
Normal file
102
frontend/src/uxbox/main/ui/settings/change_email.cljs
Normal file
|
@ -0,0 +1,102 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.settings.change-email
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.data.users :as du]
|
||||
[uxbox.main.ui.components.forms :refer [input submit-button form]]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]))
|
||||
|
||||
(s/def ::email-1 ::fm/email)
|
||||
(s/def ::email-2 ::fm/email)
|
||||
(s/def ::email-change-form
|
||||
(s/keys :req-un [::email-1 ::email-2]))
|
||||
|
||||
(defn- on-error
|
||||
[form error]
|
||||
(cond
|
||||
(= (:code error) :uxbox.services.mutations.profile/email-already-exists)
|
||||
(swap! form (fn [data]
|
||||
(let [error {:message (tr "errors.email-already-exists")}]
|
||||
(-> data
|
||||
(assoc-in [:errors :email-1] error)
|
||||
(assoc-in [:errors :email-2] error)))))
|
||||
|
||||
:else
|
||||
(let [msg (tr "errors.unexpected-error")]
|
||||
(st/emit! (dm/error msg)))))
|
||||
|
||||
(defn- on-submit
|
||||
[form event]
|
||||
(let [data (with-meta {:email (get-in form [:clean-data :email-1])}
|
||||
{:on-error (partial on-error form)})]
|
||||
(st/emit! (du/request-email-change data))))
|
||||
|
||||
(mf/defc change-email-form
|
||||
[{:keys [locale profile] :as props}]
|
||||
[:section.modal-content.generic-form
|
||||
[:h2 (t locale "settings.change-email-title")]
|
||||
|
||||
[:span.featured-note
|
||||
[:span.text
|
||||
[:span "We’ll send you an email to your current email "]
|
||||
[:strong (:email profile)]
|
||||
[:span " to verify your identity."]]]
|
||||
|
||||
[:& form {:on-submit on-submit
|
||||
:spec ::email-change-form
|
||||
:initial {}}
|
||||
[:& input {:type "text"
|
||||
:name :email-1
|
||||
:label (t locale "settings.new-email-label")}]
|
||||
|
||||
[:& input {:type "text"
|
||||
:name :email-2
|
||||
:label (t locale "settings.confirm-email-label")}]
|
||||
|
||||
[:& submit-button
|
||||
{:label (t locale "settings.change-email-submit-label")}]]])
|
||||
|
||||
(mf/defc change-email-confirmation
|
||||
[{:keys [locale profile] :as locale}]
|
||||
[:section.modal-content.generic-form.confirmation
|
||||
[:h2 (t locale "settings.verification-sent-title")]
|
||||
|
||||
[:span.featured-note
|
||||
[:span.icon i/trash]
|
||||
[:span.text
|
||||
[:span (str/format "We have sent you an email to “")]
|
||||
[:strong (:email profile)]
|
||||
[:span "” Please follow the instructions to verify the email."]]]
|
||||
|
||||
[:button.btn-primary.btn-large
|
||||
{:on-click #(modal/hide!)}
|
||||
(t locale "settings.close-modal-label")]])
|
||||
|
||||
(mf/defc change-email-modal
|
||||
[props]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
profile (mf/deref refs/profile)]
|
||||
[:section.generic-modal.change-email-modal
|
||||
[:span.close {:on-click #(modal/hide!)} i/close]
|
||||
(if (:pending-email profile)
|
||||
[:& change-email-confirmation {:locale locale :profile profile}]
|
||||
[:& change-email-form {:locale locale :profile profile}])]))
|
42
frontend/src/uxbox/main/ui/settings/delete_account.cljs
Normal file
42
frontend/src/uxbox/main/ui/settings/delete_account.cljs
Normal file
|
@ -0,0 +1,42 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.settings.delete-account
|
||||
(:require
|
||||
[cljs.spec.alpha :as s]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.auth :as da]
|
||||
[uxbox.main.data.users :as du]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]))
|
||||
|
||||
(mf/defc delete-account-modal
|
||||
[props]
|
||||
(let [locale (mf/deref i18n/locale)]
|
||||
[:section.generic-modal.change-email-modal
|
||||
[:span.close {:on-click #(modal/hide!)} i/close]
|
||||
|
||||
[:section.modal-content.generic-form
|
||||
[:h2 (t locale "settings.delete-account-title")]
|
||||
|
||||
[:span.featured-note
|
||||
[:span.text
|
||||
[:span (t locale "settings.delete-account-info")]]]
|
||||
|
||||
[:div.button-row
|
||||
[:button.btn-warning.btn-large
|
||||
{:on-click #(do
|
||||
(modal/hide!)
|
||||
(st/emit! da/request-account-deletion))}
|
||||
(t locale "settings.yes-delete-my-account")]
|
||||
[:button.btn-secondary.btn-large
|
||||
{:on-click #(modal/hide!)}
|
||||
(t locale "settings.cancel-and-keep-my-account")]]]]))
|
|
@ -16,22 +16,60 @@
|
|||
|
||||
(mf/defc header
|
||||
[{:keys [section profile] :as props}]
|
||||
(let [profile? (= section :settings-profile)
|
||||
(let [profile? (= section :settings-profile)
|
||||
password? (= section :settings-password)
|
||||
locale (i18n/use-locale)
|
||||
team-id (:default-team-id profile)]
|
||||
[:header
|
||||
[:div.main-logo
|
||||
{:on-click #(st/emit! (rt/nav :dashboard-team {:team-id team-id}))}
|
||||
i/logo-icon]
|
||||
[:section.main-bar
|
||||
[:nav
|
||||
[:a.nav-item
|
||||
{:class (when profile? "current")
|
||||
:on-click #(st/emit! (rt/nav :settings-profile))}
|
||||
(t locale "settings.profile")]
|
||||
[:a.nav-item
|
||||
{:class (when password? "current")
|
||||
:on-click #(st/emit! (rt/nav :settings-password))}
|
||||
(t locale "settings.password")]]]]))
|
||||
options? (= section :settings-options)
|
||||
|
||||
team-id (:default-team-id profile)
|
||||
go-back #(st/emit! (rt/nav :dashboard-team {:team-id team-id}))
|
||||
logout #(st/emit! da/logout)
|
||||
|
||||
locale (mf/deref i18n/locale)
|
||||
team-id (:default-team-id profile)]
|
||||
[:header
|
||||
[:section.secondary-menu
|
||||
[:div.left {:on-click go-back}
|
||||
[:span.icon i/arrow-slide]
|
||||
[:span.label "Dashboard"]]
|
||||
[:div.right {:on-click logout}
|
||||
[:span.label "Log out"]
|
||||
[:span.icon i/logout]]]
|
||||
[:h1 "Your account"]
|
||||
[:nav
|
||||
[:a.nav-item
|
||||
{:class (when profile? "current")
|
||||
:on-click #(st/emit! (rt/nav :settings-profile))}
|
||||
(t locale "settings.profile")]
|
||||
|
||||
[:a.nav-item
|
||||
{:class (when password? "current")
|
||||
:on-click #(st/emit! (rt/nav :settings-password))}
|
||||
(t locale "settings.password")]
|
||||
|
||||
[:a.nav-item
|
||||
{:class (when options? "current")
|
||||
:on-click #(st/emit! (rt/nav :settings-options))}
|
||||
(t locale "settings.options")]
|
||||
|
||||
[:a.nav-item
|
||||
{:class "foobar"
|
||||
:on-click #(st/emit! (rt/nav :settings-profile))}
|
||||
(t locale "settings.teams")]]]))
|
||||
|
||||
|
||||
|
||||
|
||||
;; [:div.main-logo
|
||||
;; {:on-click #(st/emit! (rt/nav :dashboard-team {:team-id team-id}))}
|
||||
;; i/logo-icon]
|
||||
;; [:section.main-bar
|
||||
;; [:nav
|
||||
;; [:a.nav-item
|
||||
;; {:class (when profile? "current")
|
||||
;; :on-click #(st/emit! (rt/nav :settings-profile))}
|
||||
;; (t locale "settings.profile")]
|
||||
;; [:a.nav-item
|
||||
;; {:class (when password? "current")
|
||||
;; :on-click #(st/emit! (rt/nav :settings-password))}
|
||||
;; (t locale "settings.password")]]]]))
|
||||
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
|
||||
(ns uxbox.main.ui.settings.notifications
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[uxbox.util.i18n :refer [tr]]))
|
||||
|
||||
(mf/defc notifications-page
|
||||
[]
|
||||
[:section.dashboard-content.user-settings
|
||||
[:section.user-settings-content
|
||||
[:span.user-settings-label (tr "settings.notifications.notifications-saved")]
|
||||
[:p (tr "settings.notifications.description")]
|
||||
[:div.input-radio.radio-primary
|
||||
[:input {:type "radio"
|
||||
:id "notification-1"
|
||||
:name "notification"
|
||||
:value "none"}]
|
||||
[:label {:for "notification-1"
|
||||
:value (tr "settings.notifications.none")} (tr "settings.notifications.none")]
|
||||
[:input {:type "radio"
|
||||
:id "notification-2"
|
||||
:name "notification"
|
||||
:value "every-hour"}]
|
||||
[:label {:for "notification-2"
|
||||
:value (tr "settings.notifications.every-hour")} (tr "settings.notifications.every-hour")]
|
||||
[:input {:type "radio"
|
||||
:id "notification-3"
|
||||
:name "notification"
|
||||
:value "every-day"}]
|
||||
[:label {:for "notification-3"
|
||||
:value (tr "settings.notifications.every-day")} (tr "settings.notifications.every-day")]]
|
||||
[:input.btn-primary {:type "submit"
|
||||
:class "btn-disabled"
|
||||
:disabled true
|
||||
:value (tr "settings.update-settings")}]
|
||||
]])
|
75
frontend/src/uxbox/main/ui/settings/options.cljs
Normal file
75
frontend/src/uxbox/main/ui/settings/options.cljs
Normal file
|
@ -0,0 +1,75 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.settings.options
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[cljs.spec.alpha :as s]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.users :as udu]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.ui.components.forms :refer [select submit-button form]]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]))
|
||||
|
||||
(s/def ::lang (s/nilable ::fm/not-empty-string))
|
||||
(s/def ::theme (s/nilable ::fm/not-empty-string))
|
||||
|
||||
(s/def ::options-form
|
||||
(s/keys :opt-un [::lang ::theme]))
|
||||
|
||||
(defn- on-error
|
||||
[form error])
|
||||
|
||||
(defn- on-submit
|
||||
[form event]
|
||||
(dom/prevent-default event)
|
||||
(let [data (:clean-data form)
|
||||
on-success #(st/emit! (dm/info (tr "settings.notifications.profile-saved")))
|
||||
on-error #(on-error % form)]
|
||||
(st/emit! (udu/update-profile (with-meta data
|
||||
{:on-success on-success
|
||||
:on-error on-error})))))
|
||||
|
||||
(mf/defc options-form
|
||||
[{:keys [locale profile] :as props}]
|
||||
[:& form {:class "options-form"
|
||||
:on-submit on-submit
|
||||
:spec ::options-form
|
||||
:initial profile}
|
||||
|
||||
[:h2 (t locale "settings.language-change-title")]
|
||||
|
||||
[:& select {:options [{:label "English" :value "en"}
|
||||
{:label "Français" :value "fr"}]
|
||||
:label (t locale "settings.language-label")
|
||||
:default "en"
|
||||
:name :lang}]
|
||||
|
||||
[:h2 (t locale "settings.theme-change-title")]
|
||||
[:& select {:label (t locale "settings.theme-label")
|
||||
:name :theme
|
||||
:default "default"
|
||||
:options [{:label "Default" :value "default"}]}]
|
||||
|
||||
[:& submit-button
|
||||
{:label (t locale "settings.profile-submit-label")}]])
|
||||
|
||||
;; --- Password Page
|
||||
|
||||
(mf/defc options-page
|
||||
[props]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
profile (mf/deref refs/profile)]
|
||||
[:section.settings-options.generic-form
|
||||
[:div.forms-container
|
||||
[:& options-form {:locale locale :profile profile}]]]))
|
|
@ -5,8 +5,7 @@
|
|||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2016-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016-2020 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.settings.password
|
||||
(:require
|
||||
|
@ -15,40 +14,52 @@
|
|||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.users :as udu]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.ui.components.forms :refer [input submit-button form]]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.forms :as fm]
|
||||
[uxbox.util.i18n :refer [tr]]))
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]))
|
||||
|
||||
(defn- on-error
|
||||
[form error]
|
||||
(case (:code error)
|
||||
:uxbox.services.users/old-password-not-match
|
||||
:uxbox.services.mutations.profile/old-password-not-match
|
||||
(swap! form assoc-in [:errors :password-old]
|
||||
{:type ::api :message "settings.password.wrong-old-password"})
|
||||
{:message (tr "errors.wrong-old-password")})
|
||||
|
||||
:else (throw (ex-info "unexpected" {:error error}))))
|
||||
:else
|
||||
(let [msg (tr "generic.error")]
|
||||
(st/emit! (dm/error msg)))))
|
||||
|
||||
(defn- on-success
|
||||
[form]
|
||||
(let [msg (tr "settings.notifications.password-saved")]
|
||||
(st/emit! (dm/info msg))))
|
||||
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
[form event]
|
||||
(dom/prevent-default event)
|
||||
(let [data (:clean-data form)
|
||||
mdata {:on-success #(st/emit! (dm/info (tr "settings.password.password-saved")))
|
||||
:on-error #(on-error form %)}]
|
||||
(st/emit! (udu/update-password (with-meta data mdata)))))
|
||||
(let [params (with-meta (:clean-data form)
|
||||
{:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)})]
|
||||
(st/emit! (udu/update-password params))))
|
||||
|
||||
(s/def ::password-1 ::fm/not-empty-string)
|
||||
(s/def ::password-2 ::fm/not-empty-string)
|
||||
(s/def ::password-old ::fm/not-empty-string)
|
||||
|
||||
(defn password-equality
|
||||
(defn- password-equality
|
||||
[data]
|
||||
(let [password-1 (:password-1 data)
|
||||
password-2 (:password-2 data)]
|
||||
(when (and password-1 password-2
|
||||
(not= password-1 password-2))
|
||||
{:password-2 {:code ::password-not-equal
|
||||
:message "profile.password.not-equal"}})))
|
||||
|
||||
(cond-> {}
|
||||
(and password-1 password-2
|
||||
(not= password-1 password-2))
|
||||
(assoc :password-2 {:message (tr "errors.password-invalid-confirmation")})
|
||||
|
||||
(and password-1 (> 8 (count password-1)))
|
||||
(assoc :password-1 {:message (tr "errors.password-too-short")}))))
|
||||
|
||||
(s/def ::password-form
|
||||
(s/keys :req-un [::password-1
|
||||
|
@ -56,54 +67,37 @@
|
|||
::password-old]))
|
||||
|
||||
(mf/defc password-form
|
||||
[props]
|
||||
(let [{:keys [data] :as form} (fm/use-form2 :spec ::password-form
|
||||
:validators [password-equality]
|
||||
:initial {})]
|
||||
[:form.password-form {:on-submit #(on-submit % form)}
|
||||
[:span.settings-label (tr "settings.password.change-password")]
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:name "password-old"
|
||||
:value (:password-old data "")
|
||||
:class (fm/error-class form :password-old)
|
||||
:on-blur (fm/on-input-blur form :password-old)
|
||||
:on-change (fm/on-input-change form :password-old)
|
||||
:placeholder (tr "settings.password.old-password")}]
|
||||
[{:keys [locale] :as props}]
|
||||
[:& form {:class "password-form"
|
||||
:on-submit on-submit
|
||||
:spec ::password-form
|
||||
:validators [password-equality]
|
||||
:initial {}}
|
||||
[:h2 (t locale "settings.password-change-title")]
|
||||
|
||||
[:& fm/field-error {:form form :field :password-old :type ::api}]
|
||||
[:& input
|
||||
{:type "password"
|
||||
:name :password-old
|
||||
:label (t locale "settings.old-password-label")}]
|
||||
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:name "password-1"
|
||||
:value (:password-1 data "")
|
||||
:class (fm/error-class form :password-1)
|
||||
:on-blur (fm/on-input-blur form :password-1)
|
||||
:on-change (fm/on-input-change form :password-1)
|
||||
:placeholder (tr "settings.password.new-password")}]
|
||||
[:& input
|
||||
{:type "password"
|
||||
:name :password-1
|
||||
:label (t locale "settings.new-password-label")}]
|
||||
|
||||
[:& fm/field-error {:form form :field :password-1}]
|
||||
[:& input
|
||||
{:type "password"
|
||||
:name :password-2
|
||||
:label (t locale "settings.confirm-password-label")}]
|
||||
|
||||
[:input.input-text
|
||||
{:type "password"
|
||||
:name "password-2"
|
||||
:value (:password-2 data "")
|
||||
:class (fm/error-class form :password-2)
|
||||
:on-blur (fm/on-input-blur form :password-2)
|
||||
:on-change (fm/on-input-change form :password-2)
|
||||
:placeholder (tr "settings.password.confirm-password")}]
|
||||
|
||||
[:& fm/field-error {:form form :field :password-2}]
|
||||
|
||||
[:input.btn-primary.btn-large
|
||||
{:type "submit"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (tr "settings.update-settings")}]]))
|
||||
[:& submit-button
|
||||
{:label (t locale "settings.profile-submit-label")}]])
|
||||
|
||||
;; --- Password Page
|
||||
|
||||
(mf/defc password-page
|
||||
[props]
|
||||
[:section.settings-password
|
||||
[:& password-form]])
|
||||
(let [locale (mf/deref i18n/locale)]
|
||||
[:section.settings-password.generic-form
|
||||
[:div.forms-container
|
||||
[:& password-form {:locale locale}]]]))
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
|
||||
;; Copyright (c) 2016-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns uxbox.main.ui.settings.profile
|
||||
(:require
|
||||
|
@ -13,6 +15,10 @@
|
|||
[rumext.alpha :as mf]
|
||||
[uxbox.main.ui.icons :as i]
|
||||
[uxbox.main.data.users :as udu]
|
||||
[uxbox.main.ui.components.forms :refer [input submit-button form]]
|
||||
[uxbox.main.ui.settings.change-email :refer [change-email-modal]]
|
||||
[uxbox.main.ui.settings.delete-account :refer [delete-account-modal]]
|
||||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.data.messages :as dm]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.refs :as refs]
|
||||
|
@ -21,8 +27,6 @@
|
|||
[uxbox.util.i18n :as i18n :refer [tr t]]))
|
||||
|
||||
(s/def ::fullname ::fm/not-empty-string)
|
||||
(s/def ::lang (s/nilable ::fm/not-empty-string))
|
||||
(s/def ::theme ::fm/not-empty-string)
|
||||
(s/def ::email ::fm/email)
|
||||
|
||||
(s/def ::profile-form
|
||||
|
@ -30,22 +34,12 @@
|
|||
|
||||
(defn- on-error
|
||||
[error form]
|
||||
(case (:code error)
|
||||
:uxbox.services.users/email-already-exists
|
||||
(swap! form assoc-in [:errors :email]
|
||||
{:type ::api
|
||||
:message "errors.api.form.email-already-exists"})
|
||||
|
||||
:uxbox.services.users/username-already-exists
|
||||
(swap! form assoc-in [:errors :username]
|
||||
{:type ::api
|
||||
:message "errors.api.form.username-already-exists"})))
|
||||
(st/emit! (dm/error (tr "errors.generic"))))
|
||||
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
(dom/prevent-default event)
|
||||
[form event]
|
||||
(let [data (:clean-data form)
|
||||
on-success #(st/emit! (dm/info (tr "settings.profile.profile-saved")))
|
||||
on-success #(st/emit! (dm/info (tr "settings.notifications.profile-saved")))
|
||||
on-error #(on-error % form)]
|
||||
(st/emit! (udu/update-profile (with-meta data
|
||||
{:on-success on-success
|
||||
|
@ -54,64 +48,58 @@
|
|||
;; --- Profile Form
|
||||
|
||||
(mf/defc profile-form
|
||||
[props]
|
||||
(let [locale (i18n/use-locale)
|
||||
form (fm/use-form ::profile-form #(deref refs/profile))
|
||||
data (:data form)]
|
||||
[:form.profile-form {:on-submit #(on-submit % form)}
|
||||
[:span.settings-label (t locale "settings.profile.section-basic-data")]
|
||||
|
||||
[:input.input-text
|
||||
[{:keys [locale] :as props}]
|
||||
(let [prof (mf/deref refs/profile)]
|
||||
[:& form {:on-submit on-submit
|
||||
:class "profile-form"
|
||||
:spec ::profile-form
|
||||
:initial prof}
|
||||
[:& input
|
||||
{:type "text"
|
||||
:name "fullname"
|
||||
:class (fm/error-class form :fullname)
|
||||
:on-blur (fm/on-input-blur form :fullname)
|
||||
:on-change (fm/on-input-change form :fullname)
|
||||
:value (:fullname data "")
|
||||
:placeholder (t locale "settings.profile.your-name")}]
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :fullname}]
|
||||
:name :fullname
|
||||
:label (t locale "settings.fullname-label")}]
|
||||
|
||||
[:input.input-text
|
||||
[:& input
|
||||
{:type "email"
|
||||
:name "email"
|
||||
: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 (t locale "settings.profile.your-email")}]
|
||||
[:& fm/field-error {:form form
|
||||
:type #{::api}
|
||||
:field :email}]
|
||||
:name :email
|
||||
:disabled true
|
||||
:help-icon i/at
|
||||
:label (t locale "settings.email-label")}]
|
||||
|
||||
[:span.settings-label (t locale "settings.profile.lang")]
|
||||
[:select.input-select {:value (:lang data)
|
||||
:name "lang"
|
||||
:class (fm/error-class form :lang)
|
||||
:on-blur (fm/on-input-blur form :lang)
|
||||
:on-change (fm/on-input-change form :lang)}
|
||||
[:option {:value "en"} "English"]
|
||||
[:option {:value "fr"} "Français"]]
|
||||
(cond
|
||||
(nil? (:pending-email prof))
|
||||
[:div.change-email
|
||||
[:a {:on-click #(modal/show! change-email-modal {})}
|
||||
(t locale "settings.change-email-label")]]
|
||||
|
||||
[:span.user-settings-label (tr "settings.profile.section-theme-data")]
|
||||
[:select.input-select {:value (:theme data)
|
||||
:name "theme"
|
||||
:class (fm/error-class form :theme)
|
||||
:on-blur (fm/on-input-blur form :theme)
|
||||
:on-change (fm/on-input-change form :theme)}
|
||||
[:option {:value "light"} "Default"]]
|
||||
(not= (:pending-email prof) (:email prof))
|
||||
[:span.featured-note
|
||||
[:span.icon i/trash]
|
||||
[:span.text
|
||||
[:span "There is a pending change of your email to "]
|
||||
[:strong (:pending-email prof)]
|
||||
[:span "."] [:br]
|
||||
[:a {:on-click #(st/emit! udu/cancel-email-change)}
|
||||
"Dismiss"]]]
|
||||
|
||||
[:input.btn-primary.btn-large
|
||||
{:type "submit"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (t locale "settings.update-settings")}]]))
|
||||
:else
|
||||
[:span.featured-note.warning
|
||||
[:span.text
|
||||
[:span "There is a pending email validation."]]])
|
||||
|
||||
|
||||
[:& submit-button
|
||||
{:label (t locale "settings.profile-submit-label")}]
|
||||
|
||||
[:div.links
|
||||
[:div.link-item
|
||||
[:a {:on-click #(modal/show! delete-account-modal {})}
|
||||
(t locale "settings.remove-account-label")]]]]))
|
||||
|
||||
;; --- Profile Photo Form
|
||||
|
||||
(mf/defc profile-photo-form
|
||||
[props]
|
||||
[{:keys [locale] :as props}]
|
||||
(let [profile (mf/deref refs/profile)
|
||||
photo (:photo-uri profile)
|
||||
photo (if (or (str/empty? photo) (nil? photo))
|
||||
|
@ -127,10 +115,12 @@
|
|||
(st/emit! (udu/update-photo {:file file}))
|
||||
(dom/clean-value! target)))]
|
||||
[:form.avatar-form
|
||||
[:img {:src photo}]
|
||||
[:input {:type "file"
|
||||
:value ""
|
||||
:on-change on-change}]]))
|
||||
[:div.image-change-field
|
||||
[:span.update-overlay (t locale "settings.update-photo-label")]
|
||||
[:img {:src photo}]
|
||||
[:input {:type "file"
|
||||
:value ""
|
||||
:on-change on-change}]]]))
|
||||
|
||||
;; --- Profile Page
|
||||
|
||||
|
@ -138,7 +128,7 @@
|
|||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [locale (i18n/use-locale)]
|
||||
[:section.settings-profile
|
||||
[:span.settings-label (t locale "settings.profile.your-avatar")]
|
||||
[:& profile-photo-form]
|
||||
[:& profile-form]]))
|
||||
[:section.settings-profile.generic-form
|
||||
[:div.forms-container
|
||||
[:& profile-photo-form {:locale locale}]
|
||||
[:& profile-form {:locale locale}]]]))
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
[& params]
|
||||
(assert (even? (count params)))
|
||||
(str/join " " (reduce (fn [acc [k v]]
|
||||
(if (true? v)
|
||||
(if (true? (boolean v))
|
||||
(conj acc (name k))
|
||||
acc))
|
||||
[]
|
||||
|
|
|
@ -50,44 +50,25 @@
|
|||
:else acc))
|
||||
|
||||
(defn use-form
|
||||
[spec initial]
|
||||
(let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial)
|
||||
:errors {}
|
||||
:touched {}})
|
||||
clean-data (s/conform spec (:data state))
|
||||
problems (when (= ::s/invalid clean-data)
|
||||
(::s/problems (s/explain-data spec (:data state))))
|
||||
|
||||
|
||||
errors (merge (reduce interpret-problem {} problems)
|
||||
(:errors state))]
|
||||
(-> (assoc state
|
||||
:errors errors
|
||||
:clean-data (when (not= clean-data ::s/invalid) clean-data)
|
||||
:valid (and (empty? errors)
|
||||
(not= clean-data ::s/invalid)))
|
||||
(impl-mutator update-state))))
|
||||
|
||||
(defn use-form2
|
||||
[& {:keys [spec validators initial]}]
|
||||
(let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial)
|
||||
:errors {}
|
||||
:touched {}})
|
||||
clean-data (s/conform spec (:data state))
|
||||
problems (when (= ::s/invalid clean-data)
|
||||
|
||||
cleaned (s/conform spec (:data state))
|
||||
problems (when (= ::s/invalid cleaned)
|
||||
(::s/problems (s/explain-data spec (:data state))))
|
||||
|
||||
errors (merge (reduce interpret-problem {} problems)
|
||||
(when (not= clean-data ::s/invalid)
|
||||
errors (merge (reduce interpret-problem {} problems)
|
||||
(reduce (fn [errors vf]
|
||||
(merge errors (vf clean-data)))
|
||||
{} validators))
|
||||
(:errors state))]
|
||||
(merge errors (vf (:data state))))
|
||||
{} validators)
|
||||
(:errors state))]
|
||||
(-> (assoc state
|
||||
:errors errors
|
||||
:clean-data (when (not= clean-data ::s/invalid) clean-data)
|
||||
:clean-data (when (not= cleaned ::s/invalid) cleaned)
|
||||
:valid (and (empty? errors)
|
||||
(not= clean-data ::s/invalid)))
|
||||
(not= cleaned ::s/invalid)))
|
||||
(impl-mutator update-state))))
|
||||
|
||||
(defn on-input-change
|
||||
|
|
|
@ -9,8 +9,11 @@
|
|||
|
||||
(ns uxbox.util.object
|
||||
"A collection of helpers for work with javascript objects."
|
||||
(:refer-clojure :exclude [get get-in assoc!])
|
||||
(:require [goog.object :as gobj]))
|
||||
(:refer-clojure :exclude [set! get get-in assoc!])
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[goog.object :as gobj]
|
||||
["lodash/omit" :as omit]))
|
||||
|
||||
(defn get
|
||||
([obj k]
|
||||
|
@ -32,6 +35,14 @@
|
|||
(rest keys)
|
||||
(unchecked-get res key))))))
|
||||
|
||||
(defn without
|
||||
[obj keys]
|
||||
(let [keys (cond
|
||||
(vector? keys) (into-array keys)
|
||||
(array? keys) keys
|
||||
:else (throw (js/Error. "unexpected input")))]
|
||||
(omit obj keys)))
|
||||
|
||||
(defn merge!
|
||||
([a b]
|
||||
(js/Object.assign a b))
|
||||
|
@ -42,3 +53,13 @@
|
|||
[obj key value]
|
||||
(unchecked-set obj key value)
|
||||
obj)
|
||||
|
||||
(defn- props-key-fn
|
||||
[key]
|
||||
(if (or (= key :class) (= key :class-name))
|
||||
"className"
|
||||
(str/camel (name key))))
|
||||
|
||||
(defn clj->props
|
||||
[props]
|
||||
(clj->js props :keyword-fn props-key-fn))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue