mirror of
https://github.com/penpot/penpot.git
synced 2025-05-26 05:56:11 +02:00
📎 Add more events instrumentation
This commit is contained in:
parent
bf6211903c
commit
8acc9af1f5
10 changed files with 216 additions and 189 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
@ -395,19 +398,19 @@
|
||||||
|
|
||||||
(defn- create-team-invitation
|
(defn- create-team-invitation
|
||||||
[{:keys [conn tokens team profile role email] :as cfg}]
|
[{:keys [conn tokens team profile role email] :as cfg}]
|
||||||
(let [member (profile/retrieve-profile-data-by-email conn email)
|
(let [member (profile/retrieve-profile-data-by-email conn email)
|
||||||
token-exp (dt/in-future "48h")
|
token-exp (dt/in-future "48h")
|
||||||
itoken (tokens :generate
|
itoken (tokens :generate
|
||||||
{:iss :team-invitation
|
{:iss :team-invitation
|
||||||
:exp token-exp
|
:exp token-exp
|
||||||
:profile-id (:id profile)
|
:profile-id (:id profile)
|
||||||
:role role
|
:role role
|
||||||
:team-id (:id team)
|
:team-id (:id team)
|
||||||
:member-email (:email member email)
|
:member-email (:email member email)
|
||||||
:member-id (:id member)})
|
:member-id (:id member)})
|
||||||
ptoken (tokens :generate-predefined
|
ptoken (tokens :generate-predefined
|
||||||
{:iss :profile-identity
|
{:iss :profile-identity
|
||||||
:profile-id (:id profile)})]
|
:profile-id (:id profile)})]
|
||||||
|
|
||||||
(when (and member (not (eml/allow-send-emails? conn member)))
|
(when (and member (not (eml/allow-send-emails? conn member)))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
|
@ -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)
|
||||||
profile (db/get-by-id conn :profile profile-id)]
|
audit-fn (:audit cfg)
|
||||||
|
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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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))))))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"})
|
||||||
|
|
|
@ -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 []
|
||||||
(modal/hide)
|
(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
|
||||||
(dd/fetch-team-invitations))
|
(modal/hide)
|
||||||
|
(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,101 +219,126 @@
|
||||||
{::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])
|
||||||
(fn [role]
|
set-role
|
||||||
(let [params {:member-id (:id member) :role role}]
|
(mf/use-fn
|
||||||
(st/emit! (dd/update-team-member-role params))))
|
(mf/deps member)
|
||||||
owner? (get-in team [:permissions :is-owner])
|
(fn [role]
|
||||||
|
(let [params {:member-id (:id member) :role role}]
|
||||||
|
(st/emit! (dd/update-team-member-role params)))))
|
||||||
|
|
||||||
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
|
||||||
(fn [member]
|
(mf/use-fn
|
||||||
(st/emit! (modal/show
|
(mf/deps set-owner-fn member)
|
||||||
{:type :confirm
|
(fn [member]
|
||||||
:title (tr "modals.promote-owner-confirm.title")
|
(st/emit! (modal/show
|
||||||
:message (tr "modals.promote-owner-confirm.message" (:name member))
|
{:type :confirm
|
||||||
:scd-message (tr "modals.promote-owner-confirm.hint")
|
:title (tr "modals.promote-owner-confirm.title")
|
||||||
:accept-label (tr "modals.promote-owner-confirm.accept")
|
:message (tr "modals.promote-owner-confirm.message" (:name member))
|
||||||
:on-accept set-owner-fn
|
:scd-message (tr "modals.promote-owner-confirm.hint")
|
||||||
:accept-style :primary})))
|
:accept-label (tr "modals.promote-owner-confirm.accept")
|
||||||
|
:on-accept set-owner-fn
|
||||||
|
: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
|
||||||
(fn []
|
(mf/use-fn
|
||||||
(st/emit! (dd/go-to-projects (:default-team-id profile))
|
(mf/deps profile)
|
||||||
(modal/hide)
|
(fn []
|
||||||
(du/fetch-teams)))
|
(st/emit! (dd/go-to-projects (:default-team-id profile))
|
||||||
|
(modal/hide)
|
||||||
|
(du/fetch-teams))))
|
||||||
|
|
||||||
on-error
|
on-error
|
||||||
(fn [{:keys [code] :as error}]
|
(mf/use-fn
|
||||||
(condp = code
|
(fn [{:keys [code] :as error}]
|
||||||
|
(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
|
||||||
(fn []
|
(mf/use-fn
|
||||||
(st/emit! (dd/delete-team (with-meta team {:on-success on-success
|
(mf/deps team on-success on-error)
|
||||||
:on-error on-error}))))
|
(fn []
|
||||||
|
(st/emit! (dd/delete-team (with-meta team {:on-success on-success
|
||||||
|
:on-error on-error})))))
|
||||||
|
|
||||||
leave-fn
|
leave-fn
|
||||||
(fn [member-id]
|
(mf/use-fn
|
||||||
(let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))]
|
(mf/deps on-success on-error)
|
||||||
(st/emit! (dd/leave-team (with-meta params
|
(fn [member-id]
|
||||||
{:on-success on-success
|
(let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))]
|
||||||
:on-error on-error})))))
|
(st/emit! (dd/leave-team (with-meta params
|
||||||
|
{:on-success on-success
|
||||||
|
:on-error on-error}))))))
|
||||||
|
|
||||||
leave-and-close
|
leave-and-close
|
||||||
(st/emitf (modal/show
|
(mf/use-fn
|
||||||
{:type :confirm
|
(mf/deps delete-fn)
|
||||||
:title (tr "modals.leave-confirm.title")
|
(fn []
|
||||||
:message (tr "modals.leave-and-close-confirm.message" (:name team))
|
(st/emit! (modal/show
|
||||||
:scd-message (tr "modals.leave-and-close-confirm.hint")
|
{:type :confirm
|
||||||
:accept-label (tr "modals.leave-confirm.accept")
|
:title (tr "modals.leave-confirm.title")
|
||||||
:on-accept delete-fn}))
|
:message (tr "modals.leave-and-close-confirm.message" (:name team))
|
||||||
|
:scd-message (tr "modals.leave-and-close-confirm.hint")
|
||||||
|
:accept-label (tr "modals.leave-confirm.accept")
|
||||||
|
:on-accept delete-fn}))))
|
||||||
|
|
||||||
change-owner-and-leave
|
change-owner-and-leave
|
||||||
(fn []
|
(mf/use-fn
|
||||||
(st/emit! (dd/fetch-team-members)
|
(mf/deps profile team leave-fn)
|
||||||
(modal/show
|
(fn []
|
||||||
{:type :leave-and-reassign
|
(st/emit! (dd/fetch-team-members)
|
||||||
:profile profile
|
(modal/show
|
||||||
:team team
|
{:type :leave-and-reassign
|
||||||
:accept leave-fn})))
|
:profile profile
|
||||||
|
:team team
|
||||||
|
:accept leave-fn}))))
|
||||||
|
|
||||||
leave
|
leave
|
||||||
(st/emitf (modal/show
|
(mf/use-fn
|
||||||
{:type :confirm
|
(mf/deps leave-fn)
|
||||||
:title (tr "modals.leave-confirm.title")
|
(fn []
|
||||||
:message (tr "modals.leave-confirm.message")
|
(st/emit! (modal/show
|
||||||
:accept-label (tr "modals.leave-confirm.accept")
|
{:type :confirm
|
||||||
:on-accept leave-fn}))
|
:title (tr "modals.leave-confirm.title")
|
||||||
|
:message (tr "modals.leave-confirm.message")
|
||||||
|
:accept-label (tr "modals.leave-confirm.accept")
|
||||||
|
: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
|
||||||
{:type :confirm
|
(mf/deps delete-member-fn)
|
||||||
:title (tr "modals.delete-team-member-confirm.title")
|
(fn []
|
||||||
:message (tr "modals.delete-team-member-confirm.message")
|
(st/emit! (modal/show
|
||||||
:accept-label (tr "modals.delete-team-member-confirm.accept")
|
{:type :confirm
|
||||||
:on-accept delete-member-fn}))]
|
:title (tr "modals.delete-team-member-confirm.title")
|
||||||
|
:message (tr "modals.delete-team-member-confirm.message")
|
||||||
|
:accept-label (tr "modals.delete-team-member-confirm.accept")
|
||||||
|
: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
|
||||||
:role invitation-role
|
[:& invitation-role-selector
|
||||||
:status status
|
{:can-invite? can-invite?
|
||||||
:change-to-editor (partial change-rol :editor)
|
:role invitation-role
|
||||||
:change-to-admin (partial change-rol :admin)}]]
|
:status status
|
||||||
[:div.table-field.status [:& invitation-status-badge {:status status}]]
|
:change-to-editor (partial change-rol :editor)
|
||||||
[:div.table-field.actions [:& invitation-actions {:can-modify? can-invite? :delete delete-invitation :resend resend-invitation}]]]))
|
: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}]]]))
|
||||||
|
|
||||||
(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}]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue