mirror of
https://github.com/penpot/penpot.git
synced 2026-05-24 09:23:40 +00:00
✨ Add Nitrate's advanced permissions
* ✨ Add Nitrate's advanced permissions * 📎 Code review
This commit is contained in:
parent
94f8370d98
commit
4ddabaebff
@ -348,6 +348,20 @@
|
|||||||
[:map
|
[:map
|
||||||
[:cancel-at [:maybe schema:timestamp]]])
|
[:cancel-at [:maybe schema:timestamp]]])
|
||||||
|
|
||||||
|
(defn- get-org-permissions-api
|
||||||
|
[cfg {:keys [organization-id] :as params}]
|
||||||
|
(let [baseuri (cf/get :nitrate-backend-uri)]
|
||||||
|
(request-to-nitrate cfg :get
|
||||||
|
(str baseuri
|
||||||
|
"/api/organizations/"
|
||||||
|
organization-id
|
||||||
|
"/permissions")
|
||||||
|
[:map
|
||||||
|
[:organization-id ::sm/uuid]
|
||||||
|
[:owner-id ::sm/uuid]
|
||||||
|
[:create-teams [:enum "any" "onlyMe"]]]
|
||||||
|
params)))
|
||||||
|
|
||||||
(defn- redeem-activation-code-api
|
(defn- redeem-activation-code-api
|
||||||
[cfg params]
|
[cfg params]
|
||||||
(let [baseuri (cf/get :nitrate-backend-uri)]
|
(let [baseuri (cf/get :nitrate-backend-uri)]
|
||||||
@ -372,6 +386,7 @@
|
|||||||
:add-profile-to-org (partial add-profile-to-org-api cfg)
|
:add-profile-to-org (partial add-profile-to-org-api cfg)
|
||||||
:remove-profile-from-org (partial remove-profile-from-org-api cfg)
|
:remove-profile-from-org (partial remove-profile-from-org-api cfg)
|
||||||
:remove-profile-from-all-orgs (partial remove-profile-from-all-orgs-api cfg)
|
:remove-profile-from-all-orgs (partial remove-profile-from-all-orgs-api cfg)
|
||||||
|
:get-org-permissions (partial get-org-permissions-api cfg)
|
||||||
:delete-team (partial delete-team-api cfg)
|
:delete-team (partial delete-team-api cfg)
|
||||||
:remove-team-from-org (partial remove-team-from-org-api cfg)
|
:remove-team-from-org (partial remove-team-from-org-api cfg)
|
||||||
:get-subscription (partial get-subscription-api cfg)
|
:get-subscription (partial get-subscription-api cfg)
|
||||||
|
|||||||
@ -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.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.nitrate :as nitrate]
|
[app.nitrate :as nitrate]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
@ -305,6 +306,20 @@
|
|||||||
(assert-not-default-team cfg team-id)
|
(assert-not-default-team cfg team-id)
|
||||||
(assert-membership cfg profile-id organization-id)
|
(assert-membership cfg profile-id organization-id)
|
||||||
|
|
||||||
|
(when (contains? cf/flags :nitrate)
|
||||||
|
(let [org-perms (nitrate/call cfg :get-org-permissions
|
||||||
|
{:organization-id organization-id})]
|
||||||
|
(if (nil? org-perms)
|
||||||
|
(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"))))))
|
||||||
|
|
||||||
(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
|
||||||
(doseq [{member-id :profile-id} team-members
|
(doseq [{member-id :profile-id} team-members
|
||||||
|
|||||||
@ -506,11 +506,27 @@
|
|||||||
(sv/defmethod ::create-team
|
(sv/defmethod ::create-team
|
||||||
{::doc/added "1.17"
|
{::doc/added "1.17"
|
||||||
::sm/params schema:create-team}
|
::sm/params schema:create-team}
|
||||||
[cfg {:keys [::rpc/profile-id] :as params}]
|
[cfg {:keys [::rpc/profile-id organization-id] :as params}]
|
||||||
|
|
||||||
(quotes/check! cfg {::quotes/id ::quotes/teams-per-profile
|
(quotes/check! cfg {::quotes/id ::quotes/teams-per-profile
|
||||||
::quotes/profile-id profile-id})
|
::quotes/profile-id profile-id})
|
||||||
|
|
||||||
|
;; When creating inside an org, verify the user has permission to do so.
|
||||||
|
;; Fail closed: if org permissions cannot be fetched, deny the operation.
|
||||||
|
(when (and organization-id (contains? cf/flags :nitrate))
|
||||||
|
(let [org-perms (nitrate/call cfg :get-org-permissions
|
||||||
|
{:organization-id organization-id})]
|
||||||
|
(if (nil? org-perms)
|
||||||
|
(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"))))))
|
||||||
|
|
||||||
(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)
|
||||||
(set/difference cfeat/no-team-inheritable-features))
|
(set/difference cfeat/no-team-inheritable-features))
|
||||||
|
|||||||
@ -15,7 +15,8 @@
|
|||||||
[:slug ::sm/text]
|
[:slug ::sm/text]
|
||||||
[: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"]]]])
|
||||||
|
|
||||||
|
|
||||||
(def schema:team-with-organization
|
(def schema:team-with-organization
|
||||||
@ -31,7 +32,8 @@
|
|||||||
[:custom-photo :organization-custom-photo]
|
[:custom-photo :organization-custom-photo]
|
||||||
[: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]])
|
||||||
|
|
||||||
(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.
|
||||||
@ -47,4 +49,4 @@
|
|||||||
(assoc acc team-k v)
|
(assoc acc team-k v)
|
||||||
(dissoc acc team-k))))
|
(dissoc acc team-k))))
|
||||||
team
|
team
|
||||||
organization->team-keys)))
|
organization->team-keys)))
|
||||||
|
|||||||
@ -134,3 +134,81 @@
|
|||||||
(rx/mapcat
|
(rx/mapcat
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(rx/of (modal/hide))))))))
|
(rx/of (modal/hide))))))))
|
||||||
|
|
||||||
|
(defn show-add-team-to-org-modal
|
||||||
|
"Fetches fresh team/org data, then shows the add-to-org modal
|
||||||
|
restricted to orgs where the user has permission, or the no-permission
|
||||||
|
modal if none qualify."
|
||||||
|
[{:keys [team-id]}]
|
||||||
|
(ptk/reify ::show-add-team-to-org-modal
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [profile-id (dm/get-in state [:profile :id])]
|
||||||
|
(->> (rp/cmd! :get-teams)
|
||||||
|
(rx/mapcat
|
||||||
|
(fn [teams]
|
||||||
|
(let [all-orgs (map dt/team->organization
|
||||||
|
(filter #(and (:is-default %) (:organization-id %)) teams))
|
||||||
|
orgs (filter (fn [org]
|
||||||
|
(let [perm (:create-teams org)
|
||||||
|
is-own? (= profile-id (:owner-id org))]
|
||||||
|
(or (= perm "any") is-own?))) all-orgs)
|
||||||
|
team (first (filter #(= (:id %) team-id) teams))
|
||||||
|
on-confirm (fn [organization-id]
|
||||||
|
(st/emit! (add-team-to-org {:team-id team-id
|
||||||
|
:organization-id organization-id})))]
|
||||||
|
(rx/of (dt/teams-fetched teams)
|
||||||
|
(if (empty? orgs)
|
||||||
|
(modal/show :no-org-allows-create-team {})
|
||||||
|
(let [has-filtered? (< (count orgs) (count all-orgs))
|
||||||
|
extra-props (when has-filtered?
|
||||||
|
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
||||||
|
(modal/show :select-organization-modal
|
||||||
|
(merge {:organizations orgs
|
||||||
|
:current-organization-id (:organization-id team)
|
||||||
|
:on-confirm on-confirm
|
||||||
|
:title-key "dashboard.select-org-modal.title"
|
||||||
|
:choose-key "dashboard.select-org-modal.choose"
|
||||||
|
:placeholder-key "dashboard.select-org-modal.select"
|
||||||
|
:accept-key "dashboard.select-org-modal.accept"
|
||||||
|
:cancel-key "labels.cancel"}
|
||||||
|
extra-props)))))))))))))
|
||||||
|
|
||||||
|
(defn show-change-team-org-modal
|
||||||
|
"Fetches fresh team/org data, then shows the change-org modal
|
||||||
|
restricted to orgs where the user has permission, or the no-permission
|
||||||
|
modal if none qualify."
|
||||||
|
[{:keys [team-id]}]
|
||||||
|
(ptk/reify ::show-change-team-org-modal
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [profile-id (dm/get-in state [:profile :id])]
|
||||||
|
(->> (rp/cmd! :get-teams)
|
||||||
|
(rx/mapcat
|
||||||
|
(fn [teams]
|
||||||
|
(let [all-orgs (map dt/team->organization
|
||||||
|
(filter #(and (:is-default %) (:organization-id %)) teams))
|
||||||
|
orgs (filter (fn [org]
|
||||||
|
(let [perm (:create-teams org)
|
||||||
|
is-own? (= profile-id (:owner-id org))]
|
||||||
|
(or (= perm "any") is-own?))) all-orgs)
|
||||||
|
team (first (filter #(= (:id %) team-id) teams))
|
||||||
|
on-confirm (fn [organization-id]
|
||||||
|
(st/emit! (add-team-to-org {:team-id team-id
|
||||||
|
:organization-id organization-id})))]
|
||||||
|
(rx/of (dt/teams-fetched teams)
|
||||||
|
(if (empty? orgs)
|
||||||
|
(modal/show :no-org-allows-create-team {})
|
||||||
|
(let [has-filtered? (< (count orgs) (count all-orgs))
|
||||||
|
extra-props (when has-filtered?
|
||||||
|
{:info-message-key "dashboard.select-org-modal.permission-info"})]
|
||||||
|
(modal/show :select-organization-modal
|
||||||
|
(merge {:organizations orgs
|
||||||
|
:current-organization-id (:organization-id team)
|
||||||
|
:on-confirm on-confirm
|
||||||
|
:title-key "dashboard.change-org-modal.title"
|
||||||
|
:choose-key "dashboard.change-org-modal.choose"
|
||||||
|
:placeholder-key "dashboard.change-org-modal.select"
|
||||||
|
:accept-key "dashboard.change-org-modal.accept"
|
||||||
|
:cancel-key "labels.cancel"}
|
||||||
|
extra-props)))))))))))))
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.media :as di]
|
[app.main.data.media :as di]
|
||||||
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.profile :as dp]
|
[app.main.data.profile :as dp]
|
||||||
[app.main.features :as features]
|
[app.main.features :as features]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
@ -50,7 +51,8 @@
|
|||||||
:organization-name
|
:organization-name
|
||||||
:organization-slug
|
:organization-slug
|
||||||
:organization-owner-id
|
:organization-owner-id
|
||||||
:organization-avatar-bg-url))]
|
:organization-avatar-bg-url
|
||||||
|
:organization-create-teams))]
|
||||||
(update state :teams assoc id team-updated)))
|
(update state :teams assoc id team-updated)))
|
||||||
state
|
state
|
||||||
teams)))))
|
teams)))))
|
||||||
@ -63,6 +65,30 @@
|
|||||||
(->> (rp/cmd! :get-teams)
|
(->> (rp/cmd! :get-teams)
|
||||||
(rx/map teams-fetched)))))
|
(rx/map teams-fetched)))))
|
||||||
|
|
||||||
|
(defn check-and-create-team
|
||||||
|
"Fetches fresh team data from the server to ensure up-to-date org
|
||||||
|
permissions, then shows the team-form modal or a no-permission modal."
|
||||||
|
[team-id]
|
||||||
|
(ptk/reify ::check-and-create-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))
|
||||||
|
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?)]
|
||||||
|
(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)})))))))))))
|
||||||
|
|
||||||
;; --- EVENT: fetch-members
|
;; --- EVENT: fetch-members
|
||||||
|
|
||||||
(defn- members-fetched
|
(defn- members-fetched
|
||||||
@ -598,5 +624,6 @@
|
|||||||
:avatar-bg-url (:organization-avatar-bg-url team)
|
:avatar-bg-url (:organization-avatar-bg-url team)
|
||||||
: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)})
|
||||||
|
|
||||||
|
|||||||
@ -384,10 +384,9 @@
|
|||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps team)
|
(mf/deps team)
|
||||||
(fn []
|
(fn []
|
||||||
(let [params (if (and (contains? cf/flags :nitrate) (:organization-id team))
|
(if (contains? cf/flags :nitrate)
|
||||||
{:organization-id (:organization-id team)}
|
(st/emit! (dtm/check-and-create-team (:id team)))
|
||||||
{})]
|
(st/emit! (modal/show :team-form {})))))
|
||||||
(st/emit! (modal/show :team-form params)))))
|
|
||||||
|
|
||||||
on-team-click
|
on-team-click
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
|||||||
@ -810,7 +810,7 @@
|
|||||||
(mf/defc select-organization-modal
|
(mf/defc select-organization-modal
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
::mf/register-as :select-organization-modal}
|
::mf/register-as :select-organization-modal}
|
||||||
[{:keys [organizations current-organization-id on-confirm title-key text-key choose-key placeholder-key accept-key cancel-key]}]
|
[{:keys [organizations current-organization-id on-confirm title-key text-key choose-key placeholder-key accept-key cancel-key info-message-key]}]
|
||||||
(let [valid-organizations (mf/with-memo [organizations]
|
(let [valid-organizations (mf/with-memo [organizations]
|
||||||
(remove #(= (:id %) current-organization-id) organizations))
|
(remove #(= (:id %) current-organization-id) organizations))
|
||||||
options (mf/with-memo [valid-organizations]
|
options (mf/with-memo [valid-organizations]
|
||||||
@ -848,6 +848,9 @@
|
|||||||
[:div {:class (stl/css :modal-content :modal-select-org-text)} (tr text-key)])
|
[:div {:class (stl/css :modal-content :modal-select-org-text)} (tr text-key)])
|
||||||
|
|
||||||
[:div
|
[:div
|
||||||
|
(when info-message-key
|
||||||
|
[:div {:class (stl/css :modal-select-org-info)}
|
||||||
|
(tr info-message-key)])
|
||||||
[:div {:class (stl/css :modal-select-org-content)}
|
[:div {:class (stl/css :modal-select-org-content)}
|
||||||
(tr choose-key)]
|
(tr choose-key)]
|
||||||
[:> combobox* {:id "selected-id"
|
[:> combobox* {:id "selected-id"
|
||||||
@ -1366,18 +1369,29 @@
|
|||||||
can-edit (or (:is-owner permissions)
|
can-edit (or (:is-owner permissions)
|
||||||
(:is-admin permissions))
|
(:is-admin permissions))
|
||||||
|
|
||||||
organizations (mf/deref refs/teams)
|
profile (mf/deref refs/profile)
|
||||||
organizations (mf/with-memo [organizations]
|
profile-id (:id profile)
|
||||||
(->> (vals organizations)
|
|
||||||
(filter :is-default)
|
all-organizations (mf/deref refs/teams)
|
||||||
(filter :organization-id)
|
all-organizations (mf/with-memo [all-organizations]
|
||||||
(map dtm/team->organization)))
|
(->> (vals all-organizations)
|
||||||
|
(filter :is-default)
|
||||||
|
(filter :organization-id)
|
||||||
|
(map dtm/team->organization)))
|
||||||
|
|
||||||
|
;; Filter to orgs where user is allowed to create/add teams
|
||||||
|
organizations (mf/with-memo [all-organizations profile-id]
|
||||||
|
(->> all-organizations
|
||||||
|
(filter (fn [org]
|
||||||
|
(let [perm (:create-teams org)
|
||||||
|
is-owner? (= profile-id (:owner-id org))]
|
||||||
|
(or (= perm "any") is-owner?))))))
|
||||||
|
|
||||||
can-change-organization? (mf/with-memo [organizations]
|
can-change-organization? (mf/with-memo [organizations]
|
||||||
(> (count organizations) 1))
|
(> (count organizations) 1))
|
||||||
|
|
||||||
can-add-to-organization? (mf/with-memo [organizations]
|
can-add-to-organization? (mf/with-memo [organizations all-organizations]
|
||||||
(and (pos? (count organizations))
|
(and (pos? (count all-organizations))
|
||||||
(not (:is-default team))))
|
(not (:is-default team))))
|
||||||
|
|
||||||
show-org-options-menu*
|
show-org-options-menu*
|
||||||
@ -1424,41 +1438,17 @@
|
|||||||
:accept-style :danger}]
|
:accept-style :danger}]
|
||||||
(st/emit! (modal/show params)))))
|
(st/emit! (modal/show params)))))
|
||||||
|
|
||||||
on-add-team-to-org-confirm
|
|
||||||
(mf/use-fn
|
|
||||||
(mf/deps team)
|
|
||||||
(fn [organization-id]
|
|
||||||
(let [organization (d/seek #(= organization-id (:id %)) organizations)]
|
|
||||||
(when organization
|
|
||||||
(st/emit! (dnt/add-team-to-org {:team-id (:id team)
|
|
||||||
:organization-id organization-id}))))))
|
|
||||||
|
|
||||||
on-add-team-to-org
|
on-add-team-to-org
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps organizations on-add-team-to-org-confirm)
|
(mf/deps team)
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (modal/show :select-organization-modal {:organizations organizations
|
(st/emit! (dnt/show-add-team-to-org-modal {:team-id (:id team)}))))
|
||||||
:current-organization-id (:organization-id team)
|
|
||||||
:on-confirm on-add-team-to-org-confirm
|
|
||||||
:title-key "dashboard.select-org-modal.title"
|
|
||||||
:choose-key "dashboard.select-org-modal.choose"
|
|
||||||
:placeholder-key "dashboard.select-org-modal.select"
|
|
||||||
:accept-key "dashboard.select-org-modal.accept"
|
|
||||||
:cancel-key "labels.cancel"}))))
|
|
||||||
|
|
||||||
on-change-team-org
|
on-change-team-org
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps organizations on-add-team-to-org-confirm)
|
(mf/deps team)
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (modal/show :select-organization-modal {:organizations organizations
|
(st/emit! (dnt/show-change-team-org-modal {:team-id (:id team)}))))]
|
||||||
:current-organization-id (:organization-id team)
|
|
||||||
:on-confirm on-add-team-to-org-confirm
|
|
||||||
:title-key "dashboard.change-org-modal.title"
|
|
||||||
:text-key "dashboard.change-org-modal.text"
|
|
||||||
:choose-key "dashboard.change-org-modal.choose"
|
|
||||||
:placeholder-key "dashboard.change-org-modal.select"
|
|
||||||
:accept-key "dashboard.change-org-modal.accept"
|
|
||||||
:cancel-key "labels.cancel"}))))]
|
|
||||||
|
|
||||||
(mf/with-effect [team]
|
(mf/with-effect [team]
|
||||||
(dom/set-html-title (tr "title.team-settings"
|
(dom/set-html-title (tr "title.team-settings"
|
||||||
|
|||||||
@ -887,6 +887,13 @@
|
|||||||
margin-block-end: var(--sp-s);
|
margin-block-end: var(--sp-s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-select-org-info {
|
||||||
|
@include t.use-typography("body-medium");
|
||||||
|
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
margin-block-end: var(--sp-xxxl);
|
||||||
|
}
|
||||||
|
|
||||||
.modal-select-org-title {
|
.modal-select-org-title {
|
||||||
@include t.use-typography("title-medium");
|
@include t.use-typography("title-medium");
|
||||||
|
|
||||||
|
|||||||
@ -42,16 +42,19 @@
|
|||||||
(modal/hide))))
|
(modal/hide))))
|
||||||
|
|
||||||
(defn- on-error
|
(defn- on-error
|
||||||
[form _response]
|
[form organization-name response]
|
||||||
(let [id (get-in @form [:clean-data :id])]
|
(let [id (get-in @form [:clean-data :id])
|
||||||
(if id
|
code (-> response ex-data :code)]
|
||||||
(rx/of (ntf/error "Error on updating team."))
|
(if (= code :not-allowed)
|
||||||
(rx/of (ntf/error "Error on creating team.")))))
|
(rx/of (modal/show :no-permission-create-team {:organization-name organization-name}))
|
||||||
|
(if id
|
||||||
|
(rx/of (ntf/error "Error on updating team."))
|
||||||
|
(rx/of (ntf/error "Error on creating team."))))))
|
||||||
|
|
||||||
(defn- on-create-submit
|
(defn- on-create-submit
|
||||||
[form]
|
[form organization-name]
|
||||||
(let [mdata {:on-success (partial on-create-success form)
|
(let [mdata {:on-success (partial on-create-success form)
|
||||||
:on-error (partial on-error form)}
|
:on-error (partial on-error form organization-name)}
|
||||||
data (:clean-data @form)
|
data (:clean-data @form)
|
||||||
params (cond-> {:name (:name data)}
|
params (cond-> {:name (:name data)}
|
||||||
(:organization-id data) (assoc :organization-id (:organization-id data)))]
|
(:organization-id data) (assoc :organization-id (:organization-id data)))]
|
||||||
@ -61,23 +64,23 @@
|
|||||||
(defn- on-update-submit
|
(defn- on-update-submit
|
||||||
[form]
|
[form]
|
||||||
(let [mdata {:on-success (partial on-update-success form)
|
(let [mdata {:on-success (partial on-update-success form)
|
||||||
:on-error (partial on-error form)}
|
:on-error (partial on-error form nil)}
|
||||||
data (:clean-data @form)
|
data (:clean-data @form)
|
||||||
team (select-keys data [:id :name])] ;; Only send name and id for updates
|
team (select-keys data [:id :name])]
|
||||||
(st/emit! (dtm/update-team (with-meta team mdata))
|
(st/emit! (dtm/update-team (with-meta team mdata))
|
||||||
(modal/hide))))
|
(modal/hide))))
|
||||||
|
|
||||||
(defn- on-submit
|
(defn- on-submit
|
||||||
[form _]
|
[organization-name form _]
|
||||||
(let [data (:clean-data @form)]
|
(let [data (:clean-data @form)]
|
||||||
(if (:id data)
|
(if (:id data)
|
||||||
(on-update-submit form)
|
(on-update-submit form)
|
||||||
(on-create-submit form))))
|
(on-create-submit form organization-name))))
|
||||||
|
|
||||||
(mf/defc team-form-modal
|
(mf/defc team-form-modal
|
||||||
{::mf/register modal/components
|
{::mf/register modal/components
|
||||||
::mf/register-as :team-form}
|
::mf/register-as :team-form}
|
||||||
[{:keys [team organization-id] :as props}]
|
[{:keys [team organization-id organization-name] :as props}]
|
||||||
(let [initial (mf/use-memo
|
(let [initial (mf/use-memo
|
||||||
(mf/deps team organization-id)
|
(mf/deps team organization-id)
|
||||||
(fn []
|
(fn []
|
||||||
@ -89,18 +92,22 @@
|
|||||||
organization-id (assoc :organization-id organization-id)))))
|
organization-id (assoc :organization-id organization-id)))))
|
||||||
form (fm/use-form :schema schema:team-form
|
form (fm/use-form :schema schema:team-form
|
||||||
:initial initial)
|
:initial initial)
|
||||||
|
on-submit* (mf/use-fn
|
||||||
|
(mf/deps organization-name)
|
||||||
|
(partial on-submit organization-name))
|
||||||
handle-keydown
|
handle-keydown
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
|
(mf/deps organization-name)
|
||||||
(fn [e]
|
(fn [e]
|
||||||
(when (kbd/enter? e)
|
(when (kbd/enter? e)
|
||||||
(dom/prevent-default e)
|
(dom/prevent-default e)
|
||||||
(dom/stop-propagation e)
|
(dom/stop-propagation e)
|
||||||
(on-submit form e))))]
|
(on-submit organization-name form e))))]
|
||||||
|
|
||||||
[:div {:class (stl/css :modal-overlay)}
|
[:div {:class (stl/css :modal-overlay)}
|
||||||
[:div {:class (stl/css :modal-container)}
|
[:div {:class (stl/css :modal-container)}
|
||||||
[:& fm/form {:form form
|
[:& fm/form {:form form
|
||||||
:on-submit on-submit
|
:on-submit on-submit*
|
||||||
:class (stl/css :team-form)}
|
:class (stl/css :team-form)}
|
||||||
|
|
||||||
[:div {:class (stl/css :modal-header)}
|
[:div {:class (stl/css :modal-header)}
|
||||||
@ -132,3 +139,31 @@
|
|||||||
:class (stl/css :accept-btn)}]]]]]]))
|
:class (stl/css :accept-btn)}]]]]]]))
|
||||||
|
|
||||||
|
|
||||||
|
(mf/defc no-permission-create-team-modal*
|
||||||
|
{::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")]]]])
|
||||||
|
|||||||
@ -5,6 +5,8 @@
|
|||||||
// Copyright (c) KALEIDOS INC
|
// Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
@use "refactor/common-refactor.scss" as deprecated;
|
@use "refactor/common-refactor.scss" as deprecated;
|
||||||
|
@use "ds/_utils.scss" as *;
|
||||||
|
@use "ds/typography.scss" as t;
|
||||||
|
|
||||||
.modal-overlay {
|
.modal-overlay {
|
||||||
@extend %modal-overlay-base;
|
@extend %modal-overlay-base;
|
||||||
@ -15,11 +17,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
margin-bottom: deprecated.$s-24;
|
margin-block-end: var(--sp-xxl);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-title {
|
.modal-title {
|
||||||
@include deprecated.uppercase-title-typography;
|
@include t.use-typography("headline-large");
|
||||||
|
|
||||||
color: var(--modal-title-foreground-color);
|
color: var(--modal-title-foreground-color);
|
||||||
}
|
}
|
||||||
@ -29,33 +31,31 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
margin-bottom: deprecated.$s-24;
|
margin-block-end: var(--sp-xxl);
|
||||||
|
color: var(--color-foreground-secondary);
|
||||||
|
|
||||||
|
@include t.use-typography("body-large");
|
||||||
}
|
}
|
||||||
|
|
||||||
.team-form {
|
.team-form {
|
||||||
min-width: deprecated.$s-400;
|
min-inline-size: px2rem(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-name-input {
|
.group-name-input {
|
||||||
@extend %input-element-label;
|
@extend %input-element-label;
|
||||||
@include deprecated.body-small-typography;
|
|
||||||
|
|
||||||
margin-bottom: deprecated.$s-8;
|
margin-block-end: var(--sp-s);
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
.group-name-input label {
|
||||||
@include deprecated.flex-column;
|
display: flex;
|
||||||
@include deprecated.body-small-typography;
|
flex-direction: column;
|
||||||
|
gap: var(--sp-xs);
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
input {
|
|
||||||
@include deprecated.body-small-typography;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
|
|||||||
@ -9395,4 +9395,13 @@ msgid "subscription.current-plan.nitrate-trial"
|
|||||||
msgstr "Nitrate Trial"
|
msgstr "Nitrate Trial"
|
||||||
|
|
||||||
msgid "subscription.current-plan.enterprise"
|
msgid "subscription.current-plan.enterprise"
|
||||||
msgstr "Enterprise"
|
msgstr "Enterprise"
|
||||||
|
|
||||||
|
msgid "dashboard.no-permission-create-team.message"
|
||||||
|
msgstr "You are not allowed to create teams within %s organization. If you need more information, contact the owner."
|
||||||
|
|
||||||
|
msgid "dashboard.no-org-allows-create-team.message"
|
||||||
|
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."
|
||||||
|
|||||||
@ -9103,4 +9103,13 @@ msgid "subscription.current-plan.nitrate-trial"
|
|||||||
msgstr "Nitrate (Prueba)"
|
msgstr "Nitrate (Prueba)"
|
||||||
|
|
||||||
msgid "subscription.current-plan.enterprise"
|
msgid "subscription.current-plan.enterprise"
|
||||||
msgstr "Enterprise"
|
msgstr "Enterprise"
|
||||||
|
|
||||||
|
msgid "dashboard.no-permission-create-team.message"
|
||||||
|
msgstr "No tienes permiso para crear equipos en la organización %s. Si necesitas más información, contacta con el propietario."
|
||||||
|
|
||||||
|
msgid "dashboard.no-org-allows-create-team.message"
|
||||||
|
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."
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user