diff --git a/backend/src/app/http/oauth.clj b/backend/src/app/http/oauth.clj index 4e2748529..26c2f80c4 100644 --- a/backend/src/app/http/oauth.clj +++ b/backend/src/app/http/oauth.clj @@ -180,17 +180,6 @@ ;; --- 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 [{:keys [pool executor] :as cfg} info] (px/with-dispatch executor @@ -252,7 +241,7 @@ (defn- auth-handler [{:keys [tokens] :as cfg} {:keys [params] :as request} respond raise] (try - (let [props (extract-utm-props params) + (let [props (audit/extract-utm-params params) state (tokens :generate {:iss :oauth :invitation-token (:invitation-token params) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 952c4d640..8b5a8c2ec 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -34,6 +34,20 @@ (yrq/get-header request "x-real-ip") (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 [profile] (-> profile diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index fbc59b8b7..5834b0588 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -12,7 +12,6 @@ [app.config :as cf] [app.db :as db] [app.emails :as eml] - [app.http.oauth :refer [extract-utm-props]] [app.loggers.audit :as audit] [app.media :as media] [app.rpc.mutations.teams :as teams] @@ -223,7 +222,7 @@ [conn params] (let [id (or (:id params) (uuid/next)) - props (-> (extract-utm-props params) + props (-> (audit/extract-utm-params params) (merge (:props params)) (db/tjson)) diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index 77fa0b9e0..494204ca8 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -13,6 +13,7 @@ [app.config :as cf] [app.db :as db] [app.emails :as eml] + [app.loggers.audit :as audit] [app.media :as media] [app.rpc.mutations.projects :as projects] [app.rpc.permissions :as perms] @@ -357,14 +358,14 @@ :opt-un [::email ::emails])) (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}] (db/with-atomic [conn pool] (let [perms (teams/get-permissions conn profile-id team-id) profile (db/get-by-id conn :profile profile-id) team (db/get-by-id conn :team team-id) - emails (or emails #{}) - emails (if email (conj emails email) emails) - ] + emails (cond-> (or emails #{}) (string? email) (conj email))] (when-not (:is-admin perms) (ex/raise :type :validation @@ -385,7 +386,9 @@ :profile profile :role role)) ) - nil))) + + (with-meta {} + {::audit/props {:invitations (count emails)}})))) (def sql:upsert-team-invitation "insert into team_invitation(team_id, email_to, role, valid_until) @@ -395,19 +398,19 @@ (defn- create-team-invitation [{: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") - itoken (tokens :generate - {:iss :team-invitation - :exp token-exp - :profile-id (:id profile) - :role role - :team-id (:id team) - :member-email (:email member email) - :member-id (:id member)}) - ptoken (tokens :generate-predefined - {:iss :profile-identity - :profile-id (:id profile)})] + itoken (tokens :generate + {:iss :team-invitation + :exp token-exp + :profile-id (:id profile) + :role role + :team-id (:id team) + :member-email (:email member email) + :member-id (:id member)}) + ptoken (tokens :generate-predefined + {:iss :profile-identity + :profile-id (:id profile)})] (when (and member (not (eml/allow-send-emails? conn member))) (ex/raise :type :validation @@ -443,21 +446,14 @@ (s/and ::create-team (s/keys :req-un [::emails ::role]))) (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] - (let [team (create-team conn params) - profile (db/get-by-id conn :profile profile-id)] + (let [team (create-team conn params) + audit-fn (:audit cfg) + profile (db/get-by-id conn :profile profile-id)] ;; Create invitations for all provided 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 (assoc cfg :conn conn @@ -465,8 +461,17 @@ :profile profile :email email :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 diff --git a/backend/test/app/services_teams_test.clj b/backend/test/app/services_teams_test.clj index fa233ce71..61199fbf3 100644 --- a/backend/test/app/services_teams_test.clj +++ b/backend/test/app/services_teams_test.clj @@ -44,16 +44,15 @@ ;; (th/print-result! out) - (t/is (nil? (:result out))) + (t/is (= {} (:result out))) (t/is (= 1 (:call-count (deref mock)))) (t/is (= 1 (:num invitation)))) - ;; invite internal user without complaints (th/reset-mock! mock) (let [data (assoc data :email (:email profile2)) out (th/mutation! data)] - (t/is (nil? (:result out))) + (t/is (= {} (:result out))) (t/is (= 1 (:call-count (deref mock))))) ;; invite user with complaint @@ -61,7 +60,7 @@ (th/reset-mock! mock) (let [data (assoc data :email "foo@bar.com") out (th/mutation! data)] - (t/is (nil? (:result out))) + (t/is (= {} (:result out))) (t/is (= 1 (:call-count (deref mock))))) ;; invite user with bounce diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 1a495e871..ce9f7982d 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -426,21 +426,21 @@ (rx/tap #(tm/schedule on-success)) (rx/catch on-error)))))) -(defn invite-team-member - [{:keys [emails role] :as params}] +(defn invite-team-members + [{:keys [emails role team-id resend?] :as params}] (us/assert ::us/set-of-emails emails) (us/assert ::us/keyword role) - (ptk/reify ::invite-team-member + (us/assert ::us/uuid team-id) + (ptk/reify ::invite-team-members IDeref - (-deref [_] {:role role}) + (-deref [_] {:role role :team-id team-id :resend? resend?}) ptk/WatchEvent - (watch [_ state _] + (watch [_ _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) - team-id (:current-team-id state) - params (assoc params :team-id team-id)] + params (dissoc params :resend?)] (->> (rp/mutation! :invite-team-member params) (rx/tap on-success) (rx/catch on-error)))))) diff --git a/frontend/src/app/main/data/events.cljs b/frontend/src/app/main/data/events.cljs index bcb6dc548..7adf4c86c 100644 --- a/frontend/src/app/main/data/events.cljs +++ b/frontend/src/app/main/data/events.cljs @@ -85,7 +85,7 @@ (derive :app.main.data.dashboard/delete-team-member ::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/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/move-files ::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/detach-library ::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/set-workspace-layout ::generic-action) (derive :app.main.data.workspace/toggle-layout-flag ::generic-action) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index a13b37116..cd5ca770e 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -293,29 +293,6 @@ (rx/catch (constantly (rx/of 1))) (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 (defn update-profile diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 9cb0ed293..739b7ad1d 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -68,8 +68,8 @@ (st/emit! (dm/error (tr "errors.generic"))))) (defn- handle-prepare-register-success - [_form {:keys [token] :as result}] - (st/emit! (rt/nav :auth-register-validate {} {:token token}))) + [_ params] + (st/emit! (rt/nav :auth-register-validate {} params))) (mf/defc register-form [{:keys [params] :as props}] @@ -83,8 +83,9 @@ (mf/use-callback (fn [form _event] (reset! submitted? true) - (let [params (:clean-data @form)] - (->> (rp/mutation :prepare-register-profile params) + (let [cdata (:clean-data @form)] + (->> (rp/mutation :prepare-register-profile cdata) + (rx/map #(merge % params)) (rx/finalize #(reset! submitted? false)) (rx/subs (partial handle-prepare-register-success form) (partial handle-prepare-register-error form)))))) @@ -160,13 +161,6 @@ (defn- handle-register-error [form 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 (swap! form assoc-in [:errors :email] {:message "errors.email-already-exists"}) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index ca0e2502b..905879eff 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -7,10 +7,11 @@ (ns app.main.ui.dashboard.team (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.spec :as us] [app.config :as cfg] [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.users :as du] [app.main.refs :as refs] @@ -27,15 +28,14 @@ [cljs.spec.alpha :as s] [rumext.alpha :as mf])) -;; TEAM SECTION - (mf/defc header {::mf/wrap [mf/memo]} [{:keys [section team] :as props}] - (let [go-members (st/emitf (dd/go-to-team-members)) - go-settings (st/emitf (dd/go-to-team-settings)) - go-invitations (st/emitf (dd/go-to-team-invitations)) - invite-member (st/emitf (modal/show {:type ::invite-member :team team})) + (let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members))) + go-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings))) + go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations))) + invite-member (mf/use-fn #(st/emit! (modal/show {:type :invite-members :team team}))) + members-section? (= section :dashboard-team-members) settings-section? (= section :dashboard-team-settings) invitations-section? (= section :dashboard-team-invitations) @@ -62,12 +62,16 @@ (tr "dashboard.invite-profile")] [:div.blank-space])]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; INVITATIONS MODAL +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn get-available-roles [permissions] (->> [{:value "editor" :label (tr "labels.editor")} (when (:is-admin permissions) {:value "admin" :label (tr "labels.admin")}) - ;; Temporarily disabled viewer role + ;; Temporarily disabled viewer roles ;; https://tree.taiga.io/project/uxboxproject/issue/1083 ;; {:value "viewer" :label (tr "labels.viewer")} ] @@ -75,31 +79,34 @@ (s/def ::emails (s/and ::us/set-of-emails d/not-empty?)) (s/def ::role ::us/keyword) -(s/def ::invite-member-form - (s/keys :req-un [::role ::emails])) +(s/def ::team-id ::us/uuid) -(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-as ::invite-member} + ::mf/register-as :invite-members} [{:keys [team]}] (let [perms (:permissions team) 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 :initial initial) error-text (mf/use-state "") on-success - (st/emitf (dm/success (tr "notifications.invitation-email-sent")) - (modal/hide) - (dd/fetch-team-invitations)) + (fn [] + (st/emit! (msg/success (tr "notifications.invitation-email-sent")) + (modal/hide) + (dd/fetch-team-invitations))) on-error (fn [{:keys [type code] :as error}] (cond (and (= :validation type) (= :profile-is-muted code)) - (st/emit! (dm/error (tr "errors.profile-is-muted")) + (st/emit! (msg/error (tr "errors.profile-is-muted")) (modal/hide)) (and (= :validation type) @@ -108,7 +115,7 @@ (swap! error-text (tr "errors.email-spam-or-permanent-bounces" (:email error))) :else - (st/emit! (dm/error (tr "errors.generic")) + (st/emit! (msg/error (tr "errors.generic")) (modal/hide)))) on-submit @@ -116,9 +123,9 @@ (let [params (:clean-data @form) mdata {:on-success (partial on-success 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))))] - + [:div.modal.dashboard-invite-modal.form-container [:& fm/form {:on-submit on-submit :form form} [:div.title @@ -141,7 +148,9 @@ [:div.action-buttons [:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")}]]]])) -;; TEAM MEMBERS SECTION +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; MEMBERS SECTION +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (mf/defc member-info [{:keys [member profile] :as props}] (let [is-you? (= (:id profile) (:id member))] @@ -210,101 +219,126 @@ {::mf/wrap [mf/memo]} [{:keys [team member members profile] :as props}] - (let [set-role - (fn [role] - (let [params {:member-id (:id member) :role role}] - (st/emit! (dd/update-team-member-role params)))) - owner? (get-in team [:permissions :is-owner]) + (let [owner? (dm/get-in team [:permissions :is-owner]) + set-role + (mf/use-fn + (mf/deps member) + (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-editor (partial set-role :editor) + + set-owner-fn (mf/use-fn (mf/deps set-role) (partial set-role :owner)) + 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-owner - (fn [member] - (st/emit! (modal/show - {:type :confirm - :title (tr "modals.promote-owner-confirm.title") - :message (tr "modals.promote-owner-confirm.message" (:name member)) - :scd-message (tr "modals.promote-owner-confirm.hint") - :accept-label (tr "modals.promote-owner-confirm.accept") - :on-accept set-owner-fn - :accept-style :primary}))) + (mf/use-fn + (mf/deps set-owner-fn member) + (fn [member] + (st/emit! (modal/show + {:type :confirm + :title (tr "modals.promote-owner-confirm.title") + :message (tr "modals.promote-owner-confirm.message" (:name member)) + :scd-message (tr "modals.promote-owner-confirm.hint") + :accept-label (tr "modals.promote-owner-confirm.accept") + :on-accept set-owner-fn + :accept-style :primary})))) 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 - (fn [] - (st/emit! (dd/go-to-projects (:default-team-id profile)) - (modal/hide) - (du/fetch-teams))) + (mf/use-fn + (mf/deps profile) + (fn [] + (st/emit! (dd/go-to-projects (:default-team-id profile)) + (modal/hide) + (du/fetch-teams)))) on-error - (fn [{:keys [code] :as error}] - (condp = code + (mf/use-fn + (fn [{:keys [code] :as error}] + (condp = code - :no-enough-members-for-leave - (rx/of (dm/error (tr "errors.team-leave.insufficient-members"))) + :no-enough-members-for-leave + (rx/of (msg/error (tr "errors.team-leave.insufficient-members"))) - :member-does-not-exist - (rx/of (dm/error (tr "errors.team-leave.member-does-not-exists"))) + :member-does-not-exist + (rx/of (msg/error (tr "errors.team-leave.member-does-not-exists"))) - :owner-cant-leave-team - (rx/of (dm/error (tr "errors.team-leave.owner-cant-leave"))) + :owner-cant-leave-team + (rx/of (msg/error (tr "errors.team-leave.owner-cant-leave"))) - (rx/throw error))) + (rx/throw error)))) delete-fn - (fn [] - (st/emit! (dd/delete-team (with-meta team {:on-success on-success - :on-error on-error})))) + (mf/use-fn + (mf/deps team on-success on-error) + (fn [] + (st/emit! (dd/delete-team (with-meta team {:on-success on-success + :on-error on-error}))))) leave-fn - (fn [member-id] - (let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))] - (st/emit! (dd/leave-team (with-meta params - {:on-success on-success - :on-error on-error}))))) + (mf/use-fn + (mf/deps on-success on-error) + (fn [member-id] + (let [params (cond-> {} (uuid? member-id) (assoc :reassign-to member-id))] + (st/emit! (dd/leave-team (with-meta params + {:on-success on-success + :on-error on-error})))))) leave-and-close - (st/emitf (modal/show - {:type :confirm - :title (tr "modals.leave-confirm.title") - :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})) + (mf/use-fn + (mf/deps delete-fn) + (fn [] + (st/emit! (modal/show + {:type :confirm + :title (tr "modals.leave-confirm.title") + :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 - (fn [] - (st/emit! (dd/fetch-team-members) - (modal/show - {:type :leave-and-reassign - :profile profile - :team team - :accept leave-fn}))) + (mf/use-fn + (mf/deps profile team leave-fn) + (fn [] + (st/emit! (dd/fetch-team-members) + (modal/show + {:type :leave-and-reassign + :profile profile + :team team + :accept leave-fn})))) leave - (st/emitf (modal/show - {:type :confirm - :title (tr "modals.leave-confirm.title") - :message (tr "modals.leave-confirm.message") - :accept-label (tr "modals.leave-confirm.accept") - :on-accept leave-fn})) + (mf/use-fn + (mf/deps leave-fn) + (fn [] + (st/emit! (modal/show + {:type :confirm + :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 (= true owner?) change-owner-and-leave :else leave) delete - (st/emitf (modal/show - {:type :confirm - :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}))] + (mf/use-fn + (mf/deps delete-member-fn) + (fn [] + (st/emit! (modal/show + {:type :confirm + :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-field.name @@ -361,7 +395,9 @@ :team team :members-map members-map}]]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; INVITATIONS SECTION +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (mf/defc invitation-role-selector [{:keys [can-invite? role status change-to-admin change-to-editor] :as props}] @@ -418,7 +454,7 @@ :pending) on-success - #(st/emit! (dm/success (tr "notifications.invitation-email-sent")) + #(st/emit! (msg/success (tr "notifications.invitation-email-sent")) (modal/hide) (dd/fetch-team-invitations)) @@ -428,18 +464,18 @@ (cond (and (= :validation type) (= :profile-is-muted code)) - (dm/error (tr "errors.profile-is-muted")) + (msg/error (tr "errors.profile-is-muted")) (and (= :validation type) (= :member-is-muted code)) - (dm/error (tr "errors.member-is-muted")) + (msg/error (tr "errors.member-is-muted")) (and (= :validation type) (= :email-has-permanent-bounces code)) - (dm/error (tr "errors.email-has-permanent-bounces" email)) + (msg/error (tr "errors.email-has-permanent-bounces" email)) :else - (dm/error (tr "errors.generic")))) + (msg/error (tr "errors.generic")))) change-rol (fn [role] @@ -455,20 +491,31 @@ resend-invitation (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 :on-error (partial on-error email)}] - (st/emit! (dd/invite-team-member (with-meta params mdata))) - (st/emit! (dd/fetch-team-invitations))))] + (st/emit! (dd/invite-team-members (with-meta params mdata)) + (dd/fetch-team-invitations))))] [:div.table-row [:div.table-field.mail email] - [:div.table-field.roles [:& invitation-role-selector {:can-invite? can-invite? - :role invitation-role - :status status - :change-to-editor (partial change-rol :editor) - :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.roles + [:& invitation-role-selector + {:can-invite? can-invite? + :role invitation-role + :status status + :change-to-editor (partial change-rol :editor) + :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?] [:div.empty-invitations @@ -513,7 +560,9 @@ [:& invitation-section {:team team :invitations invitations}]]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SETTINGS SECTION +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (mf/defc team-settings-page [{:keys [team] :as props}]