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