🎉 Add new onboarding flow.

This commit is contained in:
Andrey Antukh 2021-11-03 13:10:49 +01:00 committed by Andrés Moya
parent 898ae64a57
commit fced22bc60
28 changed files with 743 additions and 209 deletions

View file

@ -300,6 +300,28 @@
(rx/map team-created)
(rx/catch on-error))))))
;; --- EVENT: create-team-with-invitations
;; NOTE: right now, it only handles a single email, in a near future
;; this will be changed to the ability to specify multiple emails.
(defn create-team-with-invitations
[{:keys [name email role] :as params}]
(us/assert string? name)
(ptk/reify ::create-team-with-invitations
ptk/WatchEvent
(watch [_ _ _]
(let [{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)
params {:name name
:emails #{email}
:role role}]
(->> (rp/mutation! :create-team-and-invite-members params)
(rx/tap on-success)
(rx/map team-created)
(rx/catch on-error))))))
;; --- EVENT: update-team
(defn update-team

View file

@ -61,12 +61,6 @@
(def dashboard-search-result
(l/derived :dashboard-search-result st/state))
(def dashboard-team
(l/derived (fn [state]
(let [team-id (:current-team-id state)]
(get-in state [:teams team-id])))
st/state))
(def dashboard-team-stats
(l/derived :dashboard-team-stats st/state))

View file

@ -72,7 +72,10 @@
:dashboard-team-settings)
[:*
#_[:div.modal-wrapper
[:& app.main.ui.onboarding/release-notes-modal {:version "1.8"}]]
#_[:& app.main.ui.onboarding/onboarding-templates-modal]
#_[:& app.main.ui.onboarding/onboarding-modal]
#_[:& app.main.ui.onboarding/onboarding-team-modal]
]
[:& dashboard {:route route}]]
:viewer

View file

@ -104,11 +104,11 @@
(when (and (:onboarding-viewed props)
(not= version (:main @cf/version))
(not= "0.0" (:main @cf/version)))
(tm/schedule 1000 #(st/emit! (modal/show {:type :release-notes :version (:main @cf/version)})))))))
(tm/schedule 1000 #(st/emit! (modal/show {:type :release-notes
:version (:main @cf/version)})))))))
[:& (mf/provider ctx/current-team-id) {:value team-id}
[:& (mf/provider ctx/current-project-id) {:value project-id}
;; NOTE: dashboard events and other related functions assumes
;; that the team is a implicit context variable that is
;; available using react context or accessing

View file

@ -120,6 +120,6 @@
[:*
[:& header {:team team :project project}]
[:section.dashboard-container
[:& grid {:project-id (:id project)
[:& grid {:project project
:files files}]]]))

View file

@ -15,6 +15,7 @@
[app.main.ui.dashboard.file-menu :refer [file-menu]]
[app.main.ui.dashboard.import :refer [use-import-file]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.placeholder :refer [empty-placeholder loading-placeholder]]
[app.main.ui.icons :as i]
[app.main.worker :as wrk]
[app.util.dom :as dom]
@ -195,24 +196,10 @@
:on-edit on-edit
:on-menu-close on-menu-close}])]]]))
(mf/defc empty-placeholder
[{:keys [dragging?] :as props}]
(if-not dragging?
[:div.grid-empty-placeholder
[:div.icon i/file-html]
[:div.text (tr "dashboard.empty-files")]]
[:div.grid-row.no-wrap
[:div.grid-item]]))
(mf/defc loading-placeholder
[]
[:div.grid-empty-placeholder
[:div.icon i/loader]
[:div.text (tr "dashboard.loading-files")]])
(mf/defc grid
[{:keys [files project-id] :as props}]
(let [dragging? (mf/use-state false)
[{:keys [files project] :as props}]
(let [dragging? (mf/use-state false)
project-id (:id project)
on-finish-import
(mf/use-callback
@ -272,7 +259,7 @@
:navigate? true}])]
:else
[:& empty-placeholder])]))
[:& empty-placeholder {:default? (:is-default project)}])]))
(mf/defc line-grid-row
[{:keys [files selected-files on-load-more dragging?] :as props}]
@ -330,8 +317,11 @@
(tr "dashboard.show-all-files")]])]))
(mf/defc line-grid
[{:keys [project-id team-id files on-load-more] :as props}]
[{:keys [project team files on-load-more] :as props}]
(let [dragging? (mf/use-state false)
project-id (:id project)
team-id (:id team)
selected-files (mf/deref refs/dashboard-selected-files)
selected-project (mf/deref refs/dashboard-selected-project)
@ -413,5 +403,6 @@
:dragging? @dragging?}]
:else
[:& empty-placeholder {:dragging? @dragging?}])]))
[:& empty-placeholder {:dragging? @dragging?
:default? (:is-default project)}])]))

