mirror of
https://github.com/penpot/penpot.git
synced 2025-01-10 08:50:57 -05:00
✨ Improve state management on onboarding team modal
This commit is contained in:
parent
0dda893d73
commit
273a5f7a0a
1 changed files with 177 additions and 167 deletions
|
@ -7,34 +7,29 @@
|
|||
(ns app.main.ui.onboarding.team-choice
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data.macros :as dmc]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.spec :as us]
|
||||
[app.config :as cf]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.events :as ev]
|
||||
[app.main.data.messages :as msg]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.data.users :as du]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.forms :as fm]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.timers :as tm]
|
||||
[cljs.spec.alpha :as s]
|
||||
[potok.v2.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(s/def ::name ::us/not-empty-string)
|
||||
(s/def ::team-form
|
||||
(s/keys :req-un [::name]))
|
||||
|
||||
(mf/defc team-modal-left
|
||||
(mf/defc left-sidebar
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[]
|
||||
[:div {:class (stl/css :modal-left)}
|
||||
[:h1 {:class (stl/css :modal-title)}
|
||||
(tr "onboarding-v2.welcome.title")]
|
||||
|
||||
[:h2 {:class (stl/css :modal-subtitle)}
|
||||
(tr "onboarding.team-modal.team-definition")]
|
||||
[:p {:class (stl/css :modal-text)}
|
||||
|
@ -61,99 +56,27 @@
|
|||
[:p {:class (stl/css :modal-desc)}
|
||||
(tr "onboarding.team-modal.create-team-feature-5")]]]])
|
||||
|
||||
(mf/defc onboarding-team-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :onboarding-team}
|
||||
[]
|
||||
(let [form (fm/use-form :spec ::team-form
|
||||
:initial {}
|
||||
:validators [(fm/validate-not-empty :name (tr "auth.name.not-all-space"))
|
||||
(fm/validate-length :name fm/max-length-allowed (tr "auth.name.too-long"))])
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(fn [form _]
|
||||
(let [tname (get-in @form [:clean-data :name])]
|
||||
(st/emit! (modal/show {:type :onboarding-team-invitations :name tname})
|
||||
(ptk/event ::ev/event {::ev/name "choose-team-name"
|
||||
::ev/origin "onboarding"
|
||||
:name tname
|
||||
:step 1})))))
|
||||
on-skip
|
||||
(fn []
|
||||
(tm/schedule 400 #(st/emit! (modal/hide)
|
||||
(ptk/event ::ev/event {::ev/name "create-team-later"
|
||||
::ev/origin "onboarding"
|
||||
:step 1}))))
|
||||
|
||||
teams (mf/deref refs/teams)
|
||||
onboarding-a-b-test? (cf/external-feature-flag "signup-background" "test")]
|
||||
|
||||
(mf/with-effect [teams]
|
||||
(when (> (count teams) 1)
|
||||
(st/emit! (modal/hide))))
|
||||
|
||||
(when (< (count teams) 2)
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated.fadeIn {:class (stl/css :modal-container)}
|
||||
[:& team-modal-left]
|
||||
[:div {:class (stl/css :separator)}]
|
||||
[:div {:class (stl/css :modal-right)}
|
||||
[:div {:class (stl/css :first-block)}
|
||||
[:h2 {:class (stl/css :modal-subtitle)}
|
||||
(tr "onboarding.team-modal.create-team")]
|
||||
[:p {:class (stl/css :modal-text)}
|
||||
(tr "onboarding.choice.team-up.create-team-desc")]
|
||||
[:& fm/form {:form form
|
||||
:class (stl/css :modal-form)
|
||||
:on-submit on-submit}
|
||||
|
||||
[:& fm/input {:type "text"
|
||||
:class (stl/css :team-name-input)
|
||||
:name :name
|
||||
:placeholder "Team name"
|
||||
:label (tr "onboarding.choice.team-up.create-team-placeholder")}]
|
||||
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
[:> fm/submit-button*
|
||||
{:class (stl/css :accept-button)
|
||||
:label (tr "onboarding.choice.team-up.continue-creating-team")}]]]]
|
||||
[:div {:class (stl/css :second-block)}
|
||||
[:h2 {:class (stl/css :modal-subtitle)}
|
||||
(tr "onboarding.choice.team-up.start-without-a-team")]
|
||||
[:p {:class (stl/css :modal-text)}
|
||||
(tr "onboarding.choice.team-up.start-without-a-team-description")]
|
||||
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
[:button {:class (stl/css :accept-button)
|
||||
:on-click on-skip}
|
||||
(tr "onboarding.choice.team-up.continue-without-a-team")]]]]
|
||||
|
||||
[:div {:class (stl/css :paginator)} "1/2"]]])))
|
||||
|
||||
(defn get-available-roles
|
||||
[]
|
||||
[{:value "editor" :label (tr "labels.editor")}
|
||||
{:value "admin" :label (tr "labels.admin")}])
|
||||
|
||||
(s/def ::emails (s/and ::us/set-of-valid-emails))
|
||||
(s/def ::role ::us/keyword)
|
||||
(s/def ::invite-form
|
||||
(s/keys :req-un [::role ::emails]))
|
||||
|
||||
;; This is the final step of team creation, consists in provide a
|
||||
;; shortcut for invite users.
|
||||
(defn- get-available-roles
|
||||
[]
|
||||
[{:value "editor" :label (tr "labels.editor")}
|
||||
{:value "admin" :label (tr "labels.admin")}])
|
||||
|
||||
(mf/defc team-form-step-2
|
||||
{::mf/props :obj}
|
||||
[{:keys [name on-back]}]
|
||||
(let [initial (mf/use-memo
|
||||
#(do {:role "editor"
|
||||
:name name}))
|
||||
|
||||
(mf/defc onboarding-team-invitations-modal
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :onboarding-team-invitations
|
||||
::mf/props :obj}
|
||||
[{:keys [name]}]
|
||||
(let [initial (mf/use-memo (constantly
|
||||
{:role "editor"
|
||||
:name name}))
|
||||
form (fm/use-form :spec ::invite-form
|
||||
:initial initial)
|
||||
|
||||
params (:clean-data @form)
|
||||
emails (:emails params)
|
||||
|
||||
|
@ -161,51 +84,47 @@
|
|||
|
||||
on-success
|
||||
(mf/use-fn
|
||||
(fn [_form response]
|
||||
(let [team-id (:id response)]
|
||||
(st/emit!
|
||||
(modal/hide)
|
||||
(rt/nav :dashboard-projects {:team-id team-id}))
|
||||
(tm/schedule 400 #(st/emit!
|
||||
(modal/hide))))))
|
||||
(fn [response]
|
||||
(let [team-id (:id response)]
|
||||
(st/emit! (du/update-profile-props {:onboarding-team-id team-id})
|
||||
(rt/nav :dashboard-projects {:team-id team-id})))))
|
||||
|
||||
on-error
|
||||
(mf/use-fn
|
||||
(fn [_form _cause]
|
||||
(st/emit! (msg/error "Error on creating team."))))
|
||||
(fn [_]
|
||||
(st/emit! (msg/error (tr "errors.generic")))))
|
||||
|
||||
;; The SKIP branch only creates the team, without invitations
|
||||
on-invite-later
|
||||
(mf/use-fn
|
||||
(fn [_]
|
||||
(let [mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}
|
||||
(fn [{:keys [name]}]
|
||||
(let [mdata {:on-success on-success
|
||||
:on-error on-error}
|
||||
params {:name name}]
|
||||
(st/emit! (dd/create-team (with-meta params mdata))
|
||||
(ptk/event ::ev/event {::ev/name "create-team-and-invite-later"
|
||||
::ev/origin "onboarding"
|
||||
:name name
|
||||
:step 2})))))
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "onboarding-step"
|
||||
:label "team:create-team-and-invite-later"
|
||||
:team-name name
|
||||
:step 7})
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "onboarding-finish"})))))
|
||||
|
||||
;; The SUBMIT branch creates the team with the invitations
|
||||
on-invite-now
|
||||
(mf/use-fn
|
||||
(fn [form]
|
||||
(let [mdata {:on-success (partial on-success form)
|
||||
:on-error (partial on-error form)}
|
||||
params (:clean-data @form)
|
||||
emails (:emails params)]
|
||||
(fn [{:keys [name] :as params}]
|
||||
(let [mdata {:on-success on-success
|
||||
:on-error on-error}]
|
||||
|
||||
(st/emit! (if (> (count emails) 0)
|
||||
;; If the user is only inviting to itself we don't call to create-team-with-invitations
|
||||
(dd/create-team-with-invitations (with-meta params mdata))
|
||||
(dd/create-team (with-meta {:name name} mdata)))
|
||||
(ptk/event ::ev/event {::ev/name "create-team-and-send-invitations"
|
||||
::ev/origin "onboarding"
|
||||
:invites (count emails)
|
||||
:role (:role params)
|
||||
:name name
|
||||
:step 2})))))
|
||||
(st/emit! (dd/create-team-with-invitations (with-meta params mdata))
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "onboarding-step"
|
||||
:label "team:create-team-and-invite"
|
||||
:invites (count emails)
|
||||
:team-name name
|
||||
:role (:role params)
|
||||
:step 7})
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "onboarding-finish"})))))
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
|
@ -213,55 +132,146 @@
|
|||
(let [params (:clean-data @form)
|
||||
emails (:emails params)]
|
||||
(if (> (count emails) 0)
|
||||
(on-invite-now form)
|
||||
(on-invite-later form))
|
||||
(modal/hide!))))
|
||||
onboarding-a-b-test? (cf/external-feature-flag "signup-background" "test")]
|
||||
(on-invite-now params)
|
||||
(on-invite-later params)))))]
|
||||
|
||||
[:div {:class (stl/css-case :modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
[:div.animated.fadeIn {:class (stl/css :modal-container)}
|
||||
[:& team-modal-left]
|
||||
[:*
|
||||
[:div {:class (stl/css :modal-right-invitations)}
|
||||
[:h2 {:class (stl/css :modal-subtitle)} (tr "onboarding.choice.team-up.invite-members")]
|
||||
[:p {:class (stl/css :modal-text)} (tr "onboarding.choice.team-up.invite-members-info")]
|
||||
[:& fm/form {:form form
|
||||
:class (stl/css :modal-form-invitations)
|
||||
:on-submit on-submit}
|
||||
[:div {:class (stl/css :role-select)}
|
||||
[:p {:class (stl/css :role-title)} (tr "onboarding.choice.team-up.roles")]
|
||||
[:& fm/select {:name :role :options roles}]]
|
||||
|
||||
[:div {:class (stl/css :separator)}]
|
||||
[:div {:class (stl/css :modal-right-invitations)}
|
||||
[:h2 {:class (stl/css :modal-subtitle)} (tr "onboarding.choice.team-up.invite-members")]
|
||||
[:p {:class (stl/css :modal-text)} (tr "onboarding.choice.team-up.invite-members-info")]
|
||||
[:div {:class (stl/css :invitation-row)}
|
||||
[:& fm/multi-input {:type "email"
|
||||
:name :emails
|
||||
:auto-focus? true
|
||||
:trim true
|
||||
:valid-item-fn us/parse-email
|
||||
:caution-item-fn #{}
|
||||
:label (tr "modals.invite-member.emails")
|
||||
:on-submit on-submit}]]
|
||||
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
[:button {:class (stl/css :back-button)
|
||||
:on-click on-back}
|
||||
(tr "labels.back")]
|
||||
|
||||
[:> fm/submit-button*
|
||||
{:class (stl/css :accept-button)
|
||||
:label (if (> (count emails) 0)
|
||||
(tr "onboarding.choice.team-up.create-team-and-invite")
|
||||
(tr "onboarding.choice.team-up.create-team-without-invite"))}]]
|
||||
[:div {:class (stl/css :modal-hint)}
|
||||
"(" (tr "onboarding.choice.team-up.create-team-and-send-invites-description") ")"]]]
|
||||
|
||||
|
||||
[:div {:class (stl/css :paginator)} "2/2"]]))
|
||||
|
||||
(mf/defc team-form-step-1
|
||||
{::mf/props :obj
|
||||
::mf/private true}
|
||||
[{:keys [on-submit]}]
|
||||
(let [validators (mf/with-memo []
|
||||
[(fm/validate-not-empty :name (tr "auth.name.not-all-space"))
|
||||
(fm/validate-length :name fm/max-length-allowed (tr "auth.name.too-long"))])
|
||||
|
||||
form (fm/use-form
|
||||
:spec ::team-form
|
||||
:initial {}
|
||||
:validators validators)
|
||||
|
||||
on-submit*
|
||||
(mf/use-fn
|
||||
(fn [form]
|
||||
(let [name (dm/get-in @form [:clean-data :name])]
|
||||
|
||||
(st/emit! (ptk/data-event ::ev/event
|
||||
{::ev/name "onboarding-step"
|
||||
:label "team:choice-team-name"
|
||||
:step 7}))
|
||||
(on-submit name))))
|
||||
|
||||
on-skip
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(st/emit! (du/update-profile-props {:onboarding-viewed true})
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "onboarding-step"
|
||||
:label "team:skip-team-creation"
|
||||
:step 7})
|
||||
(ptk/data-event ::ev/event
|
||||
{::ev/name "onboarding-finish"}))))]
|
||||
[:*
|
||||
[:div {:class (stl/css :modal-right)}
|
||||
[:div {:class (stl/css :first-block)}
|
||||
[:h2 {:class (stl/css :modal-subtitle)}
|
||||
(tr "onboarding.team-modal.create-team")]
|
||||
[:p {:class (stl/css :modal-text)}
|
||||
(tr "onboarding.choice.team-up.create-team-desc")]
|
||||
[:& fm/form {:form form
|
||||
:class (stl/css :modal-form-invitations)
|
||||
:on-submit on-submit}
|
||||
[:div {:class (stl/css :role-select)}
|
||||
[:p {:class (stl/css :role-title)} (tr "onboarding.choice.team-up.roles")]
|
||||
[:& fm/select {:name :role :options roles}]]
|
||||
:class (stl/css :modal-form)
|
||||
:on-submit on-submit*}
|
||||
|
||||
[:div {:class (stl/css :invitation-row)}
|
||||
[:& fm/multi-input {:type "email"
|
||||
:name :emails
|
||||
:auto-focus? true
|
||||
:trim true
|
||||
:valid-item-fn us/parse-email
|
||||
:caution-item-fn #{}
|
||||
:label (tr "modals.invite-member.emails")
|
||||
:on-submit on-submit}]]
|
||||
[:& fm/input {:type "text"
|
||||
:class (stl/css :team-name-input)
|
||||
:name :name
|
||||
:placeholder "Team name"
|
||||
:label (tr "onboarding.choice.team-up.create-team-placeholder")}]
|
||||
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
[:button {:class (stl/css :back-button)
|
||||
:on-click #(st/emit! (modal/show {:type :onboarding-team})
|
||||
(ptk/event ::ev/event {::ev/name "invite-members-back"
|
||||
::ev/origin "onboarding"
|
||||
:name name
|
||||
:step 2}))}
|
||||
(tr "labels.back")]
|
||||
|
||||
[:> fm/submit-button*
|
||||
{:class (stl/css :accept-button)
|
||||
:label (if (> (count emails) 0)
|
||||
(tr "onboarding.choice.team-up.create-team-and-invite")
|
||||
(tr "onboarding.choice.team-up.create-team-without-invite"))}]]
|
||||
[:div {:class (stl/css :modal-hint)}
|
||||
(dmc/str "(" (tr "onboarding.choice.team-up.create-team-and-send-invites-description") ")")]]]
|
||||
:label (tr "onboarding.choice.team-up.continue-creating-team")}]]]]
|
||||
[:div {:class (stl/css :second-block)}
|
||||
[:h2 {:class (stl/css :modal-subtitle)}
|
||||
(tr "onboarding.choice.team-up.start-without-a-team")]
|
||||
[:p {:class (stl/css :modal-text)}
|
||||
(tr "onboarding.choice.team-up.start-without-a-team-description")]
|
||||
|
||||
[:div {:class (stl/css :action-buttons)}
|
||||
[:button {:class (stl/css :accept-button)
|
||||
:on-click on-skip}
|
||||
(tr "onboarding.choice.team-up.continue-without-a-team")]]]]
|
||||
|
||||
[:div {:class (stl/css :paginator)} "1/2"]]))
|
||||
|
||||
(s/def ::name ::us/not-empty-string)
|
||||
(s/def ::team-form
|
||||
(s/keys :req-un [::name]))
|
||||
|
||||
(mf/defc onboarding-team-modal
|
||||
{::mf/props :obj}
|
||||
[]
|
||||
(let [name* (mf/use-state nil)
|
||||
name (deref name*)
|
||||
|
||||
on-submit
|
||||
(mf/use-fn
|
||||
(fn [tname]
|
||||
(swap! name* (constantly tname))))
|
||||
|
||||
|
||||
[:div {:class (stl/css :paginator)} "2/2"]]]))
|
||||
on-back
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(swap! name* (constantly nil))))
|
||||
|
||||
onboarding-a-b-test?
|
||||
(cf/external-feature-flag "signup-background" "test")]
|
||||
|
||||
[:div {:class (stl/css-case
|
||||
:modal-overlay true
|
||||
:onboarding-a-b-test onboarding-a-b-test?)}
|
||||
|
||||
[:div.animated.fadeIn {:class (stl/css :modal-container)}
|
||||
[:& left-sidebar]
|
||||
[:div {:class (stl/css :separator)}]
|
||||
(if name
|
||||
[:& team-form-step-2 {:name name :on-back on-back}]
|
||||
[:& team-form-step-1 {:on-submit on-submit}])]]))
|
||||
|
||||
|
|
Loading…
Reference in a new issue