📎 Add more events instrumentation

This commit is contained in:
Andrey Antukh 2022-03-21 15:00:50 +01:00 committed by Andrés Moya
parent bf6211903c
commit 8acc9af1f5
10 changed files with 216 additions and 189 deletions

View file

@ -180,17 +180,6 @@
;; --- HTTP HANDLERS ;; --- HTTP HANDLERS
(defn extract-utm-props
"Extracts additional data from user params."
[params]
(reduce-kv (fn [params k v]
(let [sk (name k)]
(cond-> params
(str/starts-with? sk "utm_")
(assoc (->> sk str/kebab (keyword "penpot")) v))))
{}
params))
(defn- retrieve-profile (defn- retrieve-profile
[{:keys [pool executor] :as cfg} info] [{:keys [pool executor] :as cfg} info]
(px/with-dispatch executor (px/with-dispatch executor
@ -252,7 +241,7 @@
(defn- auth-handler (defn- auth-handler
[{:keys [tokens] :as cfg} {:keys [params] :as request} respond raise] [{:keys [tokens] :as cfg} {:keys [params] :as request} respond raise]
(try (try
(let [props (extract-utm-props params) (let [props (audit/extract-utm-params params)
state (tokens :generate state (tokens :generate
{:iss :oauth {:iss :oauth
:invitation-token (:invitation-token params) :invitation-token (:invitation-token params)

View file

@ -34,6 +34,20 @@
(yrq/get-header request "x-real-ip") (yrq/get-header request "x-real-ip")
(yrq/remote-addr request))) (yrq/remote-addr request)))
(defn extract-utm-params
"Extracts additional data from params and namespace them under
`penpot` ns."
[params]
(letfn [(process-param [params k v]
(let [sk (d/name k)]
(cond-> params
(str/starts-with? sk "utm_")
(assoc (->> sk str/kebab (keyword "penpot")) v)
(str/starts-with? sk "mtm_")
(assoc (->> sk str/kebab (keyword "penpot")) v))))]
(reduce-kv process-param {} params)))
(defn profile->props (defn profile->props
[profile] [profile]
(-> profile (-> profile

View file

@ -12,7 +12,6 @@
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
[app.emails :as eml] [app.emails :as eml]
[app.http.oauth :refer [extract-utm-props]]
[app.loggers.audit :as audit] [app.loggers.audit :as audit]
[app.media :as media] [app.media :as media]
[app.rpc.mutations.teams :as teams] [app.rpc.mutations.teams :as teams]
@ -223,7 +222,7 @@
[conn params] [conn params]
(let [id (or (:id params) (uuid/next)) (let [id (or (:id params) (uuid/next))
props (-> (extract-utm-props params) props (-> (audit/extract-utm-params params)
(merge (:props params)) (merge (:props params))
(db/tjson)) (db/tjson))

View file

@ -13,6 +13,7 @@
[app.config :as cf] [app.config :as cf]
[app.db :as db] [app.db :as db]
[app.emails :as eml] [app.emails :as eml]
[app.loggers.audit :as audit]
[app.media :as media] [app.media :as media]
[app.rpc.mutations.projects :as projects] [app.rpc.mutations.projects :as projects]
[app.rpc.permissions :as perms] [app.rpc.permissions :as perms]
@ -357,14 +358,14 @@
:opt-un [::email ::emails])) :opt-un [::email ::emails]))
(sv/defmethod ::invite-team-member (sv/defmethod ::invite-team-member
"A rpc call that allow to send a single or multiple invitations to
join the team."
[{:keys [pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}] [{:keys [pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}]
(db/with-atomic [conn pool] (db/with-atomic [conn pool]
(let [perms (teams/get-permissions conn profile-id team-id) (let [perms (teams/get-permissions conn profile-id team-id)
profile (db/get-by-id conn :profile profile-id) profile (db/get-by-id conn :profile profile-id)
team (db/get-by-id conn :team team-id) team (db/get-by-id conn :team team-id)
emails (or emails #{}) emails (cond-> (or emails #{}) (string? email) (conj email))]
emails (if email (conj emails email) emails)
]
(when-not (:is-admin perms) (when-not (:is-admin perms)
(ex/raise :type :validation (ex/raise :type :validation
@ -385,7 +386,9 @@
:profile profile :profile profile
:role role)) :role role))
) )
nil)))
(with-meta {}
{::audit/props {:invitations (count emails)}}))))
(def sql:upsert-team-invitation (def sql:upsert-team-invitation
"insert into team_invitation(team_id, email_to, role, valid_until) "insert into team_invitation(team_id, email_to, role, valid_until)
@ -443,21 +446,14 @@
(s/and ::create-team (s/keys :req-un [::emails ::role]))) (s/and ::create-team (s/keys :req-un [::emails ::role])))
(sv/defmethod ::create-team-and-invite-members (sv/defmethod ::create-team-and-invite-members
[{:keys [pool audit] :as cfg} {:keys [profile-id emails role] :as params}] [{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}]
(db/with-atomic [conn pool] (db/with-atomic [conn pool]
(let [team (create-team conn params) (let [team (create-team conn params)
audit-fn (:audit cfg)
profile (db/get-by-id conn :profile profile-id)] profile (db/get-by-id conn :profile profile-id)]
;; Create invitations for all provided emails. ;; Create invitations for all provided emails.
(doseq [email emails] (doseq [email emails]
(audit :cmd :submit
:type "mutation"
:name "create-team-invitation"
:profile-id profile-id
:props {:email email
:role role
:profile-id profile-id})
(create-team-invitation (create-team-invitation
(assoc cfg (assoc cfg
:conn conn :conn conn
@ -465,8 +461,17 @@
:profile profile :profile profile
:email email :email email
:role role))) :role role)))
team)))
(with-meta team
{:before-complete
#(audit-fn :cmd :submit
:type "mutation"
:name "invite-team-member"
:profile-id profile-id
:props {:emails emails
:role role
:profile-id profile-id
:invitations (count emails)})}))))
;; --- Mutation: Update invitation role ;; --- Mutation: Update invitation role

View file

@ -44,16 +44,15 @@
;; (th/print-result! out) ;; (th/print-result! out)
(t/is (nil? (:result out))) (t/is (= {} (:result out)))
(t/is (= 1 (:call-count (deref mock)))) (t/is (= 1 (:call-count (deref mock))))
(t/is (= 1 (:num invitation)))) (t/is (= 1 (:num invitation))))
;; invite internal user without complaints ;; invite internal user without complaints
(th/reset-mock! mock) (th/reset-mock! mock)
(let [data (assoc data :email (:email profile2)) (let [data (assoc data :email (:email profile2))
out (th/mutation! data)] out (th/mutation! data)]
(t/is (nil? (:result out))) (t/is (= {} (:result out)))
(t/is (= 1 (:call-count (deref mock))))) (t/is (= 1 (:call-count (deref mock)))))
;; invite user with complaint ;; invite user with complaint
@ -61,7 +60,7 @@
(th/reset-mock! mock) (th/reset-mock! mock)
(let [data (assoc data :email "foo@bar.com") (let [data (assoc data :email "foo@bar.com")
out (th/mutation! data)] out (th/mutation! data)]
(t/is (nil? (:result out))) (t/is (= {} (:result out)))
(t/is (= 1 (:call-count (deref mock))))) (t/is (= 1 (:call-count (deref mock)))))
;; invite user with bounce ;; invite user with bounce

View file

@ -426,21 +426,21 @@
(rx/tap #(tm/schedule on-success)) (rx/tap #(tm/schedule on-success))
(rx/catch on-error)))))) (rx/catch on-error))))))
(defn invite-team-member (defn invite-team-members
[{:keys [emails role] :as params}] [{:keys [emails role team-id resend?] :as params}]
(us/assert ::us/set-of-emails emails) (us/assert ::us/set-of-emails emails)
(us/assert ::us/keyword role) (us/assert ::us/keyword role)
(ptk/reify ::invite-team-member (us/assert ::us/uuid team-id)
(ptk/reify ::invite-team-members
IDeref IDeref
(-deref [_] {:role role}) (-deref [_] {:role role :team-id team-id :resend? resend?})
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ _ _]
(let [{:keys [on-success on-error] (let [{:keys [on-success on-error]
:or {on-success identity :or {on-success identity
on-error rx/throw}} (meta params) on-error rx/throw}} (meta params)
team-id (:current-team-id state) params (dissoc params :resend?)]
params (assoc params :team-id team-id)]
(->> (rp/mutation! :invite-team-member params) (->> (rp/mutation! :invite-team-member params)
(rx/tap on-success) (rx/tap on-success)
(rx/catch on-error)))))) (rx/catch on-error))))))

View file

@ -85,7 +85,7 @@
(derive :app.main.data.dashboard/delete-team-member ::generic-action) (derive :app.main.data.dashboard/delete-team-member ::generic-action)
(derive :app.main.data.dashboard/duplicate-project ::generic-action) (derive :app.main.data.dashboard/duplicate-project ::generic-action)
(derive :app.main.data.dashboard/file-created ::generic-action) (derive :app.main.data.dashboard/file-created ::generic-action)
(derive :app.main.data.dashboard/invite-team-member ::generic-action) (derive :app.main.data.dashboard/invite-team-members ::generic-action)
(derive :app.main.data.dashboard/leave-team ::generic-action) (derive :app.main.data.dashboard/leave-team ::generic-action)
(derive :app.main.data.dashboard/move-files ::generic-action) (derive :app.main.data.dashboard/move-files ::generic-action)
(derive :app.main.data.dashboard/move-project ::generic-action) (derive :app.main.data.dashboard/move-project ::generic-action)
@ -113,6 +113,7 @@
(derive :app.main.data.workspace.persistence/attach-library ::generic-action) (derive :app.main.data.workspace.persistence/attach-library ::generic-action)
(derive :app.main.data.workspace.persistence/detach-library ::generic-action) (derive :app.main.data.workspace.persistence/detach-library ::generic-action)
(derive :app.main.data.workspace.persistence/set-file-shard ::generic-action) (derive :app.main.data.workspace.persistence/set-file-shard ::generic-action)
(derive :app.main.data.workspace.selection/toggle-focus-mode ::generic-action)
(derive :app.main.data.workspace/create-page ::generic-action) (derive :app.main.data.workspace/create-page ::generic-action)
(derive :app.main.data.workspace/set-workspace-layout ::generic-action) (derive :app.main.data.workspace/set-workspace-layout ::generic-action)
(derive :app.main.data.workspace/toggle-layout-flag ::generic-action) (derive :app.main.data.workspace/toggle-layout-flag ::generic-action)

View file

@ -293,29 +293,6 @@
(rx/catch (constantly (rx/of 1))) (rx/catch (constantly (rx/of 1)))
(rx/map #(logged-out params))))))) (rx/map #(logged-out params)))))))
;; --- EVENT: register
(s/def ::register
(s/keys :req-un [::fullname ::password ::email]))
(defn register
"Create a register event instance."
[data]
(s/assert ::register data)
(ptk/reify ::register
ptk/WatchEvent
(watch [_ _ _]
(let [{:keys [on-error on-success]
:or {on-error identity
on-success identity}} (meta data)]
(->> (rp/mutation :register-profile data)
(rx/tap on-success)
(rx/catch on-error))))
ptk/EffectEvent
(effect [_ _ _]
(swap! storage dissoc ::redirect-to))))
;; --- Update Profile ;; --- Update Profile
(defn update-profile (defn update-profile

View file

@ -68,8 +68,8 @@
(st/emit! (dm/error (tr "errors.generic"))))) (st/emit! (dm/error (tr "errors.generic")))))
(defn- handle-prepare-register-success (defn- handle-prepare-register-success
[_form {:keys [token] :as result}] [_ params]
(st/emit! (rt/nav :auth-register-validate {} {:token token}))) (st/emit! (rt/nav :auth-register-validate {} params)))
(mf/defc register-form (mf/defc register-form
[{:keys [params] :as props}] [{:keys [params] :as props}]
@ -83,8 +83,9 @@
(mf/use-callback (mf/use-callback
(fn [form _event] (fn [form _event]
(reset! submitted? true) (reset! submitted? true)
(let [params (:clean-data @form)] (let [cdata (:clean-data @form)]
(->> (rp/mutation :prepare-register-profile params) (->> (rp/mutation :prepare-register-profile cdata)
(rx/map #(merge % params))
(rx/finalize #(reset! submitted? false)) (rx/finalize #(reset! submitted? false))
(rx/subs (partial handle-prepare-register-success form) (rx/subs (partial handle-prepare-register-success form)
(partial handle-prepare-register-error form)))))) (partial handle-prepare-register-error form))))))
@ -160,13 +161,6 @@
(defn- handle-register-error (defn- handle-register-error
[form error] [form error]
(case (:code error) (case (:code error)
:registration-disabled
(st/emit! (dm/error (tr "errors.registration-disabled")))
:email-has-permanent-bounces
(let [email (get @form [:data :email])]
(st/emit! (dm/error (tr "errors.email-has-permanent-bounces" email))))
:email-already-exists :email-already-exists
(swap! form assoc-in [:errors :email] (swap! form assoc-in [:errors :email]
{:message "errors.email-already-exists"}) {:message "errors.email-already-exists"})

View file

@ -7,10 +7,11 @@
(ns app.main.ui.dashboard.team (ns app.main.ui.dashboard.team
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.spec :as us] [app.common.spec :as us]
[app.config :as cfg] [app.config :as cfg]
[app.main.data.dashboard :as dd] [app.main.data.dashboard :as dd]
[app.main.data.messages :as dm] [app.main.data.messages :as msg]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.users :as du] [app.main.data.users :as du]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -27,15 +28,14 @@
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
;; TEAM SECTION
(mf/defc header (mf/defc header
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [section team] :as props}] [{:keys [section team] :as props}]
(let [go-members (st/emitf (dd/go-to-team-members)) (let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members)))
go-settings (st/emitf (dd/go-to-team-settings)) go-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings)))
go-invitations (st/emitf (dd/go-to-team-invitations)) go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations)))
invite-member (st/emitf (modal/show {:type ::invite-member :team team})) invite-member (mf/use-fn #(st/emit! (modal/show {:type :invite-members :team team})))
members-section? (= section :dashboard-team-members) members-section? (= section :dashboard-team-members)
settings-section? (= section :dashboard-team-settings) settings-section? (= section :dashboard-team-settings)
invitations-section? (= section :dashboard-team-invitations) invitations-section? (= section :dashboard-team-invitations)
@ -62,12 +62,16 @@
(tr "dashboard.invite-profile")] (tr "dashboard.invite-profile")]
[:div.blank-space])]])) [:div.blank-space])]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INVITATIONS MODAL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-available-roles (defn get-available-roles
[permissions] [permissions]
(->> [{:value "editor" :label (tr "labels.editor")} (->> [{:value "editor" :label (tr "labels.editor")}
(when (:is-admin permissions) (when (:is-admin permissions)
{:value "admin" :label (tr "labels.admin")}) {:value "admin" :label (tr "labels.admin")})
;; Temporarily disabled viewer role ;; Temporarily disabled viewer roles
;; https://tree.taiga.io/project/uxboxproject/issue/1083 ;; https://tree.taiga.io/project/uxboxproject/issue/1083
;; {:value "viewer" :label (tr "labels.viewer")} ;; {:value "viewer" :label (tr "labels.viewer")}
] ]
@ -75,31 +79,34 @@
(s/def ::emails (s/and ::us/set-of-emails d/not-empty?)) (s/def ::emails (s/and ::us/set-of-emails d/not-empty?))
(s/def ::role ::us/keyword) (s/def ::role ::us/keyword)
(s/def ::invite-member-form (s/def ::team-id ::us/uuid)
(s/keys :req-un [::role ::emails]))
(mf/defc invite-member-modal (s/def ::invite-member-form
(s/keys :req-un [::role ::emails ::team-id]))
(mf/defc invite-members-modal
{::mf/register modal/components {::mf/register modal/components
::mf/register-as ::invite-member} ::mf/register-as :invite-members}
[{:keys [team]}] [{:keys [team]}]
(let [perms (:permissions team) (let [perms (:permissions team)
roles (mf/use-memo (mf/deps perms) #(get-available-roles perms)) roles (mf/use-memo (mf/deps perms) #(get-available-roles perms))
initial (mf/use-memo (constantly {:role "editor"})) initial (mf/use-memo (constantly {:role "editor" :team-id (:id team)}))
form (fm/use-form :spec ::invite-member-form form (fm/use-form :spec ::invite-member-form
:initial initial) :initial initial)
error-text (mf/use-state "") error-text (mf/use-state "")
on-success on-success
(st/emitf (dm/success (tr "notifications.invitation-email-sent")) (fn []
(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
(modal/hide) (modal/hide)
(dd/fetch-team-invitations)) (dd/fetch-team-invitations)))
on-error on-error
(fn [{:keys [type code] :as error}] (fn [{:keys [type code] :as error}]
(cond (cond
(and (= :validation type) (and (= :validation type)
(= :profile-is-muted code)) (= :profile-is-muted code))
(st/emit! (dm/error (tr "errors.profile-is-muted")) (st/emit! (msg/error (tr "errors.profile-is-muted"))
(modal/hide)) (modal/hide))
(and (= :validation type) (and (= :validation type)
@ -108,7 +115,7 @@
(swap! error-text (tr "errors.email-spam-or-permanent-bounces" (:email error))) (swap! error-text (tr "errors.email-spam-or-permanent-bounces" (:email error)))
:else :else
(st/emit! (dm/error (tr "errors.generic")) (st/emit! (msg/error (tr "errors.generic"))
(modal/hide)))) (modal/hide))))
on-submit on-submit
@ -116,7 +123,7 @@
(let [params (:clean-data @form) (let [params (:clean-data @form)
mdata {:on-success (partial on-success form) mdata {:on-success (partial on-success form)
:on-error (partial on-error form)}] :on-error (partial on-error form)}]
(st/emit! (dd/invite-team-member (with-meta params mdata)) (st/emit! (dd/invite-team-members (with-meta params mdata))
(dd/fetch-team-invitations))))] (dd/fetch-team-invitations))))]
[:div.modal.dashboard-invite-modal.form-container [:div.modal.dashboard-invite-modal.form-container
@ -141,7 +148,9 @@
[:div.action-buttons [:div.action-buttons
[:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")}]]]])) [:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")}]]]]))
;; TEAM MEMBERS SECTION ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MEMBERS SECTION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc member-info [{:keys [member profile] :as props}] (mf/defc member-info [{:keys [member profile] :as props}]
(let [is-you? (= (:id profile) (:id member))] (let [is-you? (= (:id profile) (:id member))]
@ -210,18 +219,23 @@
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [team member members profile] :as props}] [{:keys [team member members profile] :as props}]
(let [set-role (let [owner? (dm/get-in team [:permissions :is-owner])
set-role
(mf/use-fn
(mf/deps member)
(fn [role] (fn [role]
(let [params {:member-id (:id member) :role role}] (let [params {:member-id (:id member) :role role}]
(st/emit! (dd/update-team-member-role params)))) (st/emit! (dd/update-team-member-role params)))))
owner? (get-in team [:permissions :is-owner])
set-owner-fn (partial set-role :owner)
set-admin (partial set-role :admin) set-owner-fn (mf/use-fn (mf/deps set-role) (partial set-role :owner))
set-editor (partial set-role :editor) set-admin (mf/use-fn (mf/deps set-role) (partial set-role :admin))
set-editor (mf/use-fn (mf/deps set-role) (partial set-role :editor))
;; set-viewer (partial set-role :viewer) ;; set-viewer (partial set-role :viewer)
set-owner set-owner
(mf/use-fn
(mf/deps set-owner-fn member)
(fn [member] (fn [member]
(st/emit! (modal/show (st/emit! (modal/show
{:type :confirm {:type :confirm
@ -230,81 +244,101 @@
:scd-message (tr "modals.promote-owner-confirm.hint") :scd-message (tr "modals.promote-owner-confirm.hint")
:accept-label (tr "modals.promote-owner-confirm.accept") :accept-label (tr "modals.promote-owner-confirm.accept")
:on-accept set-owner-fn :on-accept set-owner-fn
:accept-style :primary}))) :accept-style :primary}))))
delete-member-fn delete-member-fn
(st/emitf (dd/delete-team-member {:member-id (:id member)})) (mf/use-fn
(mf/deps member)
(fn [] (st/emit! (dd/delete-team-member {:member-id (:id member)}))))
on-success on-success
(mf/use-fn
(mf/deps profile)
(fn [] (fn []
(st/emit! (dd/go-to-projects (:default-team-id profile)) (st/emit! (dd/go-to-projects (:default-team-id profile))
(modal/hide) (modal/hide)
(du/fetch-teams))) (du/fetch-teams))))
on-error on-error
(mf/use-fn
(fn [{:keys [code] :as error}] (fn [{:keys [code] :as error}]
(condp = code (condp = code
:no-enough-members-for-leave :no-enough-members-for-leave
(rx/of (dm/error (tr "errors.team-leave.insufficient-members"))) (rx/of (msg/error (tr "errors.team-leave.insufficient-members")))
:member-does-not-exist :member-does-not-exist
(rx/of (dm/error (tr "errors.team-leave.member-does-not-exists"))) (rx/of (msg/error (tr "errors.team-leave.member-does-not-exists")))
:owner-cant-leave-team :owner-cant-leave-team
(rx/of (dm/error (tr "errors.team-leave.owner-cant-leave"))) (rx/of (msg/error (tr "errors.team-leave.owner-cant-leave")))
(rx/throw error))) (rx/throw error))))
delete-fn delete-fn
(mf/use-fn
(mf/deps team on-success on-error)
(fn [] (fn []
(st/emit! (dd/delete-team (with-meta team {:on-success on-success (st/emit! (dd/delete-team (with-meta team {:on-success on-success
:on-error on-error})))) :on-error on-error})))))
leave-fn leave-fn
(mf/use-fn
(mf/deps on-success on-error)
(fn [member-id] (fn [member-id]
(let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))] (let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))]
(st/emit! (dd/leave-team (with-meta params (st/emit! (dd/leave-team (with-meta params
{:on-success on-success {:on-success on-success
:on-error on-error}))))) :on-error on-error}))))))
leave-and-close leave-and-close
(st/emitf (modal/show (mf/use-fn
(mf/deps delete-fn)
(fn []
(st/emit! (modal/show
{:type :confirm {:type :confirm
:title (tr "modals.leave-confirm.title") :title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-and-close-confirm.message" (:name team)) :message (tr "modals.leave-and-close-confirm.message" (:name team))
:scd-message (tr "modals.leave-and-close-confirm.hint") :scd-message (tr "modals.leave-and-close-confirm.hint")
:accept-label (tr "modals.leave-confirm.accept") :accept-label (tr "modals.leave-confirm.accept")
:on-accept delete-fn})) :on-accept delete-fn}))))
change-owner-and-leave change-owner-and-leave
(mf/use-fn
(mf/deps profile team leave-fn)
(fn [] (fn []
(st/emit! (dd/fetch-team-members) (st/emit! (dd/fetch-team-members)
(modal/show (modal/show
{:type :leave-and-reassign {:type :leave-and-reassign
:profile profile :profile profile
:team team :team team
:accept leave-fn}))) :accept leave-fn}))))
leave leave
(st/emitf (modal/show (mf/use-fn
(mf/deps leave-fn)
(fn []
(st/emit! (modal/show
{:type :confirm {:type :confirm
:title (tr "modals.leave-confirm.title") :title (tr "modals.leave-confirm.title")
:message (tr "modals.leave-confirm.message") :message (tr "modals.leave-confirm.message")
:accept-label (tr "modals.leave-confirm.accept") :accept-label (tr "modals.leave-confirm.accept")
:on-accept leave-fn})) :on-accept leave-fn}))))
preset-leave (cond (= 1 (count members)) leave-and-close preset-leave (cond (= 1 (count members)) leave-and-close
(= true owner?) change-owner-and-leave (= true owner?) change-owner-and-leave
:else leave) :else leave)
delete delete
(st/emitf (modal/show (mf/use-fn
(mf/deps delete-member-fn)
(fn []
(st/emit! (modal/show
{:type :confirm {:type :confirm
:title (tr "modals.delete-team-member-confirm.title") :title (tr "modals.delete-team-member-confirm.title")
:message (tr "modals.delete-team-member-confirm.message") :message (tr "modals.delete-team-member-confirm.message")
:accept-label (tr "modals.delete-team-member-confirm.accept") :accept-label (tr "modals.delete-team-member-confirm.accept")
:on-accept delete-member-fn}))] :on-accept delete-member-fn}))))]
[:div.table-row [:div.table-row
[:div.table-field.name [:div.table-field.name
@ -361,7 +395,9 @@
:team team :team team
:members-map members-map}]]])) :members-map members-map}]]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INVITATIONS SECTION ;; INVITATIONS SECTION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc invitation-role-selector (mf/defc invitation-role-selector
[{:keys [can-invite? role status change-to-admin change-to-editor] :as props}] [{:keys [can-invite? role status change-to-admin change-to-editor] :as props}]
@ -418,7 +454,7 @@
:pending) :pending)
on-success on-success
#(st/emit! (dm/success (tr "notifications.invitation-email-sent")) #(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
(modal/hide) (modal/hide)
(dd/fetch-team-invitations)) (dd/fetch-team-invitations))
@ -428,18 +464,18 @@
(cond (cond
(and (= :validation type) (and (= :validation type)
(= :profile-is-muted code)) (= :profile-is-muted code))
(dm/error (tr "errors.profile-is-muted")) (msg/error (tr "errors.profile-is-muted"))
(and (= :validation type) (and (= :validation type)
(= :member-is-muted code)) (= :member-is-muted code))
(dm/error (tr "errors.member-is-muted")) (msg/error (tr "errors.member-is-muted"))
(and (= :validation type) (and (= :validation type)
(= :email-has-permanent-bounces code)) (= :email-has-permanent-bounces code))
(dm/error (tr "errors.email-has-permanent-bounces" email)) (msg/error (tr "errors.email-has-permanent-bounces" email))
:else :else
(dm/error (tr "errors.generic")))) (msg/error (tr "errors.generic"))))
change-rol change-rol
(fn [role] (fn [role]
@ -455,20 +491,31 @@
resend-invitation resend-invitation
(fn [] (fn []
(let [params {:email email :team-id (:id team) :role invitation-role} (let [params {:email email
:team-id (:id team)
:resend? true
:role invitation-role}
mdata {:on-success on-success mdata {:on-success on-success
:on-error (partial on-error email)}] :on-error (partial on-error email)}]
(st/emit! (dd/invite-team-member (with-meta params mdata))) (st/emit! (dd/invite-team-members (with-meta params mdata))
(st/emit! (dd/fetch-team-invitations))))] (dd/fetch-team-invitations))))]
[:div.table-row [:div.table-row
[:div.table-field.mail email] [:div.table-field.mail email]
[:div.table-field.roles [:& invitation-role-selector {:can-invite? can-invite? [:div.table-field.roles
[:& invitation-role-selector
{:can-invite? can-invite?
:role invitation-role :role invitation-role
:status status :status status
:change-to-editor (partial change-rol :editor) :change-to-editor (partial change-rol :editor)
:change-to-admin (partial change-rol :admin)}]] :change-to-admin (partial change-rol :admin)}]]
[:div.table-field.status [:& invitation-status-badge {:status status}]]
[:div.table-field.actions [:& invitation-actions {:can-modify? can-invite? :delete delete-invitation :resend resend-invitation}]]])) [:div.table-field.status
[:& invitation-status-badge {:status status}]]
[:div.table-field.actions
[:& invitation-actions
{:can-modify? can-invite?
:delete delete-invitation
:resend resend-invitation}]]]))
(mf/defc empty-invitation-table [can-invite?] (mf/defc empty-invitation-table [can-invite?]
[:div.empty-invitations [:div.empty-invitations
@ -513,7 +560,9 @@
[:& invitation-section {:team team [:& invitation-section {:team team
:invitations invitations}]]])) :invitations invitations}]]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; SETTINGS SECTION ;; SETTINGS SECTION
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mf/defc team-settings-page (mf/defc team-settings-page
[{:keys [team] :as props}] [{:keys [team] :as props}]