mirror of
https://github.com/penpot/penpot.git
synced 2025-05-16 10:46:12 +02:00
Merge branch 'main' into develop
This commit is contained in:
commit
fdeaac7f65
11 changed files with 183 additions and 123 deletions
|
@ -49,8 +49,10 @@
|
||||||
(declare register-profile)
|
(declare register-profile)
|
||||||
|
|
||||||
(s/def ::invitation-token ::us/not-empty-string)
|
(s/def ::invitation-token ::us/not-empty-string)
|
||||||
|
(s/def ::terms-privacy ::us/boolean)
|
||||||
|
|
||||||
(s/def ::register-profile
|
(s/def ::register-profile
|
||||||
(s/keys :req-un [::email ::password ::fullname]
|
(s/keys :req-un [::email ::password ::fullname ::terms-privacy]
|
||||||
:opt-un [::invitation-token]))
|
:opt-un [::invitation-token]))
|
||||||
|
|
||||||
(sv/defmethod ::register-profile {:auth false :rlimit :password}
|
(sv/defmethod ::register-profile {:auth false :rlimit :password}
|
||||||
|
@ -63,6 +65,10 @@
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :email-domain-is-not-allowed))
|
:code :email-domain-is-not-allowed))
|
||||||
|
|
||||||
|
(when-not (:terms-privacy params)
|
||||||
|
(ex/raise :type :validation
|
||||||
|
:code :invalid-terms-and-privacy))
|
||||||
|
|
||||||
(db/with-atomic [conn pool]
|
(db/with-atomic [conn pool]
|
||||||
(let [cfg (assoc cfg :conn conn)]
|
(let [cfg (assoc cfg :conn conn)]
|
||||||
(register-profile cfg params))))
|
(register-profile cfg params))))
|
||||||
|
@ -331,7 +337,8 @@
|
||||||
{:id id}))
|
{:id id}))
|
||||||
|
|
||||||
(s/def ::update-profile
|
(s/def ::update-profile
|
||||||
(s/keys :req-un [::id ::fullname ::lang ::theme]))
|
(s/keys :req-un [::id ::fullname]
|
||||||
|
:opt-un [::lang ::theme]))
|
||||||
|
|
||||||
(sv/defmethod ::update-profile
|
(sv/defmethod ::update-profile
|
||||||
[{:keys [pool] :as cfg} params]
|
[{:keys [pool] :as cfg} params]
|
||||||
|
|
|
@ -190,6 +190,32 @@
|
||||||
(t/testing "not allowed email domain"
|
(t/testing "not allowed email domain"
|
||||||
(t/is (false? (profile/email-domain-in-whitelist? whitelist "username@somedomain.com"))))))
|
(t/is (false? (profile/email-domain-in-whitelist? whitelist "username@somedomain.com"))))))
|
||||||
|
|
||||||
|
(t/deftest test-register-with-no-terms-and-privacy
|
||||||
|
(let [data {::th/type :register-profile
|
||||||
|
:email "user@example.com"
|
||||||
|
:password "foobar"
|
||||||
|
:fullname "foobar"
|
||||||
|
:terms-privacy nil}
|
||||||
|
out (th/mutation! data)
|
||||||
|
error (:error out)
|
||||||
|
edata (ex-data error)]
|
||||||
|
(t/is (th/ex-info? error))
|
||||||
|
(t/is (= (:type edata) :validation))
|
||||||
|
(t/is (= (:code edata) :spec-validation))))
|
||||||
|
|
||||||
|
(t/deftest test-register-with-bad-terms-and-privacy
|
||||||
|
(let [data {::th/type :register-profile
|
||||||
|
:email "user@example.com"
|
||||||
|
:password "foobar"
|
||||||
|
:fullname "foobar"
|
||||||
|
:terms-privacy false}
|
||||||
|
out (th/mutation! data)
|
||||||
|
error (:error out)
|
||||||
|
edata (ex-data error)]
|
||||||
|
(t/is (th/ex-info? error))
|
||||||
|
(t/is (= (:type edata) :validation))
|
||||||
|
(t/is (= (:code edata) :invalid-terms-and-privacy))))
|
||||||
|
|
||||||
(t/deftest test-register-when-registration-disabled
|
(t/deftest test-register-when-registration-disabled
|
||||||
(with-mocks [mock {:target 'app.config/get
|
(with-mocks [mock {:target 'app.config/get
|
||||||
:return (th/mock-config-get-with
|
:return (th/mock-config-get-with
|
||||||
|
@ -197,7 +223,8 @@
|
||||||
(let [data {::th/type :register-profile
|
(let [data {::th/type :register-profile
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"
|
:password "foobar"
|
||||||
:fullname "foobar"}
|
:fullname "foobar"
|
||||||
|
:terms-privacy true}
|
||||||
out (th/mutation! data)
|
out (th/mutation! data)
|
||||||
error (:error out)
|
error (:error out)
|
||||||
edata (ex-data error)]
|
edata (ex-data error)]
|
||||||
|
@ -210,7 +237,8 @@
|
||||||
data {::th/type :register-profile
|
data {::th/type :register-profile
|
||||||
:email (:email profile)
|
:email (:email profile)
|
||||||
:password "foobar"
|
:password "foobar"
|
||||||
:fullname "foobar"}
|
:fullname "foobar"
|
||||||
|
:terms-privacy true}
|
||||||
out (th/mutation! data)
|
out (th/mutation! data)
|
||||||
error (:error out)
|
error (:error out)
|
||||||
edata (ex-data error)]
|
edata (ex-data error)]
|
||||||
|
@ -225,7 +253,8 @@
|
||||||
data {::th/type :register-profile
|
data {::th/type :register-profile
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"
|
:password "foobar"
|
||||||
:fullname "foobar"}
|
:fullname "foobar"
|
||||||
|
:terms-privacy true}
|
||||||
out (th/mutation! data)]
|
out (th/mutation! data)]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(let [mock (deref mock)
|
(let [mock (deref mock)
|
||||||
|
@ -250,7 +279,8 @@
|
||||||
data {::th/type :register-profile
|
data {::th/type :register-profile
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"
|
:password "foobar"
|
||||||
:fullname "foobar"}
|
:fullname "foobar"
|
||||||
|
:terms-privacy true}
|
||||||
_ (th/create-global-complaint-for pool {:type :bounce :email "user@example.com"})
|
_ (th/create-global-complaint-for pool {:type :bounce :email "user@example.com"})
|
||||||
out (th/mutation! data)]
|
out (th/mutation! data)]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
|
@ -270,7 +300,8 @@
|
||||||
data {::th/type :register-profile
|
data {::th/type :register-profile
|
||||||
:email "user@example.com"
|
:email "user@example.com"
|
||||||
:password "foobar"
|
:password "foobar"
|
||||||
:fullname "foobar"}
|
:fullname "foobar"
|
||||||
|
:terms-privacy true}
|
||||||
_ (th/create-global-complaint-for pool {:type :complaint :email "user@example.com"})
|
_ (th/create-global-complaint-for pool {:type :complaint :email "user@example.com"})
|
||||||
out (th/mutation! data)]
|
out (th/mutation! data)]
|
||||||
|
|
||||||
|
|
|
@ -369,6 +369,13 @@
|
||||||
},
|
},
|
||||||
"used-in" : [ "src/app/main/ui/auth.cljs" ]
|
"used-in" : [ "src/app/main/ui/auth.cljs" ]
|
||||||
},
|
},
|
||||||
|
"auth.terms-privacy-agreement" : {
|
||||||
|
"translations" : {
|
||||||
|
"en" : "When creating a new account, you agree to our terms of service and privacy policy.",
|
||||||
|
"es" : "Al crear una nueva cuenta, aceptas nuestros términos de servicio y política de privacidad."
|
||||||
|
},
|
||||||
|
"used-in" : [ "src/app/main/ui/auth/register.cljs" ]
|
||||||
|
},
|
||||||
"auth.verification-email-sent" : {
|
"auth.verification-email-sent" : {
|
||||||
"translations" : {
|
"translations" : {
|
||||||
"ca" : "Em enviat un correu de verificació a",
|
"ca" : "Em enviat un correu de verificació a",
|
||||||
|
@ -941,6 +948,13 @@
|
||||||
},
|
},
|
||||||
"used-in" : [ "src/app/main/ui/dashboard/grid.cljs" ]
|
"used-in" : [ "src/app/main/ui/dashboard/grid.cljs" ]
|
||||||
},
|
},
|
||||||
|
"errors.terms-privacy-agreement-invalid" : {
|
||||||
|
"translations" : {
|
||||||
|
"en" : "You must accept our terms of service and privacy policy.",
|
||||||
|
"es" : "Debes aceptar nuestros términos de servicio y política de privacidad."
|
||||||
|
},
|
||||||
|
"used-in" : [ "src/app/main/ui/auth/register.cljs" ]
|
||||||
|
},
|
||||||
"errors.clipboard-not-implemented" : {
|
"errors.clipboard-not-implemented" : {
|
||||||
"translations" : {
|
"translations" : {
|
||||||
"ca" : "El teu navegador no pot realitzar aquesta operació",
|
"ca" : "El teu navegador no pot realitzar aquesta operació",
|
||||||
|
|
|
@ -561,27 +561,28 @@ input.element-name {
|
||||||
.input-radio,
|
.input-radio,
|
||||||
.input-checkbox {
|
.input-checkbox {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
color: $color-gray-40;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding-left: 0px;
|
padding-left: 0px;
|
||||||
|
|
||||||
label{
|
label{
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-right: 15px;
|
margin-right: 15px;
|
||||||
font-size: $fs13;
|
font-size: $fs12;
|
||||||
|
|
||||||
&:before{
|
&:before{
|
||||||
content:"";
|
content:"";
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
background-color: $color-gray-50;
|
background-color: $color-gray-10;
|
||||||
border: 1px solid $color-gray-60;
|
border: 1px solid $color-gray-30;
|
||||||
box-shadow: inset 0 0 0 0 $color-primary;
|
box-shadow: inset 0 0 0 0 $color-primary;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -676,7 +677,6 @@ input[type=radio]:checked + label:before{
|
||||||
|
|
||||||
label {
|
label {
|
||||||
transition: border 0.2s linear 0s, color 0.2s linear 0s;
|
transition: border 0.2s linear 0s, color 0.2s linear 0s;
|
||||||
white-space: nowrap;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&:before {
|
&:before {
|
||||||
|
@ -687,11 +687,11 @@ input[type=radio]:checked + label:before{
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 16px;
|
width: 20px;
|
||||||
height: 16px;
|
height: 20px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 3.2px;
|
left: 3.2px;
|
||||||
top: 0px;
|
top: 0;
|
||||||
font-size: $fs11;
|
font-size: $fs11;
|
||||||
transition: border 0.2s linear 0s, color 0.2s linear 0s;
|
transition: border 0.2s linear 0s, color 0.2s linear 0s;
|
||||||
}
|
}
|
||||||
|
@ -732,7 +732,7 @@ input[type=radio]:checked + label:before{
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content:"✓";
|
content:"✓";
|
||||||
color: #000000;
|
color: #ffffff;
|
||||||
font-size: $fs16;
|
font-size: $fs16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -275,18 +275,6 @@
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-checkbox {
|
|
||||||
margin: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 5px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// STYLES FOR LIBRARIES
|
// STYLES FOR LIBRARIES
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
(s/def ::email ::us/email)
|
(s/def ::email ::us/email)
|
||||||
(s/def ::password ::us/string)
|
(s/def ::password ::us/string)
|
||||||
(s/def ::lang (s/nilable ::us/string))
|
(s/def ::lang (s/nilable ::us/string))
|
||||||
(s/def ::theme ::us/string)
|
(s/def ::theme (s/nilable ::us/string))
|
||||||
(s/def ::created-at ::us/inst)
|
(s/def ::created-at ::us/inst)
|
||||||
(s/def ::password-1 ::us/string)
|
(s/def ::password-1 ::us/string)
|
||||||
(s/def ::password-2 ::us/string)
|
(s/def ::password-2 ::us/string)
|
||||||
|
@ -55,17 +55,15 @@
|
||||||
(ptk/reify ::profile-fetched
|
(ptk/reify ::profile-fetched
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(assoc state :profile
|
(assoc state :profile data))
|
||||||
(cond-> data
|
|
||||||
(nil? (:theme data))
|
|
||||||
(assoc :theme cfg/default-theme))))
|
|
||||||
|
|
||||||
ptk/EffectEvent
|
ptk/EffectEvent
|
||||||
(effect [_ state stream]
|
(effect [_ state stream]
|
||||||
(let [profile (:profile state)]
|
(let [profile (:profile state)]
|
||||||
(swap! storage assoc :profile profile)
|
(swap! storage assoc :profile profile)
|
||||||
(i18n/set-locale! (:lang profile))
|
(i18n/set-locale! (:lang profile))
|
||||||
(theme/set-current-theme! (:theme profile))))))
|
(some-> (:theme profile)
|
||||||
|
(theme/set-current-theme!))))))
|
||||||
|
|
||||||
;; --- Fetch Profile
|
;; --- Fetch Profile
|
||||||
|
|
||||||
|
@ -91,16 +89,19 @@
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [mdata (meta data)
|
(let [mdata (meta data)
|
||||||
on-success (:on-success mdata identity)
|
on-success (:on-success mdata identity)
|
||||||
on-error (:on-error mdata identity)]
|
on-error (:on-error mdata #(rx/throw %))]
|
||||||
(rx/merge
|
|
||||||
(->> (rp/mutation :update-profile data)
|
(->> (rp/mutation :update-profile data)
|
||||||
(rx/map fetch-profile)
|
(rx/catch on-error)
|
||||||
(rx/catch on-error))
|
(rx/mapcat
|
||||||
|
(fn [_]
|
||||||
|
(rx/merge
|
||||||
(->> stream
|
(->> stream
|
||||||
(rx/filter (ptk/type? ::profile-fetched))
|
(rx/filter (ptk/type? ::profile-fetched))
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/ignore)))))))
|
(rx/ignore))
|
||||||
|
(rx/of (profile-fetched data))))))))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Request Email Change
|
;; --- Request Email Change
|
||||||
|
|
||||||
|
|
|
@ -36,17 +36,23 @@
|
||||||
|
|
||||||
(defn- validate
|
(defn- validate
|
||||||
[data]
|
[data]
|
||||||
(let [password (:password data)]
|
(let [password (:password data)
|
||||||
(when (> 8 (count password))
|
terms-privacy (:terms-privacy data)]
|
||||||
{:password {:message "errors.password-too-short"}})))
|
(cond-> {}
|
||||||
|
(> 8 (count password))
|
||||||
|
(assoc :password {:message "errors.password-too-short"})
|
||||||
|
|
||||||
|
(and (not terms-privacy) false)
|
||||||
|
(assoc :terms-privacy {:message "errors.terms-privacy-agreement-invalid"}))))
|
||||||
|
|
||||||
(s/def ::fullname ::us/not-empty-string)
|
(s/def ::fullname ::us/not-empty-string)
|
||||||
(s/def ::password ::us/not-empty-string)
|
(s/def ::password ::us/not-empty-string)
|
||||||
(s/def ::email ::us/email)
|
(s/def ::email ::us/email)
|
||||||
(s/def ::invitation-token ::us/not-empty-string)
|
(s/def ::invitation-token ::us/not-empty-string)
|
||||||
|
(s/def ::terms-privacy ::us/boolean)
|
||||||
|
|
||||||
(s/def ::register-form
|
(s/def ::register-form
|
||||||
(s/keys :req-un [::password ::fullname ::email]
|
(s/keys :req-un [::password ::fullname ::email ::terms-privacy]
|
||||||
:opt-un [::invitation-token]))
|
:opt-un [::invitation-token]))
|
||||||
|
|
||||||
(mf/defc register-form
|
(mf/defc register-form
|
||||||
|
@ -113,10 +119,16 @@
|
||||||
:label (tr "auth.password")
|
:label (tr "auth.password")
|
||||||
:type "password"}]]
|
:type "password"}]]
|
||||||
|
|
||||||
|
[:div.fields-row
|
||||||
|
[:& fm/input {:name :terms-privacy
|
||||||
|
:class "check-primary"
|
||||||
|
:tab-index "4"
|
||||||
|
:label (tr "auth.terms-privacy-agreement")
|
||||||
|
:type "checkbox"}]]
|
||||||
|
|
||||||
[:& fm/submit-button
|
[:& fm/submit-button
|
||||||
{:label (tr "auth.register-submit")
|
{:label (tr "auth.register-submit")
|
||||||
:disabled @submitted?
|
:disabled @submitted?}]]))
|
||||||
}]]))
|
|
||||||
|
|
||||||
;; --- Register Page
|
;; --- Register Page
|
||||||
|
|
||||||
|
|
|
@ -9,70 +9,84 @@
|
||||||
|
|
||||||
(ns app.main.ui.components.forms
|
(ns app.main.ui.components.forms
|
||||||
(:require
|
(:require
|
||||||
[rumext.alpha :as mf]
|
|
||||||
[cuerdas.core :as str]
|
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.util.object :as obj]
|
[app.util.dom :as dom]
|
||||||
[app.util.forms :as fm]
|
[app.util.forms :as fm]
|
||||||
[app.util.i18n :as i18n :refer [t tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
["react" :as react]
|
[app.util.object :as obj]
|
||||||
[app.util.dom :as dom]))
|
[cuerdas.core :as str]
|
||||||
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(def form-ctx (mf/create-context nil))
|
(def form-ctx (mf/create-context nil))
|
||||||
(def use-form fm/use-form)
|
(def use-form fm/use-form)
|
||||||
|
|
||||||
(mf/defc input
|
(mf/defc input
|
||||||
[{:keys [type label help-icon disabled name form hint trim] :as props}]
|
[{:keys [label help-icon disabled form hint trim] :as props}]
|
||||||
(let [form (or form (mf/use-ctx form-ctx))
|
(let [input-type (get props :type)
|
||||||
|
input-name (get props :name)
|
||||||
|
more-classes (get props :class)
|
||||||
|
|
||||||
type' (mf/use-state type)
|
form (or form (mf/use-ctx form-ctx))
|
||||||
|
|
||||||
|
type' (mf/use-state input-type)
|
||||||
focus? (mf/use-state false)
|
focus? (mf/use-state false)
|
||||||
|
|
||||||
touched? (get-in @form [:touched name])
|
is-checkbox? (= @type' "checkbox")
|
||||||
error (get-in @form [:errors name])
|
is-radio? (= @type' "radio")
|
||||||
|
is-text? (or (= @type' "password")
|
||||||
|
(= @type' "text")
|
||||||
|
(= @type' "email"))
|
||||||
|
|
||||||
value (get-in @form [:data name] "")
|
touched? (get-in @form [:touched input-name])
|
||||||
|
error (get-in @form [:errors input-name])
|
||||||
|
|
||||||
|
value (get-in @form [:data input-name] "")
|
||||||
|
|
||||||
help-icon' (cond
|
help-icon' (cond
|
||||||
(and (= type "password")
|
(and (= input-type "password")
|
||||||
(= @type' "password"))
|
(= @type' "password"))
|
||||||
i/eye
|
i/eye
|
||||||
|
|
||||||
(and (= type "password")
|
(and (= input-type "password")
|
||||||
(= @type' "text"))
|
(= @type' "text"))
|
||||||
i/eye-closed
|
i/eye-closed
|
||||||
|
|
||||||
:else
|
:else
|
||||||
help-icon)
|
help-icon)
|
||||||
|
|
||||||
klass (dom/classnames
|
klass (str more-classes " "
|
||||||
|
(dom/classnames
|
||||||
:focus @focus?
|
:focus @focus?
|
||||||
:valid (and touched? (not error))
|
:valid (and touched? (not error))
|
||||||
:invalid (and touched? error)
|
:invalid (and touched? error)
|
||||||
:disabled disabled
|
:disabled disabled
|
||||||
:empty (str/empty? value)
|
:empty (and is-text? (str/empty? value))
|
||||||
:with-icon (not (nil? help-icon')))
|
:with-icon (not (nil? help-icon'))
|
||||||
|
:custom-input is-text?
|
||||||
|
:input-radio is-radio?
|
||||||
|
:input-checkbox is-checkbox?))
|
||||||
|
|
||||||
swap-text-password
|
swap-text-password
|
||||||
(fn []
|
(fn []
|
||||||
(swap! type' (fn [type]
|
(swap! type' (fn [input-type]
|
||||||
(if (= "password" type)
|
(if (= "password" input-type)
|
||||||
"text"
|
"text"
|
||||||
"password"))))
|
"password"))))
|
||||||
|
|
||||||
on-focus #(reset! focus? true)
|
on-focus #(reset! focus? true)
|
||||||
on-change (fm/on-input-change form name trim)
|
on-change (fm/on-input-change form input-name trim)
|
||||||
|
|
||||||
on-blur
|
on-blur
|
||||||
(fn [event]
|
(fn [_]
|
||||||
(reset! focus? false)
|
(reset! focus? false)
|
||||||
(when-not (get-in @form [:touched name])
|
(when-not (get-in @form [:touched input-name])
|
||||||
(swap! form assoc-in [:touched name] true)))
|
(swap! form assoc-in [:touched input-name] true)))
|
||||||
|
|
||||||
props (-> props
|
props (-> props
|
||||||
(dissoc :help-icon :form :trim)
|
(dissoc :help-icon :form :trim)
|
||||||
(assoc :value value
|
(assoc :id (name input-name)
|
||||||
|
:value value
|
||||||
:on-focus on-focus
|
:on-focus on-focus
|
||||||
:on-blur on-blur
|
:on-blur on-blur
|
||||||
:placeholder label
|
:placeholder label
|
||||||
|
@ -80,15 +94,15 @@
|
||||||
:type @type')
|
:type @type')
|
||||||
(obj/clj->props))]
|
(obj/clj->props))]
|
||||||
|
|
||||||
[:div.custom-input
|
[:div
|
||||||
{:class klass}
|
{:class klass}
|
||||||
[:*
|
[:*
|
||||||
[:label label]
|
|
||||||
[:> :input props]
|
[:> :input props]
|
||||||
|
[:label {:for (name input-name)} label]
|
||||||
(when help-icon'
|
(when help-icon'
|
||||||
[:div.help-icon
|
[:div.help-icon
|
||||||
{:style {:cursor "pointer"}
|
{:style {:cursor "pointer"}
|
||||||
:on-click (when (= "password" type)
|
:on-click (when (= "password" input-type)
|
||||||
swap-text-password)}
|
swap-text-password)}
|
||||||
help-icon'])
|
help-icon'])
|
||||||
(cond
|
(cond
|
||||||
|
@ -100,16 +114,17 @@
|
||||||
|
|
||||||
|
|
||||||
(mf/defc textarea
|
(mf/defc textarea
|
||||||
[{:keys [label disabled name form hint trim] :as props}]
|
[{:keys [label disabled form hint trim] :as props}]
|
||||||
(let [form (or form (mf/use-ctx form-ctx))
|
(let [input-name (get props :name)
|
||||||
|
|
||||||
|
form (or form (mf/use-ctx form-ctx))
|
||||||
|
|
||||||
type' (mf/use-state type)
|
|
||||||
focus? (mf/use-state false)
|
focus? (mf/use-state false)
|
||||||
|
|
||||||
touched? (get-in @form [:touched name])
|
touched? (get-in @form [:touched input-name])
|
||||||
error (get-in @form [:errors name])
|
error (get-in @form [:errors input-name])
|
||||||
|
|
||||||
value (get-in @form [:data name] "")
|
value (get-in @form [:data input-name] "")
|
||||||
|
|
||||||
klass (dom/classnames
|
klass (dom/classnames
|
||||||
:focus @focus?
|
:focus @focus?
|
||||||
|
@ -120,13 +135,13 @@
|
||||||
)
|
)
|
||||||
|
|
||||||
on-focus #(reset! focus? true)
|
on-focus #(reset! focus? true)
|
||||||
on-change (fm/on-input-change form name trim)
|
on-change (fm/on-input-change form input-name trim)
|
||||||
|
|
||||||
on-blur
|
on-blur
|
||||||
(fn [event]
|
(fn [_]
|
||||||
(reset! focus? false)
|
(reset! focus? false)
|
||||||
(when-not (get-in @form [:touched name])
|
(when-not (get-in @form [:touched input-name])
|
||||||
(swap! form assoc-in [:touched name] true)))
|
(swap! form assoc-in [:touched input-name] true)))
|
||||||
|
|
||||||
props (-> props
|
props (-> props
|
||||||
(dissoc :help-icon :form :trim)
|
(dissoc :help-icon :form :trim)
|
||||||
|
@ -134,8 +149,7 @@
|
||||||
:on-focus on-focus
|
:on-focus on-focus
|
||||||
:on-blur on-blur
|
:on-blur on-blur
|
||||||
;; :placeholder label
|
;; :placeholder label
|
||||||
:on-change on-change
|
:on-change on-change)
|
||||||
:type @type')
|
|
||||||
(obj/clj->props))]
|
(obj/clj->props))]
|
||||||
|
|
||||||
[:div.custom-input
|
[:div.custom-input
|
||||||
|
@ -151,12 +165,14 @@
|
||||||
[:span.hint hint])]]))
|
[:span.hint hint])]]))
|
||||||
|
|
||||||
(mf/defc select
|
(mf/defc select
|
||||||
[{:keys [options label name form default]
|
[{:keys [options label form default] :as props
|
||||||
:or {default ""}}]
|
:or {default ""}}]
|
||||||
(let [form (or form (mf/use-ctx form-ctx))
|
(let [input-name (get props :name)
|
||||||
value (get-in @form [:data name] default)
|
|
||||||
|
form (or form (mf/use-ctx form-ctx))
|
||||||
|
value (or (get-in @form [:data input-name]) default)
|
||||||
cvalue (d/seek #(= value (:value %)) options)
|
cvalue (d/seek #(= value (:value %)) options)
|
||||||
on-change (fm/on-input-change form name)]
|
on-change (fm/on-input-change form input-name)]
|
||||||
|
|
||||||
[:div.custom-select
|
[:div.custom-select
|
||||||
[:select {:value value
|
[:select {:value value
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
(ns app.main.ui.settings.options
|
(ns app.main.ui.settings.options
|
||||||
(:require
|
(:require
|
||||||
[app.common.spec :as us]
|
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.spec :as us]
|
||||||
[app.main.data.messages :as dm]
|
[app.main.data.messages :as dm]
|
||||||
[app.main.data.users :as du]
|
[app.main.data.users :as du]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
|
@ -28,10 +28,6 @@
|
||||||
(s/def ::options-form
|
(s/def ::options-form
|
||||||
(s/keys :opt-un [::lang ::theme]))
|
(s/keys :opt-un [::lang ::theme]))
|
||||||
|
|
||||||
(defn- on-error
|
|
||||||
[form error]
|
|
||||||
(st/emit! (dm/error (tr "errors.generic"))))
|
|
||||||
|
|
||||||
(defn- on-success
|
(defn- on-success
|
||||||
[form]
|
[form]
|
||||||
(st/emit! (dm/success (tr "notifications.profile-saved"))))
|
(st/emit! (dm/success (tr "notifications.profile-saved"))))
|
||||||
|
@ -42,8 +38,7 @@
|
||||||
data (cond-> data
|
data (cond-> data
|
||||||
(empty? (:lang data))
|
(empty? (:lang data))
|
||||||
(assoc :lang nil))
|
(assoc :lang nil))
|
||||||
mdata {:on-success (partial on-success form)
|
mdata {:on-success (partial on-success form)}]
|
||||||
:on-error (partial on-error form)}]
|
|
||||||
(st/emit! (du/update-profile (with-meta data mdata)))))
|
(st/emit! (du/update-profile (with-meta data mdata)))))
|
||||||
|
|
||||||
(mf/defc options-form
|
(mf/defc options-form
|
||||||
|
|
|
@ -31,24 +31,18 @@
|
||||||
(s/def ::email ::us/email)
|
(s/def ::email ::us/email)
|
||||||
|
|
||||||
(s/def ::profile-form
|
(s/def ::profile-form
|
||||||
(s/keys :req-un [::fullname ::lang ::theme ::email]))
|
(s/keys :req-un [::fullname ::email]))
|
||||||
|
|
||||||
(defn- on-success
|
(defn- on-success
|
||||||
[form]
|
[form]
|
||||||
(st/emit! (dm/success (tr "notifications.profile-saved"))))
|
(st/emit! (dm/success (tr "notifications.profile-saved"))))
|
||||||
|
|
||||||
(defn- on-error
|
|
||||||
[form error]
|
|
||||||
(st/emit! (dm/error (tr "errors.generic"))))
|
|
||||||
|
|
||||||
(defn- on-submit
|
(defn- on-submit
|
||||||
[form event]
|
[form event]
|
||||||
(let [data (:clean-data @form)
|
(let [data (:clean-data @form)
|
||||||
mdata {:on-success (partial on-success form)
|
mdata {:on-success (partial on-success form)}]
|
||||||
:on-error (partial on-error form)}]
|
|
||||||
(st/emit! (du/update-profile (with-meta data mdata)))))
|
(st/emit! (du/update-profile (with-meta data mdata)))))
|
||||||
|
|
||||||
|
|
||||||
;; --- Profile Form
|
;; --- Profile Form
|
||||||
|
|
||||||
(mf/defc profile-form
|
(mf/defc profile-form
|
||||||
|
|
|
@ -89,7 +89,6 @@
|
||||||
(mf/set-ref-val! state-ref new-value)
|
(mf/set-ref-val! state-ref new-value)
|
||||||
(render inc))
|
(render inc))
|
||||||
|
|
||||||
|
|
||||||
ISwap
|
ISwap
|
||||||
(-swap! [self f]
|
(-swap! [self f]
|
||||||
(let [f (wrap-update-fn f opts)]
|
(let [f (wrap-update-fn f opts)]
|
||||||
|
@ -119,7 +118,10 @@
|
||||||
([form field trim?]
|
([form field trim?]
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [target (dom/get-target event)
|
(let [target (dom/get-target event)
|
||||||
value (dom/get-value target)]
|
value (if (or (= (.-type target) "checkbox")
|
||||||
|
(= (.-type target) "radio"))
|
||||||
|
(.-checked target)
|
||||||
|
(dom/get-value target))]
|
||||||
(swap! form (fn [state]
|
(swap! form (fn [state]
|
||||||
(-> state
|
(-> state
|
||||||
(assoc-in [:data field] (if trim? (str/trim value) value))
|
(assoc-in [:data field] (if trim? (str/trim value) value))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue