mirror of
https://github.com/penpot/penpot.git
synced 2026-09-08 21:19:49 +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
|
[:map
|
||||||
[:organization-id ::sm/uuid]
|
[:organization-id ::sm/uuid]
|
||||||
[:owner-id ::sm/uuid]
|
[:owner-id ::sm/uuid]
|
||||||
[:create-teams [:enum "any" "onlyMe"]]]
|
[:permissions [:map-of :keyword :string]]]
|
||||||
params)))
|
params)))
|
||||||
|
|
||||||
(defn- redeem-activation-code-api
|
(defn- redeem-activation-code-api
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
|
[app.common.types.nitrate-permissions :as nitrate-perms]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.nitrate :as nitrate]
|
[app.nitrate :as nitrate]
|
||||||
@ -313,12 +314,12 @@
|
|||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :not-allowed
|
:code :not-allowed
|
||||||
:hint "Unable to verify organization permissions")
|
:hint "Unable to verify organization permissions")
|
||||||
(let [create-perm (:create-teams org-perms)
|
(when-not (nitrate-perms/allowed? :create-team
|
||||||
is-owner? (= profile-id (:owner-id org-perms))]
|
{:org-perms org-perms
|
||||||
(when (and (= create-perm "onlyMe") (not is-owner?))
|
:profile-id profile-id})
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :not-allowed
|
:code :not-allowed
|
||||||
:hint "You are not allowed to add teams in this organization"))))))
|
:hint "You are not allowed to add teams in this organization")))))
|
||||||
|
|
||||||
(let [team-members (db/query cfg :team-profile-rel {:team-id team-id})]
|
(let [team-members (db/query cfg :team-profile-rel {:team-id team-id})]
|
||||||
;; Add teammates to the org if needed
|
;; Add teammates to the org if needed
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
[app.common.features :as cfeat]
|
[app.common.features :as cfeat]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
|
[app.common.types.nitrate-permissions :as nitrate-perms]
|
||||||
[app.common.types.team :as types.team]
|
[app.common.types.team :as types.team]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
@ -520,12 +521,12 @@
|
|||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :not-allowed
|
:code :not-allowed
|
||||||
:hint "Unable to verify organization permissions")
|
:hint "Unable to verify organization permissions")
|
||||||
(let [create-perm (:create-teams org-perms)
|
(when-not (nitrate-perms/allowed? :create-team
|
||||||
is-owner? (= profile-id (:owner-id org-perms))]
|
{:org-perms org-perms
|
||||||
(when (and (= create-perm "onlyMe") (not is-owner?))
|
:profile-id profile-id})
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :not-allowed
|
:code :not-allowed
|
||||||
:hint "You are not allowed to create teams in this organization"))))))
|
:hint "You are not allowed to create teams in this organization")))))
|
||||||
|
|
||||||
(let [features (-> (cfeat/get-enabled-features cf/flags)
|
(let [features (-> (cfeat/get-enabled-features cf/flags)
|
||||||
(set/difference cfeat/frontend-only-features)
|
(set/difference cfeat/frontend-only-features)
|
||||||
@ -773,20 +774,35 @@
|
|||||||
|
|
||||||
(defn delete-team
|
(defn delete-team
|
||||||
"Mark a team for deletion"
|
"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)
|
(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)]
|
perms (get team :permissions)]
|
||||||
|
|
||||||
(when-not (:is-owner perms)
|
|
||||||
(ex/raise :type :validation
|
|
||||||
:code :only-owner-can-delete-team))
|
|
||||||
|
|
||||||
(when (:is-default team)
|
(when (:is-default team)
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :non-deletable-team
|
:code :non-deletable-team
|
||||||
:hint "impossible to delete default 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)
|
(let [delay (ldel/get-deletion-delay team)
|
||||||
team (db/update! conn :team
|
team (db/update! conn :team
|
||||||
{:deleted-at (ct/in-future delay)}
|
{: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]
|
[:owner-id ::sm/uuid]
|
||||||
[:avatar-bg-url ::sm/uri]
|
[:avatar-bg-url ::sm/uri]
|
||||||
[:logo-id {:optional true} [:maybe ::sm/uuid]]
|
[: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
|
(def schema:team-with-organization
|
||||||
@ -33,7 +36,7 @@
|
|||||||
[:slug :organization-slug]
|
[:slug :organization-slug]
|
||||||
[:avatar-bg-url :organization-avatar-bg-url]
|
[:avatar-bg-url :organization-avatar-bg-url]
|
||||||
[:owner-id :organization-owner-id]
|
[:owner-id :organization-owner-id]
|
||||||
[:create-teams :organization-create-teams]])
|
[:permissions :organization-permissions]])
|
||||||
|
|
||||||
(defn apply-organization
|
(defn apply-organization
|
||||||
"Updates a team map with organization fields sourced from org.
|
"Updates a team map with organization fields sourced from org.
|
||||||
|
|||||||
@ -67,6 +67,7 @@
|
|||||||
[common-tests.types.container-test]
|
[common-tests.types.container-test]
|
||||||
[common-tests.types.fill-test]
|
[common-tests.types.fill-test]
|
||||||
[common-tests.types.modifiers-test]
|
[common-tests.types.modifiers-test]
|
||||||
|
[common-tests.types.nitrate-permissions-test]
|
||||||
[common-tests.types.objects-map-test]
|
[common-tests.types.objects-map-test]
|
||||||
[common-tests.types.path-data-test]
|
[common-tests.types.path-data-test]
|
||||||
[common-tests.types.shape-decode-encode-test]
|
[common-tests.types.shape-decode-encode-test]
|
||||||
@ -147,6 +148,7 @@
|
|||||||
'common-tests.types.container-test
|
'common-tests.types.container-test
|
||||||
'common-tests.types.fill-test
|
'common-tests.types.fill-test
|
||||||
'common-tests.types.modifiers-test
|
'common-tests.types.modifiers-test
|
||||||
|
'common-tests.types.nitrate-permissions-test
|
||||||
'common-tests.types.objects-map-test
|
'common-tests.types.objects-map-test
|
||||||
'common-tests.types.path-data-test
|
'common-tests.types.path-data-test
|
||||||
'common-tests.types.shape-decode-encode-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
|
(let [all-orgs (map dt/team->organization
|
||||||
(filter #(and (:is-default %) (:organization-id %)) teams))
|
(filter #(and (:is-default %) (:organization-id %)) teams))
|
||||||
orgs (filter (fn [org]
|
orgs (filter (fn [org]
|
||||||
(let [perm (:create-teams org)
|
(let [perm (get-in org [:permissions :create-teams])
|
||||||
is-own? (= profile-id (:owner-id org))]
|
is-own? (= profile-id (:owner-id org))]
|
||||||
(or (= perm "any") is-own?))) all-orgs)
|
(or (= perm "any") is-own?))) all-orgs)
|
||||||
team (first (filter #(= (:id %) team-id) teams))
|
team (first (filter #(= (:id %) team-id) teams))
|
||||||
@ -198,7 +198,7 @@
|
|||||||
:organization-id organization-id})))]
|
:organization-id organization-id})))]
|
||||||
(rx/of (dt/teams-fetched teams)
|
(rx/of (dt/teams-fetched teams)
|
||||||
(if (empty? orgs)
|
(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))
|
(let [has-filtered? (< (count orgs) (count all-orgs))
|
||||||
extra-props (when has-filtered?
|
extra-props (when has-filtered?
|
||||||
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
||||||
@ -228,7 +228,7 @@
|
|||||||
(let [all-orgs (map dt/team->organization
|
(let [all-orgs (map dt/team->organization
|
||||||
(filter #(and (:is-default %) (:organization-id %)) teams))
|
(filter #(and (:is-default %) (:organization-id %)) teams))
|
||||||
orgs (filter (fn [org]
|
orgs (filter (fn [org]
|
||||||
(let [perm (:create-teams org)
|
(let [perm (get-in org [:permissions :create-teams])
|
||||||
is-own? (= profile-id (:owner-id org))]
|
is-own? (= profile-id (:owner-id org))]
|
||||||
(or (= perm "any") is-own?))) all-orgs)
|
(or (= perm "any") is-own?))) all-orgs)
|
||||||
team (first (filter #(= (:id %) team-id) teams))
|
team (first (filter #(= (:id %) team-id) teams))
|
||||||
@ -237,7 +237,7 @@
|
|||||||
:organization-id organization-id})))]
|
:organization-id organization-id})))]
|
||||||
(rx/of (dt/teams-fetched teams)
|
(rx/of (dt/teams-fetched teams)
|
||||||
(if (empty? orgs)
|
(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))
|
(let [has-filtered? (< (count orgs) (count all-orgs))
|
||||||
extra-props (when has-filtered?
|
extra-props (when has-filtered?
|
||||||
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
|
[app.common.types.nitrate-permissions :as nitrate-perms]
|
||||||
[app.common.types.team :as ctt]
|
[app.common.types.team :as ctt]
|
||||||
[app.common.uri :as u]
|
[app.common.uri :as u]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
@ -22,6 +23,7 @@
|
|||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.router :as rt]
|
[app.main.router :as rt]
|
||||||
[app.util.clipboard :as clipboard]
|
[app.util.clipboard :as clipboard]
|
||||||
|
[app.util.i18n :refer [tr]]
|
||||||
[app.util.storage :as storage]
|
[app.util.storage :as storage]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
@ -52,7 +54,7 @@
|
|||||||
:organization-slug
|
:organization-slug
|
||||||
:organization-owner-id
|
:organization-owner-id
|
||||||
:organization-avatar-bg-url
|
:organization-avatar-bg-url
|
||||||
:organization-create-teams))]
|
:organization-permissions))]
|
||||||
(update state :teams assoc id team-updated)))
|
(update state :teams assoc id team-updated)))
|
||||||
state
|
state
|
||||||
teams)))))
|
teams)))))
|
||||||
@ -78,16 +80,55 @@
|
|||||||
(fn [teams]
|
(fn [teams]
|
||||||
(let [team (d/seek #(= (:id %) team-id) teams)
|
(let [team (d/seek #(= (:id %) team-id) teams)
|
||||||
in-org? (and (contains? cf/flags :nitrate) (:organization-id team))
|
in-org? (and (contains? cf/flags :nitrate) (:organization-id team))
|
||||||
create-perm (:organization-create-teams team)
|
can-create? (if in-org?
|
||||||
is-owner? (= profile-id (:organization-owner-id team))
|
(nitrate-perms/allowed? :create-team
|
||||||
can-create? (or (not in-org?) (= create-perm "any") is-owner?)]
|
{: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)
|
(rx/of (teams-fetched teams)
|
||||||
(if can-create?
|
(if can-create?
|
||||||
(modal/show :team-form (if in-org?
|
(modal/show :team-form (if in-org?
|
||||||
{:organization-id (:organization-id team)
|
{:organization-id (:organization-id team)
|
||||||
:organization-name (:organization-name 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
|
;; --- EVENT: fetch-members
|
||||||
|
|
||||||
@ -625,5 +666,5 @@
|
|||||||
:custom-photo (:organization-custom-photo team)
|
:custom-photo (:organization-custom-photo team)
|
||||||
:name (:organization-name team)
|
:name (:organization-name team)
|
||||||
:default-team-id (:id team)
|
:default-team-id (:id team)
|
||||||
:create-teams (:organization-create-teams team)})
|
:permissions (:organization-permissions team)})
|
||||||
|
|
||||||
|
|||||||
@ -468,6 +468,10 @@
|
|||||||
:owner-cant-leave-team
|
:owner-cant-leave-team
|
||||||
(rx/of (ntf/error (tr "errors.team-leave.owner-cant-leave")))
|
(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))))
|
(rx/throw error))))
|
||||||
|
|
||||||
leave-fn
|
leave-fn
|
||||||
@ -526,17 +530,9 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps team delete-fn)
|
(mf/deps team delete-fn)
|
||||||
(fn []
|
(fn []
|
||||||
(let [is-org-team? (some? (:organization-id team))
|
;; Fetch fresh team data to check current permission, then show appropriate modal
|
||||||
message (if is-org-team?
|
(st/emit! (dtm/check-and-delete-team {:team-id (:id team)
|
||||||
(tr "modals.delete-org-team-confirm.message" (:organization-name team))
|
:delete-fn delete-fn}))))]
|
||||||
(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})))))]
|
|
||||||
[:> dropdown-menu* props
|
[:> dropdown-menu* props
|
||||||
|
|
||||||
[:> dropdown-menu-item* {:on-click go-members
|
[:> dropdown-menu-item* {:on-click go-members
|
||||||
@ -583,11 +579,19 @@
|
|||||||
:class (stl/css :team-options-item)}
|
:class (stl/css :team-options-item)}
|
||||||
(tr "dashboard.leave-team")])
|
(tr "dashboard.leave-team")])
|
||||||
|
|
||||||
(when (get-in team [:permissions :is-owner])
|
(let [is-owner? (get-in team [:permissions :is-owner])
|
||||||
[:> dropdown-menu-item* {:on-click on-delete-clicked
|
is-admin? (get-in team [:permissions :is-admin])
|
||||||
:class (stl/css :team-options-item :warning)
|
is-org-team? (some? (:organization-id team))
|
||||||
:data-testid "delete-team"}
|
in-org? (and (contains? cf/flags :nitrate) is-org-team?)
|
||||||
(tr "dashboard.delete-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/defc org-options-dropdown*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
|
|||||||
@ -1381,7 +1381,7 @@
|
|||||||
organizations (mf/with-memo [all-organizations profile-id]
|
organizations (mf/with-memo [all-organizations profile-id]
|
||||||
(->> all-organizations
|
(->> all-organizations
|
||||||
(filter (fn [org]
|
(filter (fn [org]
|
||||||
(let [perm (:create-teams org)
|
(let [perm (get-in org [:permissions :create-teams])
|
||||||
is-owner? (= profile-id (:owner-id org))]
|
is-owner? (= profile-id (:owner-id org))]
|
||||||
(or (= perm "any") is-owner?))))))
|
(or (= perm "any") is-owner?))))))
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,8 @@
|
|||||||
(let [id (get-in @form [:clean-data :id])
|
(let [id (get-in @form [:clean-data :id])
|
||||||
code (-> response ex-data :code)]
|
code (-> response ex-data :code)]
|
||||||
(if (= code :not-allowed)
|
(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
|
(if id
|
||||||
(rx/of (ntf/error "Error on updating team."))
|
(rx/of (ntf/error "Error on updating team."))
|
||||||
(rx/of (ntf/error "Error on creating team."))))))
|
(rx/of (ntf/error "Error on creating team."))))))
|
||||||
@ -139,31 +140,25 @@
|
|||||||
:class (stl/css :accept-btn)}]]]]]]))
|
: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 modal/components
|
||||||
::mf/register-as :no-permission-create-team}
|
::mf/register-as :no-permission-modal}
|
||||||
[{:keys [organization-name]}]
|
[{:keys [type organization-name]}]
|
||||||
[:div {:class (stl/css :modal-overlay)}
|
(let [[title message] (case type
|
||||||
[:div {:class (stl/css :modal-container)}
|
:create-team [(tr "labels.create-team")
|
||||||
[:div {:class (stl/css :modal-header)}
|
(tr "dashboard.no-permission-create-team.message" organization-name)]
|
||||||
[:h2 {:class (stl/css :modal-title)}
|
:delete-team [(tr "dashboard.delete-team")
|
||||||
(tr "labels.create-team")]
|
(tr "dashboard.no-permission-delete-team.message" organization-name)]
|
||||||
[:button {:class (stl/css :modal-close-btn)
|
:no-orgs-create [(tr "dashboard.select-org-modal.title")
|
||||||
:on-click modal/hide!} deprecated-icon/close]]
|
(tr "dashboard.no-org-allows-create-team.message")]
|
||||||
[:div {:class (stl/css :modal-content)}
|
:no-orgs-change [(tr "dashboard.change-org-modal.title")
|
||||||
[:div (tr "dashboard.no-permission-create-team.message" organization-name)]]]])
|
(tr "dashboard.no-org-allows-create-team.message")])]
|
||||||
|
[:div {:class (stl/css :modal-overlay)}
|
||||||
|
[:div {:class (stl/css :modal-container)}
|
||||||
(mf/defc no-org-allows-create-team-modal*
|
[:div {:class (stl/css :modal-header)}
|
||||||
{::mf/register modal/components
|
[:h2 {:class (stl/css :modal-title)} title]
|
||||||
::mf/register-as :no-org-allows-create-team}
|
[:button {:class (stl/css :modal-close-btn)
|
||||||
[_props]
|
:on-click modal/hide!} deprecated-icon/close]]
|
||||||
[:div {:class (stl/css :modal-overlay)}
|
[:div {:class (stl/css :modal-content)}
|
||||||
[:div {:class (stl/css :modal-container)}
|
[:div message]]]]))
|
||||||
[: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")]]]])
|
|
||||||
|
|||||||
@ -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"
|
msgid "dashboard.select-org-modal.permission-info"
|
||||||
msgstr "Here you find all your organizations where you are allowed to create or add teams."
|
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"
|
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."
|
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