View file

@ -0,0 +1,34 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.main.ui.dashboard.placeholder
(:require
[app.main.ui.icons :as i]
[app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf]))
(mf/defc empty-placeholder
[{:keys [dragging? default?] :as props}]
(cond
(true? dragging?)
[:div.grid-row.no-wrap
[:div.grid-item]]
(true? default?)
[:div.grid-empty-placeholder.drafts
[:div.text
[:& i18n/tr-html {:label "dashboard.empty-placeholder-drafts"}]]]
:else
[:div.grid-empty-placeholder
[:img.ph-files {:src "images/ph-file.svg"}]]))
(mf/defc loading-placeholder
[]
[:div.grid-empty-placeholder
[:div.icon i/loader]
[:div.text (tr "dashboard.loading-files")]])

View file

@ -73,7 +73,6 @@
:accept-label (tr "modals.delete-project-confirm.accept")
:on-accept delete-fn}))
file-input (mf/use-ref nil)
on-import-files

View file

@ -33,10 +33,8 @@
(tr "dashboard.new-project")]]))
(mf/defc project-item
[{:keys [project first? files] :as props}]
[{:keys [project first? team files] :as props}]
(let [locale (mf/deref i18n/locale)
team-id (:team-id project)
file-count (or (:count project) 0)
dstate (mf/deref refs/dashboard-local)
@ -145,9 +143,8 @@
i/actions]]
[:& line-grid
{:project-id (:id project)
:project project
:team-id team-id
{:project project
:team team
:on-load-more on-nav
:files files}]]))
@ -186,7 +183,8 @@
(filterv #(= id (:project-id %)))
(sort-by :modified-at #(compare %2 %1))))]
[:& project-item {:project project
:files files
:team team
:files files
:first? (= project (first projects))
:key (:id project)}]))]])))

View file

