mirror of
https://github.com/penpot/penpot.git
synced 2026-05-08 17:48:39 +00:00
✨ Add Nitrate advanced permissions delete (#9416)
* ✨ Add Nitrate advanced permissions delete * 📎 Code review
This commit is contained in:
parent
d84685c0cb
commit
7c5fa038c1
@ -360,7 +360,7 @@
|
||||
[:map
|
||||
[:organization-id ::sm/uuid]
|
||||
[:owner-id ::sm/uuid]
|
||||
[:create-teams [:enum "any" "onlyMe"]]]
|
||||
[:permissions [:map-of :keyword :string]]]
|
||||
params)))
|
||||
|
||||
(defn- redeem-activation-code-api
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.nitrate-permissions :as nitrate-perms]
|
||||
[app.config :as cf]
|
||||
[app.db :as db]
|
||||
[app.nitrate :as nitrate]
|
||||
@ -313,12 +314,12 @@
|
||||
(ex/raise :type :validation
|
||||
:code :not-allowed
|
||||
:hint "Unable to verify organization permissions")
|
||||
(let [create-perm (:create-teams org-perms)
|
||||
is-owner? (= profile-id (:owner-id org-perms))]
|
||||
(when (and (= create-perm "onlyMe") (not is-owner?))
|
||||
(ex/raise :type :validation
|
||||
:code :not-allowed
|
||||
:hint "You are not allowed to add teams in this organization"))))))
|
||||
(when-not (nitrate-perms/allowed? :create-team
|
||||
{:org-perms org-perms
|
||||
:profile-id profile-id})
|
||||
(ex/raise :type :validation
|
||||
:code :not-allowed
|
||||
:hint "You are not allowed to add teams in this organization")))))
|
||||
|
||||
(let [team-members (db/query cfg :team-profile-rel {:team-id team-id})]
|
||||
;; Add teammates to the org if needed
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
[app.common.features :as cfeat]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.time :as ct]
|
||||
[app.common.types.nitrate-permissions :as nitrate-perms]
|
||||
[app.common.types.team :as types.team]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.config :as cf]
|
||||
@ -520,12 +521,12 @@
|
||||
(ex/raise :type :validation
|
||||
:code :not-allowed
|
||||
:hint "Unable to verify organization permissions")
|
||||
(let [create-perm (:create-teams org-perms)
|
||||
is-owner? (= profile-id (:owner-id org-perms))]
|
||||
(when (and (= create-perm "onlyMe") (not is-owner?))
|
||||
(ex/raise :type :validation
|
||||
:code :not-allowed
|
||||
:hint "You are not allowed to create teams in this organization"))))))
|
||||
(when-not (nitrate-perms/allowed? :create-team
|
||||
{:org-perms org-perms
|
||||
:profile-id profile-id})
|
||||
(ex/raise :type :validation
|
||||
:code :not-allowed
|
||||
:hint "You are not allowed to create teams in this organization")))))
|
||||
|
||||
(let [features (-> (cfeat/get-enabled-features cf/flags)
|
||||
(set/difference cfeat/frontend-only-features)
|
||||
@ -773,20 +774,35 @@
|
||||
|
||||
(defn delete-team
|
||||
"Mark a team for deletion"
|
||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id team-id]}]
|
||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id team-id] :as params}]
|
||||
|
||||
(let [team (get-team conn :profile-id profile-id :team-id team-id)
|
||||
team (if (contains? cf/flags :nitrate)
|
||||
(nitrate/add-org-info-to-team cfg team params)
|
||||
team)
|
||||
perms (get team :permissions)]
|
||||
|
||||
(when-not (:is-owner perms)
|
||||
(ex/raise :type :validation
|
||||
:code :only-owner-can-delete-team))
|
||||
|
||||
(when (:is-default team)
|
||||
(ex/raise :type :validation
|
||||
:code :non-deletable-team
|
||||
:hint "impossible to delete default team"))
|
||||
|
||||
;; Check delete permissions based on organization settings.
|
||||
;; For non-org teams or when nitrate is disabled, only owners can delete.
|
||||
(if (and (:organization-id team) (contains? cf/flags :nitrate))
|
||||
(let [org-perms {:owner-id (:organization-owner-id team)
|
||||
:permissions (:organization-permissions team)}]
|
||||
(when-not (nitrate-perms/allowed? :delete-team
|
||||
{:org-perms org-perms
|
||||
:profile-id profile-id
|
||||
:team-perms perms})
|
||||
(ex/raise :type :validation
|
||||
:code :not-allowed
|
||||
:hint "You are not allowed to delete teams in this organization")))
|
||||
(when-not (:is-owner perms)
|
||||
(ex/raise :type :validation
|
||||
:code :only-owner-can-delete-team)))
|
||||
|
||||
(let [delay (ldel/get-deletion-delay team)
|
||||
team (db/update! conn :team
|
||||
{:deleted-at (ct/in-future delay)}
|
||||
|
||||
41
common/src/app/common/types/nitrate_permissions.cljc
Normal file
41
common/src/app/common/types/nitrate_permissions.cljc
Normal file
@ -0,0 +1,41 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns app.common.types.nitrate-permissions)
|
||||
|
||||
(def ^:private defaults
|
||||
{:create-teams "any"
|
||||
:delete-teams "ownersAndAdmins"})
|
||||
|
||||
(def ^:private action-rules
|
||||
{:create-team {:permission-key :create-teams
|
||||
:allowed-values #{"any"}
|
||||
:requires-admin? false}
|
||||
:delete-team {:permission-key :delete-teams
|
||||
:allowed-values #{"ownersAndAdmins"}
|
||||
:requires-admin? true}})
|
||||
|
||||
(defn- normalize-org-permissions
|
||||
[org-perms]
|
||||
(merge defaults (or (:permissions org-perms) {})))
|
||||
|
||||
(defn- owner?
|
||||
[org-perms profile-id]
|
||||
(= profile-id (:owner-id org-perms)))
|
||||
|
||||
(defn allowed?
|
||||
"Returns true only for explicitly allowed actions (fail-closed)."
|
||||
[action {:keys [org-perms profile-id team-perms]}]
|
||||
(let [{:keys [permission-key allowed-values requires-admin?] :as rule}
|
||||
(get action-rules action)
|
||||
permissions (normalize-org-permissions org-perms)
|
||||
is-owner? (owner? org-perms profile-id)
|
||||
is-admin? (boolean (:is-admin team-perms))]
|
||||
(cond
|
||||
(nil? rule) false
|
||||
is-owner? true
|
||||
(and requires-admin? (not is-admin?)) false
|
||||
:else (contains? allowed-values (get permissions permission-key)))))
|
||||
@ -16,7 +16,10 @@
|
||||
[:owner-id ::sm/uuid]
|
||||
[:avatar-bg-url ::sm/uri]
|
||||
[:logo-id {:optional true} [:maybe ::sm/uuid]]
|
||||
[:create-teams {:optional true} [:maybe [:enum "any" "onlyMe"]]]])
|
||||
[:permissions {:optional true}
|
||||
[:maybe [:map
|
||||
[:create-teams {:optional true} [:maybe [:enum "any" "onlyMe"]]]
|
||||
[:delete-teams {:optional true} [:maybe [:enum "ownersAndAdmins" "onlyOwners"]]]]]]])
|
||||
|
||||
|
||||
(def schema:team-with-organization
|
||||
@ -33,7 +36,7 @@
|
||||
[:slug :organization-slug]
|
||||
[:avatar-bg-url :organization-avatar-bg-url]
|
||||
[:owner-id :organization-owner-id]
|
||||
[:create-teams :organization-create-teams]])
|
||||
[:permissions :organization-permissions]])
|
||||
|
||||
(defn apply-organization
|
||||
"Updates a team map with organization fields sourced from org.
|
||||
|
||||
@ -67,6 +67,7 @@
|
||||
[common-tests.types.container-test]
|
||||
[common-tests.types.fill-test]
|
||||
[common-tests.types.modifiers-test]
|
||||
[common-tests.types.nitrate-permissions-test]
|
||||
[common-tests.types.objects-map-test]
|
||||
[common-tests.types.path-data-test]
|
||||
[common-tests.types.shape-decode-encode-test]
|
||||
@ -147,6 +148,7 @@
|
||||
'common-tests.types.container-test
|
||||
'common-tests.types.fill-test
|
||||
'common-tests.types.modifiers-test
|
||||
'common-tests.types.nitrate-permissions-test
|
||||
'common-tests.types.objects-map-test
|
||||
'common-tests.types.path-data-test
|
||||
'common-tests.types.shape-decode-encode-test
|
||||
|
||||
57
common/test/common_tests/types/nitrate_permissions_test.cljc
Normal file
57
common/test/common_tests/types/nitrate_permissions_test.cljc
Normal file
@ -0,0 +1,57 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC
|
||||
|
||||
(ns common-tests.types.nitrate-permissions-test
|
||||
(:require
|
||||
[app.common.types.nitrate-permissions :as nitrate-perms]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(def org-perms
|
||||
{:owner-id :owner
|
||||
:permissions {:create-teams "any"
|
||||
:delete-teams "ownersAndAdmins"}})
|
||||
|
||||
(t/deftest unknown-action-is-denied
|
||||
(t/is (false? (nitrate-perms/allowed? :unknown
|
||||
{:org-perms org-perms
|
||||
:profile-id :member
|
||||
:team-perms {:is-admin true}}))))
|
||||
|
||||
(t/deftest owner-is-always-allowed
|
||||
(t/is (true? (nitrate-perms/allowed? :create-team
|
||||
{:org-perms org-perms
|
||||
:profile-id :owner
|
||||
:team-perms {:is-admin false}})))
|
||||
(t/is (true? (nitrate-perms/allowed? :delete-team
|
||||
{:org-perms org-perms
|
||||
:profile-id :owner
|
||||
:team-perms {:is-admin false}}))))
|
||||
|
||||
(t/deftest create-team-permission-rules
|
||||
(t/is (true? (nitrate-perms/allowed? :create-team
|
||||
{:org-perms org-perms
|
||||
:profile-id :member
|
||||
:team-perms {:is-admin false}})))
|
||||
(t/is (false? (nitrate-perms/allowed? :create-team
|
||||
{:org-perms (assoc org-perms :permissions {:create-teams "none"
|
||||
:delete-teams "ownersAndAdmins"})
|
||||
:profile-id :member
|
||||
:team-perms {:is-admin false}}))))
|
||||
|
||||
(t/deftest delete-team-requires-admin-and-policy
|
||||
(t/is (false? (nitrate-perms/allowed? :delete-team
|
||||
{:org-perms org-perms
|
||||
:profile-id :member
|
||||
:team-perms {:is-admin false}})))
|
||||
(t/is (true? (nitrate-perms/allowed? :delete-team
|
||||
{:org-perms org-perms
|
||||
:profile-id :member
|
||||
:team-perms {:is-admin true}})))
|
||||
(t/is (false? (nitrate-perms/allowed? :delete-team
|
||||
{:org-perms (assoc org-perms :permissions {:create-teams "any"
|
||||
:delete-teams "onlyOwners"})
|
||||
:profile-id :member
|
||||
:team-perms {:is-admin true}}))))
|
||||
@ -189,7 +189,7 @@
|
||||
(let [all-orgs (map dt/team->organization
|
||||
(filter #(and (:is-default %) (:organization-id %)) teams))
|
||||
orgs (filter (fn [org]
|
||||
(let [perm (:create-teams org)
|
||||
(let [perm (get-in org [:permissions :create-teams])
|
||||
is-own? (= profile-id (:owner-id org))]
|
||||
(or (= perm "any") is-own?))) all-orgs)
|
||||
team (first (filter #(= (:id %) team-id) teams))
|
||||
@ -198,7 +198,7 @@
|
||||
:organization-id organization-id})))]
|
||||
(rx/of (dt/teams-fetched teams)
|
||||
(if (empty? orgs)
|
||||
(modal/show :no-org-allows-create-team {})
|
||||
(modal/show :no-permission-modal {:type :no-orgs-create})
|
||||
(let [has-filtered? (< (count orgs) (count all-orgs))
|
||||
extra-props (when has-filtered?
|
||||
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
||||
@ -228,7 +228,7 @@
|
||||
(let [all-orgs (map dt/team->organization
|
||||
(filter #(and (:is-default %) (:organization-id %)) teams))
|
||||
orgs (filter (fn [org]
|
||||
(let [perm (:create-teams org)
|
||||
(let [perm (get-in org [:permissions :create-teams])
|
||||
is-own? (= profile-id (:owner-id org))]
|
||||
(or (= perm "any") is-own?))) all-orgs)
|
||||
team (first (filter #(= (:id %) team-id) teams))
|
||||
@ -237,7 +237,7 @@
|
||||
:organization-id organization-id})))]
|
||||
(rx/of (dt/teams-fetched teams)
|
||||
(if (empty? orgs)
|
||||
(modal/show :no-org-allows-create-team {})
|
||||
(modal/show :no-permission-modal {:type :no-orgs-change})
|
||||
(let [has-filtered? (< (count orgs) (count all-orgs))
|
||||
extra-props (when has-filtered?
|
||||
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.logging :as log]
|
||||
[app.common.schema :as sm]
|
||||
[app.common.types.nitrate-permissions :as nitrate-perms]
|
||||
[app.common.types.team :as ctt]
|
||||
[app.common.uri :as u]
|
||||
[app.config :as cf]
|
||||
@ -22,6 +23,7 @@
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.util.clipboard :as clipboard]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.storage :as storage]
|
||||
[beicon.v2.core :as rx]
|
||||
[clojure.string :as str]
|
||||
@ -52,7 +54,7 @@
|
||||
:organization-slug
|
||||
:organization-owner-id
|
||||
:organization-avatar-bg-url
|
||||
:organization-create-teams))]
|
||||
:organization-permissions))]
|
||||
(update state :teams assoc id team-updated)))
|
||||
state
|
||||
teams)))))
|
||||
@ -78,16 +80,55 @@
|
||||
(fn [teams]
|
||||
(let [team (d/seek #(= (:id %) team-id) teams)
|
||||
in-org? (and (contains? cf/flags :nitrate) (:organization-id team))
|
||||
create-perm (:organization-create-teams team)
|
||||
is-owner? (= profile-id (:organization-owner-id team))
|
||||
can-create? (or (not in-org?) (= create-perm "any") is-owner?)]
|
||||
can-create? (if in-org?
|
||||
(nitrate-perms/allowed? :create-team
|
||||
{:org-perms {:owner-id (:organization-owner-id team)
|
||||
:permissions (:organization-permissions team)}
|
||||
:profile-id profile-id
|
||||
:team-perms (:permissions team)})
|
||||
true)]
|
||||
(rx/of (teams-fetched teams)
|
||||
(if can-create?
|
||||
(modal/show :team-form (if in-org?
|
||||
{:organization-id (:organization-id team)
|
||||
:organization-name (:organization-name team)}
|
||||
{}))
|
||||
(modal/show :no-permission-create-team {:organization-name (:organization-name team)})))))))))))
|
||||
(modal/show :no-permission-modal {:type :create-team
|
||||
:organization-name (:organization-name team)})))))))))))
|
||||
|
||||
(defn check-and-delete-team
|
||||
"Fetches fresh team data from the server to ensure up-to-date org
|
||||
permissions, then shows the confirmation modal or a no-permission modal."
|
||||
[{:keys [team-id delete-fn]}]
|
||||
(ptk/reify ::check-and-delete-team
|
||||
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)
|
||||
in-org? (and (contains? cf/flags :nitrate) (:organization-id team))
|
||||
can-delete? (if in-org?
|
||||
(nitrate-perms/allowed? :delete-team
|
||||
{:org-perms {:owner-id (:organization-owner-id team)
|
||||
:permissions (:organization-permissions team)}
|
||||
:profile-id profile-id
|
||||
:team-perms (:permissions team)})
|
||||
(boolean (dm/get-in team [:permissions :is-owner])))
|
||||
message (if in-org?
|
||||
(tr "modals.delete-org-team-confirm.message" (:organization-name team))
|
||||
(tr "modals.delete-team-confirm.message"))]
|
||||
(rx/of (teams-fetched teams)
|
||||
(if can-delete?
|
||||
(modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-team-confirm.title")
|
||||
:message message
|
||||
:accept-label (tr "modals.delete-team-confirm.accept")
|
||||
:on-accept delete-fn})
|
||||
(modal/show :no-permission-modal {:type :delete-team
|
||||
:organization-name (:organization-name team)})))))))))))
|
||||
|
||||
;; --- EVENT: fetch-members
|
||||
|
||||
@ -625,5 +666,5 @@
|
||||
:custom-photo (:organization-custom-photo team)
|
||||
:name (:organization-name team)
|
||||
:default-team-id (:id team)
|
||||
:create-teams (:organization-create-teams team)})
|
||||
:permissions (:organization-permissions team)})
|
||||
|
||||
|
||||
@ -468,6 +468,10 @@
|
||||
:owner-cant-leave-team
|
||||
(rx/of (ntf/error (tr "errors.team-leave.owner-cant-leave")))
|
||||
|
||||
:not-allowed
|
||||
(rx/of (modal/show :no-permission-modal {:type :delete-team
|
||||
:organization-name (:organization-name team)}))
|
||||
|
||||
(rx/throw error))))
|
||||
|
||||
leave-fn
|
||||
@ -526,17 +530,9 @@
|
||||
(mf/use-fn
|
||||
(mf/deps team delete-fn)
|
||||
(fn []
|
||||
(let [is-org-team? (some? (:organization-id team))
|
||||
message (if is-org-team?
|
||||
(tr "modals.delete-org-team-confirm.message" (:organization-name team))
|
||||
(tr "modals.delete-team-confirm.message"))]
|
||||
(st/emit!
|
||||
(modal/show
|
||||
{:type :confirm
|
||||
:title (tr "modals.delete-team-confirm.title")
|
||||
:message message
|
||||
:accept-label (tr "modals.delete-team-confirm.accept")
|
||||
:on-accept delete-fn})))))]
|
||||
;; Fetch fresh team data to check current permission, then show appropriate modal
|
||||
(st/emit! (dtm/check-and-delete-team {:team-id (:id team)
|
||||
:delete-fn delete-fn}))))]
|
||||
[:> dropdown-menu* props
|
||||
|
||||
[:> dropdown-menu-item* {:on-click go-members
|
||||
@ -583,11 +579,19 @@
|
||||
:class (stl/css :team-options-item)}
|
||||
(tr "dashboard.leave-team")])
|
||||
|
||||
(when (get-in team [:permissions :is-owner])
|
||||
[:> dropdown-menu-item* {:on-click on-delete-clicked
|
||||
:class (stl/css :team-options-item :warning)
|
||||
:data-testid "delete-team"}
|
||||
(tr "dashboard.delete-team")])]))
|
||||
(let [is-owner? (get-in team [:permissions :is-owner])
|
||||
is-admin? (get-in team [:permissions :is-admin])
|
||||
is-org-team? (some? (:organization-id team))
|
||||
in-org? (and (contains? cf/flags :nitrate) is-org-team?)
|
||||
show-delete? (if in-org?
|
||||
(or is-owner? is-admin?)
|
||||
is-owner?)]
|
||||
|
||||
(when show-delete?
|
||||
[:> dropdown-menu-item* {:on-click on-delete-clicked
|
||||
:class (stl/css :team-options-item :warning)
|
||||
:data-testid "delete-team"}
|
||||
(tr "dashboard.delete-team")]))]))
|
||||
|
||||
(mf/defc org-options-dropdown*
|
||||
{::mf/private true}
|
||||
|
||||
@ -1381,7 +1381,7 @@
|
||||
organizations (mf/with-memo [all-organizations profile-id]
|
||||
(->> all-organizations
|
||||
(filter (fn [org]
|
||||
(let [perm (:create-teams org)
|
||||
(let [perm (get-in org [:permissions :create-teams])
|
||||
is-owner? (= profile-id (:owner-id org))]
|
||||
(or (= perm "any") is-owner?))))))
|
||||
|
||||
|
||||
@ -46,7 +46,8 @@
|
||||
(let [id (get-in @form [:clean-data :id])
|
||||
code (-> response ex-data :code)]
|
||||
(if (= code :not-allowed)
|
||||
(rx/of (modal/show :no-permission-create-team {:organization-name organization-name}))
|
||||
(rx/of (modal/show :no-permission-modal {:type :create-team
|
||||
:organization-name organization-name}))
|
||||
(if id
|
||||
(rx/of (ntf/error "Error on updating team."))
|
||||
(rx/of (ntf/error "Error on creating team."))))))
|
||||
@ -139,31 +140,25 @@
|
||||
:class (stl/css :accept-btn)}]]]]]]))
|
||||
|
||||
|
||||
(mf/defc no-permission-create-team-modal*
|
||||
(mf/defc no-permission-modal*
|
||||
"Generic modal for displaying permission-related messages based on error type"
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :no-permission-create-team}
|
||||
[{:keys [organization-name]}]
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h2 {:class (stl/css :modal-title)}
|
||||
(tr "labels.create-team")]
|
||||
[:button {:class (stl/css :modal-close-btn)
|
||||
:on-click modal/hide!} deprecated-icon/close]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div (tr "dashboard.no-permission-create-team.message" organization-name)]]]])
|
||||
|
||||
|
||||
(mf/defc no-org-allows-create-team-modal*
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :no-org-allows-create-team}
|
||||
[_props]
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h2 {:class (stl/css :modal-title)}
|
||||
(tr "dashboard.select-org-modal.title")]
|
||||
[:button {:class (stl/css :modal-close-btn)
|
||||
:on-click modal/hide!} deprecated-icon/close]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div (tr "dashboard.no-org-allows-create-team.message")]]]])
|
||||
::mf/register-as :no-permission-modal}
|
||||
[{:keys [type organization-name]}]
|
||||
(let [[title message] (case type
|
||||
:create-team [(tr "labels.create-team")
|
||||
(tr "dashboard.no-permission-create-team.message" organization-name)]
|
||||
:delete-team [(tr "dashboard.delete-team")
|
||||
(tr "dashboard.no-permission-delete-team.message" organization-name)]
|
||||
:no-orgs-create [(tr "dashboard.select-org-modal.title")
|
||||
(tr "dashboard.no-org-allows-create-team.message")]
|
||||
:no-orgs-change [(tr "dashboard.change-org-modal.title")
|
||||
(tr "dashboard.no-org-allows-create-team.message")])]
|
||||
[:div {:class (stl/css :modal-overlay)}
|
||||
[:div {:class (stl/css :modal-container)}
|
||||
[:div {:class (stl/css :modal-header)}
|
||||
[:h2 {:class (stl/css :modal-title)} title]
|
||||
[:button {:class (stl/css :modal-close-btn)
|
||||
:on-click modal/hide!} deprecated-icon/close]]
|
||||
[:div {:class (stl/css :modal-content)}
|
||||
[:div message]]]]))
|
||||
|
||||
@ -9423,3 +9423,6 @@ msgstr "You don't have permission to add teams to any of your organizations."
|
||||
|
||||
msgid "dashboard.select-org-modal.permission-info"
|
||||
msgstr "Here you find all your organizations where you are allowed to create or add teams."
|
||||
|
||||
msgid "dashboard.no-permission-delete-team.message"
|
||||
msgstr "You are not allowed to delete teams that are part of %s organization. If you need more information, contact the owner."
|
||||
|
||||
@ -9128,3 +9128,6 @@ msgstr "No tienes permiso para añadir equipos a ninguna de tus organizaciones."
|
||||
|
||||
msgid "dashboard.select-org-modal.permission-info"
|
||||
msgstr "Aquí encontrarás todas las organizaciones en las que tienes permiso para crear o añadir equipos."
|
||||
|
||||
msgid "dashboard.no-permission-delete-team.message"
|
||||
msgstr "No tienes permiso para eliminar equipos que pertenecen a la organización %s. Si necesitas más información, contacta con el propietario."
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user