mirror of
https://github.com/penpot/penpot.git
synced 2026-08-01 19:06:18 +00:00
✨ Add nitrate advanced permissions for invite to teams
* ✨ Add nitrate advanced permissions for invite to teams * 📎 Code review
This commit is contained in:
parent
371bd58878
commit
a157ecdc5b
@ -9,7 +9,8 @@
|
|||||||
(def ^:private defaults
|
(def ^:private defaults
|
||||||
{:create-teams "any"
|
{:create-teams "any"
|
||||||
:delete-teams "onlyOwners"
|
:delete-teams "onlyOwners"
|
||||||
:move-teams "always"})
|
:move-teams "always"
|
||||||
|
:send-invitations "ownersAndAdmins"})
|
||||||
|
|
||||||
(defn- can-create-team?
|
(defn- can-create-team?
|
||||||
[{:keys [is-org-owner? permission-value]}]
|
[{:keys [is-org-owner? permission-value]}]
|
||||||
@ -36,13 +37,27 @@
|
|||||||
(true? target-org-same-owner?)
|
(true? target-org-same-owner?)
|
||||||
:else false))
|
:else false))
|
||||||
|
|
||||||
|
(defn- can-invite-to-team?
|
||||||
|
[{:keys [permission-value team-perms]}]
|
||||||
|
(cond
|
||||||
|
(= permission-value "ownersAndAdmins")
|
||||||
|
(or (boolean (:is-owner team-perms))
|
||||||
|
(boolean (:is-admin team-perms)))
|
||||||
|
|
||||||
|
(= permission-value "owners")
|
||||||
|
(boolean (:is-owner team-perms))
|
||||||
|
|
||||||
|
:else false))
|
||||||
|
|
||||||
(def ^:private action-rules
|
(def ^:private action-rules
|
||||||
{:create-team {:permission-key :create-teams
|
{:create-team {:permission-key :create-teams
|
||||||
:check-fn can-create-team?}
|
:check-fn can-create-team?}
|
||||||
:delete-team {:permission-key :delete-teams
|
:delete-team {:permission-key :delete-teams
|
||||||
:check-fn can-delete-team?}
|
:check-fn can-delete-team?}
|
||||||
:move-team {:permission-key :move-teams
|
:move-team {:permission-key :move-teams
|
||||||
:check-fn can-move-team?}})
|
:check-fn can-move-team?}
|
||||||
|
:send-invitations {:permission-key :send-invitations
|
||||||
|
:check-fn can-invite-to-team?}})
|
||||||
|
|
||||||
(defn- normalize-org-permissions
|
(defn- normalize-org-permissions
|
||||||
[org-perms]
|
[org-perms]
|
||||||
@ -67,3 +82,15 @@
|
|||||||
:team-perms team-perms
|
:team-perms team-perms
|
||||||
:allow-org-owner-delete? allow-org-owner-delete?
|
:allow-org-owner-delete? allow-org-owner-delete?
|
||||||
:target-org-same-owner? target-org-same-owner?})))))
|
:target-org-same-owner? target-org-same-owner?})))))
|
||||||
|
|
||||||
|
(defn can-send-invitations?
|
||||||
|
[{:keys [nitrate-enabled? organization profile-id team-permissions]}]
|
||||||
|
(let [in-org? (and nitrate-enabled? organization)]
|
||||||
|
(if in-org?
|
||||||
|
(allowed? :send-invitations
|
||||||
|
{:org-perms {:owner-id (:owner-id organization)
|
||||||
|
:permissions (:permissions organization)}
|
||||||
|
:profile-id profile-id
|
||||||
|
:team-perms team-permissions})
|
||||||
|
(or (boolean (:is-owner team-permissions))
|
||||||
|
(boolean (:is-admin team-permissions))))))
|
||||||
|
|||||||
@ -12,7 +12,8 @@
|
|||||||
(def org-perms
|
(def org-perms
|
||||||
{:owner-id :owner
|
{:owner-id :owner
|
||||||
:permissions {:create-teams "any"
|
:permissions {:create-teams "any"
|
||||||
:delete-teams "onlyOwners"}})
|
:delete-teams "onlyOwners"
|
||||||
|
:send-invitations "ownersAndAdmins"}})
|
||||||
|
|
||||||
(t/deftest unknown-action-is-denied
|
(t/deftest unknown-action-is-denied
|
||||||
(t/is (false? (nitrate-perms/allowed? :unknown
|
(t/is (false? (nitrate-perms/allowed? :unknown
|
||||||
@ -138,3 +139,45 @@
|
|||||||
{:org-perms default-org
|
{:org-perms default-org
|
||||||
:profile-id :member
|
:profile-id :member
|
||||||
:team-perms {}})))))
|
:team-perms {}})))))
|
||||||
|
|
||||||
|
(t/deftest send-invitations-defaults-to-owners-and-admins
|
||||||
|
(let [default-org (assoc org-perms :permissions {:create-teams "any"
|
||||||
|
:delete-teams "onlyOwners"})]
|
||||||
|
(t/is (true? (nitrate-perms/allowed? :send-invitations
|
||||||
|
{:org-perms default-org
|
||||||
|
:profile-id :owner
|
||||||
|
:team-perms {:is-owner true :is-admin false}})))
|
||||||
|
(t/is (true? (nitrate-perms/allowed? :send-invitations
|
||||||
|
{:org-perms default-org
|
||||||
|
:profile-id :member
|
||||||
|
:team-perms {:is-owner false :is-admin true}})))
|
||||||
|
(t/is (false? (nitrate-perms/allowed? :send-invitations
|
||||||
|
{:org-perms default-org
|
||||||
|
:profile-id :member
|
||||||
|
:team-perms {:is-owner false :is-admin false}})))))
|
||||||
|
|
||||||
|
(t/deftest send-invitations-owners-allows-only-team-owners
|
||||||
|
(let [only-owners-org (assoc org-perms :permissions {:create-teams "any"
|
||||||
|
:delete-teams "onlyOwners"
|
||||||
|
:send-invitations "owners"})]
|
||||||
|
(t/is (true? (nitrate-perms/allowed? :send-invitations
|
||||||
|
{:org-perms only-owners-org
|
||||||
|
:profile-id :member
|
||||||
|
:team-perms {:is-owner true :is-admin true}})))
|
||||||
|
(t/is (false? (nitrate-perms/allowed? :send-invitations
|
||||||
|
{:org-perms only-owners-org
|
||||||
|
:profile-id :owner
|
||||||
|
:team-perms {:is-owner false :is-admin false}})))
|
||||||
|
(t/is (false? (nitrate-perms/allowed? :send-invitations
|
||||||
|
{:org-perms only-owners-org
|
||||||
|
:profile-id :member
|
||||||
|
:team-perms {:is-owner false :is-admin true}})))))
|
||||||
|
|
||||||
|
(t/deftest send-invitations-invalid-value-is-denied
|
||||||
|
(let [invalid-org (assoc org-perms :permissions {:create-teams "any"
|
||||||
|
:delete-teams "onlyOwners"
|
||||||
|
:send-invitations "invalid-value"})]
|
||||||
|
(t/is (false? (nitrate-perms/allowed? :send-invitations
|
||||||
|
{:org-perms invalid-org
|
||||||
|
:profile-id :member
|
||||||
|
:team-perms {:is-owner true :is-admin true}})))))
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
(ns app.main.data.nitrate
|
(ns app.main.data.nitrate
|
||||||
(:require
|
(:require
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.types.nitrate-permissions :as nitrate-perms]
|
||||||
[app.common.uri :as u]
|
[app.common.uri :as u]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.main.data.common :as dcm]
|
[app.main.data.common :as dcm]
|
||||||
@ -68,6 +69,14 @@
|
|||||||
[]
|
[]
|
||||||
(st/emit! (rt/nav-raw :href "/admin-console/?action=create-org")))
|
(st/emit! (rt/nav-raw :href "/admin-console/?action=create-org")))
|
||||||
|
|
||||||
|
(defn can-send-invitations?
|
||||||
|
[{:keys [organization profile-id team-permissions]}]
|
||||||
|
(nitrate-perms/can-send-invitations?
|
||||||
|
{:nitrate-enabled? (contains? cf/flags :nitrate)
|
||||||
|
:organization organization
|
||||||
|
:profile-id profile-id
|
||||||
|
:team-permissions team-permissions}))
|
||||||
|
|
||||||
(def go-to-subscription-url (u/join cf/public-uri "#/settings/subscriptions"))
|
(def go-to-subscription-url (u/join cf/public-uri "#/settings/subscriptions"))
|
||||||
|
|
||||||
(defn go-to-nitrate-billing
|
(defn go-to-nitrate-billing
|
||||||
|
|||||||
@ -130,6 +130,33 @@
|
|||||||
:on-accept delete-fn})
|
:on-accept delete-fn})
|
||||||
(modal/show :no-permission-modal {:type :delete-team})))))))))))
|
(modal/show :no-permission-modal {:type :delete-team})))))))))))
|
||||||
|
|
||||||
|
(defn check-and-invite-members
|
||||||
|
"Fetches fresh team data from the server to ensure up-to-date org
|
||||||
|
permissions, then shows invite-members modal or a permission error."
|
||||||
|
[{:keys [team-id origin invite-email]
|
||||||
|
:or {origin :team}}]
|
||||||
|
(ptk/reify ::check-and-invite-members
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [profile-id (dm/get-in state [:profile :id])]
|
||||||
|
(->> (rp/cmd! :get-teams)
|
||||||
|
(rx/mapcat
|
||||||
|
(fn [teams]
|
||||||
|
(let [team (d/seek #(= (:id %) team-id) teams)
|
||||||
|
org (:organization team)
|
||||||
|
can-invite? (nitrate-perms/can-send-invitations?
|
||||||
|
{:nitrate-enabled? (contains? cf/flags :nitrate)
|
||||||
|
:organization org
|
||||||
|
:profile-id profile-id
|
||||||
|
:team-permissions (:permissions team)})]
|
||||||
|
(rx/of (teams-fetched teams)
|
||||||
|
(if can-invite?
|
||||||
|
(modal/show {:type :invite-members
|
||||||
|
:team team
|
||||||
|
:origin origin
|
||||||
|
:invite-email invite-email})
|
||||||
|
(modal/show :no-permission-modal {:type :invite-members})))))))))))
|
||||||
|
|
||||||
;; --- EVENT: fetch-members
|
;; --- EVENT: fetch-members
|
||||||
|
|
||||||
(defn- members-fetched
|
(defn- members-fetched
|
||||||
|
|||||||
@ -13,8 +13,9 @@
|
|||||||
[app.main.data.dashboard :as dd]
|
[app.main.data.dashboard :as dd]
|
||||||
[app.main.data.dashboard.shortcuts :as sc]
|
[app.main.data.dashboard.shortcuts :as sc]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.nitrate :as dnt]
|
||||||
[app.main.data.project :as dpj]
|
[app.main.data.project :as dpj]
|
||||||
|
[app.main.data.team :as dtm]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.dashboard.deleted :as deleted]
|
[app.main.ui.dashboard.deleted :as deleted]
|
||||||
@ -68,9 +69,8 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps team)
|
(mf/deps team)
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (modal/show {:type :invite-members
|
(st/emit! (dtm/check-and-invite-members {:team-id (:id team)
|
||||||
:team team
|
:origin :hero}))))
|
||||||
:origin :hero}))))
|
|
||||||
on-close'
|
on-close'
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps on-close)
|
(mf/deps on-close)
|
||||||
@ -317,8 +317,10 @@
|
|||||||
permisions (:permissions team)
|
permisions (:permissions team)
|
||||||
|
|
||||||
can-edit (:can-edit permisions)
|
can-edit (:can-edit permisions)
|
||||||
can-invite (or (:is-owner permisions)
|
can-invite (dnt/can-send-invitations?
|
||||||
(:is-admin permisions))
|
{:organization (:organization team)
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:team-permissions permisions})
|
||||||
|
|
||||||
show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true))
|
show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true))
|
||||||
show-team-hero? (deref show-team-hero*)
|
show-team-hero? (deref show-team-hero*)
|
||||||
|
|||||||
@ -73,7 +73,7 @@
|
|||||||
(mf/defc header
|
(mf/defc header
|
||||||
{::mf/wrap [mf/memo]
|
{::mf/wrap [mf/memo]
|
||||||
::mf/props :obj}
|
::mf/props :obj}
|
||||||
[{:keys [section team]}]
|
[{:keys [section team profile]}]
|
||||||
(let [on-nav-members (mf/use-fn #(st/emit! (dcm/go-to-dashboard-members)))
|
(let [on-nav-members (mf/use-fn #(st/emit! (dcm/go-to-dashboard-members)))
|
||||||
on-nav-settings (mf/use-fn #(st/emit! (dcm/go-to-dashboard-settings)))
|
on-nav-settings (mf/use-fn #(st/emit! (dcm/go-to-dashboard-settings)))
|
||||||
on-nav-invitations (mf/use-fn #(st/emit! (dcm/go-to-dashboard-invitations)))
|
on-nav-invitations (mf/use-fn #(st/emit! (dcm/go-to-dashboard-invitations)))
|
||||||
@ -87,16 +87,19 @@
|
|||||||
invitations-section? (= section :dashboard-team-invitations)
|
invitations-section? (= section :dashboard-team-invitations)
|
||||||
webhooks-section? (= section :dashboard-team-webhooks)
|
webhooks-section? (= section :dashboard-team-webhooks)
|
||||||
permissions (:permissions team)
|
permissions (:permissions team)
|
||||||
|
can-invite? (dnt/can-send-invitations?
|
||||||
|
{:organization (:organization team)
|
||||||
|
:profile-id (:id profile)
|
||||||
|
:team-permissions permissions})
|
||||||
invitations (:invitations team)
|
invitations (:invitations team)
|
||||||
|
|
||||||
on-invite-member
|
on-invite-member
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps team invite-email)
|
(mf/deps team invite-email)
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (modal/show {:type :invite-members
|
(st/emit! (dtm/check-and-invite-members {:team-id (:id team)
|
||||||
:team team
|
:origin :team
|
||||||
:origin :team
|
:invite-email invite-email}))))]
|
||||||
:invite-email invite-email}))))]
|
|
||||||
|
|
||||||
(mf/with-effect [team invite-email]
|
(mf/with-effect [team invite-email]
|
||||||
(when invite-email
|
(when invite-email
|
||||||
@ -122,9 +125,11 @@
|
|||||||
[:li {:class (when settings-section? (stl/css :active))}
|
[:li {:class (when settings-section? (stl/css :active))}
|
||||||
[:a {:on-click on-nav-settings} (tr "labels.settings")]]]]
|
[:a {:on-click on-nav-settings} (tr "labels.settings")]]]]
|
||||||
[:div {:class (stl/css :dashboard-buttons)}
|
[:div {:class (stl/css :dashboard-buttons)}
|
||||||
(if (and (or invitations-section? members-section?) (:is-admin permissions) (not-empty invitations))
|
(if (and (or invitations-section? members-section?) (not-empty invitations))
|
||||||
[:a
|
[:button
|
||||||
{:class (stl/css :btn-secondary :btn-small)
|
{:class (stl/css :btn-secondary :btn-small)
|
||||||
|
:type "button"
|
||||||
|
:disabled (not can-invite?)
|
||||||
:on-click on-invite-member
|
:on-click on-invite-member
|
||||||
:data-testid "invite-member"}
|
:data-testid "invite-member"}
|
||||||
(tr "dashboard.invite-profile")]
|
(tr "dashboard.invite-profile")]
|
||||||
@ -551,7 +556,9 @@
|
|||||||
(st/emit! (dtm/fetch-members)))
|
(st/emit! (dtm/fetch-members)))
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
[:& header {:section :dashboard-team-members :team team}]
|
[:& header {:section :dashboard-team-members
|
||||||
|
:team team
|
||||||
|
:profile profile}]
|
||||||
[:section {:class (stl/css :dashboard-container :dashboard-team-members)}
|
[:section {:class (stl/css :dashboard-container :dashboard-team-members)}
|
||||||
|
|
||||||
[:> team-members*
|
[:> team-members*
|
||||||
@ -685,11 +692,12 @@
|
|||||||
|
|
||||||
on-change
|
on-change
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps on-select-change)
|
(mf/deps can-invite on-select-change)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(let [email (-> (dom/get-current-target event)
|
(when can-invite
|
||||||
(dom/get-data "attr"))]
|
(let [email (-> (dom/get-current-target event)
|
||||||
(on-select-change email))))
|
(dom/get-data "attr"))]
|
||||||
|
(on-select-change email)))))
|
||||||
|
|
||||||
on-change-role
|
on-change-role
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -701,19 +709,21 @@
|
|||||||
|
|
||||||
[:div {:class (stl/css :table-row :table-row-invitations)}
|
[:div {:class (stl/css :table-row :table-row-invitations)}
|
||||||
[:div {:class (stl/css :table-field :field-email)}
|
[:div {:class (stl/css :table-field :field-email)}
|
||||||
[:div {:class (stl/css :input-wrapper)}
|
(if can-invite
|
||||||
[:label
|
[:div {:class (stl/css :input-wrapper)}
|
||||||
[:span {:class (stl/css-case :input-checkbox true
|
[:label
|
||||||
:global/checked (is-selected? email))}
|
[:span {:class (stl/css-case :input-checkbox true
|
||||||
deprecated-icon/status-tick]
|
:global/checked (is-selected? email))}
|
||||||
|
deprecated-icon/status-tick]
|
||||||
|
|
||||||
[:input {:type "checkbox"
|
[:input {:type "checkbox"
|
||||||
:id (dm/str "email-" email)
|
:id (dm/str "email-" email)
|
||||||
:data-attr email
|
:data-attr email
|
||||||
:value email
|
:value email
|
||||||
:checked (is-selected? email)
|
:checked (is-selected? email)
|
||||||
:on-change on-change}]
|
:on-change on-change}]
|
||||||
email]]]
|
email]]
|
||||||
|
[:div email])]
|
||||||
|
|
||||||
[:div {:class (stl/css :table-field :field-roles)}
|
[:div {:class (stl/css :table-field :field-roles)}
|
||||||
[:> invitation-role-selector*
|
[:> invitation-role-selector*
|
||||||
@ -740,20 +750,20 @@
|
|||||||
on-invite-member (mf/use-fn
|
on-invite-member (mf/use-fn
|
||||||
(mf/deps team invite-email)
|
(mf/deps team invite-email)
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (modal/show {:type :invite-members
|
(st/emit! (dtm/check-and-invite-members {:team-id (:id team)
|
||||||
:team team
|
:origin :team
|
||||||
:origin :team
|
:invite-email invite-email}))))]
|
||||||
:invite-email invite-email}))))]
|
|
||||||
[:div {:class (stl/css :empty-invitations)}
|
[:div {:class (stl/css :empty-invitations)}
|
||||||
[:span (tr "labels.no-invitations")]
|
[:span (tr "labels.no-invitations")]
|
||||||
(when ^boolean can-invite
|
(if ^boolean can-invite
|
||||||
[[:span (tr "labels.no-invitations-gather-people")]
|
[[:span (tr "labels.no-invitations-gather-people")]
|
||||||
[:a
|
[:a
|
||||||
{:class (stl/css :btn-empty-invitations)
|
{:class (stl/css :btn-empty-invitations)
|
||||||
:on-click on-invite-member
|
:on-click on-invite-member
|
||||||
:data-testid "invite-member"}
|
:data-testid "invite-member"}
|
||||||
(tr "dashboard.invite-profile")]
|
(tr "dashboard.invite-profile")]
|
||||||
[:div {:class (stl/css :blank-space)}]])]))
|
[:div {:class (stl/css :blank-space)}]]
|
||||||
|
[:span (tr "dashboard.invitations.no-permission")])]))
|
||||||
|
|
||||||
(mf/defc invitation-modal
|
(mf/defc invitation-modal
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
@ -880,15 +890,16 @@
|
|||||||
|
|
||||||
(mf/defc invitation-section*
|
(mf/defc invitation-section*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [team]}]
|
[{:keys [team profile]}]
|
||||||
(let [permissions (get team :permissions)
|
(let [permissions (get team :permissions)
|
||||||
invitations (mf/use-state (get team :invitations))
|
invitations (mf/use-state (get team :invitations))
|
||||||
|
|
||||||
team-id (get team :id)
|
team-id (get team :id)
|
||||||
|
|
||||||
owner? (get permissions :is-owner)
|
can-invite? (dnt/can-send-invitations?
|
||||||
admin? (get permissions :is-admin)
|
{:organization (:organization team)
|
||||||
can-invite? (or owner? admin?)
|
:profile-id (:id profile)
|
||||||
|
:team-permissions permissions})
|
||||||
|
|
||||||
selected (mf/use-state #{})
|
selected (mf/use-state #{})
|
||||||
|
|
||||||
@ -900,11 +911,12 @@
|
|||||||
|
|
||||||
on-select-change
|
on-select-change
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps selected)
|
(mf/deps can-invite? selected)
|
||||||
(fn [email]
|
(fn [email]
|
||||||
(if (contains? @selected email)
|
(when can-invite?
|
||||||
(swap! selected disj email)
|
(if (contains? @selected email)
|
||||||
(swap! selected conj email))))
|
(swap! selected disj email)
|
||||||
|
(swap! selected conj email)))))
|
||||||
|
|
||||||
on-confirm-delete
|
on-confirm-delete
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -1014,7 +1026,12 @@
|
|||||||
(reset! sort-state {:field nil :direction :asc}))
|
(reset! sort-state {:field nil :direction :asc}))
|
||||||
|
|
||||||
[:div {:class (stl/css :invitations)}
|
[:div {:class (stl/css :invitations)}
|
||||||
(when (> (count @selected) 0)
|
(when (and (not can-invite?)
|
||||||
|
(seq @invitations))
|
||||||
|
[:div {:class (stl/css :empty-invitations)}
|
||||||
|
[:span (tr "dashboard.invitations.no-permission")]])
|
||||||
|
(when (and can-invite?
|
||||||
|
(> (count @selected) 0))
|
||||||
[:*
|
[:*
|
||||||
[:div {:class (stl/css :invitations-actions)}
|
[:div {:class (stl/css :invitations-actions)}
|
||||||
[:div
|
[:div
|
||||||
@ -1075,10 +1092,11 @@
|
|||||||
|
|
||||||
[:*
|
[:*
|
||||||
[:& header {:section :dashboard-team-invitations
|
[:& header {:section :dashboard-team-invitations
|
||||||
:team team}]
|
:team team
|
||||||
|
:profile profile}]
|
||||||
[:section {:class (stl/css :dashboard-team-invitations)}
|
[:section {:class (stl/css :dashboard-team-invitations)}
|
||||||
|
|
||||||
[:> invitation-section* {:team team}]
|
[:> invitation-section* {:team team :profile profile}]
|
||||||
|
|
||||||
(when (and (contains? cfg/flags :subscriptions)
|
(when (and (contains? cfg/flags :subscriptions)
|
||||||
(show-subscription-members-banner? team profile))
|
(show-subscription-members-banner? team profile))
|
||||||
|
|||||||
@ -9,8 +9,8 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.main.data.common :as dcm]
|
[app.main.data.common :as dcm]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.modal :as modal]
|
|
||||||
[app.main.data.shortcuts :as scd]
|
[app.main.data.shortcuts :as scd]
|
||||||
|
[app.main.data.team :as dtm]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace :as dw]
|
||||||
[app.main.data.workspace.drawing.common :as dwc]
|
[app.main.data.workspace.drawing.common :as dwc]
|
||||||
[app.main.data.workspace.history :as dwh]
|
[app.main.data.workspace.history :as dwh]
|
||||||
@ -186,9 +186,8 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps team)
|
(mf/deps team)
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (modal/show {:type :invite-members
|
(st/emit! (dtm/check-and-invite-members {:team-id (:id team)
|
||||||
:team team
|
:origin :workspace}))))]
|
||||||
:origin :workspace}))))]
|
|
||||||
|
|
||||||
(mf/with-effect [editing?]
|
(mf/with-effect [editing?]
|
||||||
(when ^boolean editing?
|
(when ^boolean editing?
|
||||||
|
|||||||
@ -9550,3 +9550,7 @@ msgstr "You are not allowed to delete teams that are part of %s organization. If
|
|||||||
|
|
||||||
msgid "dashboard.no-permission-move-team.message"
|
msgid "dashboard.no-permission-move-team.message"
|
||||||
msgstr "You are not allowed to move teams that are part of %s organization. If you need more information, contact the owner."
|
msgstr "You are not allowed to move teams that are part of %s organization. If you need more information, contact the owner."
|
||||||
|
|
||||||
|
msgid "dashboard.invitations.no-permission"
|
||||||
|
msgstr "You do not have permission to invite people to join or to edit or delete invitations in this team."
|
||||||
|
|
||||||
|
|||||||
@ -9237,3 +9237,7 @@ msgstr "No tienes permiso para eliminar equipos que pertenecen a la organizació
|
|||||||
|
|
||||||
msgid "dashboard.no-permission-move-team.message"
|
msgid "dashboard.no-permission-move-team.message"
|
||||||
msgstr "No tienes permiso para mover equipos que son parte de la organización %s. Si necesitas más información, contacta con el propietario."
|
msgstr "No tienes permiso para mover equipos que son parte de la organización %s. Si necesitas más información, contacta con el propietario."
|
||||||
|
|
||||||
|
msgid "dashboard.invitations.no-permission"
|
||||||
|
msgstr "No tienes permiso para invitar a personas a unirse ni para editar o eliminar invitaciones en este equipo."
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user