0
Fork 0
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:
Andrey Antukh 2019-08-28 19:01:11 +02:00
parent 88cf5483c8
commit 08bd135d55
4 changed files with 132 additions and 124 deletions

View file

@ -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)

View file

@ -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]

View file

@ -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]]])

View file

@ -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