Change team organization structure on state

This commit is contained in:
Pablo Alba 2026-05-07 23:17:53 +02:00 committed by Pablo Alba
parent 18e289b15a
commit f3c2c0bee2
9 changed files with 85 additions and 90 deletions

View File

@ -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

View File

@ -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

View File

@ -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}))))

View File

@ -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))))

View File

@ -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}))))))))

View File

@ -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"

View File

@ -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))))

View File

@ -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 {}))

View File

@ -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}])]]]))