0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-06 14:50:20 -05:00

♻️ Refactor auth related components

This commit is contained in:
Andrey Antukh 2023-07-20 14:54:05 +02:00
parent c598656f60
commit 3f2cd30f52
6 changed files with 267 additions and 222 deletions

View file

@ -92,7 +92,7 @@
(assoc :message (tr "errors.email-invalid"))))))
(mf/defc login-form
[{:keys [params on-success-callback] :as props}]
[{:keys [params on-success] :as props}]
(let [initial (mf/use-memo (mf/deps params) (constantly params))
totp* (mf/use-state false)
@ -104,64 +104,74 @@
:initial initial)
on-error
(fn [cause]
(when (map? cause)
(err/print-trace! cause)
(err/print-data! cause)
(err/print-explain! cause))
(mf/use-fn
(fn [form cause]
(when (map? cause)
(err/print-trace! cause)
(err/print-data! cause)
(err/print-explain! cause))
(cond
(and (= :totp (:code cause))
(= :negotiation (:type cause)))
(do
(reset! totp* true)
(reset! error (tr "errors.missing-totp"))
(swap! form (fn [form]
(-> form
(update :errors assoc :totp {:message (tr "errors.missing-totp")})
(update :touched assoc :totp true)))))
(cond
(and (= :totp (:code cause))
(= :negotiation (:type cause)))
(reset! totp* true)
(and (= :invalid-totp (:code cause))
(= :negotiation (:type cause)))
(do
;; (reset! error (tr "errors.invalid-totp"))
(swap! form (fn [form]
(-> form
(update :errors assoc :totp {:message (tr "errors.invalid-totp")})
(update :touched assoc :totp true)))))
(and (= :restriction (:type cause))
(= :passkey-disabled (:code cause)))
(reset! error (tr "errors.wrong-credentials"))
(and (= :restriction (:type cause))
(= :passkey-disabled (:code cause)))
(reset! error (tr "errors.wrong-credentials"))
(and (= :restriction (:type cause))
(= :profile-blocked (:code cause)))
(reset! error (tr "errors.profile-blocked"))
(and (= :restriction (:type cause))
(= :profile-blocked (:code cause)))
(reset! error (tr "errors.profile-blocked"))
(and (= :restriction (:type cause))
(= :admin-only-profile (:code cause)))
(reset! error (tr "errors.profile-blocked"))
(and (= :restriction (:type cause))
(= :admin-only-profile (:code cause)))
(reset! error (tr "errors.profile-blocked"))
(and (= :validation (:type cause))
(= :wrong-credentials (:code cause)))
(reset! error (tr "errors.wrong-credentials"))
(and (= :validation (:type cause))
(= :wrong-credentials (:code cause)))
(reset! error (tr "errors.wrong-credentials"))
(and (= :validation (:type cause))
(= :account-without-password (:code cause)))
(reset! error (tr "errors.wrong-credentials"))
(and (= :validation (:type cause))
(= :account-without-password (:code cause)))
(reset! error (tr "errors.wrong-credentials"))
:else
(reset! error (tr "errors.generic"))))
:else
(reset! error (tr "errors.generic")))))
on-success-default
(fn [data]
(when-let [token (:invitation-token data)]
(st/emit! (rt/nav :auth-verify-token {} {:token token}))))
(mf/use-fn
(fn [data]
(when-let [token (:invitation-token data)]
(st/emit! (rt/nav :auth-verify-token {} {:token token})))))
on-success
(fn [data]
(if (nil? on-success-callback)
(on-success-default data)
(on-success-callback)))
(mf/use-fn
(mf/deps on-success)
(fn [data]
(if (fn? on-success)
(on-success data)
(on-success-default data))))
on-submit
(mf/use-fn
(fn [form event]
(let [event (dom/event->native-event event)
submitter (unchecked-get event "submitter")
submitter (dom/get-data submitter "role")]
submitter (dom/get-data submitter "role")
on-error (partial on-error form)
on-success (partial on-success form)]
(case submitter
"login-with-passkey"
@ -175,6 +185,7 @@
{:on-error on-error
:on-success on-success})]
(st/emit! (du/login-with-password params)))
nil))))
on-submit-ldap
@ -284,7 +295,7 @@
(tr "auth.login-with-oidc-submit")]]))
(mf/defc login-methods
[{:keys [params on-success-callback] :as props}]
[{:keys [params on-success] :as props}]
[:*
(when show-alt-login-buttons?
[:*
@ -306,35 +317,40 @@
(when (or (contains? cf/flags :login)
(contains? cf/flags :login-with-password)
(contains? cf/flags :login-with-ldap))
[:& login-form {:params params :on-success-callback on-success-callback}])])
[:& login-form {:params params :on-success on-success}])])
(mf/defc login-page
[{:keys [params] :as props}]
[:div.generic-form.login-form
[:div.form-container
[:h1 {:data-test "login-title"} (tr "auth.login-title")]
{::mf/wrap-props false}
[{:keys [params]}]
(let [nav-to-recovery (mf/use-fn #(st/emit! (rt/nav :auth-recovery-request)))
nav-to-register (mf/use-fn (mf/deps params) #(st/emit! (rt/nav :auth-register {} params)))
create-demo (mf/use-fn #(st/emit! (du/create-demo-profile)))]
[:& login-methods {:params params}]
[:div.generic-form.login-form
[:div.form-container
[:h1 {:data-test "login-title"} (tr "auth.login-title")]
[:div.links
(when (or (contains? cf/flags :login)
(contains? cf/flags :login-with-password))
[:div.link-entry
[:& lk/link {:action #(st/emit! (rt/nav :auth-recovery-request))
:data-test "forgot-password"}
(tr "auth.forgot-password")]])
[:& login-methods {:params params}]
(when (contains? cf/flags :registration)
[:div.link-entry
[:span (tr "auth.register") " "]
[:& lk/link {:action #(st/emit! (rt/nav :auth-register {} params))
:data-test "register-submit"}
(tr "auth.register-submit")]])]
[:div.links
(when (or (contains? cf/flags :login)
(contains? cf/flags :login-with-password))
[:div.link-entry
[:& lk/link {:on-click nav-to-recovery
:data-test "forgot-password"}
(tr "auth.forgot-password")]])
(when (contains? cf/flags :demo-users)
[:div.links.demo
[:div.link-entry
[:span (tr "auth.create-demo-profile") " "]
[:& lk/link {:action #(st/emit! (du/create-demo-profile))
:data-test "demo-account-link"}
(tr "auth.create-demo-account")]]])]])
(when (contains? cf/flags :registration)
[:div.link-entry
[:span (tr "auth.register") " "]
[:& lk/link {:on-click nav-to-register
:data-test "register-submit"}
(tr "auth.register-submit")]])]
(when (contains? cf/flags :demo-users)
[:div.links.demo
[:div.link-entry
[:span (tr "auth.create-demo-profile") " "]
[:& lk/link {:on-click create-demo
:data-test "demo-account-link"}
(tr "auth.create-demo-account")]]])]]))

View file

@ -21,7 +21,9 @@
[rumext.v2 :as mf]))
(s/def ::email ::us/email)
(s/def ::recovery-request-form (s/keys :req-un [::email]))
(s/def ::recovery-request-form
(s/keys :req-un [::email]))
(defn handle-error-messages
[errors _data]
(d/update-when errors :email
@ -31,24 +33,27 @@
(assoc :message (tr "errors.email-invalid"))))))
(mf/defc recovery-form
[{:keys [on-success-callback] :as props}]
{::mf/wrap-props false}
[{:keys [on-success]}]
(let [form (fm/use-form :spec ::recovery-request-form
:validators [handle-error-messages]
:initial {})
submitted (mf/use-state false)
default-success-finish #(st/emit! (dm/info (tr "auth.notifications.recovery-token-sent")))
default-on-success
(mf/use-fn #(st/emit! (dm/info (tr "auth.notifications.recovery-token-sent"))))
on-success
(mf/use-callback
(mf/use-fn
(mf/deps default-on-success on-success)
(fn [cdata _]
(reset! submitted false)
(if (nil? on-success-callback)
(default-success-finish)
(on-success-callback (:email cdata)))))
(if (fn? on-success)
(on-success (:email cdata))
(default-on-success))))
on-error
(mf/use-callback
(mf/use-fn
(fn [data {:keys [code] :as error}]
(reset! submitted false)
(case code
@ -64,7 +69,7 @@
(rx/throw error))))
on-submit
(mf/use-callback
(mf/use-fn
(fn []
(reset! submitted true)
(let [cdata (:clean-data @form)
@ -74,32 +79,34 @@
(reset! form nil)
(st/emit! (du/request-profile-recovery params)))))]
[:& fm/form {:on-submit on-submit
:form form}
[:& fm/form {:on-submit on-submit :form form}
[:div.fields-row
[:& fm/input {:name :email
:label (tr "auth.email")
:help-icon i/at
:type "text"}]]
[:& fm/input
{:name :email
:label (tr "auth.email")
:help-icon i/at
:type "text"}]]
[:> fm/submit-button*
{:label (tr "auth.recovery-request-submit")
:data-test "recovery-resquest-submit"}]]))
;; --- Recovery Request Page
(mf/defc recovery-request-page
[{:keys [params on-success-callback go-back-callback] :as props}]
(let [default-go-back #(st/emit! (rt/nav :auth-login))
go-back (or go-back-callback default-go-back)]
{::mf/wrap-props false}
[{:keys [params on-success on-go-back]}]
(let [default-go-back (mf/use-fn #(st/emit! (rt/nav :auth-login)))
on-go-back (or on-go-back default-go-back)]
[:section.generic-form
[:div.form-container
[:h1 (tr "auth.recovery-request-title")]
[:div.subtitle (tr "auth.recovery-request-subtitle")]
[:& recovery-form {:params params :on-success-callback on-success-callback}]
[:& recovery-form
{:params params
:on-success on-success}]
[:div.links
[:div.link-entry
[:& lk/link {:action go-back
[:& lk/link {:on-click on-go-back
:data-test "go-back-link"}
(tr "labels.go-back")]]]]]))

View file

@ -16,6 +16,7 @@
[app.main.ui.auth.login :as login]
[app.main.ui.components.forms :as fm]
[app.main.ui.components.link :as lk]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.main.ui.messages :as msgs]
[app.util.i18n :refer [tr]]
@ -55,63 +56,64 @@
(s/keys :req-un [::password ::email]
:opt-un [::invitation-token]))
(defn- handle-prepare-register-error
[form {:keys [type code] :as cause}]
(condp = [type code]
[:restriction :registration-disabled]
(st/emit! (dm/error (tr "errors.registration-disabled")))
[:restriction :profile-blocked]
(st/emit! (dm/error (tr "errors.profile-blocked")))
[:validation :email-has-permanent-bounces]
(let [email (get @form [:data :email])]
(st/emit! (dm/error (tr "errors.email-has-permanent-bounces" email))))
[:validation :email-already-exists]
(swap! form assoc-in [:errors :email]
{:message "errors.email-already-exists"})
[:validation :email-as-password]
(swap! form assoc-in [:errors :password]
{:message "errors.email-as-password"})
(st/emit! (dm/error (tr "errors.generic")))))
(defn- handle-prepare-register-success
[params]
(st/emit! (rt/nav :auth-register-validate {} params)))
(mf/defc register-form
[{:keys [params on-success-callback] :as props}]
(let [initial (mf/use-memo (mf/deps params) (constantly params))
[{:keys [params on-success]}]
(let [initial (hooks/use-equal-memo params)
form (fm/use-form :spec ::register-form
:validators [validate
(fm/validate-not-empty :password (tr "auth.password-not-empty"))]
:initial initial)
submitted? (mf/use-state false)
on-success (fn [p]
(if (nil? on-success-callback)
(handle-prepare-register-success p)
(on-success-callback p)))
on-success-default
(mf/use-fn #(st/emit! (rt/nav :auth-register-validate {} %)))
on-success
(mf/use-fn
(mf/deps on-success on-success-default)
(fn [_ data]
(if (fn? on-success)
(on-success data)
(on-success-default data))))
on-error
(mf/use-fn
(fn [form {:keys [type code] :as cause}]
(condp = [type code]
[:restriction :registration-disabled]
(st/emit! (dm/error (tr "errors.registration-disabled")))
[:restriction :profile-blocked]
(st/emit! (dm/error (tr "errors.profile-blocked")))
[:validation :email-has-permanent-bounces]
(let [email (get @form [:data :email])]
(st/emit! (dm/error (tr "errors.email-has-permanent-bounces" email))))
[:validation :email-already-exists]
(swap! form assoc-in [:errors :email]
{:message "errors.email-already-exists"})
[:validation :email-as-password]
(swap! form assoc-in [:errors :password]
{:message "errors.email-as-password"})
(st/emit! (dm/error (tr "errors.generic"))))))
on-submit
(mf/use-callback
(mf/use-fn
(mf/deps on-success on-error)
(fn [form _event]
(reset! submitted? true)
(let [cdata (:clean-data @form)]
(let [cdata (:clean-data @form)
on-error (partial on-error form)
on-success (partial on-success form)]
(->> (rp/cmd! :prepare-register-profile cdata)
(rx/map #(merge % params))
(rx/finalize #(reset! submitted? false))
(rx/subs
on-success
(partial handle-prepare-register-error form))))))]
(rx/subs on-success on-error)))))]
[:& fm/form {:on-submit on-submit
:form form}
[:& fm/form {:on-submit on-submit :form form}
[:div.fields-row
[:& fm/input {:type "email"
:name :email
@ -131,7 +133,7 @@
(mf/defc register-methods
[{:keys [params on-success-callback] :as props}]
[{:keys [params on-success]}]
[:*
(when login/show-alt-login-buttons?
[:*
@ -149,62 +151,39 @@
[:span.text (tr "labels.or")]
[:span.line]])])
[:& register-form {:params params :on-success-callback on-success-callback}]])
[:& register-form {:params params :on-success on-success}]])
(mf/defc register-page
[{:keys [params] :as props}]
[:div.form-container
[{:keys [params]}]
(let [nav-to-login (mf/use-fn (mf/deps params) #(st/emit! (rt/nav :auth-login {} params)))
create-demo (mf/use-fn #(st/emit! (du/create-demo-profile)))]
[:h1 {:data-test "registration-title"} (tr "auth.register-title")]
[:div.subtitle (tr "auth.register-subtitle")]
[:div.form-container
(when (contains? cf/flags :demo-warning)
[:& demo-warning])
[:h1 {:data-test "registration-title"} (tr "auth.register-title")]
[:div.subtitle (tr "auth.register-subtitle")]
[:& register-methods {:params params}]
(when (contains? cf/flags :demo-warning)
[:& demo-warning])
[:div.links
[:div.link-entry
[:span (tr "auth.already-have-account") " "]
[:& register-methods {:params params}]
[:& lk/link {:action #(st/emit! (rt/nav :auth-login {} params))
:data-test "login-here-link"}
(tr "auth.login-here")]]
(when (contains? cf/flags :demo-users)
[:div.links
[:div.link-entry
[:span (tr "auth.create-demo-profile") " "]
[:& lk/link {:action #(st/emit! (du/create-demo-profile))}
(tr "auth.create-demo-account")]])]])
[:span (tr "auth.already-have-account") " "]
[:& lk/link {:on-click nav-to-login
:data-test "login-here-link"}
(tr "auth.login-here")]]
(when (contains? cf/flags :demo-users)
[:div.link-entry
[:span (tr "auth.create-demo-profile") " "]
[:& lk/link {:on-click create-demo}
(tr "auth.create-demo-account")]])]]))
;; --- PAGE: register validation
(defn- handle-register-error
[form error]
(case (:code error)
:email-already-exists
(swap! form assoc-in [:errors :email]
{:message "errors.email-already-exists"})
(do
(println (:explain error))
(st/emit! (dm/error (tr "errors.generic"))))))
(defn- handle-register-success
[data]
(cond
(some? (:invitation-token data))
(let [token (:invitation-token data)]
(st/emit! (rt/nav :auth-verify-token {} {:token token})))
;; The :is-active flag is true, when insecure-register is enabled
;; or the user used external auth provider.
(:is-active data)
(st/emit! (du/login-from-register))
:else
(st/emit! (rt/nav :auth-register-success {} {:email (:email data)}))))
(s/def ::accept-terms-and-privacy (s/and ::us/boolean true?))
(s/def ::accept-newsletter-subscription ::us/boolean)
@ -218,30 +197,63 @@
::accept-newsletter-subscription])))
(mf/defc register-validate-form
[{:keys [params on-success-callback] :as props}]
(let [form (fm/use-form :spec ::register-validate-form
[{:keys [params on-success]}]
(let [params (hooks/use-equal-memo params)
form (fm/use-form :spec ::register-validate-form
:validators [(fm/validate-not-empty :fullname (tr "auth.name.not-all-space"))
(fm/validate-length :fullname fm/max-length-allowed (tr "auth.name.too-long"))]
:initial params)
submitted? (mf/use-state false)
on-success (fn [p]
(if (nil? on-success-callback)
(handle-register-success p)
(on-success-callback (:email p))))
on-error
(mf/use-fn
(fn [form error]
(case (:code error)
:email-already-exists
(swap! form assoc-in [:errors :email]
{:message "errors.email-already-exists"})
(do
(println (:explain error))
(st/emit! (dm/error (tr "errors.generic")))))))
on-success-default
(mf/use-fn
(fn [data]
(cond
(some? (:invitation-token data))
(let [token (:invitation-token data)]
(st/emit! (rt/nav :auth-verify-token {} {:token token})))
;; The :is-active flag is true, when insecure-register is enabled
;; or the user used external auth provider.
(:is-active data)
(st/emit! (du/login-from-register))
:else
(st/emit! (rt/nav :auth-register-success {} {:email (:email data)})))))
on-success
(mf/use-fn
(mf/deps on-success on-success-default)
(fn [_ data]
(if (fn? on-success)
(on-success data)
(on-success-default data))))
on-submit
(mf/use-callback
(mf/use-fn
(fn [form _event]
(reset! submitted? true)
(let [params (:clean-data @form)]
(let [params (:clean-data @form)
on-success (partial on-success form)
on-error (partial on-error form)]
(->> (rp/cmd! :register-profile params)
(rx/finalize #(reset! submitted? false))
(rx/subs on-success
(partial handle-register-error form))))))]
(rx/subs on-success on-error)))))]
[:& fm/form {:on-submit on-submit
:form form}
[:& fm/form {:on-submit on-submit :form form}
[:div.fields-row
[:& fm/input {:name :fullname
:label (tr "auth.fullname")
@ -265,20 +277,20 @@
(mf/defc register-validate-page
[{:keys [params] :as props}]
[:div.form-container
[:h1 {:data-test "register-title"} (tr "auth.register-title")]
[:div.subtitle (tr "auth.register-subtitle")]
[{:keys [params]}]
(let [nav-to-register (mf/use-fn #(st/emit! (rt/nav :auth-register {} {})))]
[:div.form-container
[:h1 {:data-test "register-title"} (tr "auth.register-title")]
[:div.subtitle (tr "auth.register-subtitle")]
[:& register-validate-form {:params params}]
[:& register-validate-form {:params params}]
[:div.links
[:div.link-entry
[:& lk/link {:action #(st/emit! (rt/nav :auth-register {} {}))}
(tr "labels.go-back")]]]])
[:div.links
[:div.link-entry
[:& lk/link {:on-click nav-to-register} (tr "labels.go-back")]]]]))
(mf/defc register-success-page
[{:keys [params] :as props}]
[{:keys [params]}]
[:div.form-container
[:div.notification-icon i/icon-verify]
[:div.notification-text (tr "auth.verification-email-sent")]

View file

@ -9,13 +9,19 @@
[app.util.keyboard :as kbd]
[rumext.v2 :as mf]))
(mf/defc link [{:keys [action klass data-test keyboard-action children]}]
(let [keyboard-action (or keyboard-action action)]
[:a {:on-click action
:class klass
:on-key-down (fn [event]
(when (kbd/enter? event)
(keyboard-action event)))
(mf/defc link
{::mf/wrap-props false}
[{:keys [on-click class data-test on-key-enter children]}]
(let [on-key-enter (or on-key-enter on-click)
on-key-down
(mf/use-fn
(mf/deps on-key-enter)
(fn [event]
(when (and (kbd/enter? event) (fn? on-key-enter))
(on-key-enter event))))]
[:a {:on-click on-click
:on-key-down on-key-down
:class class
:tab-index "0"
:data-test data-test}
[:* children]]))
children]))

View file

@ -629,19 +629,19 @@
[:ul.sidebar-nav.no-overflow
[:li.recent-projects
{:class-name (when projects? "current")}
[:& link {:action go-projects
:keyboard-action go-projects-with-key}
[:& link {:on-click go-projects
:on-key-enter go-projects-with-key}
[:span.element-title (tr "labels.projects")]]]
[:li {:class-name (when drafts? "current")}
[:& link {:action go-drafts
:keyboard-action go-drafts-with-key}
[:& link {:on-click go-drafts
:on-key-enter go-drafts-with-key}
[:span.element-title (tr "labels.drafts")]]]
[:li {:class-name (when libs? "current")}
[:& link {:action go-libs
:keyboard-action go-libs-with-key}
[:& link {:on-click go-libs
:on-key-enter go-libs-with-key}
[:span.element-title (tr "labels.shared-libraries")]]]]]
[:hr]
@ -650,8 +650,8 @@
[:ul.sidebar-nav.no-overflow
[:li {:class-name (when fonts? "current")}
[:& link {:action go-fonts
:keyboard-action go-fonts-with-key
[:& link {:on-click go-fonts
:on-key-enter go-fonts-with-key
:data-test "fonts"}
[:span.element-title (tr "labels.fonts")]]]]]

View file

@ -38,17 +38,21 @@
(fn [event]
(dom/prevent-default event)
(st/emit! (modal/hide)))
success-email-sent
(fn [email]
(fn [{:keys [email]}]
(reset! user-email email)
(set-current-section :email-sent))
success-login
(fn []
(fn [_]
(.reload js/window.location true))
success-register
(fn [data]
(reset! register-token (:token data))
(set-current-section :register-validate))]
(mf/with-effect []
(swap! storage assoc :redirect-url uri))
@ -66,7 +70,7 @@
:login
[:div.generic-form.login-form
[:div.form-container
[:& login-methods {:on-success-callback success-login}]
[:& login-methods {:on-success success-login}]
[:div.links
[:div.link-entry
[:a {:on-click #(set-current-section :recovery-request)}
@ -78,7 +82,7 @@
:register
[:div.form-container
[:& register-methods {:on-success-callback success-register}]
[:& register-methods {:on-success success-register}]
[:div.links
[:div.link-entry
[:span (tr "auth.already-have-account") " "]
@ -88,15 +92,15 @@
:register-validate
[:div.form-container
[:& register-validate-form {:params {:token @register-token}
:on-success-callback success-email-sent}]
:on-success success-email-sent}]
[:div.links
[:div.link-entry
[:a {:on-click #(set-current-section :register)}
(tr "labels.go-back")]]]]
:recovery-request
[:& recovery-request-page {:go-back-callback #(set-current-section :login)
:on-success-callback success-email-sent}]
[:& recovery-request-page {:on-go-back #(set-current-section :login)
:on-success success-email-sent}]
:email-sent
[:div.form-container
[:& register-success-page {:params {:email @user-email}}]])]