mirror of
https://github.com/penpot/penpot.git
synced 2026-09-09 21:49:10 +00:00
✨ Change team organization structure on state
This commit is contained in:
parent
18e289b15a
commit
f3c2c0bee2
@ -7,6 +7,7 @@
|
|||||||
(ns app.nitrate
|
(ns app.nitrate
|
||||||
"Module that make calls to the external nitrate aplication"
|
"Module that make calls to the external nitrate aplication"
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.json :as json]
|
[app.common.json :as json]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
@ -268,7 +269,7 @@
|
|||||||
organization-id
|
organization-id
|
||||||
"/add-team")
|
"/add-team")
|
||||||
cto/schema:team-with-organization params)
|
cto/schema:team-with-organization params)
|
||||||
custom-photo (when-let [logo-id (get-in team [:organization :logo-id])]
|
custom-photo (when-let [logo-id (dm/get-in team [:organization :logo-id])]
|
||||||
(str (cf/get :public-uri) "/assets/by-id/" logo-id))]
|
(str (cf/get :public-uri) "/assets/by-id/" logo-id))]
|
||||||
(cond-> team
|
(cond-> team
|
||||||
custom-photo
|
custom-photo
|
||||||
|
|||||||
@ -789,9 +789,9 @@
|
|||||||
|
|
||||||
;; Check delete permissions based on organization settings.
|
;; Check delete permissions based on organization settings.
|
||||||
;; For non-org teams or when nitrate is disabled, only owners can delete.
|
;; For non-org teams or when nitrate is disabled, only owners can delete.
|
||||||
(if (and (:organization-id team) (contains? cf/flags :nitrate))
|
(if (and (:organization team) (contains? cf/flags :nitrate))
|
||||||
(let [org-perms {:owner-id (:organization-owner-id team)
|
(let [org-perms {:owner-id (dm/get-in team [:organization :owner-id])
|
||||||
:permissions (:organization-permissions team)}]
|
:permissions (dm/get-in team [:organization :permissions])}]
|
||||||
(when-not (nitrate-perms/allowed? :delete-team
|
(when-not (nitrate-perms/allowed? :delete-team
|
||||||
{:org-perms org-perms
|
{:org-perms org-perms
|
||||||
:profile-id profile-id
|
:profile-id profile-id
|
||||||
|
|||||||
@ -232,7 +232,7 @@
|
|||||||
:to email
|
:to email
|
||||||
:invited-by (:fullname profile)
|
:invited-by (:fullname profile)
|
||||||
:team (:name team)
|
:team (:name team)
|
||||||
:organization (:organization-name team)
|
:organization (dm/get-in team [:organization :name])
|
||||||
:token itoken
|
:token itoken
|
||||||
:extra-data ptoken}))))
|
:extra-data ptoken}))))
|
||||||
|
|
||||||
|
|||||||
@ -29,27 +29,23 @@
|
|||||||
[:organization schema:organization]])
|
[:organization schema:organization]])
|
||||||
|
|
||||||
(def organization->team-keys
|
(def organization->team-keys
|
||||||
"Mapping from organization field keys to their corresponding :organization-* team keys."
|
"Organization field keys to include in the nested :organization map."
|
||||||
[[:id :organization-id]
|
[:id :name :custom-photo :slug :avatar-bg-url :owner-id :permissions])
|
||||||
[:name :organization-name]
|
|
||||||
[:custom-photo :organization-custom-photo]
|
|
||||||
[:slug :organization-slug]
|
|
||||||
[:avatar-bg-url :organization-avatar-bg-url]
|
|
||||||
[:owner-id :organization-owner-id]
|
|
||||||
[: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 in a nested :organization map.
|
||||||
Associates each org field to the corresponding :organization-* team key when
|
Associates each org field within :organization when the value is non-nil;
|
||||||
the value is non-nil; dissociates the key otherwise. This correctly handles
|
dissociates the field otherwise. This correctly handles both attaching an org
|
||||||
both attaching an org (all values present) and detaching one (org is nil or
|
(all values present) and detaching one (org is nil or all fields absent)."
|
||||||
all fields absent)."
|
|
||||||
[team organization]
|
[team organization]
|
||||||
(let [id (:id organization)]
|
(let [id (:id organization)]
|
||||||
(reduce (fn [acc [org-k team-k]]
|
(if id
|
||||||
(let [v (get organization org-k)]
|
(assoc team :organization
|
||||||
(if (and id (some? v))
|
(reduce (fn [acc k]
|
||||||
(assoc acc team-k v)
|
(let [v (get organization k)]
|
||||||
(dissoc acc team-k))))
|
(if (some? v)
|
||||||
team
|
(assoc acc k v)
|
||||||
organization->team-keys)))
|
(dissoc acc k))))
|
||||||
|
(or (:organization team) {})
|
||||||
|
organization->team-keys))
|
||||||
|
(dissoc team :organization))))
|
||||||
|
|||||||
@ -726,7 +726,7 @@
|
|||||||
:timeout nil})
|
:timeout nil})
|
||||||
(dtm/fetch-teams)
|
(dtm/fetch-teams)
|
||||||
;; When the user is currently on a team of the org
|
;; When the user is currently on a team of the org
|
||||||
(when (= organization-id (:organization-id team))
|
(when (= organization-id (dm/get-in team [:organization :id]))
|
||||||
(dcm/go-to-dashboard-recent {:team-id :default}))))))))
|
(dcm/go-to-dashboard-recent {:team-id :default}))))))))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -204,7 +204,7 @@
|
|||||||
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
||||||
(modal/show :select-organization-modal
|
(modal/show :select-organization-modal
|
||||||
(merge {:organizations orgs
|
(merge {:organizations orgs
|
||||||
:current-organization-id (:organization-id team)
|
:current-organization-id (dm/get-in team [:organization :id])
|
||||||
:on-confirm on-confirm
|
:on-confirm on-confirm
|
||||||
:title-key "dashboard.select-org-modal.title"
|
:title-key "dashboard.select-org-modal.title"
|
||||||
:choose-key "dashboard.select-org-modal.choose"
|
:choose-key "dashboard.select-org-modal.choose"
|
||||||
@ -226,7 +226,7 @@
|
|||||||
(rx/mapcat
|
(rx/mapcat
|
||||||
(fn [teams]
|
(fn [teams]
|
||||||
(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 %)) teams))
|
||||||
orgs (filter (fn [org]
|
orgs (filter (fn [org]
|
||||||
(let [perm (get-in org [:permissions :create-teams])
|
(let [perm (get-in org [:permissions :create-teams])
|
||||||
is-own? (= profile-id (:owner-id org))]
|
is-own? (= profile-id (:owner-id org))]
|
||||||
@ -243,7 +243,7 @@
|
|||||||
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
||||||
(modal/show :select-organization-modal
|
(modal/show :select-organization-modal
|
||||||
(merge {:organizations orgs
|
(merge {:organizations orgs
|
||||||
:current-organization-id (:organization-id team)
|
:current-organization-id (dm/get-in team [:organization :id])
|
||||||
:on-confirm on-confirm
|
:on-confirm on-confirm
|
||||||
:title-key "dashboard.change-org-modal.title"
|
:title-key "dashboard.change-org-modal.title"
|
||||||
:choose-key "dashboard.change-org-modal.choose"
|
:choose-key "dashboard.change-org-modal.choose"
|
||||||
|
|||||||
@ -78,23 +78,24 @@
|
|||||||
(->> (rp/cmd! :get-teams)
|
(->> (rp/cmd! :get-teams)
|
||||||
(rx/mapcat
|
(rx/mapcat
|
||||||
(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))
|
organization (:organization team)
|
||||||
can-create? (if in-org?
|
in-org? (and (contains? cf/flags :nitrate) organization)
|
||||||
(nitrate-perms/allowed? :create-team
|
can-create? (if in-org?
|
||||||
{:org-perms {:owner-id (:organization-owner-id team)
|
(nitrate-perms/allowed? :create-team
|
||||||
:permissions (:organization-permissions team)}
|
{:org-perms {:owner-id (:owner-id organization)
|
||||||
:profile-id profile-id
|
:permissions (:permissions organization)}
|
||||||
:team-perms (:permissions team)})
|
:profile-id profile-id
|
||||||
true)]
|
: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 (:id organization)
|
||||||
:organization-name (:organization-name team)}
|
:organization-name (:name organization)}
|
||||||
{}))
|
{}))
|
||||||
(modal/show :no-permission-modal {:type :create-team
|
(modal/show :no-permission-modal {:type :create-team
|
||||||
:organization-name (:organization-name team)})))))))))))
|
:organization-name (:name organization)})))))))))))
|
||||||
|
|
||||||
(defn check-and-delete-team
|
(defn check-and-delete-team
|
||||||
"Fetches fresh team data from the server to ensure up-to-date org
|
"Fetches fresh team data from the server to ensure up-to-date org
|
||||||
@ -108,16 +109,17 @@
|
|||||||
(rx/mapcat
|
(rx/mapcat
|
||||||
(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))
|
org (:organization team)
|
||||||
|
in-org? (and (contains? cf/flags :nitrate) org)
|
||||||
can-delete? (if in-org?
|
can-delete? (if in-org?
|
||||||
(nitrate-perms/allowed? :delete-team
|
(nitrate-perms/allowed? :delete-team
|
||||||
{:org-perms {:owner-id (:organization-owner-id team)
|
{:org-perms {:owner-id (:owner-id org)
|
||||||
:permissions (:organization-permissions team)}
|
:permissions (:permissions org)}
|
||||||
:profile-id profile-id
|
:profile-id profile-id
|
||||||
:team-perms (:permissions team)})
|
:team-perms (:permissions team)})
|
||||||
(boolean (dm/get-in team [:permissions :is-owner])))
|
(boolean (dm/get-in team [:permissions :is-owner])))
|
||||||
message (if in-org?
|
message (if in-org?
|
||||||
(tr "modals.delete-org-team-confirm.message" (:organization-name team))
|
(tr "modals.delete-org-team-confirm.message" (:name org))
|
||||||
(tr "modals.delete-team-confirm.message"))]
|
(tr "modals.delete-team-confirm.message"))]
|
||||||
(rx/of (teams-fetched teams)
|
(rx/of (teams-fetched teams)
|
||||||
(if can-delete?
|
(if can-delete?
|
||||||
@ -128,7 +130,7 @@
|
|||||||
:accept-label (tr "modals.delete-team-confirm.accept")
|
:accept-label (tr "modals.delete-team-confirm.accept")
|
||||||
:on-accept delete-fn})
|
:on-accept delete-fn})
|
||||||
(modal/show :no-permission-modal {:type :delete-team
|
(modal/show :no-permission-modal {:type :delete-team
|
||||||
:organization-name (:organization-name team)})))))))))))
|
:organization-name (:name org)})))))))))))
|
||||||
|
|
||||||
;; --- EVENT: fetch-members
|
;; --- EVENT: fetch-members
|
||||||
|
|
||||||
@ -659,12 +661,6 @@
|
|||||||
|
|
||||||
|
|
||||||
(defn team->organization [team]
|
(defn team->organization [team]
|
||||||
{:id (:organization-id team)
|
(when-let [org (:organization team)]
|
||||||
:slug (:organization-slug team)
|
(assoc org :default-team-id (:id team))))
|
||||||
:owner-id (:organization-owner-id team)
|
|
||||||
:avatar-bg-url (:organization-avatar-bg-url team)
|
|
||||||
:custom-photo (:organization-custom-photo team)
|
|
||||||
:name (:organization-name team)
|
|
||||||
:default-team-id (:id team)
|
|
||||||
:permissions (:organization-permissions team)})
|
|
||||||
|
|
||||||
|
|||||||
@ -470,7 +470,7 @@
|
|||||||
|
|
||||||
:not-allowed
|
:not-allowed
|
||||||
(rx/of (modal/show :no-permission-modal {:type :delete-team
|
(rx/of (modal/show :no-permission-modal {:type :delete-team
|
||||||
:organization-name (:organization-name team)}))
|
:organization-name (dm/get-in team [:organization :name])}))
|
||||||
|
|
||||||
(rx/throw error))))
|
(rx/throw error))))
|
||||||
|
|
||||||
@ -581,7 +581,8 @@
|
|||||||
|
|
||||||
(let [is-owner? (get-in team [:permissions :is-owner])
|
(let [is-owner? (get-in team [:permissions :is-owner])
|
||||||
is-admin? (get-in team [:permissions :is-admin])
|
is-admin? (get-in team [:permissions :is-admin])
|
||||||
is-org-team? (some? (:organization-id team))
|
organization (:organization team)
|
||||||
|
is-org-team? (some? organization)
|
||||||
in-org? (and (contains? cf/flags :nitrate) is-org-team?)
|
in-org? (and (contains? cf/flags :nitrate) is-org-team?)
|
||||||
show-delete? (if in-org?
|
show-delete? (if in-org?
|
||||||
(or is-owner? is-admin?)
|
(or is-owner? is-admin?)
|
||||||
@ -825,10 +826,11 @@
|
|||||||
(mf/defc sidebar-team-switch*
|
(mf/defc sidebar-team-switch*
|
||||||
[{:keys [team profile]}]
|
[{:keys [team profile]}]
|
||||||
(let [nitrate? (contains? cf/flags :nitrate)
|
(let [nitrate? (contains? cf/flags :nitrate)
|
||||||
organization-id (when nitrate? (:organization-id team))
|
org (:organization team)
|
||||||
|
organization-id (when nitrate? (:id org))
|
||||||
teams (cond->> (mf/deref refs/teams)
|
teams (cond->> (mf/deref refs/teams)
|
||||||
nitrate?
|
nitrate?
|
||||||
(filter #(= (-> % val :organization-id) organization-id))
|
(filter #(= (dm/get-in (val %) [:organization :id]) organization-id))
|
||||||
nitrate?
|
nitrate?
|
||||||
(into {}))
|
(into {}))
|
||||||
|
|
||||||
|
|||||||
@ -1419,8 +1419,8 @@
|
|||||||
(mf/deps team)
|
(mf/deps team)
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (dnt/remove-team-from-org {:team-id (:id team)
|
(st/emit! (dnt/remove-team-from-org {:team-id (:id team)
|
||||||
:organization-id (:organization-id team)
|
:organization-id (dm/get-in team [:organization :id])
|
||||||
:organization-name (:organization-name team)}))))
|
:organization-name (dm/get-in team [:organization :name])}))))
|
||||||
|
|
||||||
on-remove-team-from-org
|
on-remove-team-from-org
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
@ -1428,7 +1428,7 @@
|
|||||||
(fn []
|
(fn []
|
||||||
(let [params {:type :confirm
|
(let [params {:type :confirm
|
||||||
:title (tr "modals.remove-team-org.title")
|
:title (tr "modals.remove-team-org.title")
|
||||||
:message (tr "modals.remove-team-org.text" (:name team) (:organization-name team))
|
:message (tr "modals.remove-team-org.text" (:name team) (dm/get-in team [:organization :name]))
|
||||||
:hint (tr "modals.remove-team-org.info")
|
:hint (tr "modals.remove-team-org.info")
|
||||||
:hint-level :default
|
:hint-level :default
|
||||||
:accept-label (tr "modals.remove-team-org.accept")
|
:accept-label (tr "modals.remove-team-org.accept")
|
||||||
@ -1484,39 +1484,40 @@
|
|||||||
[:div {:class (stl/css :block)}
|
[:div {:class (stl/css :block)}
|
||||||
[:div {:class (stl/css :block-label)}
|
[:div {:class (stl/css :block-label)}
|
||||||
(tr "dashboard.team-organization")]
|
(tr "dashboard.team-organization")]
|
||||||
(if (:organization-id team)
|
(let [organization (:organization team)]
|
||||||
[:div {:class (stl/css :block-content)}
|
(if organization
|
||||||
[:div {:class (stl/css :org-block-content)}
|
[:div {:class (stl/css :block-content)}
|
||||||
[:> org-avatar* {:org (dtm/team->organization team) :size "xxxl"}]
|
[:div {:class (stl/css :org-block-content)}
|
||||||
[:span {:class (stl/css :block-text)}
|
[:> org-avatar* {:org (dtm/team->organization team) :size "xxxl"}]
|
||||||
(:organization-name team)]
|
[:span {:class (stl/css :block-text)}
|
||||||
|
(:name organization)]
|
||||||
|
|
||||||
(when (and (:is-owner permissions) (not (:is-default team)))
|
(when (and (:is-owner permissions) (not (:is-default team)))
|
||||||
[:*
|
[:*
|
||||||
[:> button* {:variant "ghost"
|
[:> button* {:variant "ghost"
|
||||||
:type "button"
|
:type "button"
|
||||||
:class (stl/css-case :org-options-btn (not show-org-options-menu?) :org-options-btn-open show-org-options-menu?)
|
:class (stl/css-case :org-options-btn (not show-org-options-menu?) :org-options-btn-open show-org-options-menu?)
|
||||||
:on-click on-show-options-click}
|
:on-click on-show-options-click}
|
||||||
org-menu-icon
|
org-menu-icon
|
||||||
|
|
||||||
[:& dropdown {:show show-org-options-menu? :on-close close-org-options-menu :dropdown-id "org-options"}
|
[:& dropdown {:show show-org-options-menu? :on-close close-org-options-menu :dropdown-id "org-options"}
|
||||||
[:ul {:class (stl/css :org-dropdown)
|
[:ul {:class (stl/css :org-dropdown)
|
||||||
:role "listbox"}
|
:role "listbox"}
|
||||||
(when can-change-organization?
|
(when can-change-organization?
|
||||||
[:li {:on-click on-change-team-org
|
[:li {:on-click on-change-team-org
|
||||||
|
:class (stl/css :org-dropdown-item)}
|
||||||
|
(tr "dashboard.team-organization.change")])
|
||||||
|
[:li {:on-click on-remove-team-from-org
|
||||||
:class (stl/css :org-dropdown-item)}
|
:class (stl/css :org-dropdown-item)}
|
||||||
(tr "dashboard.team-organization.change")])
|
(tr "dashboard.team-organization.remove")]]]]])]]
|
||||||
[:li {:on-click on-remove-team-from-org
|
[:*
|
||||||
:class (stl/css :org-dropdown-item)}
|
|
||||||
(tr "dashboard.team-organization.remove")]]]]])]]
|
|
||||||
[:*
|
|
||||||
[:div {:class (stl/css :block-content)}
|
|
||||||
[:span {:class (stl/css :block-text)}
|
|
||||||
(tr "dashboard.team-organization.none")]]
|
|
||||||
(when can-add-to-organization?
|
|
||||||
[:div {:class (stl/css :block-content)}
|
[:div {:class (stl/css :block-content)}
|
||||||
[:span {:class (stl/css :block-text)}
|
[:span {:class (stl/css :block-text)}
|
||||||
[:a {:on-click on-add-team-to-org} (tr "dashboard.team-organization.add")]]])])])
|
(tr "dashboard.team-organization.none")]]
|
||||||
|
(when can-add-to-organization?
|
||||||
|
[:div {:class (stl/css :block-content)}
|
||||||
|
[:span {:class (stl/css :block-text)}
|
||||||
|
[:a {:on-click on-add-team-to-org} (tr "dashboard.team-organization.add")]]])]))])
|
||||||
|
|
||||||
[:div {:class (stl/css :block)}
|
[:div {:class (stl/css :block)}
|
||||||
[:div {:class (stl/css :block-label)}
|
[:div {:class (stl/css :block-label)}
|
||||||
@ -1549,4 +1550,3 @@
|
|||||||
|
|
||||||
(when (contains? cfg/flags :subscriptions)
|
(when (contains? cfg/flags :subscriptions)
|
||||||
[:> team* {:is-owner (:is-owner permissions) :team team}])]]]))
|
[:> team* {:is-owner (:is-owner permissions) :team team}])]]]))
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user