@ -72,8 +72,9 @@
#(doseq [key keys]
(events/unlistenByKey key)))))
[:div.modal-wrapper {:ref wrapper-ref}
(mf/element (get components (:type data)) (:props data))]))
(when-let [component (get components (:type data))]
[:div.modal-wrapper {:ref wrapper-ref}
(mf/element component (:props data))])))
(def modal-ref

View file

@ -12,8 +12,10 @@
[app.main.data.messages :as dm]
[app.main.data.modal :as modal]
[app.main.data.users :as du]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.forms :as fm]
[app.main.ui.icons :as i]
[app.main.ui.releases.common :as rc]
[app.main.ui.releases.v1-4]
[app.main.ui.releases.v1-5]
@ -21,10 +23,13 @@
[app.main.ui.releases.v1-7]
[app.main.ui.releases.v1-8]
[app.main.ui.releases.v1-9]
[app.util.dom :as dom]
[app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]]
[app.util.object :as obj]
[app.util.router :as rt]
[app.util.timers :as tm]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[rumext.alpha :as mf]))
@ -159,7 +164,7 @@
skip
(mf/use-callback
(st/emitf (modal/hide)
(modal/show {:type :onboarding-team})
(modal/show {:type :onboarding-choice})
(du/mark-onboarding-as-viewed)))]
(mf/use-layout-effect
@ -187,57 +192,232 @@
(s/def ::team-form
(s/keys :req-un [::name]))
(mf/defc onboarding-choice-modal
{::mf/register modal/components
::mf/register-as :onboarding-choice}
[]
(let [;; When user choices the option of `fly solo`, we proceed to show
;; the onboarding templates modal.
on-fly-solo
(fn []
(tm/schedule 400 #(st/emit! (modal/show {:type :onboarding-templates}))))
;; When user choices the option of `team up`, we proceed to show
;; the team creation modal.
on-team-up
(fn []
(st/emit! (modal/show {:type :onboarding-team})))
]
[:div.modal-overlay
[:div.modal-container.onboarding.final.animated.fadeInUp
[:div.modal-top
[:h1 (tr "onboarding.welcome.title")]
[:p (tr "onboarding.welcome.desc3")]]
[:div.modal-columns
[:div.modal-left
[:div.content-button {:on-click on-fly-solo}
[:h2 (tr "onboarding.choice.fly-solo")]
[:p (tr "onboarding.choice.fly-solo-desc")]]]
[:div.modal-right
[:div.content-button {:on-click on-team-up}
[:h2 (tr "onboarding.choice.team-up")]
[:p (tr "onboarding.choice.team-up-desc")]]]]
[:img.deco {:src "images/deco-left.png" :border "0"}]
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))
(mf/defc onboarding-team-modal
{::mf/register modal/components
::mf/register-as :onboarding-team}
[]
(let [close (mf/use-fn (st/emitf (modal/hide)))
form (fm/use-form :spec ::team-form
(let [form (fm/use-form :spec ::team-form
:initial {})
on-submit
(mf/use-callback
(fn [form _]
(let [tname (get-in @form [:clean-data :name])]
(st/emit! (modal/show {:type :onboarding-team-invitations :name tname})))))]
[:div.modal-overlay
[:div.modal-container.onboarding-team
[:div.title
[:h2 (tr "onboarding.choice.team-up")]
[:p (tr "onboarding.choice.team-up-desc")]]
[:& fm/form {:form form
:on-submit on-submit}
[:div.team-row
[:& fm/input {:type "text"
:name :name
:label (tr "onboarding.team-input-placeholder")}]]
[:div.buttons
[:button.btn-secondary.btn-large
{:on-click #(st/emit! (modal/show {:type :onboarding-choice}))}
(tr "labels.cancel")]
[:& fm/submit-button
{:label (tr "labels.next")}]]]
[:img.deco {:src "images/deco-left.png" :border "0"}]
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))
(defn get-available-roles
[]
[{:value "editor" :label (tr "labels.editor")}
{:value "admin" :label (tr "labels.admin")}])
(s/def ::email ::us/email)
(s/def ::role ::us/keyword)
(s/def ::invite-form
(s/keys :req-un [::role ::email]))
;; This is the final step of team creation, consists in provide a
;; shortcut for invite users.
(mf/defc onboarding-team-invitations-modal
{::mf/register modal/components
::mf/register-as :onboarding-team-invitations}
[{:keys [name] :as props}]
(let [initial (mf/use-memo (constantly
{:role "editor"
:name name}))
form (fm/use-form :spec ::invite-form
:initial initial)
roles (mf/use-memo #(get-available-roles))
on-success
(mf/use-callback
(fn [_form response]
(st/emit! (modal/hide)
(rt/nav :dashboard-projects {:team-id (:id response)}))))
(let [project-id (:default-project-id response)
team-id (:id response)]
(st/emit!
(modal/hide)
(rt/nav :dashboard-projects {:team-id team-id}))
(tm/schedule 400 #(st/emit!
(modal/show {:type :onboarding-templates
:project-id project-id}))))))
on-error
(mf/use-callback
(fn [_form _response]
(st/emit! (dm/error "Error on creating team."))))
on-submit
;; The SKIP branch only creates the team, without invitations
on-skip
(mf/use-callback
(fn [form _event]
(fn [_]
(let [mdata {:on-success (partial on-success form)
:on-error (partial on-error form)}
params {:name (get-in @form [:clean-data :name])}]
(st/emit! (dd/create-team (with-meta params mdata))))))]
params {:name name}]
(st/emit! (dd/create-team (with-meta params mdata))))))
;; The SUBMIT branch creates the team with the invitations
on-submit
(mf/use-callback
(fn [form _]
(let [mdata {:on-success (partial on-success form)
:on-error (partial on-error form)}
params (:clean-data @form)]
(st/emit! (dd/create-team-with-invitations (with-meta params mdata))))))]
[:div.modal-overlay
[:div.modal-container.onboarding.final.animated.fadeInUp
[:div.modal-left
[:img {:src "images/onboarding-team.jpg" :border "0" :alt (tr "onboarding.team.create.title")}]
[:h2 (tr "onboarding.team.create.title")]
[:p (tr "onboarding.team.create.desc1")]
[:div.modal-container.onboarding-team
[:div.title
[:h2 (tr "onboarding.choice.team-up")]
[:p (tr "onboarding.choice.team-up-desc")]]
[:& fm/form {:form form
:on-submit on-submit}
[:& fm/input {:type "text"
:name :name
:label (tr "onboarding.team.create.input-placeholder")}]
[:& fm/form {:form form
:on-submit on-submit}
[:div.invite-row
[:& fm/input {:name :email
:label (tr "labels.email")}]
[:& fm/select {:name :role
:options roles}]]
[:div.buttons
[:button.btn-secondary.btn-large
{:on-click #(st/emit! (modal/show {:type :onboarding-choice}))}
(tr "labels.cancel")]
[:& fm/submit-button
{:label (tr "onboarding.team.create.button")}]]]
[:div.modal-right
[:img {:src "images/onboarding-start.jpg" :border "0" :alt (tr "onboarding.team.start.title")}]
[:h2 (tr "onboarding.team.start.title")]
[:p (tr "onboarding.team.start.desc1")]
[:button.btn-primary.btn-large {:on-click close} (tr "onboarding.team.start.button")]]
{:label (tr "labels.create")}]]
[:div.skip-action
{:on-click on-skip}
[:div.action "Skip and invite later"]]]
[:img.deco {:src "images/deco-left.png" :border "0"}]
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]))
(mf/defc template-item
[{:keys [name path image project-id]}]
(let [downloading? (mf/use-state false)
link (str (assoc cf/public-uri :path path))
on-finish-import
(fn []
(st/emit! (dd/fetch-recent-files)))
open-import-modal
(fn [file]
(st/emit! (modal/show
{:type :import
:project-id project-id
:files [file]
:on-finish-import on-finish-import})))
on-click
(fn []
(reset! downloading? true)
(->> (http/send! {:method :get :uri link :response-type :blob :mode :no-cors})
(rx/subs (fn [{:keys [body] :as response}]
(open-import-modal {:name name :uri (dom/create-uri body)}))
(fn [error]
(js/console.log "error" error))
(fn []
(reset! downloading? false)))))
]
[:div.template-item
[:div.template-item-content
[:img {:src image}]]
[:div.template-item-title
[:div.label name]
(if @downloading?
[:div.action "Fetching..."]
[:div.action {:on-click on-click} "+ Add to drafts"])]]))
(mf/defc onboarding-templates-modal
{::mf/wrap-props false
::mf/register modal/components
::mf/register-as :onboarding-templates}
;; NOTE: the project usually comes empty, it only comes fullfilled
;; when a user creates a new team just after signup.
[{:keys [project-id] :as props}]
(let [close-fn (mf/use-callback #(st/emit! (modal/hide)))
profile (mf/deref refs/profile)
project-id (or project-id (:default-project-id profile))]
[:div.modal-overlay
[:div.modal-container.onboarding-templates
[:div.modal-header
[:div.modal-close-button
{:on-click close-fn} i/close]]
[:div.modal-content
[:h3 (tr "onboarding.templates.title")]
[:p (tr "onboarding.templates.subtitle")]
[:div.templates
[:& template-item
{:path "/github/penpot-files/Penpot-Design-system.penpot"
:image "https://penpot.app/images/libraries/cover-ds-penpot.jpg"
:name "Penpot Design System"
:project-id project-id}]
[:& template-item
{:path "/github/penpot-files/Material-Design-Kit.penpot"
:image "https://penpot.app/images/libraries/cover-material.jpg"
:name "Material Design Kit"
:project-id project-id}]]]]]))
;;; --- RELEASE NOTES MODAL

View file

@ -169,7 +169,7 @@
(rx/tap #(do (swap! current inc)
(progress! context
:upload-data @current total))))))
(rx/map first)
(rx/tap #(reset! revn (:revn %)))
(rx/ignore))