mirror of
https://github.com/penpot/penpot.git
synced 2025-02-13 10:38:13 -05:00
♻️ Refactor user profile form.
This commit is contained in:
parent
88cf5483c8
commit
08bd135d55
4 changed files with 132 additions and 124 deletions
|
@ -17,21 +17,19 @@
|
|||
|
||||
;; --- Profile Fetched
|
||||
|
||||
(deftype ProfileFetched [data]
|
||||
ptk/UpdateEvent
|
||||
(update [this state]
|
||||
(assoc state :profile data))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [this state stream]
|
||||
(swap! storage assoc :profile data)
|
||||
;; (prn "profile-fetched" data)
|
||||
(when-let [lang (get-in data [:metadata :language])]
|
||||
(i18n/set-current-locale! lang))))
|
||||
|
||||
(defn profile-fetched
|
||||
[data]
|
||||
(ProfileFetched. data))
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [this state]
|
||||
(assoc state :profile data))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [this state stream]
|
||||
(swap! storage assoc :profile data)
|
||||
;; (prn "profile-fetched" data)
|
||||
(when-let [lang (get-in data [:metadata :language])]
|
||||
(i18n/set-current-locale! lang)))))
|
||||
|
||||
;; --- Fetch Profile
|
||||
|
||||
|
@ -60,24 +58,6 @@
|
|||
|
||||
;; --- Update Profile
|
||||
|
||||
(deftype UpdateProfile [data on-success on-error]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(letfn [(handle-error [{payload :payload}]
|
||||
(on-error payload)
|
||||
(rx/empty))]
|
||||
(let [data (-> (:profile state)
|
||||
(assoc :fullname (:fullname data))
|
||||
(assoc :email (:email data))
|
||||
(assoc :username (:username data))
|
||||
(assoc-in [:metadata :language] (:language data)))]
|
||||
(prn "update-profile" data)
|
||||
(->> (rp/req :update/profile data)
|
||||
(rx/map :payload)
|
||||
(rx/do on-success)
|
||||
(rx/map profile-updated)
|
||||
(rx/catch rp/client-error? handle-error))))))
|
||||
|
||||
(s/def ::fullname string?)
|
||||
(s/def ::email us/email?)
|
||||
(s/def ::username string?)
|
||||
|
@ -90,11 +70,28 @@
|
|||
::username]))
|
||||
|
||||
(defn update-profile
|
||||
[data on-success on-error]
|
||||
[data {:keys [on-success on-error]}]
|
||||
{:pre [(us/valid? ::update-profile data)
|
||||
(fn? on-error)
|
||||
(fn? on-success)]}
|
||||
(UpdateProfile. data on-success on-error))
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(letfn [(handle-error [{payload :payload}]
|
||||
(on-error payload)
|
||||
(rx/empty))]
|
||||
(let [data (-> (:profile state)
|
||||
(assoc :fullname (:fullname data))
|
||||
(assoc :email (:email data))
|
||||
(assoc :username (:username data))
|
||||
(assoc-in [:metadata :language] (:language data)))]
|
||||
(prn "update-profile" data)
|
||||
(->> (rp/req :update/profile data)
|
||||
(rx/map :payload)
|
||||
(rx/do on-success)
|
||||
(rx/map profile-updated)
|
||||
;; (rx/map profile-fetched)
|
||||
(rx/catch rp/client-error? handle-error)))))))
|
||||
|
||||
;; --- Update Password (Form)
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.workers :as uwrk]
|
||||
[uxbox.util.data :refer [dissoc-in index-of seek]]
|
||||
[uxbox.util.data :refer [dissoc-in index-of]]
|
||||
[uxbox.util.forms :as sc]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
|
|
|
@ -20,14 +20,6 @@
|
|||
[uxbox.util.i18n :as i18n :refer [tr]]
|
||||
[uxbox.util.interop :refer [iterable->seq]]))
|
||||
|
||||
|
||||
(def form-data (fm/focus-data :profile st/state))
|
||||
(def form-errors (fm/focus-errors :profile st/state))
|
||||
|
||||
(def assoc-value (partial fm/assoc-value :profile))
|
||||
(def assoc-error (partial fm/assoc-error :profile))
|
||||
(def clear-form (partial fm/clear-form :profile))
|
||||
|
||||
(defn profile->form
|
||||
[profile]
|
||||
(let [language (get-in profile [:metadata :language])]
|
||||
|
@ -35,80 +27,84 @@
|
|||
(cond-> language (assoc :language language)))))
|
||||
|
||||
(def profile-ref
|
||||
(-> (comp (l/key :profile)
|
||||
(l/lens profile->form))
|
||||
(-> (l/key :profile)
|
||||
(l/derive st/state)))
|
||||
|
||||
(s/def ::fullname ::fm/non-empty-string)
|
||||
(s/def ::username ::fm/non-empty-string)
|
||||
(s/def ::email ::fm/email)
|
||||
(s/def ::language #{"en" "fr"})
|
||||
|
||||
(s/def ::profile-form
|
||||
(s/keys :req-un [::fullname
|
||||
::username
|
||||
::language
|
||||
::email]))
|
||||
(def profile-form-spec
|
||||
{:fullname [fm/required fm/string fm/non-empty-string]
|
||||
:username [fm/required fm/string fm/non-empty-string]
|
||||
:email [fm/required fm/email]
|
||||
:language [fm/required fm/string]})
|
||||
|
||||
(defn- on-error
|
||||
[{:keys [code] :as payload}]
|
||||
(case code
|
||||
:uxbox.services.users/registration-disabled
|
||||
(st/emit! (tr "errors.api.form.registration-disabled"))
|
||||
[error {:keys [errors] :as form}]
|
||||
(prn "on-error" error form)
|
||||
(case (:code error)
|
||||
:uxbox.services.users/email-already-exists
|
||||
(st/emit! (assoc-error :email (tr "errors.api.form.email-already-exists")))
|
||||
:uxbox.services.users/username-already-exists
|
||||
(st/emit! (assoc-error :username (tr "errors.api.form.username-already-exists")))))
|
||||
(swap! form assoc-in [:errors :email] "errors.api.form.email-already-exists")
|
||||
|
||||
(defn- on-field-change
|
||||
[event field]
|
||||
(let [value (dom/event->value event)]
|
||||
(st/emit! (assoc-value field value))))
|
||||
:uxbox.services.users/username-already-exists
|
||||
(swap! form assoc-in [:errors :username] "errors.api.form.username-already-exists")))
|
||||
|
||||
(defn- initial-data
|
||||
[]
|
||||
(merge {:language @i18n/locale}
|
||||
(profile->form (deref profile-ref))))
|
||||
|
||||
(defn- on-submit
|
||||
[event form]
|
||||
(dom/prevent-default event)
|
||||
(let [data (:clean-data form)
|
||||
opts {:on-success #(prn "On Success" %)
|
||||
:on-error #(on-error % form)}]
|
||||
(st/emit! (udu/update-profile data opts))))
|
||||
|
||||
;; --- Profile Form
|
||||
(mf/def profile-form
|
||||
:mixins [mf/memo mf/reactive (fm/clear-mixin st/store :profile)]
|
||||
:render
|
||||
(fn [own props]
|
||||
(let [data (merge {:language @i18n/locale}
|
||||
(mf/react profile-ref)
|
||||
(mf/react form-data))
|
||||
errors (mf/react form-errors)
|
||||
valid? (fm/valid? ::profile-form data)
|
||||
on-success #(st/emit! (clear-form))
|
||||
on-submit #(st/emit! (udu/update-profile data on-success on-error))]
|
||||
[:form.profile-form
|
||||
[:span.user-settings-label (tr "settings.profile.section-basic-data")]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:on-change #(on-field-change % :fullname)
|
||||
:value (:fullname data "")
|
||||
:placeholder (tr "settings.profile.your-name")}]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:on-change #(on-field-change % :username)
|
||||
:value (:username data "")
|
||||
:placeholder (tr "settings.profile.your-username")}]
|
||||
(fm/input-error errors :username)
|
||||
[:input.input-text
|
||||
{:type "email"
|
||||
:on-change #(on-field-change % :email)
|
||||
:value (:email data "")
|
||||
:placeholder (tr "settings.profile.your-email")}]
|
||||
(fm/input-error errors :email)
|
||||
(mf/defc profile-form
|
||||
[props]
|
||||
(let [{:keys [data] :as form} (fm/use-form {:initial initial-data
|
||||
:spec profile-form-spec})]
|
||||
[:form.profile-form {:on-submit #(on-submit % form)}
|
||||
[:span.user-settings-label (tr "settings.profile.section-basic-data")]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:name "fullname"
|
||||
:on-blur (fm/on-input-blur form)
|
||||
:on-change (fm/on-input-change form)
|
||||
:value (:fullname data "")
|
||||
:placeholder (tr "settings.profile.your-name")}]
|
||||
[:& fm/error-input {:form form :field :fullname}]
|
||||
[:input.input-text
|
||||
{:type "text"
|
||||
:name "username"
|
||||
:on-blur (fm/on-input-blur form)
|
||||
:on-change (fm/on-input-change form)
|
||||
:value (:username data "")
|
||||
:placeholder (tr "settings.profile.your-username")}]
|
||||
[:& fm/error-input {:form form :field :username}]
|
||||
|
||||
[:span.user-settings-label (tr "settings.profile.section-i18n-data")]
|
||||
[:select.input-select {:value (:language data)
|
||||
:on-change #(on-field-change % :language)}
|
||||
[:option {:value "en"} "English"]
|
||||
[:option {:value "fr"} "Français"]]
|
||||
[:input.input-text
|
||||
{:type "email"
|
||||
:name "email"
|
||||
:on-blur (fm/on-input-blur form)
|
||||
:on-change (fm/on-input-change form)
|
||||
:value (:email data "")
|
||||
:placeholder (tr "settings.profile.your-email")}]
|
||||
[:& fm/error-input {:form form :field :email}]
|
||||
|
||||
[:input.btn-primary
|
||||
{:type "button"
|
||||
:class (when-not valid? "btn-disabled")
|
||||
:disabled (not valid?)
|
||||
:on-click on-submit
|
||||
:value (tr "settings.update-settings")}]])))
|
||||
[:span.user-settings-label (tr "settings.profile.section-i18n-data")]
|
||||
[:select.input-select {:value (:language data)
|
||||
:name "language"
|
||||
:on-blur (fm/on-input-blur form)
|
||||
:on-change (fm/on-input-change form)}
|
||||
[:option {:value "en"} "English"]
|
||||
[:option {:value "fr"} "Français"]]
|
||||
|
||||
[:input.btn-primary
|
||||
{:type "submit"
|
||||
:class (when-not (:valid form) "btn-disabled")
|
||||
:disabled (not (:valid form))
|
||||
:value (tr "settings.update-settings")}]]))
|
||||
|
||||
;; --- Profile Photo Form
|
||||
|
||||
|
@ -121,7 +117,7 @@
|
|||
(first))]
|
||||
(st/emit! (udu/update-photo file))
|
||||
(dom/clean-value! target)))]
|
||||
(let [{:keys [photo]} (mf/deref profile-ref)
|
||||
(let [{:keys [photo] :as profile} (mf/deref profile-ref)
|
||||
photo (if (or (str/empty? photo) (nil? photo))
|
||||
"images/avatar.jpg"
|
||||
photo)]
|
||||
|
@ -139,4 +135,4 @@
|
|||
[:section.user-settings-content
|
||||
[:span.user-settings-label (tr "settings.profile.your-avatar")]
|
||||
[:& profile-photo-form]
|
||||
(profile-form)]])
|
||||
[:& profile-form]]])
|
||||
|
|
|
@ -44,22 +44,23 @@
|
|||
([self f x y] (update-fn #(f % x y)))
|
||||
([self f x y more] (update-fn #(apply f % x y more))))))
|
||||
|
||||
(defn- simplify-errors
|
||||
[errors]
|
||||
(reduce-kv #(assoc %1 %2 (:message %3)) {} errors))
|
||||
|
||||
(defn use-form
|
||||
[{:keys [initial spec] :as opts}]
|
||||
(let [[data update-data] (mf/useState initial)
|
||||
[errors update-errors] (mf/useState nil)
|
||||
[touched update-touched] (mf/useState {})
|
||||
[errors' clean-data] (validate data spec)
|
||||
|
||||
data (impl-mutator data update-data)
|
||||
errors (-> (merge {} errors' errors)
|
||||
(impl-mutator update-errors))
|
||||
touched (impl-mutator touched update-touched)]
|
||||
{:clean-data clean-data
|
||||
:touched touched
|
||||
:data data
|
||||
:errors errors
|
||||
:valid (not (seq errors))}))
|
||||
(let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial)
|
||||
:errors {}
|
||||
:touched {}})
|
||||
[errors' clean-data] (validate spec (:data state))
|
||||
errors (merge (reduce-kv #(assoc %1 %2 (:message %3)) {} errors')
|
||||
(:errors state))]
|
||||
(-> (assoc state
|
||||
:errors errors
|
||||
:clean-data clean-data
|
||||
:valid (not (seq errors)))
|
||||
(impl-mutator update-state))))
|
||||
|
||||
(defn on-input-change
|
||||
[{:keys [data] :as form}]
|
||||
|
@ -67,7 +68,10 @@
|
|||
(let [target (dom/get-target event)
|
||||
field (keyword (.-name target))
|
||||
value (dom/get-value target)]
|
||||
(swap! data assoc field value))))
|
||||
(swap! form (fn [state]
|
||||
(-> state
|
||||
(assoc-in [:data field] value)
|
||||
(update :errors dissoc field)))))))
|
||||
|
||||
(defn on-input-blur
|
||||
[{:keys [touched] :as form}]
|
||||
|
@ -75,7 +79,18 @@
|
|||
(let [target (dom/get-target event)
|
||||
field (keyword (.-name target))]
|
||||
(when-not (get touched field)
|
||||
(swap! touched assoc field true)))))
|
||||
(swap! form assoc-in [:touched field] true)))))
|
||||
|
||||
;; --- Helper Components
|
||||
|
||||
(mf/defc error-input
|
||||
[{:keys [form field] :as props}]
|
||||
(let [touched? (get-in form [:touched field])
|
||||
error? (get-in form [:errors field])]
|
||||
(when (and touched? error?)
|
||||
[:ul.form-errors
|
||||
[:li {:key error?} (tr error?)]])))
|
||||
|
||||
|
||||
;; --- Additional Validators
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue